拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Spring Boot校园快递代取小程序后端:接口契约与并发抢单

简介围绕微信小程序与校园快递代取场景展开的毕业设计论文面向高校计算机相关专业学生及需要完成课程设计、毕设选题的开发者。文档以校园快递代取系统为研究对象梳理了从需求分析到功能落地的完整思路涉及快递订单处理、接单信息更新、送达确认、代取评价与留言反馈等模块并采用 Java、Spring Boot、MySQL 与 Tomcat 完成后端架构设计。压缩包仅含 1 个 docx 文件大小约 6.51MB内容为论文正文包含中英文摘要、关键词、目录、绪论、研究意义、系统设计目的与思想等章节便于直接参考论文结构、技术选型与数据库设计。已有 273 人学习下载适合作为毕设写作、开题报告和系统方案设计的参考材料也可用于了解微信小程序与 Spring Boot 分层开发的结合方式。1. 从驿站门口那条长队说起这套小程序后端要解决什么双十一之后的校园菜鸟驿站取件队伍能从门口一直排到马路牙子。有人愿意花两块钱拜托顺路的同学帮忙带一件回宿舍也有人乐意顺手赚这个跑腿钱——校园快递代取就是这么一个典型的双边需求一端是发单的学生一端是接单的配送员中间还需要有人管账号、管状态、管纠纷、管评价。这套系统把发单、接单、送达、代取评价、留言反馈整条链路塞进微信小程序里用户不用装 App扫一下就能用。后端这边用的是 Java 加 Spring Boot按 Controller / Service / DAO 三层切开数据落在 MySQL跑在 Tomcat 上。三层结构看着像论文里的套话但真正写起来会发现它对应三个完全不同的问题Controller 负责把小程序传上来的参数洗干净Service 负责状态流转和并发控制DAO 负责把订单行锁住。角色也分成三拨普通用户发单和评价配送员接单和确认送达管理员管账号、公告和留言。适合读这篇的人有两类正在做「小程序前端 Java 后端」方向毕设的同学以及想拿一个真实双边交易场景练接口契约、状态机和并发更新的人。2. 小程序端与 Spring Boot 的接口契约分层、登录态与统一响应小程序和 Java 后端能不能顺利联调八成取决于接口契约有没有在动手前定死。很多同学的做法是前端写到哪、后端加到哪最后接口路径、字段名、返回结构全对不上真机调试一跑就一堆 undefined。2.1 Controller / Service / DAO 三层各自该切在哪论文里写了三层但落到代码边界其实很具体。Controller 只做三件事接参、校验、调用 Service 后包装返回Service 里放业务规则比如「只有状态为待接单的订单才能被抢」「配送员不能接自己发的单」「送达后才能评价」DAO 只负责和 MySQL 说话不带任何 if-else 业务判断。RestController RequestMapping(/api/order) public class KuaidiOrderController { Autowired private KuaidiOrderService orderService; // 配送员抢单路径里带订单 id账号从 token 里取不信任前端传的账号 PostMapping(/take/{id}) public RVoid take(PathVariable Long id, HttpServletRequest request) { String account (String) request.getAttribute(account); // 由拦截器解析 token 后塞入 orderService.takeOrder(id, account); return R.ok(); } // 用户发布快递订单 PostMapping(/publish) public RLong publish(RequestBody Valid OrderPublishDTO dto, HttpServletRequest request) { String account (String) request.getAttribute(account); return R.ok(orderService.publish(dto, account)); } }这里的Valid配合 DTO 上的NotBlank、Min做基础校验比在方法体里写一堆 if 干净得多。account从拦截器塞进 request 属性是因为小程序端传上来的任何账号字段都是可以被改的后端必须以 token 里的身份为准。2.2 微信登录态别再用账号密码硬扛论文里的登录模块是账号密码方案用户表和配送员表各存一份mima。这在毕设答辩时够用但小程序端体验很别扭每次打开都要输一遍。更常见的做法是走wx.login拿 code后端换 openid首次登录时再引导绑定角色。// 小程序端静默登录拿到 code 后立刻换 token wx.login({ success: (res) { if (!res.code) return; wx.request({ url: BASE_URL /api/auth/login, method: POST, data: { code: res.code }, success: (r) { // 后端返回 { token, role, needBind } wx.setStorageSync(token, r.data.data.token); if (r.data.data.needBind) { wx.redirectTo({ url: /pages/bind/bind }); // 未绑定角色先去选 用户 / 配送员 } } }); } });后端拿到 code 后调用微信的会话接口换 openid再用 openid 查用户表查不到就落一条新记录并把needBind置为 true。token 用 JWT 签一个有效期两小时的短凭据配合一个长期 refresh token 存库避免用户每次打开小程序都要重新授权。提示小程序请求的合法域名必须是 HTTPS且要在小程序后台配置。开发阶段可以在开发者工具里勾选「不校验合法域名」但上线前一定要补上否则真机一跑就是request:fail url not in domain list。2.3 统一返回体和全局异常能省掉一半联调时间前后端最容易吵架的地方就是返回结构不统一有的接口返回数组有的返回对象报错时直接抛 500 带一页堆栈。给它定一个壳所有接口都套上public class RT { private int code; // 0 成功非 0 为业务错误码 private String msg; // 给用户看的提示不要把 SQL 异常暴露出去 private T data; public static T RT ok(T data) { RT r new R(); r.code 0; r.msg ok; r.data data; return r; } }再配一个RestControllerAdvice把BizException、参数校验异常、兜底Exception分开处理业务异常返回具体 code系统异常统一返回 500 且日志里记录 traceId。小程序端只要判断code ! 0就弹 toast不用每个页面单独写错误分支。2.4 接口清单和角色边界联调前把表列出来比在群里喊「那个接口叫啥来着」高效得多路径方法可访问角色说明/api/auth/loginPOST全部code 换 token/api/order/publishPOST用户发布快递订单/api/order/listGET全部按状态分页查订单/api/order/take/{id}POST配送员抢单/api/deliver/finishPOST配送员确认送达生成送达订单/api/comment/addPOST用户代取评价/api/feedback/addPOST全部留言反馈角色边界靠拦截器 自定义注解实现比如RequireRole(delivery)在 HandlerInterceptor 里比对 token 中解析出的角色。这一步做完后面接单接口被普通用户刷的漏洞就堵上了。3. 订单数据库设计从 E-R 图到可执行的建表语句论文里的 E-R 图画得挺全配送员、用户、快递订单、送达订单四个实体加一堆属性但落到建表语句时会发现两个坑一是字段名全用拼音二是金额字段用了 double。前者无所谓后者得改。3.1 订单状态机先定表结构跟着定先把状态流转想清楚再去写字段。校园代取这条链路的正常路径是用户发布待接单→ 配送员抢单已接单→ 配送员送到已送达→ 用户评价已评价。旁路有两个用户主动取消、超时未接单自动关闭。状态值含义可执行动作触发者0待接单抢单、取消配送员 / 用户1已接单确认送达、放弃接单配送员2已送达评价用户3已评价无--1已取消无用户 / 系统状态值用 tinyint 存别用字符串否则索引和比较都吃亏。状态迁移的合法性判断写在 Service 层比如takeOrder里必须是0 → 1其余一律抛业务异常。3.2 核心表结构与索引取舍论文里快递订单表的字段是这些kuaididanhao快递单号、kuaidimingcheng、jietu截图、kuaidileixing、kuaidibeizhu、daiqufeiyong、zhanghao发单人账号、shouji、quhuodizhi、mudedizhi、peisongzhanghao、lianxidianhua、songdashijian、peisongren、zhuangtai。按这个来只做两处调整daiqufeiyong从 double 改成decimal(10,2)。double 做金额累加会出现 0.1 0.2 0.30000000000000004 这类问题结算时对不上账。jietu存的是图片用longtext存 base64 会让单行体积暴涨查询拖慢。常见做法是存对象存储返回的 URL长度给 varchar(500) 就够。CREATE TABLE kuaidi_order ( id bigint NOT NULL AUTO_INCREMENT COMMENT 主键, kuaididanhao varchar(200) DEFAULT NULL COMMENT 快递单号, kuaidimingcheng varchar(200) DEFAULT NULL COMMENT 快递名称, jietu varchar(500) DEFAULT NULL COMMENT 截图地址, kuaidileixing varchar(200) DEFAULT NULL COMMENT 快递类型, kuaidibeizhu varchar(200) DEFAULT NULL COMMENT 快递备注, daiqufeiyong decimal(10,2) DEFAULT 0.00 COMMENT 代取费用, zhanghao varchar(200) NOT NULL COMMENT 发单人账号, shouji varchar(200) DEFAULT NULL COMMENT 手机, quhuodizhi varchar(200) DEFAULT NULL COMMENT 取货地址, mudedizhi varchar(200) DEFAULT NULL COMMENT 目的地址, peisongzhanghao varchar(200) DEFAULT NULL COMMENT 配送账号, peisongren varchar(200) DEFAULT NULL COMMENT 配送人, lianxidianhua varchar(200) DEFAULT NULL COMMENT 联系电话, songdashijian datetime DEFAULT NULL COMMENT 送达时间, zhuangtai tinyint NOT NULL DEFAULT 0 COMMENT 0待接单 1已接单 2已送达 3已评价 -1已取消, addtime timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, PRIMARY KEY (id), KEY idx_status_time (zhuangtai, addtime), KEY idx_sender (zhanghao), KEY idx_delivery (peisongzhanghao) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT快递订单;idx_status_time是给「待接单列表按时间倒序分页」这个最高频查询准备的。没有它订单量上千之后列表页就要走全表扫描。idx_delivery给配送员的「我的接单」页用idx_sender给用户自己的发单历史用。3.3 抢单那一下别用先查后改并发抢单是这个系统里唯一真正有并发压力的地方。两个人同时点「接单」如果 Service 里写成先select查状态判断是 0再update成 1中间那几毫秒足够第二个人也查到 0结果两个人抢到同一单。正确写法是把判断塞进 where 条件靠数据库的行锁保证原子性UPDATE kuaidi_order SET zhuangtai 1, peisongzhanghao #{account}, peisongren #{name}, lianxidianhua #{phone} WHERE id #{id} AND zhuangtai 0;Service 里判断返回的影响行数int affected orderMapper.takeOrder(id, account, name, phone); if (affected 0) { // 要么订单不存在要么已经被别人抢了 throw new BizException(1001, 这一单已经被接走了); }这个套路叫乐观更新或者条件更新不需要显式加锁也不用引入分布式锁。单机 MySQL 上InnoDB 的行锁能保证同一行的 update 串行执行抢失败的那一方拿到 0 行直接提示用户即可。如果以后要扩展到多实例这条语句照样成立因为约束在数据库这一层。注意确认送达、评价这两步同理都要带上AND zhuangtai ?做状态守卫否则重复请求会把状态来回改代取评价也能被刷好几条。4. 抢单、送达、评价三个核心接口的实现与真机联调排错数据库和契约都定了接下来是业务代码。这三个接口是整套系统里最容易出问题的地方也最值得写细。4.1 抢单接口状态守卫加配送员校验抢单除了并发还要挡两种脏请求自己抢自己发的单、配送员账号被封禁还来接单。Transactional(rollbackFor Exception.class) public void takeOrder(Long orderId, String account) { KuaidiOrder order orderMapper.selectById(orderId); if (order null) { throw new BizException(1002, 订单不存在); } if (account.equals(order.getZhanghao())) { throw new BizException(1003, 不能接自己发布的订单); } Delivery delivery deliveryMapper.selectByAccount(account); if (delivery null || delivery.getStatus() 0) { throw new BizException(1004, 账号状态异常无法接单); } int affected orderMapper.takeOrder(orderId, account, delivery.getName(), delivery.getPhone()); if (affected 0) { throw new BizException(1001, 这一单已经被接走了); } }Transactional加在这里其实不是必须的因为只有一条 update但保留它有个好处将来如果有人在这个方法里再加一条「给发单人发订阅消息」的写库操作事务边界已经是对的不会出现订单改了、消息没发的情况。4.2 送达确认与代取费用把结算字段落到送达订单表确认送达做两件事更新快递订单状态为 2 并写入songdashijian同时在送达订单表插一条记录。送达订单表字段和快递订单高度重合多的是songdashijiandatetime和配送人信息这是论文里的设计好处是配送员的历史收入可以只查这一张表不用扫全量订单。Transactional(rollbackFor Exception.class) public void finish(Long orderId, String account) { KuaidiOrder order orderMapper.selectById(orderId); if (order null || !account.equals(order.getPeisongzhanghao())) { throw new BizException(1005, 无权操作该订单); } int affected orderMapper.finish(orderId); // WHERE id ? AND zhuangtai 1 if (affected 0) { throw new BizException(1006, 订单状态已变化请刷新后重试); } DeliverOrder d new DeliverOrder(); d.setKuaididanhao(order.getKuaididanhao()); d.setDaiqufeiyong(order.getDaiqufeiyong()); d.setPeisongzhanghao(account); d.setSongdashijian(LocalDateTime.now()); // 其余字段从 order 拷贝 deliverOrderMapper.insert(d); }金额字段用BigDecimal接别用 double。取出来之后setScale(2, RoundingMode.HALF_UP)再入库避免小数位溢出。4.3 代取评价和留言反馈评价表需要order_id唯一索引一条订单只能评一次这是数据库层面的兜底比在 Service 里查快得多也可靠得多CREATE TABLE daiqu_comment ( id bigint NOT NULL AUTO_INCREMENT, order_id bigint NOT NULL COMMENT 订单id, zhanghao varchar(200) DEFAULT NULL COMMENT 评价人账号, peisongzhanghao varchar(200) DEFAULT NULL COMMENT 被评价配送员, pingfen tinyint DEFAULT 5 COMMENT 评分 1-5, content varchar(500) DEFAULT NULL COMMENT 评价内容, addtime timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_order (order_id), KEY idx_delivery_acc (peisongzhanghao) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT代取评价;uk_order建好之后Service 里不用先查再插直接 insert捕获DuplicateKeyException转成「该订单已评价过」的业务异常。配送员的平均分可以定时任务算也可以实时AVG(pingfen)查——订单量小的校园场景实时查完全够用。4.4 真机调试连不上后端按这个顺序排小程序开发里最耗时间的不是写代码是联调。真机上请求发不出去按下面顺序查基本能定位现象大概率原因处理开发者工具正常真机失败合法域名未配置后台配置 HTTPS 域名或开发阶段开「不校验合法域名」报 connection refusedBASE_URL 写了 localhost换成电脑局域网 IP手机和电脑同一 WiFi415 / 400Content-Type 与后端不一致小程序端显式设header: {content-type:application/json}token 失效但没跳登录401 拦截没做在 request 封装里统一处理 401清 token 后跳登录页图片上传失败用了 request 而非 uploadFile文件走wx.uploadFile后端接口用MultipartFile接还有一个隐蔽的坑小程序端setData更新的是视图层数据不会同步回本地变量如果没有把最新值重新赋值回去页面看起来「刷新了」但提交的还是旧值。订单列表分页加载时尤其明显每次追加数据都要用新数组去 setData。5. 打包部署与接口回归从 Tomcat 到能自己验一遍的脚本代码写完答辩前还得让系统真的跑起来。这一章讲怎么把它部署稳以及怎么在没人帮你测的情况下自己验一遍。5.1 打成 jar 还是 war取决于你怎么用 Tomcat论文里写服务器用 Tomcat那就涉及一个选择。Spring Boot 内嵌了 Tomcat打成可执行 jar 直接java -jar就能跑部署最简单如果学校机房给的是现成的 Tomcat 容器那就得排除内嵌容器打成 war把包丢进webapps。!-- 打 war 时要排除内嵌 Tomcat否则和外部容器冲突 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-tomcat/artifactId scopeprovided/scope /dependency主类要继承SpringBootServletInitializer并重写configure否则 war 丢进 Tomcat 起不来日志里只会看到 404不报错特别容易卡住。用 jar 的话application.yml里把数据库连接、端口、文件上传路径都抽成环境变量换机器不用改代码。5.2 Nginx 转发和 HTTPS 是小程序的硬门槛小程序线上环境必须走 HTTPS所以部署里一定有一层反向代理。Nginx 配置大致是这样server { listen 443 ssl; server_name your.domain.com; ssl_certificate /etc/nginx/cert/fullchain.pem; ssl_certificate_key /etc/nginx/cert/privkey.pem; location /api/ { proxy_pass http://127.0.0.1:8080/api/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; # 让后端能拿到真实 IP日志排查用得上 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }proxy_pass末尾带不带斜杠差别很大带斜杠会把/api/替换掉不带则原样透传。配错了表现为接口 404 但后端日志里啥也没有因为请求根本没匹配上 Controller。5.3 用 curl 跑一轮回归比手点快十倍每次改完代码手动点小程序验证一遍要十几分钟。写个脚本把关键路径跑一遍几十秒就出结果#!/bin/bash BASEhttps://your.domain.com/api # 1. 登录取 token TOKEN$(curl -s -X POST $BASE/auth/login \ -H Content-Type: application/json \ -d {code:test_code} | grep -o token:[^]* | cut -d -f4) # 2. 发布订单拿到订单 id ORDER_ID$(curl -s -X POST $BASE/order/publish \ -H Authorization: Bearer $TOKEN -H Content-Type: application/json \ -d {kuaidimingcheng:圆通,daiqufeiyong:2.00,quhuodizhi:东门驿站,mudedizhi:5号楼} \ | grep -o data:[0-9]* | cut -d: -f2) # 3. 抢单第一遍应该成功第二遍应该返回 1001 curl -s -X POST $BASE/order/take/$ORDER_ID -H Authorization: Bearer $TOKEN echo curl -s -X POST $BASE/order/take/$ORDER_ID -H Authorization: Bearer $TOKEN关键看第三步第一次返回code: 0第二次返回「这一单已经被接走了」就说明条件更新那行 SQL 真的生效了。这个用例在论文的测试章节里也能直接当测试记录用比「点击按钮功能正常」这种描述扎实得多。评价接口的幂等性同理连续调两次评价第二次必须报重复否则唯一索引没建上。最后再补一个细节daiqufeiyong查出来要是2.00而不是2.0说明字段类型改对了如果出的是一长串小数回去检查表结构是不是还留着 double。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门