基于Spring Boot+Vue的校园跑腿平台系统设计与实现
2026/9/9 12:18:27 网站建设 项目流程

毕业设计选这个题目的人不少,但真正能跑通、能讲清楚的项目其实不多。校园跑腿便利平台系统,从业务上看是典型的“多角色 + 订单流转 + 实时提醒”场景,从技术上又是 Spring Boot + Vue 的标准全家桶搭配,用来做计算机毕业设计非常合适,既能展示后端接口设计和数据库建模能力,又能体现前端页面交互和工程化意识。这篇文章我会把整个项目从需求拆解到落地实现完整过一遍,尤其是那些文档里不会告诉你的坑和设计细节,尽量让还没开工的同学直接就能照着做。

这个系统能做什么,一句话说清楚:学生注册登录后发布取快递、代买饭、送文件之类的跑腿需求,骑手端接单并完成配送,管理员在后台看订单、管用户、处理投诉。整体不复杂,但核心流程环环相扣,非常适合把 Spring Boot 的认证鉴权、业务事务、接口设计,以及 Vue 的组件通信、路由权限、状态管理全部串起来练一遍。如果你是准备用来做毕设,或者刚学完 Spring Boot 和 Vue 想做项目巩固,这篇文章应该能帮你省掉不少查资料的功夫。

1. 项目整体设计与思路拆解

1.1 校园跑腿平台的真实需求

很多同学拿到这类题目第一反应是急着建表写代码,其实最应该先做的是把“角色”和“流程”理顺。校园跑腿平台里至少有三个角色:普通用户、跑腿骑手(通常也是学生)、管理员。用户和骑手可能复用同一张用户表,通过角色字段区分,也可以拆成两张表,具体看你想把权限模型做得多细。

业务核心流程就三类:下单、接单、完成。下单时用户填写取件地址、送达地址、物品描述、期望完成时间、小费金额;骑手在可接单列表里看到订单,考虑距离和报酬决定是否抢单;接单后订单进入“待取货 -> 配送中 -> 已完成”的流转;如果用户取消或骑手拒绝,订单状态还得有“已取消”“已退款”等兜底状态。别小看这几个状态,设计不合理后面写业务逻辑会非常痛苦。

需求层面还有一个容易被忽略的点:通知。用户下单后骑手接单,用户应该收到提醒;骑手完成配送,用户要确认并结算。如果不想接短信或微信推送,最简单的做法是系统内站内信通知,配合底部消息红点,做完之后拿给老师演示效果也很直观。

1.2 功能模块与角色边界

我建议你按角色拆功能模块,这样写文档和答辩都方便。

用户端功能:注册登录、浏览公告、发布跑腿订单、查看自己发布的订单状态、取消未接单订单、确认收货并支付、订单评价、个人中心、充值或绑定支付方式(毕设里可以做成虚拟余额)。

骑手端功能:查看可接单列表、抢单、更新订单状态(取货、送达)、查看我的接单记录、收益统计、个人资料维护。

管理员端功能:用户管理(禁用/启用)、骑手审核(如果骑手需要单独认证)、订单管理(查看全部订单、处理异常)、分类管理(跑腿类型如代取快递、代买饭、代排队)、公告管理、数据看板(订单量、用户量、收入概览)。

模块划分清楚后,前后端的菜单、接口、数据库表都好对应。很多同学做毕设容易把接口写得特别散,其实就是前期没做模块划分,建议在动手写代码前先画一张角色-功能-接口的粗略映射表。

1.3 为什么是 Spring Boot + Vue

这个组合是目前 Java Web 方向毕业设计的主流搭配。Spring Boot 解决了 Spring 系列繁琐的 XML 配置问题,内嵌 Tomcat,打成 Jar 包就能跑,非常适合快速交付;Vue 则把前端页面拆成组件,配合 Element Plus 或 Vant 这类组件库,后台管理系统和移动端的 H5 页面都能快速搭起来。

和传统 JSP + Servlet 相比,前后端分离是这套方案最明显的优势。后端只需要输出 JSON 接口,前端通过 Axios 调用,二者通过接口文档或 Swagger 约定数据结构。这意味着你可以把后端和前端分阶段开发,也可以直接在一个工程里分开目录管理,灵活性很高。

另外,Spring Boot + Vue 的生态资料非常庞大,遇到问题几乎都能搜到解决方案。对毕业设计这种时间紧张的任务来说,可参考案例多就是很大的优势。如果选太冷门的框架,写代码和研究框架的时间可能比业务开发还多,得不偿失。

2. 技术选型与核心原理

2.1 后端技术栈与版本取舍

后端建议使用 Spring Boot 2.7.x + JDK 8 或 JDK 11,这个组合是国内绝大多数高校机器和教程视频里的默认配置。具体组件可以按下面的表选:

组件推荐选择原因
核心框架Spring Boot 2.7.18稳定、资料多、兼容 JDK 8
ORMMyBatis-Plus 3.5.x避免写大量 XML,内置分页和代码生成器
权限认证Spring Security + JWT 或 Sa-Token无状态认证,适合前后端分离
数据库MySQL 5.7 或 8.0免费、主流、部署方便
缓存Redis(可选)存验证码、热点订单、在线状态
接口文档Knife4j 或 springdoc自动生成 Swagger 文档,答辩演示好用
工具类Hutool封装日期、验证码、ID 生成等常用功能

这里特意强调版本问题,是因为最近很多人直接装了 Spring Boot 3.x,结果发现 JDK 要 17 起步,javax 包也变成了 jakarta,很多网上的旧教程代码直接报红。我建议不要追求最新版本,毕业设计追求的是可复现和稳定,不是新特性。Spring Boot 2.7 + MyBatis-Plus 3.5 是我实测最稳妥的组合,如果老师没强制要求,就用这一套。

2.2 前端技术栈与工程化

前端如果做的是管理后台和用户端 H5,技术上可以统一到 Vue 3 + Vite + Pinia + Vue Router + Axios + Element Plus。但要注意:如果你更熟悉 Vue 2,那继续用 Vue 2 + Element UI 也完全能完成题目,别为了追新在项目里花一星期从零学 Vue 3。

我的建议是:前端分两个入口,但不一定拆两个工程。可以做一个 admin 端(桌面端管理后台),再做一个 user 端(移动端 H5,适配手机屏幕)。两个入口共享同一套登录状态和 API 封装逻辑,用 Vue Router 的 history 模式区分路由前缀,比如/admin开头的是后台,其他的是用户端。这样做能减少重复代码,答辩时也更好讲清楚“前端多端适配”的工程化思路。

Axios 请求封装是个必做项。统一拦截器里处理 JWT token 的携带、401 跳转登录、错误提示,这才是前后端分离项目里常见的真实做法。状态管理用 Pinia 存用户信息和菜单权限,比每个页面都调一遍接口舒服得多。

2.3 订单状态流转与并发控制

订单状态是这类系统的核心难点。你可以用简单的整型枚举表示,比如:0 待接单、1 已接单、2 配送中、3 已完成、4 已取消、5 已退款。但更优雅的做法是设计一个订单状态机,把每个状态下允许的操作和跳转目标状态定义清楚,并配合数据库操作保证并发安全。

这里最常见的问题是“抢单并发”。多个骑手同时抢同一单,如果代码写成先查询订单状态,再执行更新,很容易出现超卖现象:两个骑手都查到订单是待接单,于是都更新成功。解决方案有两种,我在实际项目里都试过:

第一种是乐观锁。在订单表加一个version字段,更新时带上WHERE id = ? AND version = ?,如果影响行数为 0,说明被别人先抢到了。第二种是条件更新,直接写UPDATE order SET rider_id = ?, status = 1 WHERE id = ? AND status = 0,同样利用影响行数判断。第二种写起来更简单,推荐优先使用。

服务层的业务方法要加@Transactional注解,保证订单状态更新和通知记录写入在同一个事务里,不然可能出现订单状态变成已接单但用户没收到任何提醒的诡异现象。

3. 数据库设计与核心实现

3.1 核心表结构设计思路

数据库设计至少要覆盖用户、订单、通知、评价、分类和余额流水这几张表,我建议用下面的核心表做打底,再按需求微调。这里先看三张最重要的表。

用户表user

字段类型说明
idbigint主键,用雪花算法或自增
usernamevarchar登录名
passwordvarcharBCrypt 加密后的密码
roletinyint1 用户 2 骑手 3 管理员
phonevarchar联系电话
balancedecimal虚拟余额
statustinyint0 禁用 1 正常

订单表orders

字段类型说明
idbigint主键
order_novarchar业务订单号,方便展示和查询
user_idbigint下单用户
rider_idbigint接单骑手,初始为空
category_idbigint跑腿类型
pickup_addressvarchar取件地址
delivery_addressvarchar送达地址
item_descvarchar物品描述
reward_amountdecimal小费金额
statustinyint状态枚举
versionint乐观锁版本号
create_timedatetime下单时间
update_timedatetime更新时间
finish_timedatetime完成时间

通知表notification

字段类型说明
idbigint主键
user_idbigint接收人
contentvarchar通知内容
is_readtinyint是否已读
create_timedatetime创建时间

很多人建表时会把订单表的外键直接指向用户表,我建议保留外键约束但不要过度使用,在代码层通过逻辑关联去查用户信息就够了,避免删除用户时被外键拦住。关于金额字段,只用decimal,不要用floatdouble,这个坑踩过的人不在少数。

数据库初始化脚本组织上,建议把建库建表语句、初始数据语句、测试数据语句分成三个 SQL 文件,命名schema.sqldata.sqltest_data.sql,这样交文档、答辩演示、重新初始化都很清晰。

3.2 后端核心模块代码落地

后端代码结构建议按以下包组织:config、controller、service、mapper、entity、dto、vo、common、security。不要把所有代码堆在 controller 里,哪怕项目很小也要分层,这是答辩时加分的关键点。

下面看两个核心接口的伪代码级实现。

第一是用户发布订单。这个接口的核心是校验参数、生成订单号、设置初始状态为待接单,然后插入数据库:

@PostMapping("/order") public Result<String> createOrder(@RequestBody @Valid OrderCreateDTO dto) { Orders order = new Orders(); order.setOrderNo(generateOrderNo()); order.setUserId(currentUserId()); order.setCategoryId(dto.getCategoryId()); order.setPickupAddress(dto.getPickupAddress()); order.setDeliveryAddress(dto.getDeliveryAddress()); order.setItemDesc(dto.getItemDesc()); order.setRewardAmount(dto.getRewardAmount()); order.setStatus(OrderStatus.WAITING.getCode()); order.setVersion(0); orderService.save(order); return Result.success(order.getOrderNo()); }

这里要注意getRewardAmount()的精度问题,前端传来的金额最好以元为单位,后端用 BigDecimal 接收,避免浮点误差。

第二是骑手抢单接口,这是整个项目并发风险最高的地方。用条件更新的方式保证不会重复抢单:

@Override @Transactional public boolean grabOrder(Long orderId, Long riderId) { LambdaUpdateWrapper<Orders> wrapper = new LambdaUpdateWrapper<>(); wrapper.eq(Orders::getId, orderId) .eq(Orders::getStatus, OrderStatus.WAITING.getCode()); Orders update = new Orders(); update.setRiderId(riderId); update.setStatus(OrderStatus.ACCEPTED.getCode()); int rows = ordersMapper.update(update, wrapper); if (rows == 0) { throw new BizException("订单已被抢走"); } notificationService.send(orderId, "你的订单已被骑手接单"); return true; }

加上@Transactional保证状态更新和通知产生的数据库操作要么一起成功,要么一起回滚。这里不额外用 Redis 分布式锁,是因为条件更新在数据库层面已经把并发控制住了,对毕设项目来说足够。

第三是骑手完成订单后,用户余额给骑手入账。注意不要在回调里做“先查余额,再改余额”,而是用一条 SQL 原子更新:

@Update("UPDATE user SET balance = balance + #{amount} WHERE id = #{riderId}") int increaseBalance(@Param("riderId") Long riderId, @Param("amount") BigDecimal amount);

同时要在余额明细表里插入一条流水记录,方便后续对账。

3.3 前端核心页面与 API 对接

Vue 端页面不需要做得太复杂,但一定要保证闭环。核心页面包括用户端首页、发布订单页、订单列表页、订单详情页、个人中心;管理端包括登录页、数据概览、订单列表、用户列表、分类管理、公告管理。

用户端首页可以做成一个简单的分类入口 + 附近可接单列表(如果是 H5 端)。列表数据通过GET /api/order/list?status=0拉取,用卡片形式渲染。发布订单页使用表单提交,提交成功后跳到订单列表页。

代码层面有几个必须注意的点:

token 携带。登录成功后保存到 localStorage,然后在 axios 请求拦截器里带上 Authorization 头:

service.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) { config.headers['Authorization'] = 'Bearer ' + token } return config })

响应拦截器处理统一错误:

service.interceptors.response.use( response => { const res = response.data if (res.code !== 200) { ElMessage.error(res.msg || '请求失败') if (res.code === 401) { router.push('/login') } return Promise.reject(new Error(res.msg)) } return res }, error => { ElMessage.error('网络异常') return Promise.reject(error) } )

路由守卫单独写一个文件,在进入需要登录的页面前判断用户是否存在,否则跳转登录页:

router.beforeEach((to, from, next) => { const token = localStorage.getItem('token') if (to.meta.requiresAuth && !token) { next('/login') } else { next() } })

后端接口设计上,建议统一返回结构{ code, msg, data },code 为 200 表示成功,401 表示未登录或 token 失效。这个规范一旦定好,前后端联调基本不会被数据结构问题卡住。

4. 实操过程与部署细节

4.1 本地开发环境准备

如果从零开始,你需要准备这些环境:

  1. JDK 8 或 11,设置好 JAVA_HOME。
  2. Maven 3.6+,settings.xml 里最好配置阿里云镜像,否则依赖下载慢到怀疑人生。
  3. MySQL 5.7 或 8.0,安装时设置 root 密码,建议统一用 root / 123456,省得后面代码里老是不匹配。
  4. Node.js 16 或 18,npm 源建议切到淘宝镜像:npm config set registry https://registry.npmmirror.com
  5. IDE 我用的是 IntelliJ IDEA,社区版也够用;前端用 VSCode 或 WebStorm 都可以。

环境准备阶段最容易出的问题是 Maven 依赖和 npm 依赖下载失败。解决方法也简单:依赖下载失败后不要反复重试,先把本地仓库里对应的.lastUpdated文件删掉,换镜像源再重下。

4.2 典型部署流程

开发环境下,后端直接运行main方法启动,前端执行npm run dev然后访问localhost:5173,再通过 vite 的 proxy 配置把/api代理到后端localhost:8080,这样开发时不需要处理跨域。

生产环境部署可以简化。后端先mvn clean package -DskipTests打成 jar 包,然后java -jar运行。前端执行npm run build生成dist目录,这个目录可以交给 Nginx 托管。Nginx 里把/api反向代理到后端服务,这是一种非常常见的前后端分离部署方式。

一个简化版的 Nginx 配置片段长这样:

server { listen 80; server_name localhost; location / { root /var/www/dist; try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://localhost:8080; } }

try_files那行很关键,否则前端用 history 模式时刷新页面会 404。这一点是 Vue 路由部署的高频问题,后面再细说。

4.3 关键配置与日志排查

后端application.yml里几个必配项:

server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/campus_errand?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai username: root password: 123456 mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0

serverTimezone=Asia/Shanghai不能省,否则 MySQL 连接经常报时间时区错误。MyBatis-Plus 配置日志输出后,控制台能看到每条 SQL 的实际执行语句,排查问题非常有用。上线时可以关掉,但开发阶段一定打开。

如果要用 Redis 缓存验证码或 Token,config 里还要加 Redis 连接配置,但毕设项目如果时间紧张,可以先用数据库表存储验证码,没必要为了赶上热点强行整合 Redis。

5. 常见问题与排查技巧实录

5.1 Spring Boot 版本过高引发的连锁问题

这是最近咨询里最多的一类。装了 Spring Boot 3.2 或 3.3 的,默认要求 JDK 17,并且很多老依赖包不再适用。比如javax.servlet改成了jakarta.servletspringfox的 Swagger 接入直接无法使用。解决方案有两种:一是直接退回 Spring Boot 2.7.18 和 JDK 8,兼容性最好;二是如果必须用 Spring Boot 3,就换成jakarta前缀相关依赖,Knife4j 必须用 4.x 及以上版本。

我给的建议非常明确:毕业设计不是搞技术预研,老老实实用 2.7,少给自己挖坑。

5.2 数据库连接与初始化常见坑

一个高频报错是:

java.sql.SQLException: Access denied for user 'root'@'localhost'

大多数情况不是密码错误,而是 MySQL 8.0 默认用户认证方式为caching_sha2_password,老驱动不兼容。解决方法是把驱动换成com.mysql.cj.jdbc.Driver,并确保 MySQL 连接依赖版本在 8.0.x 以上。

另一个坑是数据库脚本编码错误。如果用 Windows 记事本编辑 SQL 文件,里面包含中文就可能出现乱码,进了数据库后中文全是问号。建议所有 SQL 文件统一用 UTF-8 编码,并在 MySQL 客户端执行前先source测试,或者通过 IDE 的数据库工具导入。

5.3 前端跨域与路由问题

开发环境最容易遇到跨域。解决方法分两层:后端加上全局 CORS 配置;前端 Vite 配置 proxy。推荐以后端为主,一个全局配置类就能解决,不用每个接口单独处理。

@Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOriginPatterns("*") .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") .allowedHeaders("*") .allowCredentials(true) .maxAge(3600); } }

Vue history 路由刷新 404 的问题,前面 Nginx 配置已经提到了,本地开发时 Vite 其实默认支持 history 模式降级,不用担心;部署到服务器上才最容易踩。

5.4 并发与状态一致性问题

抢单并发问题在测试阶段可能不明显,因为本地只有一个浏览器,很难复现。你可以用 JMeter 或 Postman 的并发请求功能压一下接口,验证条件更新是否生效。我测试时曾经遇到一次两个骑手都提示抢单成功,原因是业务代码里先查再改,没有用条件更新,后来改掉才正常。

订单状态和支付状态不一致的问题也常见。比如用户点击取消订单,但订单已经进入配送中,前端又没有限制按钮,用户还可以发起取消。解决办法是在后端接口里做状态校验,前端隐藏按钮只是体验优化,真正的安全边界必须放在后端。

常见问题原因解决思路
Spring Boot 3 项目大量包名报错依赖版本不兼容 JDK17/jakarta降到 2.7 + JDK8
MySQL 连接拒绝密码错误或认证方式不兼容检查驱动、修改用户认证方式
中文乱码文件编码与数据库字符集不一致统一 UTF-8 编码
前端刷新 404history 路由未配置 fallbackNginx try_files 指向 index.html
抢单重复接单先查后改未做并发控制条件更新或乐观锁
接口 401 循环跳转token 失效后请求未放行登录页和刷新 token 接口加入白名单
接口返回后页面不更新没有重新拉取列表或状态管理未更新确认数据响应后在回调中重新 fetch

最后再多说一点经验。做这类毕业设计,最容易错的就是一开始直接写代码,写到一半发现表结构缺字段、接口结构不统一,返工成本极高。我个人建议先花两三天做设计:角色、流程、核心表、接口清单、前端页面清单,都列出来。等设计文档差不多了,再进入开发。开发时先跑通“用户发布订单 -> 骑手抢单 -> 骑手完成 -> 用户确认”这条主干链路,再去补公告、评价、数据统计这些外围功能。主干通了,项目的基本盘就稳了。扩展方向上也留有余地,比如接入地图选点、微信小程序端、消息推送,都能作为论文里的亮点,但前提是基础版本已经稳定可跑。

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

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

立即咨询