【苍穹外卖】深度解析:商品浏览四大核心接口设计(附完整数据流转图)

🔥个人主页:北极的代码(欢迎来访)

🎬作者简介:java后端学习者

❄️个人专栏:苍穹外卖日记SSM框架深入JavaWeb

命运的结局尽可永在,不屈的挑战却不可须臾或缺!

前言:我们前面完成了小程序的开发,接下来我们完善小程序的页面展示,分页查询,查询分类,菜品,套餐等接口..........

四个核心接口的完整设计详解

让我用数据流转图的方式,详细解释每个接口的设计和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)│                         │
   ▼         │                      │                         │

七、关键点总结

  1. ID的传递链

    text

    复制代码
    分类ID:数据库生成 → 接口1返回 → 前端存储 → 用于查菜品/套餐列表
    菜品ID:数据库生成 → 接口2返回 → 前端存储 → 用于查菜品详情
    套餐ID:数据库生成 → 接口3返回 → 前端存储 → 用于查套餐详情和套餐内菜品
  2. 接口设计原则

    • 每个接口职责单一(一个接口只做一件事)

    • ID作为资源标识符(RESTful风格)

    • 通过路径传参(@PathVariable)

  3. 数据流向

    • 前端只负责传递用户选择的ID

    • 后端根据ID查询对应的数据

    • 数据库通过主键ID快速定位记录

这样设计既清晰又高效,符合RESTful API的设计规范!

让我用一个餐厅点餐的场景,来理解这个功能的实现原理。

一、需求分析(为什么要做?)

想象你走进一家餐厅,服务员递给你一本菜单,你需要:

  1. 看到所有菜品分类(热菜、凉菜、主食等)

  2. 查看某个分类下的菜品(比如只看凉菜)

  3. 看到菜品详情(价格、口味、图片等)

  4. 能快速搜索想吃的菜

这就是商品浏览功能要解决的核心需求

二、接口设计(前后端如何沟通?)

就像餐厅有服务员帮你传话,接口就是前后端沟通的桥梁:

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);
}

五、核心流程总结

  1. 用户请求:点击某个菜品分类

  2. 后端处理

    • 查询该分类下的所有启售菜品

    • 组装菜品图片和口味信息

    • 返回给前端

  3. 前端展示:以网格或列表形式展示菜品

这就是商品浏览功能的实现原理,像一本智能菜单,让顾客能快速浏览、查找想点的菜品

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

相关推荐
程序员爱钓鱼2 小时前
Go静态资源嵌入方案: embed包深度解析
后端·面试·go
灰阳阳2 小时前
Docker-镜像-命令清单
java·docker·eureka
青衫码上行2 小时前
【项目开发日记 | 根据业务流程产出前后端交互文档】第二天
java·团队开发
AskHarries2 小时前
独立开发者最浪费时间的10件事
后端·ai编程
代码探秘者2 小时前
【Spring框架】彻底理解 Spring 单例线程安全
java·安全·spring
Lenyiin2 小时前
《LeetCode 顺序刷题》51 - 60
java·c++·python·算法·leetcode·深度优先·lenyiin
液态不合群2 小时前
Java低代码平台工作流引擎设计与实现:从人工审批到智能自动化
java·低代码·状态模式·工作流
SadSunset2 小时前
3.16Java基础(1)
java·开发语言
Azure DevOps2 小时前
Azure DevOps Server:扩充数据库服务器的磁盘
服务器·数据库·microsoft·azure·devops