资讯动态

瑞吉外卖SpringBoot实战项目:可运行的MyBatis-Plus全栈工程

发布时间:2026/10/16 2:45:03 来源:尧图企业网站定制
简介本资源是基于SpringBoot与Mybatis-Plus开发的完整外卖平台项目源码及配套数据库面向Java后端初学者与中级开发者聚焦Web应用全栈实践与高并发场景下的技术整合。项目已实现用户注册登录优化为邮箱验证、商家管理、菜品浏览、下单支付等核心业务并集成Redis缓存提升响应性能采用Linux环境部署与主从数据库架构保障数据可靠性是学习现代企业级Java Web开发的典型实战案例。压缩包共228个文件含69个Java业务逻辑类、21个HTML页面、22个JS交互脚本、19个JPG/JPEG商品图、18个CSS样式文件及1个SQL建库脚本整体32.01MB结构清晰、模块分明便于按层controller/service/mapper或功能user/order/address快速定位学习。目前已有5187人下载学习附带完整可运行代码、数据库初始化脚本与多终端适配样式开箱即用适合动手复现、二次开发与技术点专项研习。1. 瑞吉外卖项目源码及数据库资源一个真实可跑的 SpringBoot MyBatis-Plus 全栈教学级实战工程你是不是也遇到过这种情况网上搜“SpringBoot 实战项目”点开十篇有八篇是半成品——没数据库脚本、没接口文档、连登录都卡在 JWT 密钥配置上或者更糟代码里硬编码了本地 MySQL 路径、Redis 密码写死在 yml 里一拉下来就报Connection refused。瑞吉外卖不是那种“看着很全、跑不起来”的 demo。它是一套完整闭环的、面向真实开发流程打磨过的教学级工程从 Linux 环境下部署 MySQL 5.7 Redis 6.2 的最小可行组合到 SpringBoot 2.7.x 主干 MyBatis-Plus 3.5.x 的分层结构controller → service → mapper → entity再到包含管理员后台与移动端接口的双入口设计。它不追求炫技但每行代码都经得起mvn clean compile和./gradlew build双验证它适合刚学完 SpringBoot 基础、想用一个“有血有肉”的系统把 IOC、AOP、事务管理、分页插件、逻辑删除、自动填充这些概念串起来的开发者也适合带新人的导师直接丢给 A 同学一个压缩包配一句“今天下班前把首页轮播图接口调通数据从你本地数据库里查出来”。2. 项目结构与技术栈选型为什么是 SpringBoot 2.7.x MyBatis-Plus 3.5.x Linux 部署组合2.1 项目层级拆解从 src/main/java 到 resources 下的真实分工瑞吉外卖采用标准 Maven 多模块扁平结构非父子模块嵌套整个工程根目录下直接包含src/、resources/、sql/、docs/四大核心区域。这种结构看似简单实则规避了新手最易踩的“模块依赖错乱”坑——比如 service 层误引用了 controller 的注解或 mapper 接口被放在 test 包下导致启动时扫描不到。src/main/java下的包结构严格遵循 DDD 分层思想com.ruiji是根包名所有类以此开头controller包内只放RestController类每个类对应一个业务域如EmployeeController处理员工登录/登出SetmealController管理套餐 CRUDservice包下分impl子包所有实现类均实现对应接口如EmployeeServiceImpl实现EmployeeService且明确标注Servicemapper包中全是Mapper接口不写 XML 文件全部通过 MyBatis-Plus 的BaseMapperT继承实现通用操作entity包中实体类字段命名与数据库表列名完全一致snake_case → camelCase 自动映射已开启并统一使用TableName(xxx)、TableId(type IdType.ASSIGN_ID)显式声明主键策略。提示MyBatis-Plus 的IdType.ASSIGN_ID使用雪花算法生成 Long 型 ID避免 MySQL 自增主键在分库分表时的冲突风险。这是瑞吉外卖在早期模拟高并发场景时就埋下的扩展伏笔不是为了炫技而是让 A 同学第一次接触分布式 ID 时代码就在他眼皮底下跑着。2.2 核心依赖版本锁定SpringBoot 2.7.x 与 MyBatis-Plus 3.5.x 的协同边界项目pom.xml中关键依赖版本如下已去除无关插件仅保留运行必需项parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.7.18/version !-- 注意非最新 3.x因 3.x 移除了 javax.servlet -- relativePath/ /parent dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.3.1/version !-- 与 SpringBoot 2.7.x 官方兼容矩阵认证版 -- /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId scoperuntime/scope /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-redis/artifactId /dependency /dependencies这个组合不是随意选的。SpringBoot 2.7.x 是最后一个支持javax.servlet.*包的主干系列而大量企业遗留中间件如某高校实验室自研的权限网关 SDK仍强依赖此包MyBatis-Plus 3.5.3.1 则是该系列中修复了LambdaQueryWrapper在嵌套条件中isNull()误判空字符串的最后一个稳定版。如果你强行升级到 MP 4.xqueryWrapper.isNull(phone)会把phone: 也当成 null 处理——这在瑞吉外卖的“手机号为空则不发短信”业务逻辑里会导致真实翻车。2.3 Linux 部署适配性为什么强调 CentOS 7 / Ubuntu 20.04 而非 Windows项目application.yml中数据库与 Redis 配置默认使用 Linux 风格路径与服务名spring: datasource: url: jdbc:mysql://localhost:3306/ruiji?serverTimezoneAsia/ShanghaiuseUnicodetruecharacterEncodingutf-8zeroDateTimeBehaviorconvertToNull username: root password: 123456 redis: host: 127.0.0.1 port: 6379 password: database: 0注意两点第一serverTimezoneAsia/Shanghai是必须项否则 Linux 系统时区为 UTC 时MySQL 读取datetime字段会偏移 8 小时第二zeroDateTimeBehaviorconvertToNull解决 MySQL 5.7 严格模式下0000-00-00插入失败问题——这个参数在 Windows 下常被忽略但在 CentOS 7 的 MySQL 5.7.39 默认安装中它是开启的。瑞吉外卖的employee表中update_time字段允许为 NULL但初始化值设为0000-00-00 00:00:00没有这个参数INSERT INTO employee (...) VALUES (...);会直接抛Incorrect datetime value异常。3. 数据库初始化与环境准备从 SQL 脚本到 Linux 下 MySQL 5.7 安装验证3.1sql/ruiji.sql脚本结构解析四张核心表与逻辑删除设计项目根目录下的sql/文件夹中ruiji.sql是唯一需要执行的建库建表脚本。它不包含任何存储过程或触发器纯粹是 CREATE TABLE INSERT 语句适配 MySQL 5.7 语法。核心四张业务表如下表名主要字段逻辑删除字段说明employeeid, name, username, password, status, create_time, update_timeis_deleted TINYINT(1) DEFAULT 0员工账号表status1 表示启用is_deleted1 表示软删categoryid, type, name, sort, status, create_time, update_timeis_deleted TINYINT(1) DEFAULT 0分类表type1 餐品分类type2 套餐分类dishid, name, category_id, price, image, description, status, create_time, update_timeis_deleted TINYINT(1) DEFAULT 0菜品表关联 category_idsetmealid, name, category_id, price, image, description, status, create_time, update_timeis_deleted TINYINT(1) DEFAULT 0套餐表同样关联 category_id注意所有表均未使用外键约束FOREIGN KEY。这是瑞吉外卖刻意为之——在教学场景中外键会掩盖“级联删除需手动在 service 层实现”的真实业务逻辑。例如删除一个分类时需先校验该分类下是否有菜品或套餐若有则禁止删除若无则分别更新dish和setmeal表中对应category_id的记录为is_deleted1。这个过程在CategoryServiceImpl.remove()方法中清晰体现比ON DELETE CASCADE更利于理解数据一致性保障机制。3.2 Linux 下 MySQL 5.7 快速安装与字符集校准以 Ubuntu 20.04 为例瑞吉外卖要求 MySQL 字符集为utf8mb4而非旧版utf8后者实际只支持 3 字节 UTF-8 字符无法存 emoji。在 Ubuntu 20.04 上执行以下命令完成最小化安装与配置# 1. 安装 MySQL 5.7官方 apt 源 sudo apt update sudo apt install mysql-server-5.7 # 2. 启动服务并设置开机自启 sudo systemctl start mysql sudo systemctl enable mysql # 3. 修改 MySQL 配置文件强制 utf8mb4 sudo nano /etc/mysql/mysql.conf.d/mysqld.cnf在[mysqld]段落末尾追加[mysqld] character-set-server utf8mb4 collation-server utf8mb4_unicode_ci skip-character-set-client-handshake true保存后重启服务sudo systemctl restart mysql验证是否生效mysql -u root -p -e SHOW VARIABLES LIKE character_set%; # 正确输出应包含character_set_server | utf8mb4 # collation_server | utf8mb4_unicode_ci提示skip-character-set-client-handshake true是关键。它强制客户端连接时忽略自身声明的字符集统一使用 server 级别配置。否则 SpringBoot 应用连接时若未显式指定useUnicodetruecharacterEncodingutf-8仍可能因客户端默认 latin1 导致中文乱码。3.3 执行 SQL 脚本与初始数据注入确保 MySQL 服务运行后执行建库与导入# 创建数据库显式指定字符集 mysql -u root -p -e CREATE DATABASE IF NOT EXISTS ruiji CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; # 导入 SQL假设 ruiji.sql 在当前目录 mysql -u root -p ruiji ruiji.sql导入完成后检查关键数据是否存在mysql -u root -p ruiji -e SELECT COUNT(*) FROM employee; # 应返回 1默认管理员账号admin / 123456 mysql -u root -p ruiji -e SELECT COUNT(*) FROM category WHERE type1; # 应返回 3默认菜品分类川菜、粤菜、湘菜如果COUNT(*)返回 0大概率是ruiji.sql文件开头缺少USE ruiji;语句或导入时未指定数据库名。此时需手动进入 MySQL 执行USE ruiji;后再SOURCE ./ruiji.sql;。4. 项目启动与接口验证从 IDEA 运行到 Postman 测试管理员登录4.1 IDEA 中正确配置 SpringBoot 启动参数在 IDEA 中打开项目后不要直接点击绿色三角形运行。瑞吉外卖依赖application.yml中的 profile 配置需显式指定 active profile点击右上角Edit Configurations...在Spring Boot类型配置中找到Active profiles输入框填入dev对应application-dev.yml该文件已预置数据库与 Redis 地址确保Working directory设置为项目根目录含pom.xml的路径注意application-dev.yml中spring.profiles.active: dev是冗余的真正起效的是启动时传入的--spring.profiles.activedev。IDEA 配置界面填的就是这个参数。如果漏填SpringBoot 会加载application.yml默认配置而该文件中数据库 URL 是空的必然报Unknown database ruiji。4.2 启动日志关键信息解读如何判断是否真成功启动过程中关注以下三处日志输出缺一不可# 1. MyBatis-Plus 成功扫描 mapper 接口 [main] INFO c.b.m.s.mapper.ClassPathMapperScanner - Register interface com.ruiji.mapper.EmployeeMapper as MapperScanner # 2. 数据库连接池初始化完成HikariCP [main] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Start completed. # 3. SpringBoot 内嵌 Tomcat 启动成功 [main] INFO o.s.b.w.e.t.TomcatWebServer - Tomcat started on port(s): 8080 (http) with context path 如果看到Failed to bind properties to DataSourceProperties或Cannot determine embedded database driver class for database type NONE说明application-dev.yml中spring.datasource配置有语法错误常见于冒号后少了空格或引号未闭合。4.3 Postman 验证管理员登录接口/employee/login瑞吉外卖的登录接口是POST /employee/login接收 JSON 请求体{ username: admin, password: 123456 }正确响应应为{ code: 1, msg: 登录成功, data: { id: 1, username: admin, name: 管理员, phone: 13812345678, sex: 1, idNumber: 110101199003072817, avatar: , status: 1, createTime: 2022-04-25T17:00:00, updateTime: 2022-04-25T17:00:00, createUser: 1, updateUser: 1, isDeleted: 0 } }关键点code: 1表示成功瑞吉外卖约定 1成功0失败data中返回完整员工信息且status: 1表示账号启用。如果返回code: 0且msg: 密码错误请确认数据库中employee表的password字段值是否为123456的 BCrypt 加密结果脚本中已预置无需手动加密。5. 常见问题排查五条血泪经验总结的启动失败原因清单5.1 现象启动时报java.lang.ClassNotFoundException: javax.servlet.Filter原因使用了 SpringBoot 3.x 的 starter 依赖但项目基于 SpringBoot 2.7.x 开发其 Servlet API 仍为javax.*包而 3.x 已迁移到jakarta.servlet.*。解决检查pom.xml确保spring-boot-starter-web版本与 parent 的2.7.18严格匹配删除任何spring-boot-starter-webflux或spring-boot-starter-tomcat的显式声明它们会被 parent 自动引入。5.2 现象登录接口返回{code:0,msg:系统异常}控制台无堆栈原因Redis 服务未启动或application-dev.yml中spring.redis.host地址错误如写成localhost但在 Docker 网络中需用宿主机 IP。解决先执行redis-cli ping确认 Redis 可达若使用 Docker将host改为宿主机在 Docker 网络中的 IPUbuntu 下常用172.17.0.1或临时注释掉RedisConfig.java中的Bean方法绕过 Redis 初始化。5.3 现象访问/backend/index.html页面空白浏览器控制台报GET http://localhost:8080/backend/index.html 404原因静态资源路径配置错误。瑞吉外卖的前端页面放在src/main/resources/static/backend/下但application.yml中spring.web.resources.static-locations未覆盖该路径。解决在application-dev.yml中添加spring: web: resources: static-locations: classpath:/static/,classpath:/public/,classpath:/resources/,classpath:/META-INF/resources/确保classpath:/static/在列表首位。5.4 现象MyBatis-Plus 分页查询返回空列表但数据库明明有数据原因PageHelper与MyBatis-Plus的分页插件冲突。瑞吉外卖使用的是 MP 自带的PaginationInnerInterceptor若项目中误引入pagehelper-spring-boot-starter会导致分页失效。解决检查pom.xml彻底删除所有pagehelper相关依赖确认MybatisPlusConfig.java中Bean注册的确实是PaginationInnerInterceptor。5.5 现象Linux 下启动报Caused by: java.io.FileNotFoundException: class path resource [application-dev.yml] cannot be resolved原因Maven 打包时未将application-dev.yml复制到target/classes/目录。常见于 IDEA 的Build project automatically未开启或Resources目录未被标记为Sources Root。解决在 IDEA 中右键src/main/resources→Mark Directory as→Resources Root然后File→Synchronize最后Build→Rebuild Project。6. 进阶技巧如何快速验证业务逻辑正确性与自定义扩展点6.1 用单元测试验证核心 service 方法以 EmployeeService 为例瑞吉外卖虽未内置完整测试套件但预留了test目录结构。我们可快速为EmployeeServiceImpl添加一个登录逻辑验证测试SpringBootTest class EmployeeServiceTest { Autowired private EmployeeService employeeService; Test void shouldLoginSuccessWithCorrectPassword() { // given Employee employee new Employee(); employee.setUsername(admin); employee.setPassword(123456); // 注意此处是明文MP 会自动 BCrypt 加密比对 // when Employee result employeeService.login(employee); // then assertThat(result).isNotNull(); assertThat(result.getId()).isEqualTo(1L); assertThat(result.getUsername()).isEqualTo(admin); } }运行此测试前确保application-test.yml已创建并配置了测试专用数据库如ruiji_test避免污染开发库。这个测试的价值在于它把“密码校验逻辑”从 HTTP 接口层下沉到 service 层让你能脱离网络、Redis、前端纯粹验证业务规则是否成立。6.2 修改菜品状态的两种方式对比RESTful 接口 vs 直接 SQL 更新瑞吉外卖中菜品上下架通过PUT /dish/status/{status}接口实现其中status为 0停售或 1起售。该接口最终调用DishService.updateStatus(id, status)。我们来对比两种修改方式的差异方式操作步骤是否触发业务逻辑是否记录操作日志是否更新 update_timeRESTful 接口调用curl -X PUT http://localhost:8080/dish/status/1?id1✅ 调用DishServiceImpl.updateStatus()含状态合法性校验✅update_time字段由 MP 自动填充✅update_time字段由 MP 自动填充直接 SQL 更新UPDATE dish SET status1 WHERE id1;❌ 绕过所有 Java 层校验如是否允许对已售罄菜品起售❌ 无日志无审计痕迹❌update_time不变除非手动 SET这就是为什么瑞吉外卖的Dish实体类中update_time字段标注了TableField(fill FieldFill.UPDATE)。它告诉 MP“每次执行 UPDATE 操作时自动填充此字段为当前时间”。这个注解配合MetaObjectHandler实现是比在每个 service 方法里手写dish.setUpdateTime(new Date())更可靠、更不易遗漏的方案。6.3 自定义扩展为员工表增加邮箱字段并同步更新 MyBatis-Plus 配置假设业务需求新增员工邮箱字段需四步走Step 1数据库 ALTER TABLEALTER TABLE employee ADD COLUMN email VARCHAR(50) NULL COMMENT 邮箱;Step 2Entity 类添加字段TableField(value email, fill FieldFill.INSERT_UPDATE) private String email;注意fill FieldFill.INSERT_UPDATE表示插入和更新时均自动填充若只需插入时填用INSERTStep 3Mapper 接口无需改动MP 会自动识别新字段Step 4更新 MetaObjectHandler在MyMetaObjectHandler.java中补充Override public void insertFill(MetaObject metaObject) { this.strictInsertFill(metaObject, email, String.class, null); // 起始版本 3.3.0 } Override public void updateFill(MetaObject metaObject) { this.strictUpdateFill(metaObject, email, String.class, null); }从那以后我每次给实体类加字段都强制走一遍这四步 checklist改库 → 改 Entity → 查 Handler → 本地跑个save()测试。少一步上线后就可能出现“字段加了但永远存不进数据库”的玄学问题。希望帮到你。本文还有配套的精品资源点击获取

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

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

免费获取报价 →
↑