☰
SpringBoot校园自助商城系统:跳蚤市场与跑腿业务的设计与实现
2026/10/13 21:37:08 网站建设 项目流程

这几年陆陆续续帮不少同学看过毕业设计的代码,校园商城这个方向几乎每届都有几个人选,但大多数都把路子走窄了——只做一个商品发布加购物车下单的普通页面,后台套个模板,答辩演示完就没了。其实如果换成校园自助商城这种带跳蚤市场和跑腿业务的综合服务交易平台,能发挥的空间完全不一样。一方面业务形态更接近真实电商场景,另一方面跳蚤市场的闲置流转和跑腿任务的派单接单能够把用户角色、订单状态、支付流程这些核心点全部覆盖,无论是应付毕设还是作为求职项目写进简历,含金量都会高很多。

这篇文章我就把SpringBoot校园自助商城系统的整体思路、表结构、核心实现和踩坑记录完整梳理一遍,尽量讲清楚每个设计是为什么,而不是光贴代码。正在准备计算机毕业设计、或者想在这个题目上做功能扩展的同学,可以直接当参考手册用。

1. 需求定位:校园自助商城到底在做什么

1.1 跳蚤市场与跑腿商城的业务差异

校园商城如果只做买卖,本质上就是简化版电商。但校园场景里有两个特别接地气的需求,一个是毕业生离校、学期末换宿舍时大量闲置物品需要流转,另一个是代取快递、代买食堂、代拿外卖这类跑腿服务。这两个业务形态差异很大:跳蚤市场是C2C交易,买方和卖方都是学生,核心是商品信息展示、议价沟通、线下交付确认;跑腿业务则是任务众包,发布者付报酬、接单者出力,核心是任务分配和状态流转。

很多同学容易把这两块混在一个商品表里处理,结果跑腿任务被当成普通商品销售,状态管理一塌糊涂。我的建议很明确:跳蚤市场走标准电商流程,跑腿业务单独建任务表,两个业务共享用户体系,但订单和状态机必须分开。这样代码清晰,答辩时也能讲出业务理解。

1.2 角色模型与权限边界

这个系统里的核心角色有三个:普通学生用户、跑腿接单员、管理员。

普通用户可以浏览商品、发布闲置、加入购物车、下单购买、发布跑腿任务、查看订单进度。跑腿接单员在普通用户功能基础上多一个接单权限,能查看任务大厅、抢单、更新任务状态、确认送达。管理员负责用户管理、商品审核、分类管理、订单监管和跑腿任务监管。

这里有一个设计细节:跑腿接单员不需要单独建角色表,用user表里的role字段区分就行。如果做成独立的角色对象反而复杂,因为一个学生既可以发布跑腿任务,也可以同时去接别人的任务,身份是动态切换的。用role字段标记当前用户的身份类型是学生还是接单员,再加上一个权限标识控制接口访问权限就可以了。

1.3 功能清单梳理

在实际动手写代码之前,我建议先花半天时间把功能清单列出来,因为后面所有表设计和接口设计都要对齐这个清单。

  • 用户模块:注册登录、基本信息管理、密码修改、余额管理
  • 跳蚤市场模块:商品发布、商品浏览搜索、商品分类、收藏、商品留言、下架、编辑
  • 购物车与订单模块:加入购物车、修改数量、结算下单、订单列表、订单状态同步、取消订单、确认收货
  • 跑腿模块:任务发布、任务大厅列表、抢单、任务进度更新、完成确认、报酬结算
  • 管理后台:用户管理、商品审核、分类管理、订单查看、跑腿任务查看、统计看板

功能清单画出来后,你会发现这个项目的信息量其实不小,但每个模块单独拆出来又都不复杂,这正好适合毕业设计的体量。

2. 技术选型和数据库设计

2.1 SpringBoot + MyBatis-Plus这套组合为什么稳

技术选型上,SpringBoot是当前Java后端毕业设计的主流选择,没有悬念。具体版本需要注意一下:如果环境是JDK 1.8,就选Spring Boot 2.7.x;如果是JDK 17,可以直接上Spring Boot 3.x。我见过不少同学在自己电脑上装了新版JDK结果用Spring Boot 2.x启动报错,折腾半天最后才发现是版本兼容问题,所以这个一定要提前确认。

持久层框架我推荐MyBatis-Plus。理由很简单,单表CRUD几乎不用写SQL,分页插件有现成支持,代码量可以少写三分之一。有些同学一开始用的是Spring Data JPA,但JPA的复杂关联查询在订单统计这种场景里写起来比较绕,MyBatis-Plus配合XML可以灵活控制SQL,更契合这个项目的需求。

前端方面,毕设通常不需要过度追求工程化,一个Vue + Element UI或者Thymeleaf模板方案就够用。选Vue的时候注意后端要解决跨域问题,选Thymeleaf就省了跨域,但前后端代码耦合一些。我更推荐前后端分离,因为答辩时可以展示接口文档和独立前端项目,看起来工作量更饱满。

权限这块直接用Spring Security + JWT。不要觉得安全框架难,其实这个项目里只需要配置好登录接口放行、其余接口拦截、JWT解析校验三件事。JWT的好处是不需要做服务端session存储,后端集群部署也不受影响,而且移动端可以复用同一套接口。

2.2 商品表与订单表的核心字段设计

数据库我直接用MySQL。表结构设计是整个项目的地基,很多同学上来就写代码,写到订单时才发现少字段,然后又回去改表,非常浪费时间。我把自己觉得合理的核心表结构贴一下。

商品表product:

CREATE TABLE product ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id BIGINT NOT NULL COMMENT '发布者ID', title VARCHAR(100) NOT NULL COMMENT '商品标题', description TEXT COMMENT '商品描述', price DECIMAL(10,2) NOT NULL COMMENT '价格', original_price DECIMAL(10,2) COMMENT '原价或参考价', category_id BIGINT COMMENT '分类ID', status TINYINT DEFAULT 0 COMMENT '0在售 1已售 2下架', view_count INT DEFAULT 0 COMMENT '浏览数', create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

商品图片不要直接存到product表里,单独建product_image表,一张商品对应多张图片,排序用sort字段控制。这样做的好处是不会因为图片地址太长让主表字段变得臃肿,也方便后续扩展图片数量。

订单表要注意表名,不要叫order,因为order是MySQL的SQL关键字,写SQL时每次都要加反引号,很麻烦。我习惯用trade_order或者orders。

订单表核心字段:

CREATE TABLE trade_order ( id BIGINT PRIMARY KEY AUTO_INCREMENT, order_sn VARCHAR(32) UNIQUE NOT NULL COMMENT '订单编号', buyer_id BIGINT NOT NULL COMMENT '买家ID', seller_id BIGINT NOT NULL COMMENT '卖家ID', product_id BIGINT NOT NULL COMMENT '商品ID', amount DECIMAL(10,2) NOT NULL COMMENT '成交金额', status TINYINT DEFAULT 0 COMMENT '0待付款 1已付款待交付 2已交付 3已完成 4已取消', create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

跳蚤市场的订单和电商订单最大的区别在于,c2c场景下订单里同时有买家ID和卖家ID,这两个字段都不能缺。我在某个同学的项目里看到他只存了买家ID,结果商品成交后卖家界面查不到状态,后来只能加字段补救。

2.3 跑腿任务表的状态机设计

跑腿任务表errand_task的状态设计是整个模块的关键。先把业务流转想清楚:发布者创建任务,任务初始待接单;接单员抢单后变为进行中;任务完成后接单员标记送达,等待发布者确认;发布者确认后任务完成,报酬结算。

CREATE TABLE errand_task ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id BIGINT NOT NULL COMMENT '发布者ID', taker_id BIGINT COMMENT '接单员ID', task_title VARCHAR(100) NOT NULL COMMENT '任务标题', task_type TINYINT COMMENT '1代取快递 2代买 3代送 4其他', reward DECIMAL(10,2) NOT NULL COMMENT '报酬', from_location VARCHAR(255) COMMENT '起点', to_location VARCHAR(255) COMMENT '终点', describe_detail TEXT COMMENT '任务描述', status TINYINT DEFAULT 0 COMMENT '0待接单 1进行中 2已完成 3已取消', create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

状态机设计这里有一个要点:不要让接口随意修改任务状态,每一个状态迁移都要做合法性校验。比如只有状态为0的任务才能被抢单,只有状态为1且当前用户是接单员才能标记送达。如果不做校验,一个请求就能把任务从待接单直接改成已完成,业务逻辑就崩了。

2.4 登录鉴权的方案取舍

登录方案用JWT,生成token后在拦截器里校验。流程是:用户登录成功后,后端用用户ID和过期时间生成一个token返回,前端后续请求在header里带Authorization: Bearer token。后端写一个拦截器解析token,解析不到就返回401,解析出来就把它放到ThreadLocal里方便后续获取当前用户。

这里有一个好习惯:写一个LoginUser工具类,里面用ThreadLocal存当前登录用户信息,提供getUserId()、getUserRole()这类静态方法。这样业务代码里想取当前用户时不用到处从请求头里手动解析token,代码会很干净。我在开发时一直坚持这个写法,到后面写多个模块时能省很多事。

redis在这个项目里主要用来存验证码和做并发控制。手机验证码登录这种功能如果不想对接短信服务商,可以用Redis生成六位随机码并设置五分钟过期来模拟,演示时把验证码打印到后端日志里。这样Redis的使用也体现了,答辩时是一个加分项。

3. 核心功能模块的实现细节

3.1 商品发布与图片上传的实现逻辑

商品发布跳蚤市场这个模块的信息量其实不小。首先,这个模块的注入点在于“C2C交易”本身,买家和卖家的交互逻辑、商品状态同步、交易安全,都是可以展开讲的内容。SpringBoot项目实现时这类业务一般会用Controller + Service + Mapper的结构,商品模块的特别之处在于,Service层的事务边界怎么划,得想清楚。

发布商品目前来看是用户自己的行为,不需要审批,但商品创建后如果要新增一个“管理员审核”环节,业务链会完整一些,也能把AOP或消息通知这类技术点引出来。这个看选题要求,不是必做项。

图片上传,我建议先用本地存储加虚拟路径映射的方式。配置一个上传目录,然后通过WebMvcConfigurer把上传目录映射成访问URL。上传接口用MultipartFile接收文件,判断文件类型和后缀名,重命名为时间戳加随机数保存。这里有两个很容易踩的坑,一个是上传后访问不到图片,几乎都是静态资源配置没加;另一个是中文文件名乱码,统一用UUID命名可以规避。

从实现角度讲,这几个点是跳蚤市场必写的:

  • 商品列表接口支持分页、关键词搜索、分类筛选、排序
  • 商品详情接口要同时返回商品信息和图片列表、卖家的信誉信息
  • 商品下架接口,只允许商品所属用户操作自己的商品
  • 商品修改,管理员有强制下架权限,用户有编辑权限

跳蚤市场商品下架后,如果这个商品有未完成订单,不能直接物理删除,只能改状态。这是业务层面的一个硬性约束,写代码前先把这个规则定下来,后面就不会乱。

3.2 购物车与订单提交流程

购物车是标准的加购加结算功能,但跳蚤市场里的购物车有一个特殊性:每一个购物车项对应一个商品,而且这个商品是唯一的,没有规格。所以购物车表不需要存数量属性,只要记录user_id和product_id两个字段。

下单接口是整个项目里最容易出并发问题的地方。我推荐的实现方式是:前端点击下单后,后端先判断商品状态是不是在售,然后在事务里把商品状态改成已售,再创建订单。这里必须是用事务加行锁,而不是先查再改,不然两个用户同时下单同一个商品,就都判断成功了。

下单成功之后,如果接入了模拟支付,就用一个payment_log表记录支付流水,支付成功后把订单状态改成已付款。没有支付功能的话,直接把创建出来的订单视为已付款,跳蚤市场场景里大多数是线下交付,这个简化是合理的。

3.3 跑腿任务从发布到结算的完整链路

跑腿任务的核心链路是发布、抢单、执行、确认、结算五步。用代码做的时候,我把它们拆成五个接口,每个接口只做一件事,状态合法性判断全部放在Service层。

发布接口很简单,插入任务记录,状态为待接单。抢单接口就要多考虑一些了,因为同一个任务可能有多个接单员同时去抢,只有一个能抢到。实现时不建议用先查再更新的模式,而是直接用带条件的update语句,条件里加上status = 0,SQL写成这样:

boolean update = errandTaskService.update(new LambdaUpdateWrapper<ErrandTask>() .eq(ErrandTask::getId, taskId) .eq(ErrandTask::getStatus, 0) .set(ErrandTask::getTakerId, currentUserId) .set(ErrandTask::getStatus, 1));

这条update语句执行后,如果影响行数是1,说明抢单成功;是0,说明被别人先一步抢走了。这是很典型的乐观锁思路,不用Redis也能保证不重复接单。

任务进行中,接单员可以多次更新进度,比如填写备注或者上传照片。发布者确认完成后,任务状态变为已完成,同时给接单员结算报酬。如果账户余额功能已经做了,就直接从发布者的余额里扣减到接单员余额;如果没有余额,就只做状态变更,在线下完成报酬交付。

3.4 后台管理模块怎么快速搭

后台管理不用每个业务都从头写。我的做法是做一个通用的用户管理页面和商品管理页面,然后加一个简单的统计数据看板。统计看板可以统计每个分类下的商品数、每日注册用户数、跑腿任务完成率,这些用SQL聚合就能算出来。

商品管理页需要支持分页搜索和上下架操作,管理员不是对商品有多大的运营需求,而是要能控制非法物品的上架。所以商品列表加一个关键词搜索和一个状态筛选就够用了。

很多同学把后台管理做成一个独立的工程,为的是看起来工作量饱满,但我的建议是后台和前台放在同一个SpringBoot工程里,只是用不同的路径前缀区分,比如/api/admin。这样部署简单,代码也不会太碎片化。当然如果导师要求独立后台,那另说。

4. 开发中容易踩的坑与排查记录

4.1 分页查询SQL执行报错

MyBatis-Plus自带分页,但是版本不同需要注意。在旧版本里需要自己配置PaginationInnerInterceptor,配置完成后分页查询返回的是IPage。我遇到过几次同学把分页插件的配置类漏掉,结果前端传了pageNum和pageSize,后端返回的全量数据,还以为是前端分页没处理。

另外分页插件在连表查询时不生效,这个也常见。比如查商品列表要连分类名称、连卖家昵称,如果直接在Mapper XML里写自定义SQL,就要手动使用Page参数或者把分页条件放到wrapper里。MyBatis-Plus的官方文档有说明,但不少人没注意到联表分页这个坑,排查时报错信息还不明显,更多的是数据量多了之后内存占用变大。

建议是尽量用MyBatis-Plus的单表查询能力处理简单数据,连表场景写独立SQL并显式传入IPage参数,这样两边都能稳定工作。

4.2 图片上传和回显的路径问题

图片上传后回显404,这个Bug我见了太多次。本地存储图片时,上传保存的路径是真实磁盘路径,但前端要通过URL访问,必须有静态资源映射。我一般这样写:

@Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/upload/**") .addResourceHandler("file:" + uploadDir + "/"); } }

注意这个映射的路径末尾一定要加斜杠,不然会映射失败。另外本地存储方案在项目打包部署到服务器之后会有个问题,就是上传目录和jar包所在目录不一致,导致资源映射找不到,所以上传目录我建议配置成动态的,比如从application.yml里读取,不要写死。

4.3 并发抢单导致的重复接单

这个问题如果不用带条件的update语句,而是用先查询再更新的方式,就会有竞态条件。两个接单员同时查询到任务状态是0,都走到更新逻辑,就有可能双成功。解决方式上面提到了,把状态判断放到update的where条件里。

如果要更严谨,可以再加一层唯一约束,比如taker_id和task_id的唯一索引,但这属于兜底方案。在Service层用乐观锁更新已经足够应对绝大多数毕设场景。我建议把这个设计单独在答辩时列出来讲,说明你理解了并发问题,虽然实现不复杂,但体现了思路。

4.4 前后端联调时的跨域和日期格式问题

前后端分离时跨域几乎必遇到。在SpringBoot里写一个CorsConfig,允许指定前端地址跨域,然后spring.mvc配置里要把allowedHeaders开放。之前有同学只配了allowedOriginPatterns,忘了allowedHeaders,结果前端请求带自定义header时预检请求直接失败,这个排查了挺久。

日期格式的坑也很隐蔽。后端返回的LocalDateTime会序列化成带T的格式,比如2024-06-01T12:30:00,前端展示很难看。解决方案是给全局配置Jackson的日期格式化,在application.yml里设置spring.jackson.date-format。还需要指定时区,不然可能差8个小时,这个问题不仔细看还真发现不了。

4.5 订单号生成的唯一性

订单号如果只是用时间戳拼接随机数,并发高的时候还是可能出现重复。其实简单做也行,但既然都写了,建议用时间戳加用户ID加随机数生成一个32位订单号。订单号生成接口加一个唯一索引兜底,生成时循环判断,已经能防止大多数重复。

如果想用分布式ID,也可以引入开源组件,但对毕设项目来说有点过度设计。把时间戳和随机数组合,逻辑简单且不容易出问题,这个方案更适合这个项目。

5. 个人经验与后续扩展建议

整个项目开发下来,我最深的感受是:毕业设计的核心不是把功能堆得多满,而是能不能把每个设计决策解释清楚。比如表结构为什么要拆图片表、跑腿任务为什么独立建模、并发抢单为什么要用带条件的更新,这些想清楚了,写代码只是时间问题。

有一个建议想特别提一下:项目做完以后,一定要把数据库的ER图、接口文档、核心模块流程图整理出来,不光是答辩要用,自己后面复盘也有帮助。整理的过程中你会发现一些表结构或者代码逻辑不够合理的地方,这些都可以优化。

如果后续想在这个项目上继续扩展,方向其实不少。可以加入WebSocket实时通知,有人在商品下面留言、跑腿任务被抢单时,给用户推送一条在线消息;也可以接入真正的微信支付或支付宝沙箱支付,把支付回调的逻辑补上,就更接近生产项目了。还可以做一个简单的推荐算法,根据用户浏览和收藏记录推荐闲置商品,这个方向做出来的东西会更有亮点。

最后讲一个实在的小技巧:写代码的时候顺手写单元测试,尤其是订单服务和抢单服务这类核心逻辑,哪怕只是用JUnit跑几个用例,到答辩前调试功能时你会感激自己当时留了这些测试。真心建议不要等到所有功能写完再一起去调,那是一种可以避免的折磨。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询