基于Spring Boot与MyBatis的外卖系统实战:从登录鉴权到下单落库全链路解析
简介这是一套面向JavaWeb初学者与进阶开发者的「苍穹外卖」在线订餐系统完整学习资料作者历时一个月整理包含项目源码与配套笔记适合希望通过真实项目巩固Servlet、JSP、MVC架构及框架集成等技能的读者。压缩包共约2000个文件整体13.61MB以388个xml配置、146个java源码、144个class字节码为主另含少量yml、json、xlsx及md文档覆盖后端逻辑、数据库映射与项目说明。资源围绕用户、菜品、订单等核心业务展开涉及数据库表结构设计、SQL增删改查、Spring与MyBatis集成、文件上传下载、登录鉴权与SQL注入防御等知识点并附有Markdown项目文档与图片资源便于对照源码理解各功能实现细节。目前已有16391人学习下载适合作为课程设计、毕业项目或JavaWeb实战练习的参考素材。1. 从一份外卖系统源码说起为什么它成了 JavaWeb 练手的热门选择有个做后端的朋友最近在群里晒了一张截图是他花一个月啃完的一个外卖点单系统从用户下单、购物车、地址管理到后台菜品维护、订单派送、数据统计整条链路都跑通了。他说自己之前学完 SSM 和 Spring Boot 之后一直卡在“会写 CRUD 但不知道怎么串成一个完整项目”的阶段直到把这个外卖系统从头到尾撸了一遍才算真正理解了企业级 JavaWeb 项目长什么样。这个项目就是很多人在找的“苍穹外卖”类实战系统关键词里提到的“苍穹外码 javaWeb”说的也是它。它本质上是一套前后端分离的外卖点单平台用户端负责浏览菜品、加购、下单、支付回调管理端负责分类、菜品、套餐、订单、员工、报表的全流程管理。适合谁适合已经学完 Java 基础、Spring Boot、MyBatis但缺一个完整项目把知识点串起来的人也适合想拿它当二次开发底座、改造成校园点餐或社区团购的开发者。2. 技术栈与目录结构一套标准的前后端分离骨架2.1 后端技术选型与分层设计这套系统的后端是典型的 Spring Boot MyBatis 组合配合 MySQL 做持久化Redis 做缓存和部分状态存储Maven 做依赖管理。分层上遵循 Controller → Service → Mapper 的三层结构实体类、DTO、VO 分开存放避免一个类从入参用到出参。常见做法是把sky-common和sky-pojo拆成独立模块sky-server作为启动模块这样公共工具、常量、异常处理不会和业务代码搅在一起。# 典型的多模块 Maven 结构 sky-take-out/ ├── sky-common/ # 公共模块常量、工具类、异常、上下文 ├── sky-pojo/ # 实体模块entity、dto、vo ├── sky-server/ # 业务模块controller、service、mapper │ └── src/main/resources/ │ ├── application.yml │ └── mapper/ # MyBatis XML 映射文件 └── pom.xml # 父工程统一管理版本sky-common里一般会放BaseContext基于 ThreadLocal 存当前登录用户 ID、JwtUtil生成和解析令牌、自定义异常和统一返回结果类。sky-pojo里 entity 对应数据库表DTO 对应前端传参VO 对应返回给前端的数据。这样拆的好处是改一个字段不会牵一发动全身坏处是类会变多新手容易在 DTO 和 VO 之间绕晕。我一般建议先看 entity 摸清表结构再看 controller 的入参出参最后回头看 service 怎么转换。2.2 前端结构与接口约定前端通常是 Vue 加 Element UI 或者原生 HTML 加 Axios 的组合管理端和用户端分开打包。管理端页面包括登录、员工管理、分类管理、菜品管理、套餐管理、订单管理、数据统计用户端包括首页菜品展示、购物车、下单、订单列表、地址管理。前后端通过 JSON 交互统一返回格式一般是{ code, msg, data }code 为 1 表示成功0 表示失败。// 前端请求封装示例Axios 拦截器 import axios from axios const request axios.create({ baseURL: /api, // 开发环境走代理生产环境走 Nginx timeout: 5000 }) // 请求拦截统一携带令牌 request.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers[token] token // 后端从请求头取 token 解析用户 } return config }) // 响应拦截统一处理失败提示 request.interceptors.response.use(response { const res response.data if (res.code 0) { return Promise.reject(new Error(res.msg || 请求失败)) } return res })这段代码的关键在于请求拦截器自动带令牌响应拦截器统一抛错业务页面就不用每个接口都写一遍判断。baseURL设为/api是为了开发时用代理解决跨域生产环境交给 Nginx 转发。令牌放在 header 里而不是 cookie是为了配合前后端分离的无状态鉴权。2.3 数据库表与核心字段数据库一般有员工表、分类表、菜品表、套餐表、套餐菜品关系表、订单表、订单明细表、地址表、用户表。订单表里会有订单号、用户 ID、地址、金额、支付状态、订单状态、下单时间、结账时间等字段。菜品表里会有分类 ID、名称、价格、图片、描述、状态。这里有个容易忽略的点订单状态和支付状态是两回事支付状态管钱订单状态管流程混在一起后面做退款和统计会很难受。表名核心字段说明employeeid, name, username, password, status管理端登录账号categoryid, name, type, sort分类type 区分菜品和套餐dishid, name, category_id, price, image, status菜品setmealid, name, category_id, price, status套餐ordersid, number, user_id, amount, pay_status, status订单主表order_detailid, order_id, dish_id, setmeal_id, number订单明细3. 核心链路拆解从登录鉴权到下单落库3.1 登录与 JWT 鉴权管理端登录走的是用户名密码校验密码在数据库里存的是 MD5 加密后的值登录成功后生成 JWT 令牌返回给前端后续请求通过拦截器校验令牌并解析出员工 ID 存入 ThreadLocal。// 登录服务核心逻辑 public Employee login(EmployeeLoginDTO dto) { // 1. 根据用户名查员工 Employee emp employeeMapper.getByUsername(dto.getUsername()); if (emp null) { throw new AccountNotFoundException(账号不存在); } // 2. 密码比对前端传明文数据库存 MD5 String inputPwd DigestUtils.md5DigestAsHex(dto.getPassword().getBytes()); if (!inputPwd.equals(emp.getPassword())) { throw new PasswordErrorException(密码错误); } // 3. 校验账号状态 if (emp.getStatus() StatusConstant.DISABLE) { throw new AccountLockedException(账号被锁定); } // 4. 生成 JWT载荷里放员工 ID MapString, Object claims new HashMap(); claims.put(empId, emp.getId()); String token JwtUtil.createJWT(jwtProperties.getSecretKey(), jwtProperties.getTtl(), claims); emp.setToken(token); return emp; }参数说明dto.getUsername()和dto.getPassword()来自前端表单jwtProperties从application.yml读取密钥和过期时间claims里只放必要信息不要放密码。逻辑上先查再比对再校验状态最后签发令牌。这里有个血泪经验MD5 比对时前端传的密码不要在后端再加密一次否则永远对不上因为前端已经加密过了——具体是前端加密还是后端加密要看项目约定常见做法是前端传明文、后端加密比对或者前端加密、后端直接比对。3.2 菜品与套餐的缓存设计菜品和套餐的查询频率高、修改频率低适合放 Redis 缓存。常见做法是分类下菜品列表用dish:categoryId作为 key套餐用setmeal:categoryId修改或删除时清除对应缓存。// 根据分类查菜品带缓存 public ListDishVO listWithCache(Long categoryId) { String key dish: categoryId; // 1. 先查 Redis ListDishVO cached (ListDishVO) redisTemplate.opsForValue().get(key); if (cached ! null !cached.isEmpty()) { return cached; } // 2. 缓存没有查数据库 ListDish dishes dishMapper.listByCategoryId(categoryId); ListDishVO result dishes.stream() .map(this::toVO) .collect(Collectors.toList()); // 3. 写回缓存设置过期时间防止雪崩 redisTemplate.opsForValue().set(key, result, 30, TimeUnit.MINUTES); return result; }参数说明categoryId是分类 IDredisTemplate是 Spring 提供的操作模板过期时间设 30 分钟是折中值。逻辑上先查缓存再查库再回写这是标准的 Cache Aside 模式。注意点修改菜品时要主动删除缓存而不是更新缓存因为更新缓存在并发下容易脏数据删除缓存虽然也有窗口期但配合过期时间基本够用。3.3 下单流程与事务控制下单是整个系统最复杂的链路涉及地址校验、购物车读取、金额计算、订单落库、明细落库、清空购物车必须放在同一个事务里。Transactional public OrderSubmitVO submitOrder(OrdersSubmitDTO dto) { // 1. 校验地址是否存在 AddressBook address addressBookMapper.getById(dto.getAddressBookId()); if (address null) { throw new AddressBookBusinessException(地址不存在); } // 2. 获取当前用户 Long userId BaseContext.getCurrentId(); // 3. 查询购物车 ListShoppingCart cartList shoppingCartMapper.list(userId); if (cartList null || cartList.isEmpty()) { throw new ShoppingCartBusinessException(购物车为空); } // 4. 构造订单主表 Orders orders new Orders(); orders.setNumber(String.valueOf(System.currentTimeMillis())); orders.setUserId(userId); orders.setAddress(address.getDetail()); orders.setAmount(calcAmount(cartList)); // 计算总金额 orders.setPayStatus(Orders.UN_PAID); orders.setStatus(Orders.PENDING_PAYMENT); orders.setOrderTime(LocalDateTime.now()); orderMapper.insert(orders); // 5. 构造订单明细 ListOrderDetail details cartList.stream().map(cart - { OrderDetail od new OrderDetail(); od.setOrderId(orders.getId()); od.setDishId(cart.getDishId()); od.setSetmealId(cart.getSetmealId()); od.setNumber(cart.getNumber()); return od; }).collect(Collectors.toList()); orderDetailMapper.insertBatch(details); // 6. 清空购物车 shoppingCartMapper.deleteByUserId(userId); return new OrderSubmitVO(orders.getId(), orders.getNumber(), orders.getAmount()); }参数说明dto.getAddressBookId()是前端选的地址 IDBaseContext.getCurrentId()从 ThreadLocal 取当前用户calcAmount遍历购物车累加金额。逻辑上先校验再构造再落库再清理六步都在一个事务里任何一步失败整体回滚。注意点订单号用时间戳在并发下可能重复常见做法是时间戳加用户 ID 后几位或者用雪花算法金额计算要用 BigDecimal 而不是 double否则会出现 0.1 0.2 不等于 0.3 的玄学问题。3.4 订单状态流转与定时任务订单状态从待付款到待接单到已接单到派送中到已完成中间还有取消和退款。超时未支付的订单需要自动取消常见做法是用 Spring Task 定时扫描。// 每分钟扫描一次超时未支付订单 Scheduled(cron 0 * * * * ?) public void cancelTimeoutOrder() { // 下单超过 15 分钟且未支付 LocalDateTime time LocalDateTime.now().minusMinutes(15); ListOrders list orderMapper.listByStatusAndTime(Orders.PENDING_PAYMENT, time); for (Orders order : list) { order.setStatus(Orders.CANCELLED); order.setCancelReason(超时未支付); order.setCancelTime(LocalDateTime.now()); orderMapper.update(order); } }参数说明cron表达式0 * * * * ?表示每分钟第 0 秒执行minusMinutes(15)是超时阈值。逻辑上查出符合条件的订单批量更新状态。注意点定时任务在集群环境下会重复执行常见做法是加分布式锁或者用 Redis 的 setnx 控制只有一个节点执行另外扫描时间间隔和超时阈值要匹配间隔太长会导致取消不及时。4. 避坑与常见问题排查4.1 跨域问题导致前端请求全部失败现象前端页面能打开但所有接口请求返回 CORS 错误浏览器控制台提示跨域被拦截。原因前后端分离部署时域名或端口不同浏览器同源策略拦截。解决开发环境在后端加跨域配置或者前端配代理生产环境用 Nginx 反向代理把前后端放同一个域名下。Configuration public class WebMvcConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOriginPatterns(*) .allowedMethods(GET, POST, PUT, DELETE) .allowedHeaders(*) .allowCredentials(true) .maxAge(3600); } }注意allowedOriginPatterns和allowedOrigins的区别前者支持通配符且允许携带凭证后者在携带凭证时不能用*。4.2 令牌过期后前端没有跳转登录现象令牌过期后接口返回 401但前端页面卡在原地不跳登录页。原因响应拦截器只处理了业务失败没处理 HTTP 状态码 401。解决在响应拦截器的错误分支里判断状态码401 就清本地令牌并跳登录。request.interceptors.response.use( response response.data, error { if (error.response error.response.status 401) { localStorage.removeItem(token) window.location.href /login } return Promise.reject(error) } )4.3 图片上传后访问 404现象菜品图片上传成功但前端展示时 404。原因上传路径和静态资源映射路径不一致或者 Nginx 没配静态资源目录。解决确认application.yml里的上传路径和WebMvcConfig里addResourceHandlers的映射路径一致生产环境确认 Nginx 的location指向正确。4.4 事务不生效导致数据不一致现象下单时订单落库了但明细没落库或者购物车没清空。原因Transactional方法被同类内部调用代理不生效或者异常被 catch 了没抛出。解决确保事务方法通过代理调用异常要抛出RuntimeException子类或者手动TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()。4.5 Redis 缓存与数据库不一致现象后台改了菜品价格用户端还是旧价格。原因修改时只更新了数据库没删缓存或者删缓存失败没重试。解决修改和删除操作里同步删除对应缓存 key删除失败记录日志并重试查询时设置合理过期时间兜底。5. 二次开发改造思路从跑通到改成自己的项目把项目跑通只是第一步真正有价值的是改成自己的东西。我一般会从三个方向下手换业务场景、加功能模块、优化技术实现。换业务场景是最快的改造方式。外卖的点单逻辑和校园食堂点餐、社区团购、奶茶店小程序几乎一样改改分类名称、菜品字段、订单状态就能套用。比如把“派送中”改成“待取餐”把地址管理改成取餐点选择前端文案和图标换一换就是一个新项目。这里要注意数据库字段名不要大改改注释和前端展示就行否则 Mapper XML 和实体类都要跟着动容易翻车。加功能模块是练手的好办法。可以加优惠券模块用户领券、下单抵扣、过期失效可以加评价模块订单完成后打分写评论可以加库存模块菜品设置库存下单扣减取消回滚。加库存这个功能特别能练事务和并发控制因为多个用户同时下单同一个菜品时会出现超卖常见做法是用 Redis 预扣减或者数据库乐观锁。-- 乐观锁扣库存version 字段控制 UPDATE dish SET stock stock - #{num}, version version 1 WHERE id #{dishId} AND stock #{num} AND version #{version};这条 SQL 的关键在于stock #{num}防止扣成负数version #{version}防止并发覆盖。执行后判断影响行数为 0 说明被其他请求抢先了需要重试或提示用户。优化技术实现是进阶方向。可以把 JWT 换成 Spring Security 加 OAuth2把定时任务换成消息队列延迟队列把单机缓存换成 Redis 集群把日志换成 ELK 收集。每换一个都是在补一块企业级开发的拼图。验证改造是否成功我习惯用三步先跑通原有流程确保没改坏再针对新功能写接口测试最后用 JMeter 或 Postman 做并发测试看库存和订单是否一致。从那以后我每次改完核心链路都强制走一遍“下单-支付-取消-退款”全流程确认状态流转和金额计算都对得上。希望帮到你。本文还有配套的精品资源点击获取

相关新闻

苍穹外卖JavaWeb项目实战:环境搭建、核心模块与避坑指南

苍穹外卖JavaWeb项目实战:环境搭建、核心模块与避坑指南

简介:这是一套面向JavaWeb初学者与进阶开发者的「苍穹外卖」在线订餐系统完整项目资料,适合希望通过真实项目巩固Servlet、JSP、MVC与框架集成等技能的学习者。压缩包共约2000个文件,整体约13.61MB,以388个xml配置、146个java源码…

2026/10/17 20:59:32 阅读更多 →
贝叶斯优化调参指南:从盲调 to 高效搜索

贝叶斯优化调参指南:从盲调 to 高效搜索

简介:这份资源面向机器学习与深度学习方向的开发者、算法工程师及学生,聚焦超参数调优这一影响模型性能与训练效率的关键环节,以贝叶斯优化为核心策略,帮助读者摆脱手动试错的低效困境。压缩包共4个文件,约10.96MB&…

2026/10/17 19:21:24 阅读更多 →
Semantic Flow实战:语义分割精度与速度的平衡之道

Semantic Flow实战:语义分割精度与速度的平衡之道

语义分割这个方向,从2015年FCN把全卷积结构引入像素级分类开始,到2020年前后已经卷成了一片红海。DeepLab系列、PSPNet、HRNet、OCRNet……每隔几个月就冒出一个新结构刷榜。但如果你真正动手部署过分割模型,就会发现一个尴尬的现实&#xff…

2026/10/16 9:41:49 阅读更多 →

最新新闻

约瑟夫环C语言循环链表实现:从建环到出圈的完整方案

约瑟夫环C语言循环链表实现:从建环到出圈的完整方案

简介:这是一份面向C语言初学者与数据结构学习者的约瑟夫死亡游戏完整实现资料,围绕循环链表这一核心结构展开,帮助读者理解环形链表的创建、遍历与结点删除等经典操作。资源包内仅含1个docx文档,大小约18KB,以文字形式…

2026/10/17 21:01:07 阅读更多 →
数据库连接报无监听程序?从监听器到防火墙的排查指南

数据库连接报无监听程序?从监听器到防火墙的排查指南

数据库连接报“无监听程序”,应该是我这些年处理过出现频率最高的问题之一。它的迷惑性在于:报错文案就那么一句话,看起来就是“连不上”,但背后的原因可以从监听服务没开、配置文件丢失,一路延伸到主机名解析和防火墙…

2026/10/17 21:01:07 阅读更多 →
乐企数字开放平台测试中心沙箱联调实操指南:从能力订阅到报告提交

乐企数字开放平台测试中心沙箱联调实操指南:从能力订阅到报告提交

简介:《乐企数字开放平台操作手册-测试中心分册V2.000》面向企业纳税人及平台对接技术人员,聚焦测试中心沙箱测试与能力测试的完整操作流程,帮助读者在模拟环境中评估和验证平台各项能力。手册围绕概述、能力中心、测试中心三部分展开&#x…

2026/10/17 21:01:07 阅读更多 →
socket错误处理封装实战:从errno分类到统一重试机制

socket错误处理封装实战:从errno分类到统一重试机制

1. 从一次线上连接异常说起:socket 错误处理的真实痛点做网络编程的人大概都有过这样的经历:本地跑得好好的服务,一上线就时不时报出一些莫名其妙的错误码,日志里打印出来的信息要么是-1,要么是一串看不懂的 errno 数字…

2026/10/17 21:01:07 阅读更多 →
数据中心网络传输新思路:接收端驱动与包级交织的实践解读

数据中心网络传输新思路:接收端驱动与包级交织的实践解读

做数据中心网络的同行应该都清楚,“数据包传输”这个词听起来基础,真正把它做到极致却极其折磨人。最近我把 NSDI 2021 那篇《A New Approach to Datacenter Packet Transport》的论文又完整啃了一遍,结合我们自己在实验环境里折腾拥塞控制、…

2026/10/17 21:01:07 阅读更多 →
Kepware OPC DA Client配置实战:三层结构与QC质量码诊断

Kepware OPC DA Client配置实战:三层结构与QC质量码诊断

简介:本资源是一份面向工业物联网(IIoT)开发者与自动化工程师的Kepware OPC DA Client实战入门教程,聚焦于使用KEPServerEX构建稳定、高效的数据采集通道。内容从零开始详解项目(Project)创建、Channel配置…

2026/10/17 21:00:06 阅读更多 →

日新闻

DeepSeek选型与部署:V3/R1、Ollama、API全攻略

DeepSeek选型与部署:V3/R1、Ollama、API全攻略

简介:《DeepSeek使用方法.pdf》是一份面向DeepSeek入门及进阶用户的完整使用指南,重点围绕V3与R1两款模型的能力差异与适用场景展开,帮助读者根据任务类型与成本选择合适的模型。资源共1个PDF文件,压缩包大小约311KB,篇…

2026/10/17 0:00:25 阅读更多 →
200+Deepseek润色指令拆解:从底层原理到场景落地的完整指南

200+Deepseek润色指令拆解:从底层原理到场景落地的完整指南

简介:这是一份聚焦DeepSeek提示词优化的《200Deepseek润色指令》PDF文档,共收录两百余条可直接套用的润色指令,涵盖语法修正、风格统一、逻辑梳理、表达精炼等维度,并针对商务写作、学术论文、广告文案等场景给出差异化建议。文档…

2026/10/17 0:00:25 阅读更多 →
OpenCV轮廓法物体计数系统:轻量、可控、可落地的工业级实现

OpenCV轮廓法物体计数系统:轻量、可控、可落地的工业级实现

简介:本资源是一份基于OpenCV的本科课程设计完整报告,面向计算机视觉初学者、图像处理学习者及高校通信/信息类专业学生,解决物体智能计数与检测结果结构化记录的实际问题。项目覆盖图像预处理、轮廓检测、边界框绘制、轨迹栏交互式参数调节、…

2026/10/17 0:00:25 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/16 21:35:51 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/17 1:44:59 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/16 15:04:24 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 21:54:38 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/17 17:22:31 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 14:23:36 阅读更多 →