从用户点餐到订单完成:一个外卖请求如何穿越SpringBoot项目 📅 2026/7/19 1:59:53 想象你打开一个外卖APP从浏览菜品到吃上热乎饭整个过程在程序世界里要经过几十个岗位的协同配合。下面我们就跟着一份订单的旅程把苍穹外卖这个SpringBoot项目的每个角落都走一遍。第一章 用户还没来——餐厅开门前的准备工作餐厅不能空手开业后厨得先备好锅碗瓢盆和水电气。pom.xml 就是采购清单。它列明了要用Spring Boot、MySQL驱动、Redis客户端、阿里云OSS等所有厨具的版本号。Maven照着这张单子去中央仓库把依赖包全买回来项目才能编译运行。application.yml 是总控开关面板。这里写着数据库在哪儿Redis在哪儿服务器端口是多少。而 application-dev.yml 是开发环境的分控面板连测试库日志详细application-prod.yml 是生产环境的分控面板连正式库日志精简。多环境隔离开发调试不会影响线上真实数据。README.md 是新员工入职手册——项目介绍、怎么启动、接口文档链接都在里面。.gitignore 是保密条例告诉Git哪些本地配置文件、编译产物不要传到代码仓库避免泄露密码或引发冲突。数据库SQL脚本doc/sql目录就是餐厅的初始菜单数据库——建表语句全在这儿新环境执行一遍就能生成所有表结构。第二章 用户打开APP——首页展示阶段用户掏出手机点开外卖APP首页需要展示菜品分类、轮播图、热门推荐。这个请求的旅程开始了。请求先过门卫——拦截器每次请求进来第一个遇到的是 LoginInterceptor登录拦截器。它负责检查请求头里有没有合法的JWT令牌就像餐厅门卫检查你有没有会员卡。没令牌或令牌过期直接返回401连门都不让你进。对于登录、注册这些公开接口门卫会放行。对于需要登录才能看的首页和下单接口必须验明正身。验身通过后UserContext线程上下文会把当前登录用户的ID存进ThreadLocal——这相当于给这个请求贴上一张当前顾客是谁的便签后续所有处理环节都能随手取用不用层层传参。请求到达前台——Controller层通过门卫后请求来到 Controller控制层相当于前台点菜员。比如 UserDishController 接收 /api/user/dish/list 请求它只负责三件事收参数、调Service、把结果封装成统一格式返回。业务处理——Service层Controller转身找到 DishService菜品服务相当于厨师长说顾客要看某分类下的菜品。Service接口定义了能做什么它的实现类 DishServiceImpl 具体安排怎么做。厨师长决定去数据库取数据并可能要查Redis缓存提速。数据获取——Mapper层Service调用 DishMapper持久层相当于切配师傅。Mapper接口继承了MyBatis-Plus的BaseMapper自动拥有增删改查能力。如果涉及多表联查或复杂动态SQL就去 resources/mapper/DishMapper.xml 里写自定义SQL——相当于师傅遇到复杂菜品时需要自己动手切配。配置保障MyBatisPlusConfig 确保分页查询正常工作主键自动生成。RedisConfig 保证缓存数据序列化格式正确避免乱码。GlobalExceptionHandler 随时待命——任何时候发生异常它都会拦截并返回格式统一的错误信息不让程序裸奔崩溃。数据怎么返回给前端——VOMapper查回来的数据库实体Entity可能包含很多字段比如菜品创建时间、修改人但前端只需要展示菜名、价格、图片。于是Service把Entity转换成 DishVOView Object视图对象——相当于后厨把食材做成成品菜端上桌只给顾客看成品不给看原料和制作过程。最后Controller把DishVO装进统一返回封装 Result包含code、msg、dataJSON格式返回给前端用户看到了漂亮的菜品列表。第三章 用户加购下单——核心交易阶段用户选了几道菜点击加入购物车然后提交订单。购物车操作加入购物车请求来到 CartController调用 CartService。购物车数据通常存Redis因为频繁读写且有时效性RedisUtil 工具类提供 set、get、delete 等简易操作。缓存键前缀统一在 RedisConstant 常量类里定义比如 cart:user:123避免不同业务键名冲突。下单操作用户点击去支付提交订单。请求进入 OrderController它接收一个 OrderSubmitDTO数据传输对象。注意这个DTO里用了 NotBlank、NotNull、Size 注解——这是参数校验确保前端传来的地址、联系电话、菜品列表不为空且格式合法。校验不通过连Service都不调用直接返回参数错误。校验通过后Service开始核心业务逻辑查询购物车数据计算总价检查库存调用DishMapper查数据库生成订单号唯一扣减库存清空购物车保存订单到数据库这些操作涉及多张表必须保证事务一致性要么全成功要么全回滚。Spring的 Transactional 注解在这里发挥作用。异常处理如果库存不足Service会主动抛出 BusinessException自定义业务异常携带库存不足的提示信息。这个异常被全局异常处理器捕获后返回给前端一个友好的JSON错误而不是一堆晦涩的堆栈信息。订单状态管理订单生成后的状态待支付、待接单、配送中、已完成、已取消统一在 OrderConstant 常量类里定义用数字或枚举表示避免魔法值散落在代码各处。定时任务——清理超时订单用户下了单但一直没支付怎么办Task 定时任务模块用 Scheduled 注解每天凌晨扫描订单表把超过30分钟未支付的订单自动取消并回补库存。这就像餐厅的自动清理机制不用人工干预。第四章 支付和实时通知——用户等待阶段用户支付成功后希望立刻知道商家接单了骑手出发了。WebSocket实时推送这里用到 socket/ws 扩展模块。它建立了一个WebSocket长连接相当于餐厅和顾客之间拉了一根专用传呼线。OrderService在订单状态变更时通过WebSocket服务端主动向前端推送消息您的订单已接单预计30分钟送达。用户不用刷新页面就能实时收到通知体验更好。JWT令牌贯穿全程用户登录成功后JwtUtil 工具类生成一个JWT令牌包含用户ID和过期时间时长由 JwtConstant 配置。这个令牌后续每次请求都带在Header里拦截器用JwtUtil解析验证。令牌过期后需要重新登录保证安全性。第五章 用户上传头像或评价——文件处理阶段用户想换个头像或者给菜品上传评价图片。请求来到 UserController 的上传接口Controller调用 AliOSSUtil阿里云OSS工具类。这个工具类把前端传过来的MultipartFile上传到阿里云对象存储返回一个公开访问的图片URL存到数据库。上传所需的AccessKey、Bucket等信息由 OSSConfig 配置类提供。如果上传过程中网络超时或文件过大异常会被全局异常处理器捕获返回上传失败请重试这样的友好提示。第六章 运营后台管理——商家视角商家登录后台管理系统admin端可以管理菜品、处理订单、查看数据统计。后台接口和用户接口分离路径前缀通常是 /api/admin/xxx。后台Controller里同样有参数校验、Service调用、统一返回。不同的是后台的操作权限更严格——比如只有管理员才能上架/下架菜品。登录用户上下文同样使用ThreadLocal存储当前操作的管理员ID用于记录 create_user 和 update_user 字段做到操作留痕方便事后审计追责。第七章 接口文档和测试——质量保障项目集成 SwaggerConfig启动后自动扫描所有Controller生成在线API文档。前端同事可以直接在浏览器里调试接口不用写Mock数据也不用反复问后端这个接口返回什么字段。test目录 下有Controller和Service的单元测试使用JUnit MockMvc模拟HTTP请求。每次代码改动后跑一遍测试确保核心接口没被改坏相当于自动化的试吃员。第八章 打包部署——把餐厅搬到线上开发完成后Maven执行 mvn clean package根据pom.xml配置把项目编译、打包成一个可执行的 JAR包放在target目录。这个JAR包包含了所有依赖、配置和编译后的class文件扔到服务器上用 java -jar 就能启动。部署到生产环境时通过 --spring.profiles.activeprod 参数激活 application-prod.yml自动连接生产数据库和Redis日志级别调整为INFO减少不必要的日志输出。整个项目需要的数据库连接、Redis地址等敏感信息可以通过环境变量注入避免硬编码在配置文件里保障生产环境安全。一张表看懂所有文件夹写在最后从用户点开APP到外卖送到家一个看似简单的流程在代码世界里要经过拦截器验身、控制器接单、服务层调度、持久层取数、缓存加速、异常兜底、定时清理、实时推送等几十个环节的精密配合。这个SpringBoot单体项目虽然单体但分层清晰、职责分明。每一层各司其职每个文件都有自己的使命。理解了这个流程你就不仅知道了某个文件是干什么的更明白了为什么需要它——因为用户的一次点击就是一次横跨整个技术栈的旅行。