ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Spring Boot外卖系统实战:从JWT鉴权到订单状态机设计

Spring Boot外卖系统实战:从JWT鉴权到订单状态机设计 简介一份名为“饥了么”的外卖系统APP完整示例项目基于Java技术栈并包含Kotlin移动端源码面向高校课程设计、毕业设计及Java后端学习者可用来理解用户下单、商家接单、配送员配送等外卖核心业务。压缩包共270个文件、约150.02MB涵盖Java/Kotlin源码、XML布局与配置、APK安装包、Gradle构建脚本、SQL数据库脚本以及Word版设计报告和说明文档源码、文档与可安装包齐全可直接运行APK观察效果也能对照代码学习接口设计与前后端协作。目前已有168人学习下载。项目涉及Spring Boot后端、RESTful API、MySQL数据存储、Material Design界面等关键知识点并附多份APK与完整报告适合在掌握Java基础后进一步进阶移动端开发、业务分层与数据持久化作为课程设计或毕业设计的完整参考方案。1. 饥了么外卖系统从zip到跑通的Java单体项目如果你点开这个标题大概率是两种情况要么是Java课设/毕设要交一个能演示的外卖系统要么是想找一个完整度高的开源项目拆着学。这个饥了么外卖系统APP.zip的定位很明确——它是基于Java的Spring Boot MyBatis风格单体工程前端包含用户端、商家端和配送端三套APP界面后端统一走RESTful接口。我第一次接到类似项目时也以为是饿了么的完整复刻解压后才发现这类zip的真实价值不在功能全而在模块闭环从用户注册、商家上架菜品、购物车、下单支付模拟、接单派单到订单状态流转一条线走完恰好覆盖Java面试常问的接口设计、状态机、事务和权限控制。适合谁想快速交付课设的学生、想练Java全栈但不想从零搭界面的入门开发者。这篇笔记我会按解压后怎么跑通→核心代码抄哪几段→哪几个坑最耽误时间→怎么改出亮点的顺序直接给你能复现的路径。2. 解压zip后的第一件事读懂工程结构与启动顺序拿到zip先别急着点运行。这种课程设计级别的外卖系统打包的人水平参差不齐有的zip里是完整IDEA工程有的只是前端静态页面加几个jsp。我见过最耽误时间的情况是一解压就双击pom.xml然后被一堆红色报错劝退。所以第一步永远是先看目录确定技术栈版本再决定用IDEA还是Eclipse启动。2.1 先看pom.xml确认Spring Boot版本与依赖是否完整打开zip后第一眼找pom.xml没有这个文件说明项目是传统SSH架构Spring Struts Hibernate有则是Spring Boot工程。这里说最常见的Spring Boot MyBatis结构。pom.xml里你需要核对三个东西Spring Boot版本号、MyBatis Starter坐标、数据库驱动是否匹配你本机MySQL版本。parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.3.4.RELEASE/version relativePath/ /parent dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.mybatis.spring.boot/groupId artifactIdmybatis-spring-boot-starter/artifactId version2.1.4/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.21/version scoperuntime/scope /dependency /dependencies这段配置的核心是spring-boot-starter-parent锁版本你不需要手动指定Spring Boot内置依赖的版本号只管业务依赖。MyBatis Starter 2.1.x对应Spring Boot 2.2到2.4都能跑我一般建议直接保持zip里原来的版本不动优先跑通再升级。mysql-connector-java版本8.0对应MySQL 5.7和8.0都能连但注意连接参数后面会讲。检查完依赖后看src/main/resources下有没有application.yml或者application.properties。没有配置文件、没有mapper目录、没有templates目录的项目八成是缺文件的半成品zip这种建议直接放弃省下的时间来改自己的项目。2.2 数据库初始化执行sql脚本与修改连接配置外卖系统的数据模型是典型的用户—商家—菜品—订单四张主表架构加上购物车、地址、评价等次表。zip里通常附一个jileme.sql文件我一般用Navicat直接运行也可以用命令行导入。核心是把字符集设为utf8mb4否则菜品名里的emoji和特殊字符会成乱码。mysql -u root -p --default-character-setutf8mb4 source /你的解压路径/jileme.sql;执行完看表数量正常应该在10张以上。如果只有三四张表说明sql脚本也被截断过后面启动必然报Table not found。然后去application.yml里改数据库连接spring: datasource: url: jdbc:mysql://localhost:3306/jileme?useUnicodetruecharacterEncodingutf8mb4serverTimezoneAsia/ShanghaiuseSSLfalse username: root password: 你自己的密码 driver-class-name: com.mysql.cj.jdbc.DriverserverTimezoneAsia/Shanghai是必须的MySQL 8.0默认时区是美国不加会报Server returns invalid timezone错误。useSSLfalse是关闭SSL警告开发环境不需要加密连接。如果你本机MySQL是5.7driver可以改回com.mysql.jdbc.Driver但为了省事8.0驱动兼容5.7保持原样就行。2.3 前端APP部分Android工程还是H5套壳标题里带APP这个zip里的前端大概率有两种形态一是Android原生工程有app/src/main/java的Android Studio工程二是H5页面用WebView套壳。如果是前者你需要Android Studio打开改config.js或Constants.java里的接口地址为http://10.0.2.2:8080——这是Android模拟器访问本机电脑localhost的专用地址写localhost必连不上。如果是后者直接浏览器打开HTML文件就能预览模拟器里访问电脑IP即可。我处理这类项目的习惯顺序是先不管APP用Postman把后端接口全部测通再联调前端。原因很简单——后端接口报错你能直接在IDEA控制台看堆栈前端页面报错只有网络状态和返回值排错效率低一倍。这个顺序能帮你少踩很多联调阶段的坑。3. 核心代码抄作业登录鉴权、购物车与订单状态机跑通工程只是第一步真正能让你在答辩或面试里讲出东西的是那几个核心业务模块的实现。这一章我直接给你三段最有含金量的代码每段后面讲清楚为什么要这么写和面试官会追问什么。3.1 用户登录与Token鉴权别用session用JWT外卖APP的登录鉴权在课程设计阶段常被简化成session这在大作业里能跑但答辩时容易被问住。更稳妥的做法是使用JWT做无状态登录核心逻辑在拦截器里校验请求头。用户登录成功后后端返回一个token字符串前端存起来后续每次请求带上。RestController RequestMapping(/api/user) public class UserController { Autowired private UserService userService; PostMapping(/login) public Result login(RequestBody LoginRequest request) { // 参数校验手机号格式 密码非空 if (!Pattern.matches(^1[3-9]\\d{9}$, request.getPhone())) { return Result.error(手机号格式不正确); } User user userService.login(request.getPhone(), request.getPassword()); if (user null) { return Result.error(用户名或密码错误); } // 生成token有效期2小时盐值从配置文件读取 String token JwtUtil.createToken(user.getId(), user.getRole(), 2 * 60 * 60 * 1000); return Result.ok().put(token, token).put(userInfo, user); } }这段代码的核心在于把登录成功后的用户身份信息合并进了token而不是把整个用户对象放session里。JwtUtil.createToken里我一般用io.jsonwebtoken库payload塞userId和role签名用HS256。安卓端拿到token存SharedPreferences请求时加到Authorization头里。注意RequestBody要求前端传JSON格式数据如果安卓端用了OkHttp提交表单格式这里会一直报HttpMessageNotReadableException这是联调最常见的问题之一。拦截器校验token的写法是继承HandlerInterceptorAdapter在preHandle里取header用同一个JwtUtil解析解析失败直接返回401 JSON不放行。3.2 购物车实现用Redis还是数据库表购物车有两个存储方案Redis存Hash或者数据库建cart表。zip里给的通常是数据库表方案因为课程设计环境不一定装了Redis但你要知道Redis方案好在哪——不落库、过期时间可控、并发性能高。这里说数据库表方案的写法CREATE TABLE cart ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, shop_id INT NOT NULL, dish_id INT NOT NULL, quantity INT DEFAULT 1, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_user_dish (user_id, dish_id) );加购物车的SQL是INSERT ... ON DUPLICATE KEY UPDATE quantity quantity 1利用uk_user_dish唯一索引同一用户同一菜品重复添加时直接加数量而不是插入重复数据。Insert(INSERT INTO cart(user_id, shop_id, dish_id, quantity) VALUES(#{userId}, #{shopId}, #{dishId}, 1) ON DUPLICATE KEY UPDATE quantity quantity 1) int addCart(Param(userId) Long userId, Param(shopId) Long shopId, Param(dishId) Long dishId);这个语法MySQL独有面试时提这个能体现你写过真实业务。注意ON DUPLICATE KEY UPDATE后面不要再写user_id VALUES(user_id)因为唯一键已经定位到了那一行。还有一点购物车必须记录shop_id因为外卖场景不允许跨店拼单前端去结算时要校验购物车里所有菜的shop_id一致不一致要提示用户请分开下单。3.3 订单状态机六态流转与超时取消外卖订单的状态设计是整张订单表的核心也是答辩时最容易被深挖的点。常规六态是待支付、已支付/待接单、已接单/配送中、已完成、已取消、退款中。直接用数字存状态会有魔法值问题我建议用枚举类全项目统一管理。public enum OrderStatus { UNPAID(0, 待支付), PAID(1, 待接单), DELIVERING(2, 配送中), COMPLETED(3, 已完成), CANCELLED(4, 已取消), REFUNDING(5, 退款中); private final Integer code; private final String desc; OrderStatus(Integer code, String desc) { this.code code; this.desc desc; } }订单表里用status字段存code值更新状态的SQL必须是UPDATE orders SET status #{nextStatus} WHERE id #{orderId} AND status #{currentStatus}。这个AND status #{currentStatus}的写法是乐观锁的简化版能防止用户连续点击两次取消按钮导致状态重复流转——第一次把状态改成CANCELLED第二次因为status不再是PAID影响行数为0事务回滚。用户支付后要做的动作是改订单状态为PAID扣减商家菜品库存生成配送单。这三个动作必须在同一个Transactional方法里任何一步抛异常整体回滚。我之前接手过一个翻车案例菜都点完了商家后台库存还是满的就是因为库存扣减写在事务外面支付成功回调时抛异常没回滚。超时未支付自动取消可以用Scheduled定时任务扫订单表扫PAID之前的订单但容易扫出性能问题更现代的做法是下单后延迟队列发消息这个对课设来说超纲做完Scheduled就够讲五分钟了。4. 避坑与常见问题解压到联调最耽误时间的5个坑写这个章节时我翻了一下自己接手课设项目时的笔记挑出5个出现频率最高、每个都在网上反复被问的问题。你会发现很多报错并不是代码逻辑问题而是环境、配置和联调细节这类坑最让人抓狂。4.1 启动报错Error creating bean with name userMapper现象IDEA点击启动后控制台直接抛Invalid bound statement (not found)或Mapper bean创建失败页面访问接口全500。原因出现这类报错基本有四种可能application.yml里没有配置mybatis.mapper-locations指向XML目录XML文件没有放到resources/mapper下而是放进了java源码包XML里namespace写错了Mapper接口全限定名或者在UserMapper接口上没加Mapper注解或启动类没加MapperScan。解决先检查启动类有没有MapperScan(com.jileme.mapper)。有的话看配置mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.jileme.entitymapper-locations是XML文件的路径type-aliases-package能让XML里的resultType只写类名不写全路径。改完配置后务必点一下IDEA的Build菜单里Rebuild Project把resources重新编译进target目录否则改动不生效。4.2 数据乱码中文正常但菜品名带问号现象数据库中文正常但页面显示菜品名出现??? 这样问号或者英文正常中文乱码。原因最典型的是数据库、表、连接串三层字符集没对齐。MySQL建库时没指定utf8mb4建的还是默认latin1或者连接串里没加characterEncodingutf8mb4。解决先执行ALTER DATABASE jileme CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;把库改了再挨个表执行ALTER TABLE dish CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;。最后确认连接串确实是utf8mb4而不是utf8——utf8在MySQL里是残缺的utf8mb3存不了四字节emoji。这条血泪经验是某次给菜品加备注时踩的页面保存正常一查库全变问号。4.3 安卓模拟器连不上本机后端现象APP端打开一直在转圈等一会儿报Failed to connect to /127.0.0.1:8080或者干脆超时。原因Android模拟器里的localhost指向模拟器自身不是你的电脑。你需要在模拟器里访问10.0.2.2这是Android官方规定的宿主机环回地址。真机调试时更麻烦用的是电脑局域网IP且要保证手机电脑同一WiFi。解决找到前端代码里定义BASE_URL的常量类通常叫ApiConfig或Constants把地址改成http://10.0.2.2:8080/。改完如果还连不上检查IDEA启动的Spring Boot端口是不是真的8080——之前遇到过项目里server.port8081但前端写死8080的情况。另外Android 9以上默认禁止明文HTTP流量还要在AndroidManifest.xml的application标签里加android:usesCleartextTraffictrue。4.4 订单支付页面只能点模拟支付且状态推不动现象下单成功点模拟支付按钮页面提示支付成功但订单状态还停留在待支付商家端看不到新订单。原因我没看到这版源码里支付回调是怎么写的但这类课程设计项目最常见的坑是支付方法写了但没调orderService.updateStatus或者调了但service里的SQL字段没更新到正确的状态列。还有一种可能支付接口开了新事务但外层方法没加Transactional导致内层更新回滚。解决打开浏览器开发者工具的Network面板点模拟支付后看发的POST请求体里有没有orderId。如果请求体正常而状态不变直接翻后端Controller里对应的方法找到调用链最后面的MyBatis update语句手动在数据库执行一遍排查SQL本身有没有问题。我遇到过最玄学的一次是更新语句写的是set status1但订单表里新加了个pay_status字段页面读的是pay_status两个状态字段各管各的。4.5 前后端端口冲突与跨域报错现象点完支付后浏览器控制台显示Access to XMLHttpRequest ... has been blocked by CORS policy但功能其实已经生效。原因前端页面用localhost:8081起的后端跑在8080不同端口算跨域。课程设计里前端常是HTML直接双击打开协议是file://Origin显示为null后端没配跨域放行就会拦截。解决在Spring Boot的配置类里加一个WebMvcConfigurer跨域配置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); } }注意allowedOrigins(*)和allowCredentials(true)在Spring Boot 2.4以上版本不能同时使用会直接启动报错要用allowedOriginPatterns(*)替代。5. 进阶改进把课设项目升级成有区分度的作品项目能跑通、坑也踩过了现在到了加分环节。如果你正在准备答辩或找工作时的项目展示下面这三个改动方向成本低但收益高能直接拉开和其他课设项目的差距。5.1 用Redis改造购物车与token会话如果你的zip版本里购物车用的数据库表那你可以顺手加一个Redis版购物车Key设计为cart:{userId}Hash里的field放dishIdvalue放数量。逻辑上省掉了唯一索引和ON DUPLICATE KEY语句切换时后端Service层接口不变DAO层接口需要替换实现。Redis需要你本地装一个Windows版启动方式为redis-server.exe redis.windows.conf连不上的话先检查端口6379是否释放常见玄学问题是旧实例还占着内存第一次连上后显示响应很慢。token这块也可以直接换成Spring Security Redis存session但课设阶段不推荐改动量太大代码稳定性容易失控。5.2 商家端加一台简易WebSocket推送原版外卖系统商家端刷新订单列表通常是手动拉取或轮询你可以改成WebSocket实时推送。后端在订单状态从UNPAID变成PAID时主动推送一条新订单消息给商家的连接。Spring Boot加WebSocket很简单Component ServerEndpoint(/ws/shop/{shopId}) public class ShopWebSocket { private static final CopyOnWriteArraySetSession SESSIONS new CopyOnWriteArraySet(); OnOpen public void onOpen(Session session) { SESSIONS.add(session); } public static void sendNewOrder(Long shopId, String message) { SESSIONS.forEach(session - { try { if (session.isOpen()) { session.getBasicRemote().sendText(message); } } catch (IOException e) { e.printStackTrace(); } }); } }注意ServerEndpoint是JSR-356标准注解但Spring Boot里要用这个注解必须注入一个ServerEndpointExporter的Bean否则注解不生效。这是新手最容易踩的点。5.3 数据可视化给订单模块配一张经营统计页最后一个小改动是给商家端代码加一个统计接口聚合近7天营业额和订单量前端用ECharts画折线图。SQL用分组函数按日期聚合SELECT DATE(create_time) AS day, COUNT(*) AS order_count, SUM(total_amount) AS amount FROM orders WHERE shop_id #{shopId} AND status IN (1, 2, 3) AND create_time DATE_SUB(CURDATE(), INTERVAL 7 DAY) GROUP BY DATE(create_time) ORDER BY day;这个查询走索引的话量级很小但注意create_time聚合用DATE()函数会无法走索引。数据量大了以后正确做法是在订单表增加冗余的order_date字段时间维度查询直接匹配这个字符串字段。我把这个优化留在末尾是因为课设阶段不追求性能但说出来能让面试官觉得你考虑到了生产环境的问题。如果你实验到了这一步整个外卖系统已经从能跑变成了能讲。交付zip只是开始把关键模块讲明白才是项目真正属于你的时候。回头再看我被问最多的永远是订单状态机和事务边界这也是这套系统最值钱的部分。希望这篇笔记能帮你少走几段弯路把时间留给真正能提升代码质量的地方。本文还有配套的精品资源点击获取
返回列表