发现这个项目非常适合作为 SpringBoot 入门案例。它涵盖了企业级应用开发的常见场景,包括 RESTful 接口设计、MyBatis-Plus 实战、事务管理等核心知识点。今天就带大家深入剖析一下瑞吉外卖的源码设计与实现思路。
一、后端整体架构
瑞吉外卖采用经典的三层架构设计,整体结构清晰,职责分明:
bash
com.itheima.reggie
├── controller // 控制器层,处理HTTP请求
├── service // 业务逻辑层
│ └── impl // 业务逻辑实现
├── mapper // 数据访问层
├── entity // 实体类
├── dto // 数据传输对象
├── common // 公共组件
├── config // web mybatis等配置
├── filter // 登录过滤器
├── utils // 常用工具类
└── ReggieApplication.java // 启动类
这种架构设计符合单一职责原则,每层只关注自己的核心功能:
- 控制器层:负责接收请求、参数校验、返回响应
- 服务层:实现核心业务逻辑,处理事务
- 数据访问层:与数据库交互
- 实体类:映射数据库表结构
- DTO:处理跨层数据传输,避免暴露实体类细节
二、后端核心技术点
1. 统一响应结果封装
项目中定义了R类作为统一响应结果封装,这是企业开发中的最佳实践:
bash
// 动态数据
public static <T> R<T> success(T object) {
R<T> r = new R<T>();
r.data = object;
r.code = 1;
return r;
}
这种设计的优势在于:
- 前后端交互格式统一,便于前端处理
- 包含状态码,方便错误排查
- 泛型设计支持各种数据类型的返回
2. MyBatis-Plus 的实战应用
项目大量使用了 MyBatis-Plus 简化数据库操作,以DishController中的分页查询为例:
java
@GetMapping("/page")
public R<Page> page(int page, int pageSize, String name){
// 分页构造器对象
Page<Dish> pageInfo = new Page<>(page,pageSize);
Page<DishDto> dishDtoPage = new Page<>();
// 条件构造器
LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.like(name != null,Dish::getName,name);
queryWrapper.orderByDesc(Dish::getUpdateTime);
// 执行分页查询
dishService.page(pageInfo,queryWrapper);
// 对象拷贝与处理...
return R.success(dishDtoPage);
}
MyBatis-Plus 的优势在这里体现得淋漓尽致:
- 无需编写 XML,通过 LambdaQueryWrapper 构建查询条件
- 内置分页插件,一行代码实现分页功能
- 丰富的 CRUD 方法,极大减少重复代码
3. 事务管理
在涉及多表操作的场景中,事务管理至关重要。项目中使用@Transactional注解实现事务控制:
java
@Transactional
public void saveWithFlavor(DishDto dishDto) {
// 新增菜品的基本信息到菜品表dish
this.save(dishDto);
Long dishId = dishDto.getId();//获取菜品id
// 处理口味数据
List<DishFlavor> flavors = dishDto.getFlavors();
flavors = flavors.stream().map((item) -> {
item.setDishId(dishId);
return item;
}).collect(Collectors.toList());
// 新增菜品口味数据到菜品口味表dish_flavor
dishFlavorService.saveBatch(flavors);
}
这个方法同时操作了dish和dish_flavor两张表,使用@Transactional确保了数据的一致性。
4. DTO 模式的应用
项目中大量使用 DTO(数据传输对象)模式,以DishDto为例:
java
@Data
public class DishDto extends Dish {
// 菜品对应的口味数据
private List<DishFlavor> flavors = new ArrayList<>();
private String categoryName;
private Integer copies;
}
DTO 的主要作用:
- 封装前端需要的复杂数据,减少请求次数
- 避免直接暴露实体类,提高安全性
- 适配前端视图模型,降低前后端耦合
三、核心业务功能
1. 菜品管理模块
菜品管理涉及菜品基本信息和口味信息的维护,这是一个典型的一对多关系处理场景。
在DishServiceImpl中,saveWithFlavor方法实现了菜品和口味的同时保存:
- 先保存菜品基本信息
- 获取生成的菜品 ID
- 给所有口味设置菜品 ID
- 批量保存口味信息
这种实现方式既高效又保证了数据的完整性。
2. 套餐管理模块
套餐管理与菜品管理类似,但更复杂一些,因为套餐包含多个菜品。
在SetmealController中,save方法接收SetmealDto对象,通过setmealService.saveWithDish方法实现套餐和套餐菜品关系的保存:
java
@PostMapping
public R<String> save(@RequestBody SetmealDto setmealDto){
log.info("套餐信息:{}",setmealDto);
setmealService.saveWithDish(setmealDto);
return R.success("新增套餐成功");
}
这种设计遵循了 "聚合根" 设计模式,将套餐作为聚合根,负责管理套餐包含的菜品关系。
3. 购物车与订单模块
购物车和订单是电商系统的核心功能,瑞吉外卖的实现也很有参考价值。
购物车添加功能:
java
@PostMapping("/add")
public R<ShoppingCart> add(@RequestBody ShoppingCart shoppingCart){
// 设置用户ID,确保数据隔离
Long currentId = BaseContext.getCurrentId();
shoppingCart.setUserId(currentId);
// 查询是否已存在
LambdaQueryWrapper<ShoppingCart> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(ShoppingCart::getUserId,currentId);
// 根据是菜品还是套餐设置查询条件
if(dishId != null){
queryWrapper.eq(ShoppingCart::getDishId,dishId);
}else{
queryWrapper.eq(ShoppingCart::getSetmealId,shoppingCart.getSetmealId());
}
ShoppingCart cartServiceOne = shoppingCartService.getOne(queryWrapper);
// 存在则数量+1,不存在则新增
if(cartServiceOne != null){
cartServiceOne.setNumber(cartServiceOne.getNumber() + 1);
shoppingCartService.updateById(cartServiceOne);
}else{
shoppingCart.setNumber(1);
shoppingCart.setCreateTime(LocalDateTime.now());
shoppingCartService.save(shoppingCart);
cartServiceOne = shoppingCart;
}
return R.success(cartServiceOne);
}
订单提交功能则更为复杂,涉及多表操作和事务控制:
- 查询购物车数据
- 计算总金额
- 保存订单信息
- 保存订单明细
- 清空购物车
四、总结
瑞吉外卖项目作为 SpringBoot 实战案例非常合适,它涵盖了:
- RESTful API 设计
- 分层架构实践
- ORM 框架应用
- 事务管理
- 复杂业务逻辑实现
对于初学者来说,通过这个项目可以快速掌握企业级 Java 开发的核心技能。建议学习时不要只看代码,更要理解其中的设计思想和最佳实践,这样才能真正提升自己的开发水平。