Java+Vue+Spring Boot校园快递代取系统全栈开发实战

发布时间:2026/9/16 4:59:06
Java+Vue+Spring Boot校园快递代取系统全栈开发实战 前阵子整理项目仓库的时候翻出了当时做的这个校园快递代取系统Java Vue Spring Boot这套组合前前后后折腾了差不多三个月从需求梳理、数据库设计到前后端联调踩坑无数但也收获很大。当时做这个项目的初衷特别朴素学校驿站离宿舍太远高峰期排队老半天很多同学愿意出几块钱找人帮忙代取快递但一直缺一个能把“发单”和“接单”两边匹配起来的小平台。所以我就决定自己动手写一个——后端用Spring Boot提供接口前端用Vue做页面做成一套能真正跑通的前后端分离项目。写这篇文章的目的不只是给自己的项目做个复盘更想给准备做类似毕设或者打算练全栈的朋友一条可以直接照抄的路线核心功能怎么拆、订单状态机怎么设计、JWT鉴权怎么做、Vue页面怎么组织、前后端联调遇到问题怎么排查。我会把我在开发过程中遇到的那些奇奇怪怪的坑也一并写出来这些都是常规文档里看不到的实操经验。1. 项目整体设计与技术选型思路1.1 为什么选Spring Boot Vue聊聊选型背后的思考做这个项目之前我其实犹豫过要不要用传统方案比如JSP加Servlet或者Spring MVC配合Thymeleaf模板引擎渲染页面。但后来想了想现在的开发环境里前后端分离已经是大趋势工作以后接触的项目也基本都是这种模式。再加上“javavuespringboot”这个组合的生态太成熟了遇到问题随便一搜就有解决方案对新手来说容错率很高。所以我最后敲定的方案是后端用Spring Boot 2.x加MyBatis Plus操作MySQL前端用Vue 2加Element UI组件库通过RESTful接口完成数据交互。这个选型背后其实有一个很重要的考虑项目复杂度要匹配技术方案的复杂度。校园快递代取这种场景并发量不高业务逻辑也不算复杂核心就是解决信息撮合和订单流转两个问题。如果这时候硬上一套微服务、消息队列、分布式事务反而是自己给自己挖坑。技术栈的选择永远要服务于业务本身能用简单方案解决的事情就不要人为复杂化这是我在这个项目里体会最深的一点。另外说一下为什么选JWT而不是Shiro或者Session。因为前后端分离之后后端不能依赖Cookie自动携带Session如果还沿用传统的Session鉴权方式跨域场景下处理起来会比较麻烦。JWT的思路是把用户信息加密放进Token里前端每次请求把Token放在请求头里带上后端统一校验。这样后端服务天然就是无状态的以后想横向扩展也方便。虽然JWT有token无法主动失效的问题但在这个项目规模下完全够用。1.2 系统角色与订单核心流程梳理这个系统涉及的账号角色主要有三类发单人、接单人和系统管理员。实际业务中同一个学生既可以是发单人也可以是接单人这个设计很关键因为如果系统一开始就把用户角色写死成“要么发单、要么接单”那整个平台的供需流动性会差很多。我梳理的核心业务流程是这样的学生登录后可以发布一条代取订单填写取件码、快递点位置、送达宿舍楼、期望送达时间还有感谢费金额之类的信息。订单发布后进入订单大厅其他学生可以看到所有“待接单”状态的订单觉得合适的就直接抢单。接单后订单变成“配送中”接单人需要去驿站取件然后送到指定位置最终发单人确认收货系统把感谢费结算到接单人的账户余额里。最后双方还可以互相评价评价数据沉淀下来用于展示用户信用。这个流程看起来简单但做起来有两个难点一是订单状态变化的约束条件特别多不能出现两个人同时接同一单也不能出现订单还在配送中就被人取消了二是“抢单”这个动作天然是并发操作如果不做控制同一时间有很多人点击接单就很可能导致数据错乱。这两个问题我在后面会单独展开讲。1.3 数据库设计方案与核心表结构数据库设计我前后改了好几版一开始把用户表、订单表、余额表都揉在一张表里后来发现查询逻辑越来越乱才又重新拆分。最终的核心表大概是这样的用户表user保存账号密码、昵称、手机号、学号、用户角色和信用分订单表orders保存发单人ID、接单人ID、快递点、取件码、送达地址、状态、感谢费金额、发布时间和各个状态变更的时间点钱包表wallet保存余额和累计收入评价表comment保存订单ID、评价人ID、被评价人ID、评分和内容。关于订单表我想重点说一个经验一定要把“取件码”和“快递点”这些信息设计成冗余字段存在订单表里而不是通过外键关联一张快递单表。因为订单生成之后这些信息应该被固定下来如果关联到另一张表一旦原数据被修改历史订单就会出问题。另外订单状态这个字段我建议用整数类型存储用状态枚举类在代码层面对应这样查询可以用索引可读性也不差。索引设计方面orders表上我加了联合索引(status, create_time)因为订单大厅的核心查询就是按照状态过滤再按时间排序。user表的手机号字段加唯一索引登录的时候查询会很快。还有一个细节是金额字段千万不要用float或者double精度问题会在结算的时候坑死你我用的Decimal精确到小数点后两位。2. 后端核心功能开发与实现细节2.1 Spring Boot项目结构搭建与自动装配机制Spring Boot项目我是在Idea里通过Spring Initializr直接生成的这里有个坑创建时选择的Spring Boot版本不要太激进。我当时图新鲜选了3.0以上的版本后来发现很多第三方依赖还没有同步适配MyBatis Plus和某些工具库的兼容版本对不上折腾了很久。后来换回2.7.x版本所有问题都消失了。所以如果你做的是一个需要稳定运行的业务系统而不是技术调研项目建议选择相对成熟稳定的版本系列。项目结构我采用的是经典的分层架构controller、service、mapper、entity、config、common这几个包。Controller层只负责参数接收和结果返回不写业务逻辑Service层处理具体的业务规则Mapper层通过MyBatis Plus操作数据库。另外单独抽了一个common包用来存放统一返回结果类、异常处理器、JWT工具类和常量定义这样代码结构很清晰后面排查问题也方便。第一次用Spring Boot的人可能会好奇为什么我没有写一堆XML配置项目就能跑起来这里就要提到自动装配机制了。简单说Spring Boot通过SpringBootApplication注解开启了自动配置它会在项目启动时扫描classpath下的依赖根据依赖jar包自动创建对应的Bean。比如你引入了spring-boot-starter-web它就自动帮你配置好内嵌的Tomcat引入了spring-boot-starter-data-redis就自动帮你创建RedisTemplate。这种“约定大于配置”的设计大幅降低了项目搭建的成本。当然自动装配的背后是通过EnableAutoConfiguration结合spring.factories文件里的配置类实现的这个在面试里属于高频考点后面我会列一个问题自查表。2.2 登录鉴权模块JWT生成与拦截器校验登录功能是所有模块的基础。密码的存储我用了BCrypt加密没用MD5因为MD5彩虹表太容易破解了。登录成功后后端会生成一个JWT Token并返回给前端浏览器端把Token存在本地存储里之后每次请求都放到请求头的Authorization字段中。JWT的生成代码不算复杂我用的jjwt这个库核心逻辑就是用用户的ID和角色信息生成一个带过期时间的Token。这里面有一个设计细节不要在Token里放入太多敏感信息只要放用户ID和角色就够了手机号之类的信息应该等后端拿到用户ID后再查数据库获取。拦截器的实现思路就是写一个HandlerInterceptor在preHandle方法里从请求头取出Token然后解析校验如果解析失败就返回401状态码提示用户重新登录。解析成功之后把用户ID放进ThreadLocal里边后续的Service层就能直接获取当前登录用户信息而不需要每个接口都传一个userId参数。这里要注意的是一定要在afterCompletion方法里调用ThreadLocal的remove方法不然线程池复用线程时会串数据。2.3 订单状态机设计一个订单从发布到完成的状态流转订单模块是系统的核心状态机的设计决定了整个业务逻辑是否清晰。我把订单状态设定为0待接单、1配送中、2已完成、3已取消、4异常中。状态流转规则如下发单人发布订单后状态为待接单接单人抢单成功后状态变为配送中接单人点击“确认送达”状态保持不变等待发单人确认发单人确认收货状态变为已完成同时触发余额结算发布后超过一定时间没人接单发单人可主动取消状态变为已取消配送过程中如果出现取件码错误、联系不上发单人等情况任何一方可发起申诉状态变为异常中由管理员介入处理。这个状态机看起来不复杂但我刚开始实现的时候还是踩了坑。比如我一开始在Service里到处执行“判断当前状态、然后直接更新为下一状态”的逻辑结果代码写得很分散经常出现某个接口漏了状态检查导致状态乱跳。后来我改用了一个统一的订单状态变更方法先通过乐观锁更新数据库根据更新行数决定是否成功这样既保证了状态校验逻辑集中在同一个地方也解决了并发问题。接单这个操作的并发控制我前后试过两种方案。第一种是用数据库的乐观锁在orders表加一个version字段执行更新时带上where version #{oldVersion}如果更新行数为0说明冲突就让用户重新试一次。第二种是用Redis的分布式锁抢单前先尝试获取锁拿到锁才执行订单更新。最终我同时用了这两种方案先走Redis锁挡住大部分并发请求再用乐观锁兜底保证数据一致性。实测下来效果很稳。2.4 Redis集成缓存热点数据和使用场景这个项目里Redis主要承担了两个作用第一个是缓存订单大厅的数据第二个是处理分布式锁。订单大厅是访问频率最高的接口用户一进来就要看到最新的待接单列表。但这个列表的查询涉及订单表分页、用户昵称关联等操作每次都查MySQL的话压力不小。我的做法是把首页第一页的数据缓存到Redis里key设计成order:list:page:1过期时间设为30秒。用户请求时先查缓存缓存没有再查数据库并回填。由于订单发布频率并不高30秒的延迟对用户感知几乎为零但数据库的压力明显降下来了。这里有一个小细节缓存数据一定要做序列化建议直接用字符串或者JSON格式存储不要用JDK序列化否则后面数据迁移和排查都不方便。分布式锁在抢单业务里的用法我简单说一下。抢单接口里我先通过StringRedisTemplate的setIfAbsent方法往Redis里写一个临时keykey的命名比如order:lock:订单IDvalue写当前用户ID同时设置过期时间3秒。只有写入成功的用户才继续执行订单状态更新逻辑其他用户直接提示“手慢了订单已被抢走”。可能有人会问万一并发量真有那么大锁过期了怎么办这个场景在校园代取这种低频业务里基本不会出现真到了那种并发级别这个项目本身的设计就要推倒重来了。3. 前端Vue页面实现与前后端联调经验3.1 Vue项目搭建与环境配置注意事项前端我用的是Vue 2.6加Element UI因为网上资料最丰富遇到编译报错基本都能找到现成的答案。环境准备上Node.js的版本很有讲究Vue 2项目建议使用Node 14到16之间的版本太新的Node版本在安装依赖的时候经常会有兼容问题。我记得有一次同事用Node 18装了依赖之后项目直接起不来报了一堆openssl相关的错误最后是把Node切换到16才解决的。创建项目的命令是vue create school-express在交互式配置里选择了Router、Vuex和axios相关的选项。依赖安装的时候也踩过坑npm install经常跑到一半卡住后来我直接用淘宝镜像源这个问题就消失了。这里顺便提一下“vue安装依赖”这个搜索热词。很多新手装依赖失败第一反应是再来一次但我建议先看一下报错信息。常见的错误无非三类一是网络问题换镜像源二是Node版本不兼容切换Node版本三是某些依赖包已经被废弃导致安装失败那就需要检查package.json里的依赖版本手动指定一个稳定版本。搞清楚原因再动手比盲目重试要高效得多。3.2 前端核心页面拆解与路由设计前端页面我按功能拆分成了几个大块登录注册页、订单大厅首页、发布订单页、个人中心页和后台管理页。其中订单大厅是用户最常访问的页面展示所有待接单的订单卡片支持按照快递点或宿舍楼筛选还接了分页组件。路由设计上我用了vue-router的懒加载模式通过动态import的方式导入组件这样前端打包出来的代码会按页面拆分成多个小块首屏加载速度明显更快。路由守卫是另一个重要的点我在全局前置守卫里判断用户是否已登录如果没有登录且访问的是需要登录的页面就重定向到登录页。订单大厅里有一个“抢单”按钮点击后调用后端接口。这里有一个体验细节因为抢单的结果是异步返回的用户点击之后前端要进入“提交中”状态禁用按钮防止重复提交拿到后端返回结果后再恢复。不然用户连续点好几次虽然后端有幂等控制不会出问题但前端看起来会非常不专业。3.3 Vuex状态管理与axios请求封装项目里的用户信息和登录状态我放在Vuex中管理。Vuex的state里保存一个user对象和一个token登录成功后dispatch一个action把这两项写进去同时持久化到localStorage。页面刷新的时候在应用初始化阶段检查localStorage里有没有token有的话就恢复登录状态。axios封装上我新建了一个request.js文件创建了一个axios实例设置baseURL为“/api”并配置了请求拦截器和响应拦截器。请求拦截器里做了一件事从Vuex或者localStorage拿到token添加到请求头的Authorization字段。响应拦截器里做的事情比较关键如果后端返回的状态码是401说明token过期或者未登录这时候要清除本地登录状态并跳转到登录页如果业务状态码不是200就弹出一个错误提示消息。统一处理的好处是业务代码里不需要每个接口都写try-catch也不用在每次请求时手动加token。项目里的请求代码会变得非常简洁比如“获取订单列表”这个接口我只需要写一行api调用然后处理返回的数据就可以了。3.4 前后端跨域问题处理与联调方案前后端分离的项目在本地联调的时候几乎一定会遇到跨域问题。我当时的开发环境是后端跑在8080端口前端devServer跑在8081端口浏览器的同源策略直接拦截了跨域请求。解决跨域我当时试了三种方案。第一种是在后端写一个CorsConfig配置类实现WebMvcConfigurer接口配置允许跨域的来源、请求头和请求方法。第二种是在前端的vue.config.js里配置devServer.proxy把“/api”前缀的请求都代理到后端地址这样浏览器认为请求是同源的跨域问题自然就没了。第三种是后面前端打包后部署到Nginx时在Nginx配置里做反向代理同样是以“/api”开头转发到后端端口。我最终采用的是前端proxy方案和Nginx代理方案因为这两种方式可以避免在后端代码里写死允许的跨域来源更加灵活安全。需要注意的一点是如果同时开了后端跨域配置和前端proxy反而可能导致请求被代理转发后又经过一层跨域处理逻辑变得混乱所以建议只选一种方式不要混用。4. 常见问题与排查技巧实录4.1 Spring Boot版本太高引发的依赖兼容问题这个坑我必须放在第一位说。我第一次创建项目的时候选了当时最新的Spring Boot版本结果引入MyBatis Plus之后项目启动就报错提示找不到某个类的方法。排查了很久发现是MyBatis Plus的某个老版本和Spring Boot新版本在自动配置类上有冲突。解决方案很简单把Spring Boot的版本降级到2.7.x同时把MyBatis Plus的版本升级到3.5.x以上问题就消失了。我建议在做这种常规业务项目的时候不要追求新版本而是选择已经经过大规模验证的稳定组合。在Maven的pom.xml里锁定好版本之后后续所有依赖的引入都以这个版本为基础来搜索兼容版本这样整个依赖树会比较稳定。4.2 Vue打包后布局异常与上线部署问题前端在开发模式跑得好好的执行npm run build打包部署到Nginx之后页面出现了两个让人头疼的问题。第一个是刷新页面后出现404这是因为前端路由用了history模式刷新时Nginx找不到对应的真实文件路径。解决办法是在Nginx配置里加一个try_files配置把所有请求都重写到index.html。第二个问题是打包后图片和静态资源的路径不对。开发模式下资源路径是相对于根路径的但部署到服务器子目录时就会出问题。解决办法是在vue.config.js里设置publicPath为“./”相对路径这样打包出来的资源路径就会以相对路径加载。还有一个容易被忽略的点是如果用的图片路径是写在CSS里的也要检查一下编译后的路径是否正确。这类问题排查的时候打开浏览器开发者工具看控制台里的资源加载报错信息比盲目猜测要快得多。4.3 多线程等待与异步任务处理经验项目中有个导出功能需要从一个接口同时查询多个维度的统计数据如果串行查询的话每个维度都要访问一次数据库耗时叠加就很明显。我当时用CompletableFuture对几个查询任务做了并行处理最后用allOf等待所有任务完成后再统一返回结果。这个其实就是热词“java线程等待都完成”里经常提到的那种场景。使用CompletableFuture有一个关键点一定要自己指定线程池不要用默认的ForkJoinPool.commonPool。因为默认线程池的并行数和CPU核数相关而且整个应用共用一套一旦某个任务阻塞了其他所有并行任务都会被拖累。我当时定义了一个核心线程数为10的线程池专门给这种异步任务使用配合Future超时控制整体性能提升非常明显。代码大致是这样的模式先用thenApplyAsync定义每个子任务的逻辑再用CompletableFuture.allOf(...).get(5, TimeUnit.SECONDS)等待全部完成最后从每个future里取出结果组装。注意get一定要传超时时间否则如果某个子任务异常卡住了接口会一直不返回给用户的体验就是页面死掉了。4.4 面试高频考点自查从项目里提炼技术点做完这个项目之后我发现自己对很多知识点的理解从“背概念”变成了“真明白”。这里我把相关的面试高频考点整理成一个自查清单如果你之后要去面试Java开发岗可以照着这个清单过一遍考点项目中的对应点回答思路Spring Boot自动装配原理项目能自动运行的原因从SpringBootApplication说起讲EnableAutoConfiguration加载自动配置类的过程动态代理MyBatis的Mapper代理实现讲JDK动态代理和CGLIB的区别说清楚MyBatis是通过代理生成Mapper实现类的MySQL索引优化订单表联合索引设计以statuscreate_time为例说明最左前缀原则并发控制抢单接口的Redis锁和乐观锁讲清楚为什么不能只靠代码加锁分布式场景下怎么办Redis缓存穿透与击穿订单列表缓存设计结合实际场景讲缓存策略和过期时间设置冒泡排序面试常考手写题虽然业务没用上但手写冒泡排序要闭眼能写出来这里想多说一句做项目最大的价值不是让你在简历上多一行字而是让你在每个技术点上有真实的使用场景。比如动态代理如果你只是背概念面试官一问“你项目里哪里用了”你答不上来等于白背。而我在做MyBatis Plus集成的时候就因为好奇Mapper接口为什么不用写实现类就能直接用去翻了源码研究了一遍动态代理这才真正搞懂了。5. 个人实操体会与扩展建议5.1 做完这个项目我最想分享的几个心得第一个心得是数据库设计一定要先想清楚再动手不然后面全是坑。我第一版数据库设计特别草率订单表和用户表之间没有clear的索引策略写到查询接口的时候才发现很多SQL没法走索引又回头改表结构。做项目要养成一个习惯写代码之前先在纸上画出核心表结构和字段关系互相评审一轮再开始建表。第二个心得是前后端联调阶段要有耐心遇到问题先定位是哪一端的问题不要盲目改代码。我总结了一个排查顺序先看后端接口返回的数据对不对用Postman直接调接口验证再看前端请求有没有发出去打开浏览器Network面板看请求参数和响应最后才去看代码逻辑。很多新手一遇到问题就两边一起改结果反而把问题改没了也不知道为什么。第三个心得是不要把项目做得太“完美主义”。我当时一度纠结于要不要加上消息推送、实时聊天这些功能后来冷静想了想这些功能对核心业务流程没有本质提升反而会增加大量排查时间。先完成核心闭环再考虑锦上添花这个顺序一定不要搞反。5.2 这个项目后续还可以怎么扩展做完这个系统之后我其实又想了一些可以继续扩展的方向。比如可以接入微信小程序因为学生群体用小程序比用网页更方便前端通过uni-app复用现有Vue代码成本相对可控。还可以在订单状态变化时增加通知机制比如当接单人点击“确认送达”时给发单人发送一条微信订阅消息提醒这样用户不必一直盯着页面看。再往后如果订单量上来了可以考虑引入配送路线规划的简单策略相同宿舍楼的订单可以合并分配降低接单人的跑腿成本。另外在技术层面也有不少可以打磨的地方比如把文件上传功能加上让用户发布订单时可以上传物品照片比如把权限管理升级一点增加更细粒度的角色权限控制再比如把日志链路跟踪做好接一个简单的操作日志模块方便管理员后台查看关键操作记录。这些都是很好的练手方向也都能写进项目介绍里。最后分享一个小技巧也是我做这个项目踩了很多次坑之后总结出来的所有涉及订单状态变更的接口务必要加一个统一的审计字段记录操作人和操作时间。前期你可能觉得多余但一旦系统上线后发现某个订单状态异常这个字段就是你排查问题的最重要线索。这个习惯我一直保留到了现在后面做的所有项目都受益于此。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询