我前阵子帮一个学弟把关他的毕业设计题目就是校园周边短程配送系统。他一开始拿着题目来问我说导师只给了个方向剩下全靠自己琢磨。聊到后面我发现这个题目放在2025年其实非常讨巧——Spring Boot本身是Java方向毕设的绝对主流校园配送又自带外卖、跑腿、兼职、定位追踪这些容易出彩的业务点。只要代码结构别做崩答辩的时候能讲清楚几个关键模块的原理拿个不错的成绩是大概率事件。所以这篇文章我就把这个项目从头到尾拆一遍从需求梳理、技术选型、数据库设计到几个核心接口的实现细节和常见坑尽量按我自己的实操经验来讲给准备做Spring Boot方向毕设、或者在学SSM之后想找一个完整业务项目练手的同学做个参考。先说明一下这个项目做什么。简单说它解决的是外卖不送进校园、快递代取需要人帮忙这类短距离配送问题。系统里面有学生用户、商家、骑手和管理员四个角色学生下单买食堂或者其他校园周边的商品商家接单备货骑手在系统里抢单、取货、送到宿舍楼下用户还能实时看订单状态。技术栈上后端是Spring Boot MyBatis Plus MySQL前端是Vue鉴权走JWT数据缓存用Redis管理后台可以看订单量、用户数这些统计报表。整体是标准的前后端分离架构。下面我按做项目的实际顺序来写。1. 项目概述与需求拆解1.1 这个系统到底要解决什么问题校园配送场景和外面的外卖平台有一个本质区别配送距离极短。校园范围一般也就一到两公里宿舍楼能下单食堂教学楼能下单门口奶茶店但恰恰是这种短带来了一些很琐碎的问题。最典型的是配送高峰集中在中午和晚上某个食堂窗口高峰期可能同时有几十个订单人工调度根本忙不过来。宿舍区又把控严外卖骑手进不去只能堆在大门口找个外卖都要翻半天。这个系统本质上做的是短程配送的接单、追踪、结算闭环。它要解决三个核心问题第一用户下单之后订单信息能准确、即时地推送到商家端第二骑手能高效地接单、取货、配送且整个过程的订单状态对用户可见第三管理员能实时掌握平台运营数据——待配送订单数、骑手接单率、商家订单量——而不是靠Excel报表事后统计。需求拆解出来之后功能边界其实非常清晰用户端需要注册登录、浏览商品、下单支付、查看订单状态、确认收货和评价商家端需要管理商品上下架、接单、备货后通知骑手骑手端需要抢单、查看取送地址、更新配送状态管理员端需要审核入驻、订单监控、用户管理、基础数据统计。每个角色的需求都要能落到具体的页面和接口上这才是真正能做成的东西。1.2 四个核心角色与业务流程梳理这个系统的角色划分我是强烈建议照着一个订单从产生到完成的流程去设计的。我们设想一个用户在小程序里点了食堂某窗口的砂锅饭这个订单流是这样的用户注册登录 → 浏览商家及商品 → 选择商品加入购物车 → 结算下单 → 商家端弹出新订单提醒 → 商家确认接单并出餐 → 出餐后订单进入待配送池 → 骑手在骑手端看到可抢订单并抢单 → 骑手到店取餐 → 开始配送 → 到达宿舍楼下点击送达 → 用户收到通知取餐并确认收货 → 订单完成用户可评价。这四个角色对应的数据需求分别是用户要看自己的历史订单和积分商家要看当日营收和待处理订单骑手要看自己的配送收益和配送里程管理员要看平台整体的订单量、骑手效率、商家经营情况。如果你画一张时序图或者状态图来沟通这些流程你会发现订单状态机是整个系统的中心所有角色都围着订单状态转。1.3 功能模块拆分与优先级我一般做毕设项目会先列功能清单然后按基础版、进进阶版、加分散户三个档来区分工作量。基础版是必须实现的用户登录注册、商品浏览与下单、订单状态流转、商家与骑手端的基础操作。进阶版可以加JWT鉴权拦截、Redis缓存热门商品、骑手抢单的并发控制、订单统计报表、上传图片展示。加分项可以加WebSocket消息推送、基于高德地图API的配送路线展示、支付沙箱接入、积分评价体系。这里我给一个优先级建议先把订单状态机跑通再把并发抢单做好这两个是答辩时最有技术含量的部分。支付模块如果觉得接入沙箱太麻烦做成模拟支付点击支付直接改变订单状态也完全可以但要在论文里说明这是模拟实现不要硬写成已对接真实支付。2. 技术选型与架构设计2.1 为什么选Spring Boot而不是SSH或SSM很多同学纠结后端框架到底选什么。我从毕业设计和找工作的双重角度说Spring Boot是目前Java后端最主流的框架几乎没有之一。它不是你简历上的加分项而是必备项。毕业设计选Spring Boot一是因为自动装配让你省掉大量XML配置开发效率高二是因为网上资料多遇到问题搜得着解决方案三是因为答辩时老师对Spring Boot的预期明确你容易答到点子上。对比一下SSHSpring Struts Hibernate那套早过时了维护成本高写起来还特别难受SSMSpring SpringMVC MyBatis逻辑上和Spring Boot很接近但配置繁琐你花在配置上的时间比写业务的时间都多。Spring Boot把这些都简化了内嵌Tomcat、自动配置、starter机制、Actuator监控。你用Spring Boot写业务拿出来的时间可以花在更值得打磨的业务逻辑上。2.2 技术栈清单与版本搭配我这个项目的技术栈可以照抄版本都是Nebula实测没问题的组合组件选型说明后端框架Spring Boot 2.7.x2.x仍然是毕设项目的稳妥选择3.x要求Java 17且部分旧教程不兼容Java版本JDK 1.8 或 8绝大多数毕设环境还是1.8避免踩版本坑ORMMyBatis Plus 3.5.x单表操作几乎不用写SQL分页查询一条内置方法搞定数据库MySQL 5.7 或 8.0建议8.0性能好JSON字段支持更完善连接池Druid 1.2.x监控SQL、自动防御SQL注入毕设里写监控页能加分鉴权JWT Spring AOP拦截器无状态认证契合前后端分离缓存Redis 2.6.0及以上用于缓存热点商品和骑手位置前端Vue 2 Element UI简单好上手组件现成构建Maven团队用户都知道导入即用版本这个东西我特别提醒一句Spring Boot 3.x虽然出了好几年但很多第三方starter还没完全跟进比如某些图形验证码、文件上传的兼容性。你真要做毕设就直接用2.7.x别追求新版本。等你工作了再上3.x不迟。2.3 三层架构与模块代码规范项目采用的是教科书式的经典三层架构Controller负责接收参数、返回统一结果→ Service核心业务逻辑→ Mapper数据库交互。在此基础上我加了一层VO/DTO转换Entity对应数据库字段DTO接收前端参数VO返回给前端数据。比如订单表里面有个字段叫status数据库存的是Integer0待接单、1配送中、2已送达前端不能直接拿数字去展示VO里面就要转成对应的状态文案。代码规范方面有一点特别重要统一返回结果。我自己习惯写一个Result类code区分成功和失败message放提示信息data放业务数据。所有Controller方法都返回这个统一结构这样前端只需要做一个拦截器处理code而不用每个接口都去解析不同格式。这个习惯虽然是基础工程能力但很多人不重视最后代码越写越乱。3. 数据库设计与核心表结构3.1 数据库表设计思路数据库设计是一个系统成败的关键。我通常先列出所有实体用户、商家、商品、订单、订单明细、配送单、评价、公告、购物车可不要表前端存localStorage也行但为了毕设完整度我建议保留。然后逐个确认实体间的关系用户和订单是一对多订单和订单明细是一对多订单和配送单是一对一商家和商品是一对多。建表的时候有几条红线必须记住第一个是主键用自增id或者雪花id都可以但是不要用UUID做主键因为UUID是无序字符串索引效率极差。第二个是金额字段用 decimal(10,2)不要用float/double否则会出现9.99变成10.0000004这种脏数据。第三个是所有表都加create_time、update_time两个字段MyBatis Plus有自动填充注解处理起来非常简单。3.2 订单表设计详解订单表是整个系统的核心我给它标注了最详细的字段说明。order_info表核心字段如下order_no订单编号唯一索引业务上用于用户查询和系统对账user_id下单用户外键merchant_id商家外键driver_id接单骑手外键初始为nullgoods_id和goods_name快照字段存下单时的商品名字和单价防止商家改价清空total_amount总价status订单状态address_receiver/receiver_phone收货信息冗余存储防止用户修改后历史订单查不到原数据remark备注比如不要葱等payment_type支付方式。订单状态我强烈建议用数字0-6表示一个闭环0待支付、1待接单、2已接单待取货、3配送中、4已送达、5已完成、6已取消。注意已取消必须保留因为用户也可能在商家出餐前取消订单状态图里得有这个分支。数据库表里加一个索引在(status)字段上因为大量的业务查询都是按状态筛选的。3.3 配送单与商品表、评价表配送单表delivery_order是订单表的子表加了delivery_type配送类型校内、校外、distance预计距离、fee配送费、pickup_time取货时间、finish_time完成时间。商品表goods_info除了基础字段外还要有stock库存和sales销量这两个字段一个管库存一个用于排序时把热门商品排前面。评价表我建议用reply_id关联订单号不要关联用户id因为评价本身的粒度是一个订单一次用户不可能针对同一个订单评价两次。评价表里存user_id是为了展示某某用户评价存merchant_id是为了商家后台看到评分所以评价其实是三个维度用户、商家、订单的交叉记录外键设置要合理。4. 核心功能实现与实操细节4.1 登录注册与JWT鉴权前端用户、商家、骑手共用一套登录接口靠role字段区分身份。我的实现方式是用户提交手机号密码或者手机号验证码后端校验成功后生成JWT令牌返回前端每次请求都在Authorization请求头里带上这个令牌后端通过拦截器统一校验。JWT的生成我用的是jjwt库(java-jwt)核心代码大概是先设置签发者issuer、过期时间expire时间我习惯设成24小时、私有声明存userId和role然后用HS256算法和密钥签名。因为这个过程完全无状态服务端不用存会话很方便做多端登录。密钥不要硬编码在类里我建议放在application.yml里答辩时可以讲这是安全配置项。拦截器环节有两层第一层是Spring Interceptor写一个JwtInterceptor实现HandlerInterceptor接口preHandle方法里从request.getHeader(Authorization)取出token调用JwtUtil.parseToken校验合法性校验通过把userId和role塞到request的attribute里供后续controller使用。第二层是登录角色校验因为不同角色能访问的接口权限不一样比如创建订单只有user可以调发货和配送只有对应角色可以调所以我会在Controller方法上加自定义注解比如RequireRole(ADMIN)拦截器里解析注解做权限控制。这个设计你在论文里写一句基于RBAC轻量级权限模型会显得比较专业。4.2 下单流程与库存扣减下单是一个事务性很强的操作。用户点结算前端提交商品id和数量后端要做这几步查询商品是否存在并且在架、计算总价、扣减库存库存如果小于购买数量就报错、生成订单主表和明细表、返回订单号。这里有一个非常关键的细节扣库存。如果你先查询库存再更新库存在高并发下会出现超卖。安全做法是直接在SQL里扣UPDATE goods_info SET stock stock - #{num} WHERE id #{id} AND stock #{num}然后判断受影响行数如果等于0说明库存不足。这种原子更新是秒杀系统的经典方案毕设里用了这个点是加分项。订单创建为什么要事务因为订单表和订单明细表必须同时成功或同时失败否则会出现主表存在但明细为空的数据脏状态。我们用Transactional注解控制注意事务方法里不要try-catch吞掉异常否则事务不会回滚。如果你用了try-catch记得在catch块里手动设置rollbackOnly或者重新抛出RuntimeException。4.3 骑手抢单与并发控制骑手抢单是另一个并发重点。设想一下一个订单进入待配送池后可能有多个骑手同时点击抢单按钮。如果你先SELECT该order_Info看status是不是待配送再UPDATE status改成配送中并设置driver_id那么两个骑手可能同时通过第一步的查询然后各自更新结果是两个人同时抢到一单。解决办法统称为乐观锁。我这里的实现是UPDATE order_info SET status 3, driver_id #{driverId}, pickup_time NOW() WHERE id #{orderId} AND status 2。注意最后那个status 2条件数据库在更新时会自动检查当前值是否等于2只有等于2才更新成功受影响行数才会返回1。如果返回0说明订单已经被别人抢了你直接提示手慢了订单已被抢走。这一条SQL就解决了并发问题不需要引入分布式锁逻辑又清晰又高效。Redis的分布式锁也能做但用在毕设里反而会增加复杂度除非你想专门讲Redis的应用否则首选乐观锁。4.4 实时定位与订单轨迹实时定位这个需求有很多做法。最简单的方案是骑手端通过前端定位拿到经纬度每10秒或者每30秒向后端上报一次位置坐标POST接口后端把这些坐标存到Redis中以orderId为key存一个坐标点集合。用户端的订单详情页则每隔几秒向后端拉取最新位置GET接口前端拿到坐标后渲染在地图上。这里我建议用高德地图JavaScript API把坐标转换成地图上的标记。你不需要自己实现复杂的轨迹规划只需要拿上一段路线的起终点连线就行。如果你不想接高德API可以做成配送进度条的形式——用百分比数字表示骑手从取货点到目的地的进度前端用一个进度条组件展示。这两种方案在毕设里都是够用的看你的时间安排。如果你想做WebSocket推送状态变化思路是用户下单后建立WebSocket连接服务端持有这个连接当订单状态变化比如商家接单、骑手取货、送达时主动推送消息前端收到后更新页面可以做成一个弹窗提示您的订单已被骑手接单。WebSocket用Spring封装的SimpleMessagingTemplate网上教程很多核心点是要处理连接断开的问题否则用户退出页面后连接还挂着会报错。4.5 模拟支付与回调如果不想接支付宝/微信支付沙箱最稳妥的做法是写一个模拟支付接口。用户在支付页点确认支付后前端调用/pay/mock接口后端直接把订单状态从待支付改成待接单然后生成一条支付流水记录支付单号、支付时间、金额并标记pay_type1模拟。这样做的目的是支付逻辑有了流水记录有了又不依赖第三方平台的环境。答辩的时候如果老师问为什么不做真实支付你可以回答毕设的定位是系统设计和流程完整接真实支付需要商户资质和沙箱环境所以用模拟支付来保证核心流程闭环生产环境下替换成第三方SDK即可。这个回答是加分的因为它体现了你对业务边界的理解。4.6 ECharts统计报表管理端的统计报表我建议用ECharts做图表展示。三个核心报表第一近30天订单趋势折线图数据按天分组count一下第二商家订单量饼状图统计每个商家的订单数第三骑手配送排行柱状图按完成配送单数排前10名。这些SQL用MyBatis Plus的wrapper条件构造器加上groupBy就能实现不需要写原生SQL。前端用Vue的ECharts组件接口返回什么指标我就渲染什么图。做报表有个坑ECharts本身对数据格式有要求比如饼状图需要[{name:麻辣香锅,value:120},{name:黄焖鸡,value:80}]这种结构你在后端就直接组装成这种格式别把原始List丢给前端让前端去处理格式。前端处理数据格式的能力有限后端返回结构化的VO是更好的合作方式。5. 常见问题排查与避坑指南5.1 启动报错排查我见过最多的问题是Spring Boot项目启动时端口被占用报Port 8080 was already in use。解决办法是在application.yml里改端口比如server.port9999或者找到占用进程杀掉。还有一个很常见的问题是MyBatis Plus的Mapper扫描不到报Invalid bound statement这个大概率是因为你的Mapper接口上没加Mapper注解或者启动类上的MapperScan路径不对。检查三件事注解有没有加、路径对不对、XML文件路径和namespace是否匹配。如果遇到依赖冲突比如Spring Boot 2.7和某个starter版本不对大概率是Maven没有刷新。IDEA里点一下右侧Maven面板的刷新按钮重新reimport一下依赖问题基本能解决。导包失败很可能是Maven镜像源问题换成阿里云镜像或者腾讯云镜像拉依赖的速度和成功率都会明显提升。5.2 数据库版本导致的建表失败很多同学用的是MySQL 8.0然后用到了Navicat导出的SQL脚本结果在MySQL 5.7上建表失败报错信息要么是utf8mb4_0900_ai_ci字符集不认识要么是CHECK约束语法不支持。MySQL 8.0引入了许多5.7没有的语法特性导致SQL脚本不兼容。如果你的目标是5.7建表语句里就别写CHECK约束字符集统一用utf8mb4_general_cidatetime类型用default current_timestamp设置默认值这个写法在8.0和5.7都能过。另外一个经典坑数据库连接URL没带serverTimezoneAsia/Shanghai参数导致查询出来的时间比本地时间少8小时。这是JDBC驱动在解析datetime类型时的时区问题。解决方案是在spring.datasource.url里加上?serverTimezoneAsia/ShanghaiuseUnicodetruecharacterEncodingutf8。注意这里的useUnicode和characterEncoding也要写否则中文会变成问号。5.3 跨域问题与上传文件路径问题前端页面跑在8081或者80端口后端跑在8080端口浏览器就会拦截跨域请求。解决方案有两个一个是在后端写一个WebMvcConfigurer的配置类重写addCorsMappings方法设置允许的源、请求头和方法另一个是前端通过Nginx反向代理把/api的请求转发到后端地址。毕设阶段我建议用后端允许跨域的方式代码简单不用额外配Nginx。静态资源上传是另一个高频问题。商品图片上传到后端的本地磁盘文件夹但前端通过访问IP加路径却找不到图片这是因为SpringBoot默认不会把你自定义的磁盘目录映射为静态资源。解决方式是配置一个资源映射用WebMvcConfigurer的addResourceHandlers方法把/file/**这个请求路径映射到你的本地上传目录物理路径。这样前端就能通过http://localhost:8080/file/xxx.jpg访问到了。注意Linux服务器和Windows服务器的路径写法不同Windows下要用file:E:/upload/用一个配置项维护这个路径方便部署时修改。5.4 答辩高频问题应对答辩环节老师有几个问题特别爱问。第一个是你这个项目的亮点是什么不要回答什么用了SpringBoot开发效率高这种话要讲具体的比如抢单功能的敏感条件在数据库层面做并发控制避免超卖、订单状态机让整个流程有明确可追踪的节点、统计分析用缓存减少了对数据库的压力。第二个是项目中遇到最大的技术难点是什么回答要有故事性比如骑手抢单场景并发导致超单我最初用了悲观锁性能较差后来改成了Update语句带where条件的乐观锁方案性能提升了而且逻辑更简洁。第三个是数据库为什么这样设计你可以讲冗余字段的设计思路比如订单快照字段如何保证历史数据的可追溯性。提前准备好这三个问题的回答答辩现场会比临场发挥稳很多。5.5 从毕设到完整项目的几个加分扩展点如果你时间还有富余建议做这几个扩展。第一个是把静态文件上传换成对接MinIO对象存储这个可以在论文里写解耦本地存储依赖提高可扩展性。第二个是给系统加一个RabbitMQ消息队列订单创建后通过MQ通知商家和骑手而不是在业务方法里同步调用这样业务链路更解耦也更接近生产环境。第三个是把前端Vue2换成Vue3 TypeScript虽然学习成本高一些但简历上写Vue3会更有竞争力。第四个是加一个基于WebSocket的全站消息中心把订单状态通知集成进去。不过我得提醒一句这些扩展点量力而行。如果你的核心功能还没做完、论文还没写那就别盲目加先把基本盘做稳。一个功能完整、代码整洁、能流畅跑通的系统比一个有残缺的高级功能更能打动评审老师。写在最后的个人体会回头再看这个项目我觉得最值得学的不是Spring Boot怎么配、CRUD怎么写而是把一个真实场景拆成一整套系统方案的能力。从需求分析到数据建模从并发控制到权限设计每一步都在逼你想清楚为什么。很多同学在校阶段写代码习惯先把功能跑通再说但做毕设这段时间如果能养成先设计再编码、每段代码都知道自己在解决什么问题的习惯那这个毕业设计对你实际能力的提升会比想象中大得多。我学弟做完这版之后自己又在上面加了个定时任务每天凌晨对前一天订单做汇总、生成日报他说没想到写起来这么顺手这就是平时训练的结果。如果你现在正在为毕设选题发愁或者已经开了头又卡在某个环节那这套校园配送系统值得你动手试一遍。代码不难难点全在把细节想清楚。照着上面的思路把数据库表建好把订单状态跑通把抢单的并发控制写上你拿到的不仅是一个能答辩的项目更是一套能写进简历的完整经验。