
🔥个人主页:北极的代码(欢迎来访)
🎬作者简介:java后端学习者
✨命运的结局尽可永在,不屈的挑战却不可须臾或缺!
前言:我们前面完成了小程序的开发,接下来我们完善小程序的页面展示,分页查询,查询分类,菜品,套餐等接口..........
四个核心接口的完整设计详解
让我用数据流转图的方式,详细解释每个接口的设计和ID的来源:
一、接口概览
java
1. 查询分类接口 → /user/category/list 2. 根据ID查菜品 → /user/dish/{id} 3. 根据ID查套餐 → /user/setmeal/{id} 4. 查套餐下的菜品 → /user/setmeal/dish/list/{setmealId}
二、详细设计(含ID来源)
接口1:查询分类接口
java
java
/**
* 查询所有分类
* 场景:用户打开APP首页,需要显示菜品分类和套餐分类
* ID来源:这个接口不传ID,而是获取所有分类的ID
*/
@GetMapping("/user/category/list")
public Result<List<Category>> list(@RequestParam(required = false) Integer type) {
// type=1查菜品分类,type=2查套餐分类,不传就查所有
List<Category> list = categoryService.list(type);
return Result.success(list);
}
// 返回数据示例
[
{
"id": 101, // ← 这是分类ID,会在接口2、3中使用
"name": "凉菜",
"type": 1, // 菜品分类
"sort": 1,
"image": "xxx.jpg"
},
{
"id": 102,
"name": "热菜",
"type": 1
},
{
"id": 201, // ← 套餐分类ID
"name": "超值套餐",
"type": 2 // 套餐分类
}
]
接口2:根据ID查询菜品
java
java
/**
* 根据ID查询菜品详情
* 场景:用户点击某个菜品,查看详细信息
* ID来源:从接口1返回的菜品分类ID,或者从菜品列表中点击
*/
@GetMapping("/user/dish/{id}")
public Result<DishVO> getById(@PathVariable Long id) {
// id 是从哪里来的?
// 来源1:用户点击菜品分类后,从菜品列表中选择一个菜品
// 来源2:用户直接搜索菜品ID
// 来源3:从套餐详情中点击某个菜品
DishVO dishVO = dishService.getWithFlavor(id);
return Result.success(dishVO);
}
// 调用示例
GET /user/dish/501 // 501是菜品ID
// 返回数据
{
"id": 501, // ← 菜品ID
"name": "香辣鸡腿堡",
"categoryId": 101, // 所属分类ID
"price": 18.00,
"image": "burger.jpg",
"description": "香辣可口",
"flavors": [ // 口味选项
{"name": "辣度", "options": ["微辣", "中辣", "特辣"]},
{"name": "酱料", "options": ["番茄酱", "沙拉酱", "不要酱"]}
]
}
接口3:根据ID查询套餐
java
java
/**
* 根据ID查询套餐基本信息
* 场景:用户点击某个套餐,查看套餐概览
* ID来源:从接口1返回的套餐分类ID,然后点击具体套餐
*/
@GetMapping("/user/setmeal/{id}")
public Result<Setmeal> getById(@PathVariable Long id) {
// id 是从哪里来的?
// 来源1:用户点击套餐分类后,从套餐列表中选择一个套餐
// 来源2:用户直接搜索套餐ID
Setmeal setmeal = setmealService.getById(id);
return Result.success(setmeal);
}
// 调用示例
GET /user/setmeal/1001 // 1001是套餐ID
// 返回数据
{
"id": 1001, // ← 套餐ID
"name": "经典双人餐",
"categoryId": 201, // 所属套餐分类ID
"price": 68.00,
"image": "setmeal.jpg",
"description": "超值优惠套餐",
"status": 1 // 启售状态
}
接口4:查询套餐下的菜品
java
java
/**
* 查询套餐包含的所有菜品
* 场景:用户查看套餐详情时,需要知道套餐里具体有哪些菜
* ID来源:从接口3返回的套餐ID
*/
@GetMapping("/user/setmeal/dish/list/{setmealId}")
public Result<List<DishVO>> getDishBySetmealId(@PathVariable Long setmealId) {
// setmealId 是从哪里来的?
// 来源:接口3返回的套餐ID(如1001)
List<DishVO> list = setmealDishService.getDishBySetmealId(setmealId);
return Result.success(list);
}
// 调用示例
GET /user/setmeal/dish/list/1001 // 1001是套餐ID
// 返回数据
[
{
"id": 501, // ← 菜品ID
"name": "香辣鸡腿堡",
"price": 18.00,
"copies": 2, // 套餐中包含2份
"image": "burger.jpg",
"flavors": [...] // 口味选项
},
{
"id": 502,
"name": "中份薯条",
"price": 12.00,
"copies": 1,
"image": "fries.jpg"
},
{
"id": 503,
"name": "可乐",
"price": 8.00,
"copies": 2,
"image": "cola.jpg"
}
]
三、完整的数据流转图
四、后端实现代码
java
java
@RestController
@RequestMapping("/user")
public class UserBrowseController {
@Autowired
private CategoryService categoryService;
@Autowired
private DishService dishService;
@Autowired
private SetmealService setmealService;
@Autowired
private SetmealDishService setmealDishService;
// 1. 查询分类接口
@GetMapping("/category/list")
public Result<List<Category>> list(Integer type) {
// SQL: SELECT * FROM category
// WHERE (type = ? OR ? IS NULL)
// ORDER BY sort DESC
List<Category> list = categoryService.lambdaQuery()
.eq(type != null, Category::getType, type)
.orderByDesc(Category::getSort)
.list();
return Result.success(list);
}
// 2. 根据ID查询菜品
@GetMapping("/dish/{id}")
public Result<DishVO> getDishById(@PathVariable Long id) {
// 1. 查询菜品基本信息
// SQL: SELECT * FROM dish WHERE id = #{id}
Dish dish = dishService.getById(id);
// 2. 查询菜品口味
// SQL: SELECT * FROM dish_flavor WHERE dish_id = #{id}
List<DishFlavor> flavors = dishFlavorService.lambdaQuery()
.eq(DishFlavor::getDishId, id)
.list();
// 3. 组装VO
DishVO dishVO = new DishVO();
BeanUtils.copyProperties(dish, dishVO);
dishVO.setFlavors(flavors);
return Result.success(dishVO);
}
// 3. 根据ID查询套餐
@GetMapping("/setmeal/{id}")
public Result<Setmeal> getSetmealById(@PathVariable Long id) {
// SQL: SELECT * FROM setmeal WHERE id = #{id} AND status = 1
Setmeal setmeal = setmealService.lambdaQuery()
.eq(Setmeal::getId, id)
.eq(Setmeal::getStatus, 1) // 只查启售的
.one();
return Result.success(setmeal);
}
// 4. 查询套餐下的菜品
@GetMapping("/setmeal/dish/list/{setmealId}")
public Result<List<DishVO>> getDishBySetmealId(@PathVariable Long setmealId) {
// SQL: SELECT d.*, sd.copies
// FROM setmeal_dish sd
// LEFT JOIN dish d ON sd.dish_id = d.id
// WHERE sd.setmeal_id = #{setmealId}
// ORDER BY sd.sort
List<DishVO> list = setmealDishService.getBaseMapper().selectDishBySetmealId(setmealId);
return Result.success(list);
}
}
五、ID来源汇总表
| 接口 | ID参数 | ID来源 | 示例 |
|---|---|---|---|
| 查询分类 | 无 | - | - |
| 根据ID查菜品 | dishId | 1. 从菜品列表点击 2. 从套餐详情点击菜品 | 501 |
| 根据ID查套餐 | setmealId | 从套餐列表点击 | 1001 |
| 查套餐下的菜品 | setmealId | 从接口3获取的套餐ID | 1001 |
六、调用时序图
text
时间线 用户操作 前端请求 后端处理
│ │ │ │
│ 打开APP │ │
│ │─── /category/list ─→│ │
│ │ │─── 查询category表 ─────→│
│ │ │←── 返回分类列表 ────────│
│ │←── 显示分类列表 ────│ │
│ │ │ │
│ 点击"超值套餐" │ │
│ │─── /setmeal/list? │ │
│ │ categoryId=201 ─→│ │
│ │ │─── 查询setmeal表 ─────→│
│ │ │←── 返回套餐列表 ────────│
│ │←── 显示套餐列表 ────│ │
│ │ │ │
│ 点击"经典双人餐" │ │
│ │─── /setmeal/1001 ──→│ │
│ │ │─── 查询setmeal表 ─────→│
│ │ │←── 返回套餐信息 ────────│
│ │ │ │
│ │─── /setmeal/dish/ │ │
│ │ list/1001 ──────→│ │
│ │ │─── 查询setmeal_dish表 ─→│
│ │ │─── 关联查询dish表 ─────→│
│ │ │←── 返回菜品列表 ────────│
│ │←── 显示套餐详情 ────│ │
│ │(汉堡×2,薯条×1,可乐×2)│ │
▼ │ │ │
七、关键点总结
-
ID的传递链:
text
分类ID:数据库生成 → 接口1返回 → 前端存储 → 用于查菜品/套餐列表 菜品ID:数据库生成 → 接口2返回 → 前端存储 → 用于查菜品详情 套餐ID:数据库生成 → 接口3返回 → 前端存储 → 用于查套餐详情和套餐内菜品 -
接口设计原则:
-
每个接口职责单一(一个接口只做一件事)
-
ID作为资源标识符(RESTful风格)
-
通过路径传参(@PathVariable)
-
-
数据流向:
-
前端只负责传递用户选择的ID
-
后端根据ID查询对应的数据
-
数据库通过主键ID快速定位记录
-
这样设计既清晰又高效,符合RESTful API的设计规范!
让我用一个餐厅点餐的场景,来理解这个功能的实现原理。
一、需求分析(为什么要做?)
想象你走进一家餐厅,服务员递给你一本菜单,你需要:
-
看到所有菜品分类(热菜、凉菜、主食等)
-
查看某个分类下的菜品(比如只看凉菜)
-
看到菜品详情(价格、口味、图片等)
-
能快速搜索想吃的菜
这就是商品浏览功能要解决的核心需求。
二、接口设计(前后端如何沟通?)
就像餐厅有服务员帮你传话,接口就是前后端沟通的桥梁:
text
1. 查询分类接口
GET /user/category/list
作用:像翻开菜单的第一页,看到所有菜品分类
2. 根据分类查询菜品接口
GET /user/dish/list?categoryId=xxx
作用:点击某个分类(如"凉菜"),展示该分类下的所有菜品
3. 菜品详情接口
GET /user/dish/detail/{id}
作用:点击某个菜品,查看它的详细信息
三、实现原理(怎么做到的?)
形象比喻:像整理一本"智能菜单"
第一步:数据存储(菜单怎么写好的?)
-
厨师(管理员)提前把菜品信息录入系统
-
存在数据库里,就像把菜名、价格、图片写在菜单上
-
数据结构:
sql
-- 分类表:好比菜单的章节 category表:id、名称、类型、排序 -- 菜品表:好比每道菜的介绍 dish表:id、名称、分类id、价格、图片、描述
第二步:数据查询(怎么快速找到菜?)
-
顾客(用户)点击"凉菜"分类
-
系统自动执行SQL查询:
sql
SELECT * FROM dish WHERE category_id = 凉菜的id AND status = 1 -- 只查询启售的菜品 ORDER BY sort -- 按排序字段展示 -
就像服务员快速翻到凉菜那几页
第三步:数据组装(怎么让菜品更诱人?)
-
查询到基础菜品信息后,还需要:
-
组装上图片的完整访问路径(http://.../dish/xxx.jpg)
-
如果有口味选项(辣度、规格),也要一起返回
-
就像给每道菜配上精美的图片和标注
-
第四步:缓存优化(怎么让翻菜单更快?)
-
使用Redis缓存热门菜品数据
-
第一次查询:"这个凉菜分类有哪些菜?" → 查数据库
-
第二次查询:直接从Redis拿,不用再查数据库
-
就像服务员把常点的菜品记在脑子里,不用每次都翻菜单
四、代码实现示例
java
java
// 1. 分类查询接口
@GetMapping("/user/category/list")
public Result<List<Category>> list(String type) {
List<Category> list = categoryService.list(type);
return Result.success(list);
}
// 2. 根据分类查菜品
@GetMapping("/user/dish/list")
public Result<List<DishVO>> list(Long categoryId) {
// 1. 先查Redis缓存
String key = "dish_" + categoryId;
List<DishVO> list = (List<DishVO>) redisTemplate.opsForValue().get(key);
if (list == null) {
// 2. 缓存没有,查数据库
list = dishService.listWithFlavor(categoryId);
// 3. 放入缓存
redisTemplate.opsForValue().set(key, list);
}
return Result.success(list);
}
五、核心流程总结
-
用户请求:点击某个菜品分类
-
后端处理:
-
查询该分类下的所有启售菜品
-
组装菜品图片和口味信息
-
返回给前端
-
-
前端展示:以网格或列表形式展示菜品
这就是商品浏览功能的实现原理,像一本智能菜单,让顾客能快速浏览、查找想点的菜品

结语:如果这篇文章对你有帮助,请**点赞,关注,收藏,**你的支持就是我更新的动力!