资讯动态

Spring Boot外卖项目源码实战:MySQL建库到Linux部署全流程

发布时间:2026/10/21 13:50:47 来源:尧图企业网站定制
简介瑞吉外卖项目源码及数据库资源是一套基于SpringBoot与Mybatis-Plus构建的外卖平台完整实现适合正在学习Java Web开发、希望了解企业级项目结构的开发者参考。项目覆盖用户注册、登录、下单、支付等核心业务将短信验证码登录优化为邮箱验证登录同时引入Redis缓存与主从数据库读写分离在Linux环境下提供稳定运行方案。资源共228个文件以69个Java源码、22个JavaScript脚本、21个HTML页面和18个CSS样式表为主附带SQL数据库脚本、YAML配置文件及多类图片素材压缩包整体约32MB目录划分清晰便于按模块查阅和学习。已有5187人学习下载。通过源码可以深入理解SpringBoot自动配置与启动流程、Mybatis-Plus简化CRUD的实践方式、Redis在会话与热门数据场景下的缓存策略以及主从复制如何保障数据安全与读写性能是一份兼顾基础巩固与能力进阶的全栈实战案例。1. 为什么一个外卖项目源码值得花一整晚拆开看做后端开发的人多少有个共同的焦虑看过一堆 Spring Boot 教程却从没把一个完整项目从 MySQL 建库一路跑到 Linux 上线。瑞吉外卖这套源码加数据库脚本正好把这条链路补齐了——它不是一个教学 Demo而是一个包含员工登录、菜品管理、套餐组合、购物车、订单状态流转、微信小程序端交互的真实业务闭环。整套东西跑起来之后你能顺手掌握 MyBatis-Plus 的常规写法、Nginx 静态资源代理、MySQL 建表脚本里的字段取舍以及 Linux 部署时最容易翻车的权限和路径问题。适合两类人刚学完 Spring Boot 语法、急需一个完整项目练手的新手以及没在 Linux 上实际部署过 Java Web 项目、想拿现成代码试水的转岗开发者。往下拆解的方式我会按「先看结构、再跑代码、再上服务器」的顺序来中间重点标出哪些文件值得一行行读。2. 把后端项目拆开看结构、依赖与 MyBatis-Plus 的真实用法2.1 拿到手先做的三件事改配置、建库、跑起来源码包解压之后不要急着用 IDEA 打开就点运行。第一次跑通的成功率取决于你有没有先把数据库和配置文件对齐。我一般按下面的顺序操作mysql -u root -p db/ruiji.sql先把数据库脚本导入这条命令会在 MySQL 里建出完整的 ruiji 库和全部表结构并写入默认的员工账号。注意脚本里的建表语句用的是 InnoDB 引擎排序规则是utf8mb4_general_ci便于中文菜名和地址的存储与查询。接下来修改项目的配置文件。绝大多数启动失败原因都出在这个文件的三个参数没对准server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/ruiji?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 123456不要小看serverTimezoneAsia/Shanghai这个参数MySQL 8 默认时区和本地 Java 进程有时区差不加它数据库连接会直接报时间错乱或者干脆建立不上。密码换成你自己本机的别用脚本里默认的。这里有个习惯我每次都执行改完配置先跑一遍mvn -v确认 Maven 版本再执行mvn clean package -DskipTests打包避免 IDE 里一切正常命令行一打包就报环境变量找不到的问题。2.2 从一个登录接口看 MyBatis-Plus 的正确打开方式登录模块是理解这套代码风格的钥匙。员工登录功能没有用传统的 XML 写 SQL而是直接继承了一个BaseMapper接口让 CRUD 变成了零 SQL 的体验。核心代码大致长这样public class EmployeeServiceImpl extends ServiceImplEmployeeMapper, Employee implements EmployeeService { Override public Employee login(Employee employee) { LambdaQueryWrapperEmployee wrapper new LambdaQueryWrapper(); wrapper.eq(Employee::getUsername, employee.getUsername()); return this.getOne(wrapper); } }LambdaQueryWrapper是 MyBatis-Plus 里最值得掌握的写法它的价值在于字段名通过方法引用写不直接接触数据库列名这样一来后端重构变量名时SQL 条件不会跟着断掉。getOne方法底层会自动补上LIMIT 1当 username 在表里有唯一索引时这里不会有性能问题。登录之后还需要处理密码比对和 MD5 加密。源码中用了DigestUtils.md5DigestAsHex这是 Spring 自带的工具类不需要额外引包。参数上有一个坑MD5 加密后的密文是 32 位十六进制字符串数据库表里 password 字段的 CHAR 长度必须大于等于 32如果你改了脚本里的字段长度登录会一直提示密码错误。遇到这种问题时优先检查数据库字段定义别先怀疑代码逻辑。2.3 分页与条件检索菜品管理模块的样板代码菜品管理的代码几乎覆盖了后台管理系统最常见的增删改查范式。分页查询配合条件检索是其中值得细看的组合它在业务里的表现是输入菜品名称关键字按类别筛选再按更新时间倒序分页展示。核心实现如下GetMapping(/page) public RPageDish page(int page, int pageSize, String name) { PageDish pageInfo new Page(page, pageSize); LambdaQueryWrapperDish wrapper new LambdaQueryWrapper(); wrapper.like(StringUtils.isNotBlank(name), Dish::getName, name) .orderByDesc(Dish::getUpdateTime); dishService.page(pageInfo, wrapper); return R.success(pageInfo); }like的第一个参数是布尔条件StringUtils.isNotBlank(name)为 true 时才拼接 LIKE 条件这是 MyBatis-Plus 里非常实用的动态条件写法。页面上的查询参数不传时这条 SQL 就不会带上 WHERE 条件避免出现空查询结果。分页插件PaginationInnerInterceptor在配置类里统一注册作用范围覆盖所有 Service 层分页操作你不用在每张表上重复配置。从这套代码里能总结出来的规律是在这个项目里MyBatis-Plus 至少在三个层面对传统 MyBatis 进行了替换——条件构造器替代手写 SQL、分页插件替代物理分页工具、ServiceImpl默认实现替代重复的 CRUD 方法。理解这三个点后面你自己写新模块时就能照葫芦画瓢。3. 业务核心拆解购物车、订单状态与数据库表设计逻辑3.1 购物车表为什么不建订单号订单表又为什么冗余地址字段打开db/ruiji.sql第一件事去看shopping_cart和orders两张表的字段设计。购物车表的落库思路是「用户在店内期间的行为快照」每一行记录用户加入的菜品、口味、数量以及关联的菜品 ID 和店铺 ID。它不建订单号因为购物车本身只是一个中间态真正的业务语义在结算动作发生时才建立。而订单表恰恰相反它把用户地址、收件人姓名、手机号、配送金额全部以冗余字段的形式直接落进订单行。你自己写表时可能会想着「地址用 user_id 关联用户表就好」但外卖场景下用户地址随时可能修改订单作为历史事实必须定格下单那一刻的快照。订单表里还有status字段管理状态流转1 待付款、2 待接单、3 已接单、4 派送中、5 已完成、6 已取消。源码里状态变更的逻辑并不复杂但状态常量散落在业务代码里后续维护时需要留意这些魔法数字。3.2 金额计算链路从购物车到订单精度问题藏在哪里金额计算是外卖项目里最容易踩坑的地方。从购物车勾选菜品到提交订单需要经过两轮计算第一轮在前端勾选时即时算总额第二轮在提交订单接口里重新按数据库菜品价格计算。第二轮的目的是防止前端篡改单价但前提是后端算价方式必须统一。源码里大致是这个模式BigDecimal amount new BigDecimal(0); for (ShoppingCart cart : carts) { amount amount.add(cart.getAmount()); } order.setAmount(amount);不要把amount声明成double两者在金额计算上的差异一句话就能说明白虽然10.1 0.2在 double 里会得到近似值但在 BigDecimal 里就是精确值。数据库里金额字段统一使用decimal(10, 2)Java 实体对应字段用BigDecimal这是一个必须严格遵守的原则。如果发现某张表的金额字段是float或varchar属于历史遗留问题应该在导入脚本后立即用 ALTER 语句修正。订单提交接口里还有一个关键的库存扣减动作。源码使用「先查菜品状态再插入订单最后扣减库存」的顺序。这里有一个并发隐患当同一道菜在同一秒被大量下单时最后一步扣库存可能出现超卖。针对这个项目的定位不需要引入分布式锁但可以在扣库存 SQL 上做安全校验UPDATE dish SET stock stock - #{num} WHERE id #{id} AND stock #{num}把stock #{num}直接写进 UPDATE 条件让数据库的原子性来兜底是实践中最轻量的防超卖手法。3.3 前端联调时的数据格式约束返回结果包装类的统一约定前端资源目录下有一套 Vue 相关的静态页面它们请求后端时统一接收RT结构。这是一个标准的统一返回对象包含code、msg和data三个字段。登录成功code为 1失败为 0页面端自己的 Interceptor 会根据这个 code 决定是否跳转登录页。自己扩展新接口时遵循相同的返回值约定前端代码几乎不需要改动。这里值得留意的是项目的员工登录校验通过拦截器实现但拦截器在配置里注册时放行规则必须包含backend下的登录接口、静态资源路径和前端页面路径。如果你在替换资源目录时遗漏了某些放行规则最常见的现象是前端页面能打开但所有请求都被拦截器打回登录页这是静态资源配置和拦截器配合时的经典翻车点。4. 把项目部署到 Linux构建、上传与进程守护的完整路径4.1 从本地打包到服务器落地的完整命令链开发环境跑通以后部署到 Linux 服务器是另一套流程。如果你没实际操作过记住一个原则整套部署路径可以在半小时内完成但中间任何一步路径写错或权限不足都会导致服务起不来。以下是我会执行的完整命令链mvn clean package -DskipTests scp target/ruiji-1.0.0.jar root服务器IP:/opt/ruiji/ ssh root服务器IP cd /opt/ruiji nohup java -jar ruiji-1.0.0.jar --spring.profiles.activeprod app.log 21 打包跳过测试是省时的常规操作但前提是你确认自己已经本地验证过功能。上传到/opt/ruiji这个固定目录是为了后续用 systemd 管理时路径统一。nohup加是临时后台启动方式日志会写到app.log。此时立刻执行tail -f app.log观察启动过程Spring Boot 的启动日志会明确告诉你端口是否被占用、数据库是否连通。4.2 生产环境专用配置数据库账号与日志级别隔离直接使用application.yml里的开发配置上线是不可取的生产环境和本地的主要差异在三处数据库账号权限、日志输出策略、文件上传路径。标准做法是新建一个application-prod.yml然后在启动命令里用--spring.profiles.activeprod指定激活spring: datasource: url: jdbc:mysql://localhost:3306/ruiji?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: ruiji_prod password: 替换成强密码 logging: level: com.ruiji.mapper: warn root: info file: name: /opt/ruiji/logs/ruiji.log生产库账号不要用 root 直连我的习惯是为项目单独建一个账号只授予它ruiji库的增删改查权限。日志级别里把 mapper 调整为 warn减少 MyBatis-Plus 的 SQL 日志输出量避免磁盘被快速写满。同时按天滚动日志的策略需要配合logback-spring.xml使用这套代码的标准配置在resources/logback-spring.xml里可以直接改。4.3 Nginx 反代与静态资源分离前端页面和后端接口的接驳项目的前端资源最终由 Nginx 托管后端接口通过反向代理转发。部署的关键是理解「静态资源直接访问动态请求走代理」的分工逻辑。推荐的 Nginx 配置如下server { listen 80; server_name your_domain_or_ip; location /backend/ { alias /opt/ruiji/dist/backend/; index index.html; try_files $uri $uri/ /backend/index.html; } location /front/ { alias /opt/ruiji/dist/front/; index index.html; try_files $uri $uri/ /front/index.html; } location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }try_files是 SPA 路由的核心找不到具体文件时回退到 index.html前端路由才能正常工作。location /api/里的proxy_pass末尾不带路径意思是/api/order原样转发给后端http://127.0.0.1:8080/api/order不重写路径。如果你改成proxy_pass http://127.0.0.1:8080/请求会变成http://127.0.0.1:8080/order后端没有这个路径时返回 404。这个斜杠的差异不属于玄学是 Nginx 代理配置里最容易翻车的一处。4.4 进程守护与开机自启systemd 而不是 nohup用nohup启动的服务有个问题进程会挂靠在当前 SSH 会话下一旦会话中断服务有几率随之终止。生产环境下的标准做法是注册成 systemd 服务在/etc/systemd/system/ruiji.service中写入[Unit] DescriptionRuiji Backend Service Afternetwork.target mysql.service [Service] Userwww-data WorkingDirectory/opt/ruiji ExecStart/usr/bin/java -jar /opt/ruiji/ruiji-1.0.0.jar --spring.profiles.activeprod Restarton-failure RestartSec10 [Install] WantedBymulti-user.target关键在于Userwww-data要保证该用户对/opt/ruiji目录有读写权限包括后续要写入的日志目录。写完配置后依次执行systemctl daemon-reload systemctl enable ruiji systemctl start ruiji systemctl status ruijiRestarton-failure意味着服务异常退出时 10 秒后自动拉起省去手动重启的麻烦。配合systemctl status查看进程状态能精确判断服务是正常运行还是处于反复重启的状态。到这里后端、前端、数据库三者的部署关系就完整了访问服务器 IP 的 80 端口即可进入管理端页面。5. 常见问题排查从启动失败到数据异常的五个实战记录5.1 启动时报 Access denied for user密码正确但连不上现象后端服务启动日志里频繁抛Access denied for user rootlocalhost第一时间检查application-prod.yml里的密码发现和数据库完全一致但就是连不上。原因MySQL 8 默认使用caching_sha2_password认证插件而项目依赖的 MySQL 驱动版本较老两者不兼容会导致认证失败。解决执行ALTER USER ruiji_prodlocalhost IDENTIFIED WITH mysql_native_password BY 新密码;或者直接把驱动升级到最新版。我习惯两者同时做因为前者保证兼容后者消除隐患。5.2 后端接口能通前端页面图片全部 404现象接口返回的数据正常但所有菜品图片和静态资源显示不出来浏览器控制台提示 404。原因本地开发时图片路径走的是项目内自定义的上传目录部署到服务器后没有创建对应的目录或者 Nginx 的 alias 路径没有指向真实的图片存储位置。解决在项目配置里确认自定义上传路径的配置项比如ruiji.path.upload然后在服务器上手动创建并赋予运行用户写权限。同时调整 Nginx 配置新增一个专门的location指向/opt/ruiji/upload/与后端代码里保存图片的目标目录保持一致。5.3 打包时找不到主类或依赖冲突现象执行mvn clean package报Failed to execute goal org.springframework.boot:spring-boot-maven-plugin或者运行时ClassNotFoundException。原因pom.xml里的依赖版本不一致常见于本机 Maven 缓存里同时存在多个不同版本的 MyBatis-Plus 或 fastjson 包。解决优先执行mvn dependency:tree查看依赖树找到重复或冲突的依赖在pom.xml里显式声明要使用的版本。然后执行mvn clean package重新打包这一步能解决大部分由 Maven 缓存引起的诡异报错。5.4 分页查询接口返回的数据总比预期少现象列表页数据量明显少于数据库实际记录数翻页时部分数据丢失。原因分页插件拦截器注册了多个实例或 MyBatis-Plus 与手写的 PageHelper 分页插件同时存在导致分页逻辑叠加。解决检查配置类只保留PaginationInnerInterceptor一个分页插件实例并确保它在 MyBatis 拦截器链中的顺序是唯一的。同时确认实体类的分页参数接收是否正常Page对象必须是请求参数的直接映射。5.5 前端登录一直提示「用户名或密码错误」现象无论输入什么账号密码都提示错误但数据库密码字段看起来是明文。原因源码登录逻辑里使用 MD5 加密后与数据库比对但数据库表里存的密码没有经过同样加密两者不一致导致对比失败。解决核对数据库脚本初始化时写入的默认密码是否已经是 MD5 格式。需要用工具生成一条加密后的密文更新回员工表。另外检查密码比对逻辑是否把两次 MD5 结果做了大小写统一解密结果大小写不一致也会造成判断失败。6. 进阶玩法把单体应用改造成带缓存与鉴权加固的版本拿到这套源码并跑通之后可以顺手做两个低成本改造它们带来的收益是部署更稳、安全更高、面试或者简历上也更有的写。第一个改造是给菜品列表加上 Redis 缓存。当前菜品页每次加载都直接查询 MySQL改造时在 Service 层读取菜品时优先查缓存String key dish: categoryId; String cacheVal redisUtil.get(key); if (StringUtils.isNotEmpty(cacheVal)) { return JSON.parseArray(cacheVal, Dish.class); } ListDish list this.list(...); redisUtil.set(key, JSON.toJSONString(list), 30, TimeUnit.MINUTES);注意设置 30 分钟过期时间避免后台修改菜品后用户端迟迟不更新。改完代码后在pom.xml引入spring-boot-starter-data-redis并在配置里加上 Redis 连接信息。第二个改造是给管理端的敏感操作加上操作日志。在员工登录、套餐修改、订单状态变更这些关键接口上利用 AOP 创建一个切面在方法执行后记录操作人、操作内容、IP 和时间到一张独立日志表。实现上不使用额外框架用 Spring 自带的Around注解就能完成。需要保留的参数信息大多已经在拦截器里设置了当前登录员工的 ID可以直接取用。这两处改造完成后整个项目的结构就从「能跑」变成了「抗打」。从那以后我每次拿到一个新项目源码都会强制走一遍先看表结构字段是否规范、再看配置文件的部署参数是否隔离、最后在本地跑通一次完整构建流程确认三个环节没问题才继续往下改代码。这套习惯帮我避掉了不少黑匣子一样的诡异问题。如果这套瑞吉外卖源码能让你把 Spring Boot、MyBatis-Plus 和 Linux 部署这条链路真正串起来希望它能给你节省一晚上的踩坑时间。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑