1. 外卖套餐项目开发实战:从零实现新增套餐功能
作为一个长期奋战在一线的开发者,我深知外卖系统开发中的痛点。今天分享一个真实项目中的"新增套餐"模块开发全过程,包含接口设计、参数传递、SQL优化等核心细节。这个功能看似简单,但涉及前后端交互、数据库查询、参数校验等多个环节,值得深入拆解。
2. 需求分析与接口设计
2.1 原型与功能拆解
从原型图可以看出,新增套餐页面包含以下几个核心元素:
- 套餐分类下拉框(需查询type=2的分类)
- 菜品选择区域(需根据分类ID查询可用菜品)
- 图片上传组件
- 套餐信息表单
- 保存按钮
2.2 接口清单与设计原则
根据原型分析,我们需要四个核心接口:
-
分类查询接口(已完成)
- 请求方式:GET
- 参数:type=2(固定值)
- 用途:填充套餐分类下拉框
-
菜品查询接口(重点开发)
- 请求方式:GET
- 参数:categoryId(分类ID)、status=1(启用状态)
- 用途:选择分类后显示该分类下的可用菜品
-
图片上传接口(已完成)
- 请求方式:POST
- 参数:multipart/form-data格式的图片文件
- 用途:上传套餐封面图
-
套餐保存接口
- 请求方式:POST
- 参数:套餐基本信息+包含的菜品列表
- 用途:保存完整的套餐信息
3. 菜品查询接口实现详解
3.1 参数传递方式选择
在RESTful API设计中,参数传递主要有两种方式:
-
Path参数(@PathVariable)
- 适用场景:资源唯一标识
- 示例:/dishes/{id}(查询特定ID的菜品)
-
Query参数(@RequestParam)
- 适用场景:筛选、可选参数
- 示例:/dishes?categoryId=123&status=1
在我们的菜品查询场景中,categoryId和status都是筛选条件,因此选择Query参数更符合规范。
3.2 Controller层实现
java复制@GetMapping("/dishes")
public Result<List<Dish>> listByCategory(
@RequestParam Long categoryId,
@RequestParam(required = false, defaultValue = "1") Integer status) {
List<Dish> dishes = dishService.listByCategory(categoryId, status);
return Result.success(dishes);
}
关键点说明:
- 使用@RequestParam明确表示这是查询参数
- status参数设置默认值1(启用状态)
- 返回统一封装的Result对象
3.3 Service层实现
java复制@Override
public List<Dish> listByCategory(Long categoryId, Integer status) {
// 参数校验
if (categoryId == null) {
throw new BusinessException("分类ID不能为空");
}
// 构建查询条件
Dish dish = Dish.builder()
.categoryId(categoryId)
.status(status)
.build();
return dishMapper.list(dish);
}
经验分享:
- 一定要做参数校验,避免SQL注入风险
- 使用Builder模式构建查询对象更清晰
- 直接返回Mapper查询结果,保持Service层简洁
3.4 Mapper层与SQL优化
xml复制<select id="list" resultType="com.xxx.pojo.Dish">
SELECT * FROM dish
<where>
<if test="categoryId != null">
AND category_id = #{categoryId}
</if>
<if test="status != null">
AND status = #{status}
</if>
</where>
ORDER BY update_time DESC
</select>
SQL优化建议:
- 使用
<where>标签自动处理AND前缀 - 添加动态条件判断,提高SQL复用性
- 按更新时间降序排列,最新修改的菜品排前面
4. 常见问题与解决方案
4.1 参数传递方式混淆
问题现象:
将categoryId放在Path中(如/dishes/{categoryId}),导致后续无法扩展其他查询条件
正确做法:
- 唯一标识用Path参数
- 筛选条件用Query参数
- 保持接口风格统一
4.2 N+1查询问题
问题现象:
查询菜品列表后,又循环查询每个菜品的分类信息
解决方案:
xml复制<select id="listWithCategory" resultMap="dishWithCategory">
SELECT d.*, c.name as category_name
FROM dish d LEFT JOIN category c ON d.category_id = c.id
WHERE d.category_id = #{categoryId} AND d.status = 1
</select>
4.3 分页查询实现
当菜品数量较多时,需要增加分页支持:
java复制@GetMapping("/dishes/page")
public Result<PageInfo<Dish>> pageByCategory(
@RequestParam Long categoryId,
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer size) {
PageHelper.startPage(page, size);
List<Dish> dishes = dishService.listByCategory(categoryId, 1);
return Result.success(PageInfo.of(dishes));
}
5. 项目开发心得
-
接口设计先于编码
- 先明确参数传递方式(Path/Query)
- 确定返回数据结构
- 编写接口文档(Swagger)
-
分层开发要点
- Controller:参数校验、结果封装
- Service:业务逻辑、事务管理
- Mapper:SQL优化、数据访问
-
调试技巧
- 使用Postman测试接口
- 开启SQL日志(mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl)
- 善用断点调试
这个看似简单的功能,实际上涉及了RESTful规范、SQL优化、分层架构等多个知识点。我在实际开发中最大的收获是:清晰的接口设计能减少50%的沟通成本,而良好的SQL习惯能显著提升系统性能