资讯详情

资讯详情

校园跑腿系统实战:Spring Boot+MyBatis Plus+Redis全栈开发与部署

做校园跑腿系统这个项目其实最开始是被“作业”逼出来的。之前有门课要求做一个完整的 Web 项目我一开始想得很简单不就是几个页面加一个数据库吗真做起来才发现学生下单、接单、结算、状态同步、管理员审核这一整条链路串联起来之后琐碎到让人怀疑人生。今天把整个过程整理出来给准备做 Java 课程设计、毕业设计或者想练手 Spring Boot 的同学一个参考。这个项目我用了 Spring Boot 2.x MyBatis Plus MySQL Redis前端部分采用了 Vue 和 Element UI代码量大概在一万五千行左右带完整的数据库脚本、接口文档和部署说明。文章会重点拆解设计思路、核心模块实现、开发中踩过的坑和项目交付时的注意事项照着做基本上能手把手跑起来。1. 项目整体设计与需求拆解1.1 校园场景下“互助代购”到底解决什么问题大学校园里的代购需求和外面的外卖平台不太一样。宿舍离快递点远、食堂某个窗口排队太长、图书馆突然需要打印资料这些需求的特点是金额小、距离近、时效性强。学生跑腿并不想赚太多钱更多是图个方便顺便能认识人。所以在功能设计上不能简单照搬美团跑腿那种重运营模式需要把“互助”的轻量化属性体现出来。我在划分需求时就明确了几个关键词发布、接单、结算、评价。用户角色分成三类普通学生、跑腿员、管理员。普通学生可以发布任务、查看订单、给跑腿员评价跑腿员可以抢单、上传完成凭证、赚取佣金管理员则负责审核跑腿员资质、处理纠纷、管理用户状态。这三个角色的核心业务闭环是学生发布带报酬的跑腿任务跑腿员抢单或平台指派任务完成后双方确认管理员对整个流程有兜底权限。把业务闭环拆清楚之后后边的数据表设计和接口设计才不会乱。常见的课程设计项目容易把系统做成“增删改查展示品”页面很华丽但订单状态机、支付回调、并发抢单这些实际业务逻辑一点没有。我这次就刻意把重点放在了“真正能跑通业务”上。比如用户余额体系、订单流转状态、抢单时的锁处理、超时自动取消这些细节都是在真实使用场景中一定会遇到的问题。1.2 模块划分与功能清单整理我画的第一版功能清单大概有二十几项后来砍掉了一些华而不实的功能比如实时地图轨迹追踪校园场景根本用不上、在线聊天用现有的即时通讯工具更合适。最终敲定的功能模块如下用户模块注册、登录、忘记密码、个人信息维护、手机号绑定、头像上传。发布任务模块新建代购任务、填写取送地址、设置报酬、指定送达时间。订单模块订单列表、订单详情、取消订单、确认完成、申诉订单。接单模块抢单大厅、接单、送达确认、异常上报。支付结算模块余额充值、代购费扣除、跑腿员佣金入账、提现。管理后台模块用户管理、任务审核、订单管理、数据统计。消息通知模块接单通知、订单状态变更通知、系统公告。这七个模块之间不是平行的订单模块是核心枢纽。我画了一张流程图反复看从用户下单到跑腿员抢单再到送达完成每一步都会改变订单的状态同时触发账户金额变动。状态一旦不同步数据就乱了。1.3 为什么我选择前后端分离而不是传统 JSP如果你只是为了交作业用 Spring Boot Thymeleaf 或者 JSP 确实更简单模板渲染直接在服务端完成不用处理跨域问题。但我最终还是选了前后端分离原因是这个项目除了要跑通还要给后续扩展留空间。以后如果想加小程序端或者移动端前后端分离的接口可以直接复用。技术栈定为 Spring Boot MyBatis Plus Redis JWT Vue Element UI。这里有个很关键的决策项目不需要复杂的微服务架构。单机部署完全够用没必要把服务拆得七零八落。微服务强调的是团队协作和独立扩展一个人做课设强行上微服务只会增加部署成本。我在设计架构时坚持“单体优先模块划分清晰”的原则代码里用 maven 多模块管理但最终打成同一个 jar 包运行。2. 技术栈选型与核心架构细节2.1 Spring Boot 在系统里的核心作用Spring Boot 在这个项目里负责的是“组装”。它可以帮我们快速装配 Web 层、数据层、缓存层和安全层。我用 Spring Boot 2.7 版本主要看中它的自动配置机制不需要写一堆繁琐的 XML 配置。内置 Tomcat 也省去了外部容器的部署步骤。但要特别注意Spring Boot 版本不是越新越好。我当时试过 Spring Boot 3.x发现它要求 Java 17 起步有些 MyBatis 相关的第三方依赖还在适配过程中踩了不少坑。如果你的项目用到了老版本的第三方组件尽量先确认兼容性再升级。最后我稳定在 2.7.x搭上 JDK 8整个运行环境非常顺畅。项目的整体结构不是很花哨但分层很清晰com.example.errand ├── controller // 接收前端请求 ├── service // 业务逻辑层 ├── mapper // MyBatis Plus 数据访问层 ├── entity // 数据库实体类 ├── dto // 数据传输对象 ├── config // 配置类Redis、拦截器、跨域等 ├── common // 公共工具类、统一返回结果、异常处理 └── task // 定时任务超时订单处理分层的作用不是为了好看而是为了排查问题方便。比如订单超时异常我先去 controller 看参数传递没问题再去 service 层找业务逻辑基本能快速定位。2.2 数据库表设计与状态流转数据库表在这个项目里是最重要的部分我前前后后改了四五版才定稿。最终的表结构包括user用户表、errand_order订单主表、order_status_log订单状态日志表、balance_record余额流水表、withdraw_record提现记录表、message消息通知表、feedback意见反馈表等。订单表的核心字段需要重点设计。除了基本的order_no、user_id、runner_id之外还有type代购类型、reward_amount悬赏金额、status订单状态、consignee_address取货地址、delivery_address送达地址、expected_time期望送达时间、actual_delivery_time实际送达时间、remark备注信息。其中订单状态我用的是数字字典而不是字符串。状态值含义说明0待抢单用户刚发布还没人接1已接单跑腿员抢单成功进行中2待确认跑腿员已送达等待用户确认3已完成用户确认交易完成4已取消用户取消或超时自动取消5申诉中双方对订单有争议用数字做状态的好处是扩展方便。我只需要在代码里定义常量类前端传数字后端统一判断不会出现拼写错误的问题。数据库设计还有一个重点余额流水表必须与订单表独立。用户的余额不能直接加减就完事每次变动都要记录到流水表里包含user_id、amount正负表示收入支出、biz_type、biz_order_no、create_time。这样做的好处是对账时有依据出问题时可以“溯源”而不是两眼一抹黑。2.3 JWT 鉴权方案与 Redis 缓存设计登录鉴权我使用的是 JWTJSON Web Token流程是用户输入账号密码后端校验成功生成 token 返回给前端前端把 token 存在 localStorage 里每次请求在请求头带上Authorization: Bearer token。后端有一个拦截器统一解析 token 并放入 ThreadLocal 供后续业务使用。游客登录和身份区分也在这个环节一起处理了。user表里有个role字段0 表示普通用户1 表示跑腿员2 表示管理员。拦截器里判断接口需要的角色和当前用户角色是否匹配不匹配直接返回 403。Redis 我并没有用在特别玄乎的地方主要干了两件事第一是存储登录 token 的黑名单用户注销或者管理员强制用户下线时把这个用户的 token 特征值写进 Redis有效期设置为 token 的剩余时间拦截器里先查黑名单命中就拒绝访问。第二是缓存抢单大厅的商品列表因为“待抢单”列表会被高频刷新每次都查 MySQL 压力不小缓存 30 秒过期数据一致性影响很小。2.4 接口设计统一返回格式前端和后端协作最怕接口格式不统一。我定义了统一的返回结果类ResultT字段包括code、message、data。成功时 code 为 200失败时 code 不对前端只判断 code 是否等于 200然后分支处理。这样的好处是前端写起来很统一出现错误能直接弹 toast 显示 message。我还是用了自定义异常BusinessException。业务代码里凡是校验失败或者前置条件不满足直接 throw 异常由全局异常处理器捕获并转成标准的返回格式。这样 controller 层就不会被冗长的 try/catch 淹没。3. 核心业务流程与关键实现3.1 发布任务到下订单的完整流程分析发布任务接口的入参包含任务类型、报酬、地址、备注、期望时间等信息。后端接收后第一步是校验参数比如报酬不能为 0、地址不能为空。第二步检查用户余额是否充足这里不是只扣余额而是先预冻结。简单说用户发布一个 20 元的任务系统先把这 20 元从可用余额冻结起来而不是直接扣除。这样设计的好处是订单取消时解冻即可不需要复杂的退款操作。跑腿员完成后系统把 20 元中的 80%平台抽成比例可配结算给跑腿员剩余部分归平台。如果订单取消冻结金额解冻后立即退还给用户。冻结与扣款的实现方式是通过balance_record表记录的。发布时生成一条金额为 -20 的流水类型为“冻结”同时更新用户表里的freeze_amount字段。完成时新增一条 -20 的记录类型为“解冻”再新增一条 16 的记录进入跑腿员账户。3.2 抢单环节的并发控制抢单是这个项目最需要抠细节的地方。如果不做并发控制多个跑腿员同时抢同一个订单会出现超卖问题。同一个订单被两个人同时抢到后果就是订单数据错乱用户体验极差。我采用了两种手段配合第一层是乐观锁。抢单时 SQL 语句更新订单表条件必须是status 0 AND id ?。如果更新影响行数为 0说明订单已经被抢走操作失败。MyBatis Plus 的 UpdateWrapper 里加条件就行不需要额外处理。第二层是Redis 分布式锁。在抢单接口中基于订单 ID 加锁保证同一时间只有一个线程能对某个订单执行抢单逻辑。Redis 锁我用了setnx命令并设置了过期时间防止死锁。这样即使多个请求同时进来锁能保证只有一个跑腿员能进入后续逻辑。还有一个细节是抢单成功之后要把订单的runner_id更新同时给下单用户发一条站内消息。消息是异步处理的直接发到消息表里用户在“我的消息”里能看到。3.3 订单状态机与超时处理机制订单状态流转我没用第三方状态机框架自己用常量类加枚举控制。状态机的好处是能明确哪个状态下允许执行哪个操作。比如只有“待抢单”状态的订单可以取消只有“已接单”状态的订单可以标记送达只有“待确认”状态的订单可以确认完成。这些判断散落在各业务代码中核心逻辑是一个OrderStatusValidator校验器。超时未支付、超时未抢单的处理用 Spring Boot 定时任务实现。我配置了一个Scheduled(cron 0 */5 * * * *)的任务每五分钟扫描一次订单表找到状态为“待抢单”且created_at超过 30 分钟仍未接单的订单自动取消并将冻结金额解冻退还给用户。定时任务在单机环境下很好用不需要引入消息队列。但要注意定时任务的执行时间和数据库压力。每次扫描要建好索引status和created_at字段加上联合索引避免全表扫导致慢查询。3.4 文件上传与图片存储代购任务有时候需要上传小票或者快递单照片跑腿员完成之后也需要上传凭证图。文件上传我采用了本地存储方案把图片存到服务器目录数据库只保存访问路径。上传接口接收MultipartFile校验文件大小限制在 5MB 以内文件格式只允许 jpg、png、jpeg。文件重命名用UUID 时间戳拼接避免中文文件名乱码。存储路径按日期分目录比如upload/20250118/xxxx.jpg方便管理员按时间查看。文件上传在本地跑没问题前后端联调时跨域就成了第一道坎。前端地址是localhost:8081后端是localhost:8080如果不配置跨域请求会被浏览器拦截。我在后端写了一个全局跨域过滤器允许三部分来源同时允许Authorization请求头跨域这样 JWT 才能在请求中正常传递。讲到这必须提醒一句处理上传文件时必须拦截脚本文件上传和恶意文件名。文件路径拼接时不能直接把用户提交的文件名拼进路径里用服务端重新生成的文件名最保险这能顺带躲开目录穿越问题。3.5 通知模块的轻量实现通知消息我最初考虑过接入 WebSocket但后来想了想校园互助跑腿的通知方式其实没有实时到秒级别的需求。用户在页面上做操作浏览器定时轮询未读消息数就够了。没有特殊必要没必要为一对一推送引入复杂的长连接方案。实现方式是在消息表里加一个is_read标记前端每次进系统拉取消息列表同时轮询未读数。这样接口只需要写一个“获取未读消息数量”的查询前端每 15 秒调用一次。这个小方案后台压力很小不过要注意数据库预热和索引优化。4. 开发过程中踩过的坑与排查心得4.1 Spring Boot 版本和 JDK 环境不一致导致的坑第一次搭建项目的时候我用的电脑上装的是 JDK 17但这套骨架里很多依赖在 JDK 8 下验证过。启动时直接报错UnsupportedClassVersionError。排查到后来才发现是小版本兼容问题Spring Boot 2.7 官方支持 Java 8 和 Java 11我硬要用 17 确实是自找麻烦。换成 JDK 8 后整个世界安静了。另外 Spring Boot 2.7 的配置项命名改动也比较大。比如redis.host这种老写法不再支持必须使用spring.redis.host或者 new 的命名方式。配置项对不上会静默加载失败服务虽然能启动但访问 Redis 一直报 Connection Refused。排查时优先核对配置前缀。4.2 数据库设计时遗漏索引导致的慢查询订单列表页刚开始打开是秒开数据量到了几百条就开始卡。我用EXPLAIN查看 SQL 执行计划发现查询走了全表扫描。原因是订单表上的user_id、status、created_at字段没有加索引。加了联合索引后查询速度明显上来了。特别提醒状态字段不是加索引一定有好处。如果状态值就 6 个区分度太低优化器可能依然选择全表扫描。更好的方式是用status created_at联合索引既能通过状态过滤又能按时间排序。对于学生项目数据量不大索引不是越多越好够用就好。4.3 Redis 连接池耗尽问题抢单接口上线测试并发时出现过一个奇怪问题并发越高请求越慢最后大量请求超时。排查发现是 Redis 默认连接池太小。Jedis 连接池默认只有 8 个连接高并发抢单场景下全部占满其他请求排队等待连接。解决方案是在配置文件中调大连接池参数最大连接数改到 100最大等待时间改到 1000ms。另外排查代码中发现有一个地方在循环里重复创建RedisTemplate对象导致连接资源没有复用。修正为全局单例模式后性能问题得到解决。4.4 订单状态并发更新导致逻辑错乱实际联调时出现了一个不好复现的 bug用户取消一个正在进行中的订单居然成功了。后来查日志发现是用户端点了两次取消按钮第一次取消时订单初始状态是“待抢单”第二个请求进来时订单状态已经变成“已取消”但判断条件没拦住把重复取消当成正常业务流程执行了。修复方案很简单取消接口必须带着ORDER_STATUS条件去更新具体做法就是先查订单当前状态然后更新时在 where 条件里加上状态字段约束。这样即使两个请求同时进来一个成功另一个更新影响行数为 0直接提示失败。这个方案比代码里单纯加 if 判断更可靠数据库层面才是最后的防线。4.5 前端跨域请求丢失 Token 问题前后端联调接口时前端 Vue 项目通过 axios 发请求发现部分接口能拿到数据部分接口报“未登录”。排查后发现是 axios 拦截器在请求头加Authorization时由于跨域请求先在 preflight 阶段就被拦截了后端没有正确响应Access-Control-Allow-Headers。解决方法是配置跨域过滤器时显式添加允许的请求头集合包含Authorization、Content-Type。这里有个小细节跨域时会发送OPTIONS预检请求后端要直接放行OPTIONS请求并返回 200否则被拦截器挡在门外。5. 项目部署、交付与二次开发扩展5.1 本地环境搭建三步走拿到项目源码后本地跑起来并不复杂核心就是三步。第一步安装 MySQL 5.7 和 Redis将数据库脚本执行到本地。第二步修改application-dev.yml里的数据库地址和 Redis 地址。第三步启动 Redis 服务和后端服务这个项目用的是 Maven 打包执行mvn spring-boot:run或者直接运行主类。前端部分需要先在npm install安装依赖然后改一下代理配置让请求转发到后端地址。前端页面默认端口可以设置为 8081后端端口 8080通过 Vite 或 Vue CLI 的 proxy 配置解决跨域。5.2 生产环境打包与部署生产部署不建议使用spring-boot:run那只是开发模式。我实测最稳的方案是打成可执行 jar 包mvn clean package -DskipTests然后在服务器上运行nohup java -jar errand-system.jar --spring.profiles.activeprod 需要注意服务器上的 MySQL 和 Redis 数据连接信息要与 prod 配置文件保持一致。数据库脚本在首次部署时可以手动执行后续如果涉及表结构变更建议引入 Flyway 管理迁移脚本。课程设计项目简单点可以用 sql 文件手动导入但如果是真正的商用部署数据库结构变更还是要走版本管理。前端部署也简单npm run build生成静态资源通过 Nginx 反向代理把静态文件和后端接口路径分开配置。Nginx 里配置/api路径转发到 Java 后端其余静态资源指向前端 dist 目录。5.3 交付文档怎么梳理才不痛苦如果有同学需要把项目作为课程设计或者毕业设计提交文档是占很大比重的。我整理交付文档时按五个篇章来写查起来很方便需求说明篇把功能需求和非功能需求说清楚重点描述业务背景和用例图。数据库设计篇把每张表的字段含义、表间关系写明白贴出 ER 图和建表语句。接口文档篇列出每个接口的请求方式和参数说明。我编写过程中直接用 Knife4j 在线调试自动生成接口文档省了不少时间。部署说明篇包括开发环境、生产环境搭建步骤以及常见异常排查。测试报告篇设计测试用例覆盖主要业务路径比如抢单、取消订单、提现等。项目里带的运行视频和解说视频是给自己多一重保障。视频里我一般会按功能来演示而不是黑屏开一堆代码不动。先演示用户注册、发布任务、跑腿员抢单、确认完成然后再演示管理后台的审核、数据查询最后再讲解项目结构。这样看的人能快速对系统有直观认知。5.4 二次开发与扩展空间这个项目的扩展空间很大。如果后续想把它做成一个真正商用的小程序只需要把前端换成小程序端复用现有的后端 API 即可。支付方面目前是虚拟余额结算如果接入微信支付或支付宝需要通过商户号和签名验证复杂度会上升一个级别。但业务逻辑不用改太多在支付接口的返回值处理上增加异步回调通知即可。消息通知如果有进一步的需求可以考虑升级为 WebSocket 长连接实现真正的实时通知。我建议把整个通知模块独立成服务避免后续影响主流程。6. 一些实际经验与建议说了这么多技术细节想最后分享点做项目本身的心得。第一个心得是先写数据库表再写接口最后写前端页面这个顺序不能反。我尝试过先搭页面再去反推数据库结果改了很多次表结构吃力不讨好。数据结构确定之后接口的入参出参基本就稳定了前端照着接口写好模板联调效率能提升非常多。第二个心得是代码要写注释但不是每行都写。写注释的重点放在业务状态流转的说明上比如“冻结状态订单超时后自动解冻”这种行业务信息如果不写注释过一个月自己回来看都会愣一下。第三个心得是课程设计和真实商用的差距主要体现在健壮性测试上。单机自测往往只跑正常路径但一次真实场景中的异常排查就可能发现三四个并发漏洞。所以上下行整个流程测试要覆盖用户余额不足下单、抢单超时自动取消、重复点击取消按钮、文件上传失败重试这些异常分支一定要自己主动构造出来测一遍。第四个心得是关于学习方法。做这个项目让我最有长进的其实是查错过程。遇到一个奇怪的问题去查文档、看源码、写 demo 复现才是真正学会东西的时刻。Spring Boot 的优势是生态全资料多但资料质量参差不齐。建议优先看官方文档其次才是代码仓库里的示例项目。最后再聊一个小技巧项目命名规范。虽然领到作业、毕业设计或技术分享任务是多样的但你这个项目名之下目录命名统一用下划线不要混着中文/英文/拼音风格。包名里的目录、类名、方法名、变量名统一用驼峰式常量统一用大写加下划线。后面不管是自己维护还是答辩给老师讲代码整洁度都是第一印象。项目不是一个一次性的作业它是你后续扩展成小程序、注册成你真项目、甚至放进简历里的素材代码质量好后面省心的不是一点点。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →