当前位置: 首页 > news >正文

【苍穹外卖笔记】Day04--套餐管理模块

Sky-takeout Day04


【前言】

Day04 的 Task 是套餐管理相关代码的开发 ,也是对多表业务的分析能力的锻炼

Contents

  • 新增套餐

  • 套餐分页查询

  • 删除套餐

  • 修改套餐

  • 起售停售套餐

1. 新增套餐

需求分析与接口设计

业务规则:

  • 套餐名称唯一

  • 套餐必须属于某个分类

  • 套餐必须包含菜品

  • 名称、分类、价格、图片为必填项

  • 添加菜品窗口需要根据分类类型来展示菜品

  • 新增的套餐默认为停售状态

接口设计(共涉及到 4 个接口):

  • 根据类型查询分类(已完成)

  • 根据分类 id 查询菜品

  • 图片上传(已完成)

  • 新增套餐

产品原型:

可以看到,在新增套餐中还涉及到了菜品的一个接口,就是 根据分类 id 查询菜品并列出菜品,所以需要在 DishController 再加一个接口 来根据分类 id 展示菜品(list)

接口设计:


表设计:

setmeal 表为套餐表,用于存储套餐的信息。具体表结构如下:

字段名数据类型说明备注
idbigint主键自增
namevarchar(32)套餐名称唯一
category_idbigint分类 id逻辑外键
pricedecimal(10,2)套餐价格
imagevarchar(255)图片路径
descriptionvarchar(255)套餐描述
statusint售卖状态1 起售 0 停售
create_timedatetime创建时间
update_timedatetime最后修改时间
create_userbigint创建人 id
update_userbigint最后修改人 id

setmeal_dish 表为套餐菜品关系表,用于存储套餐和菜品的关联关系。具体表结构如下:

字段名数据类型说明备注
idbigint主键自增
setmeal_idbigint套餐 id逻辑外键
dish_idbigint菜品 id逻辑外键
namevarchar(32)菜品名称冗余字段
pricedecimal(10,2)菜品单价冗余字段
copiesint菜品份数

代码编写

1)根据分类 id 查询并列出菜品

DishController.java

/*** 根据分类条件查询列出菜品* @param categoryId 分类id* @return 菜品列表*/@GetMapping("/list")@ApiOperation("根据分类条件查询并列出菜品")public Result<List<Dish>> list(Long categoryId) {log.info("根据分类条件查询并列出菜品: {}", categoryId);List<Dish> dishList = dishService.list(categoryId);return Result.success(dishList);}

DishServiceImpl.java

这里注意 status 状态是 ENABLE,一开始写成 DISABLE 了,没有菜品列出,后面发现是状态设置错了。

/*** 根据分类条件查询列出菜品* @param categoryId 分类id* @return 菜品列表*/@Overridepublic List<Dish> list(Long categoryId) {Dish dish = Dish.builder().status(StatusConstant.ENABLE) // 注意这里是只查询起售状态的菜品.categoryId(categoryId).build();return dishMapper.list(dish);}

DishMapper.java

/*** 动态条件查询菜品* @param dish 条件* @return 菜品列表*/List<Dish> list(Dish dish);

DishMapper.xml

<select id="list" resultType="com.sky.entity.Dish">select * from dish<where><if test="name != null and name != ''">and name like concat('%', #{name}, '%')</if><if test="categoryId != null">and category_id = #{categoryId}</if><if test="status != null">and status = #{status}</if></where>order by create_time desc</select>
2)新增套餐

SetmealController.java

package com.sky.controller.admin;​import com.sky.dto.SetmealDTO;import com.sky.result.Result;import com.sky.service.SetmealService;import io.swagger.annotations.Api;import io.swagger.annotations.ApiOperation;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestBody;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;​@RestController@Api(tags = "套餐管理")@RequestMapping("admin/setmeal")@Slf4jpublic class SetmealController {​@Autowiredprivate SetmealService setmealService;​/*** 新增套餐* @param setmealDTO 套餐信息* @return 结果*/@PostMapping@ApiOperation("新增套餐")public Result<Void> save(@RequestBody SetmealDTO setmealDTO) { // 这里要加 @RequestBody 将前端传递的json数据转换为java对象log.info("新增套餐: {}", setmealDTO);setmealService.saveWithDish(setmealDTO);return Result.success();}}​

SetmealServiceImpl.java

package com.sky.service.impl;​import com.sky.dto.SetmealDTO;import com.sky.entity.Setmeal;import com.sky.entity.SetmealDish;import com.sky.mapper.SetmealDishMapper;import com.sky.mapper.SetmealMapper;import com.sky.service.SetmealService;import org.springframework.beans.BeanUtils;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;​import java.util.List;​@Servicepublic class SetmealServiceImpl implements SetmealService {​@Autowiredprivate SetmealMapper setmealMapper;@Autowiredprivate SetmealDishMapper setmealDishMapper;​/*** 新增套餐* @param setmealDTO 套餐信息*/@Overridepublic void saveWithDish(SetmealDTO setmealDTO) {Setmeal setmeal = new Setmeal();BeanUtils.copyProperties(setmealDTO, setmeal);​// 1. 向setmeal表插入套餐setmealMapper.insert(setmeal);​// 2. 向setmeal_dish表插入套餐和菜品的关联关系// 先获取套餐idLong setmealId = setmeal.getId();// 再获取套餐和菜品的关联关系List<SetmealDish> setmealDishes = setmealDTO.getSetmealDishes();// 设置套餐idsetmealDishes.forEach(setmealDish -> setmealDish.setSetmealId(setmealId));// 批量插入套餐和菜品的关联关系setmealDishMapper.insertBatch(setmealDishes);}}​

SetmealMapper.java

/*** 插入套餐, 并且将主键回填到实体类对象中 (useGeneratedKeys = true, keyProperty = "id")* @param setmeal 套餐*/@AutoFill(value = OperationType.INSERT)void insert(Setmeal setmeal);​

SetmealMapper.xml

<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd" ><mapper namespace="com.sky.mapper.SetmealMapper">​<insert id="insert" useGeneratedKeys="true" keyProperty="id">insert into setmeal (category_id, name, price, description, image, create_time, update_time, create_user, update_user)values(#{categoryId}, #{name}, #{price}, #{description}, #{image}, #{createTime}, #{updateTime}, #{createUser}, #{updateUser})</insert>​</mapper>​

SetmealDishMapper.java

/*** 批量插入套餐和菜品的关联关系* @param setmealDishes 套餐和菜品的关联关系*/void insertBatch(List<SetmealDish> setmealDishes);

SetmealDishMapper.xml

<insert id="insertBatch">insert into setmeal_dish (setmeal_id, dish_id, name, price, copies)values<foreach collection="setmealDishes" item="sd" separator=",">(#{sd.setmealId}, #{sd.dishId}, #{sd.name}, #{sd.price}, #{sd.copies})</foreach></insert>

功能测试

成功在表中插入数据

  • 注意 这里有误!!后来在写删除套餐的时候才发现,插入时 status 不应该是 1,之前编写新增套餐代码的时候在 mapper 的 xml 映射文件遗漏了 status 字段,而且在 service 层忘记设置了初始状态为停售 DISABLE(也就是 0)

2. 套餐分页查询

需求分析与接口设计

业务规则:

  • 根据页码进行分页展示

  • 每页展示 10 条数据

  • 可以根据需要,按照套餐名称、分类、售卖状态进行查询

产品原型

注意因为视图需要套餐对应的分类名称,所以分页时 Page 的泛型是 VO 对象,即 SetmealVO。

接口设计:

代码编写

SetmealController.java

/*** 分页查询套餐* @param setmealPageQueryDTO 分页查询参数* @return 套餐分页结果*/@GetMapping("/page")@ApiOperation("分页查询套餐")public Result<PageResult> page(SetmealPageQueryDTO setmealPageQueryDTO) {log.info("分页查询套餐: {}", setmealPageQueryDTO);PageResult pageResult = setmealService.pageQuery(setmealPageQueryDTO);return Result.success(pageResult);}

SetmealServiceImpl.java

/*** 分页查询套餐* @param setmealPageQueryDTO 分页查询参数* @return 套餐分页结果*/@Overridepublic PageResult pageQuery(SetmealPageQueryDTO setmealPageQueryDTO) {PageHelper.startPage(setmealPageQueryDTO.getPage(), setmealPageQueryDTO.getPageSize());Page<SetmealVO> page = setmealMapper.pageQuery(setmealPageQueryDTO);return new PageResult(page.getTotal(), page.getResult());}

SetmealMapper.java

/*** 分页查询套餐* @param setmealPageQueryDTO 分页查询参数* @return 套餐分页结果*/Page<SetmealVO> pageQuery(SetmealPageQueryDTO setmealPageQueryDTO);

SetmealMapper.xml

<select id="pageQuery" resultType="com.sky.vo.SetmealVO">select s.*, c.name as categoryNamefrom setmeal s left outer join category con s.category_id = c.id<where><if test="name != null">s.name like concat('%', #{name}, '%')</if><if test="status != null">and s.status = #{status}</if><if test="categoryId != null">and s.category_id = #{categoryId}</if></where></select>

功能测试

3. 删除套餐

需求分析与接口设计

业务规则:

  • 可以一次删除一个套餐,也可以批量删除套餐

  • 起售中的套餐不能删除

接口设计:

代码编写

SetmealController.java

/*** 删除套餐* @param ids 套餐id集合* @return 结果*/@DeleteMapping@ApiOperation("删除套餐")public Result<Void> delete(@RequestParam("ids") List<Long> ids) {log.info("删除套餐: {}", ids);setmealService.deleteWithDish(ids);return Result.success();}

SetmealServiceImpl.java

/*** 删除套餐, 同时删除套餐和菜品的关联数据* @param ids 套餐id集合*/@Overridepublic void deleteWithDish(List<Long> ids) {// 起售中的套餐不能删除// 1. 查询套餐状态, 确认是否可以删除for (Long id : ids) {Setmeal setmeal = setmealMapper.getById(id);if (setmeal != null && Objects.equals(setmeal.getStatus(), StatusConstant.ENABLE)) { // 套餐起售中throw new DeletionNotAllowedException(MessageConstant.SETMEAL_ON_SALE);}}// 2. 删除套餐表中的数据// delete from setmeal where id in (1,2,3)setmealMapper.deleteByIds(ids);// 3. 删除套餐和菜品的关联数据// delete from setmeal_dish where setmeal_id in (1,2,3)setmealDishMapper.deleteBySetmealIds(ids);}

SetmealMapper.java

/*** 根据id查询套餐  (用于删除套餐时, 查询套餐状态)* @param id 套餐id*/@Select("select * from setmeal where id = #{id}")Setmeal getById(Long id);/*** 根据id删除套餐* @param ids 套餐id集合*/void deleteByIds(List<Long> ids);

SetmealMapper.xml

<delete id="deleteByIds" parameterType="list">delete from setmeal where id in<foreach collection="ids" item="id" open="(" separator="," close=")">#{id}</foreach></delete>

SetmealDishMapper.java

/*** 根据套餐id删除对应的套餐和菜品的关联关系* @param setmealIds 套餐id集合*/void deleteBySetmealIds(List<Long> setmealIds);

SetmealDishMapper.xml

<delete id="deleteBySetmealIds">delete from setmeal_dish where setmeal_id in<foreach collection="setmealIds" item="setmealId" open="(" separator="," close=")">#{setmealId}</foreach></delete>

功能测试

起售中无法删除

删除成功

4. 修改套餐

需求分析与接口设计

产品原型:

接口设计(共涉及到5个接口):

  • 根据id查询套餐

  • 根据类型查询分类(已完成)

  • 根据分类id查询菜品(已完成)

  • 图片上传(已完成)

  • 修改套餐

代码编写

SetmealController.java

/*** 根据id查询套餐信息, 包括套餐和菜品的关联信息* @param id 套餐id* @return 套餐信息*/@GetMapping("/{id}")@ApiOperation("根据id查询套餐信息(包括套餐和菜品的关联信息)")public Result<SetmealVO> getById(@PathVariable Long id) {  // 这里要加 @PathVariable 将路径参数中的id参数绑定到方法的id参数上log.info("根据id查询套餐信息: {}", id);SetmealVO setmealVO = setmealService.getByIdWithDishes(id);return Result.success(setmealVO);}/*** 修改套餐(包括套餐和菜品的关联信息)* @param setmealDTO 套餐信息* @return 结果*/@PutMapping@ApiOperation("修改套餐")public Result<Void> update(@RequestBody SetmealDTO setmealDTO) {  // 这里要加 @RequestBody 将前端传递的json数据转换为java对象log.info("修改套餐: {}", setmealDTO);setmealService.updateWithDish(setmealDTO);return Result.success();}

SetmealServiceImpl.java

/*** 根据id查询套餐信息, 包括套餐和菜品的关联信息* @param id 套餐id* @return 套餐信息*/@Overridepublic SetmealVO getByIdWithDishes(Long id) {// 1. 查询套餐基本信息Setmeal setmeal = setmealMapper.getById(id);// 2. 查询套餐和菜品的关联信息List<SetmealDish> setmealDishes = setmealDishMapper.getByIdWithDishes(id);// 3. 将套餐基本信息与套餐菜品信息封装到VO对象中并返回SetmealVO setmealVO = new SetmealVO();BeanUtils.copyProperties(setmeal, setmealVO);setmealVO.setSetmealDishes(setmealDishes);return setmealVO;}/*** 修改套餐(包括套餐和菜品的关联信息)* @param setmealDTO 套餐信息*/@Overridepublic void updateWithDish(SetmealDTO setmealDTO) {Setmeal setmeal = new Setmeal();BeanUtils.copyProperties(setmealDTO, setmeal);// 1. 首先,更新setmeal表中的基本信息setmealMapper.updateById(setmeal);// 2. 然后,更新setmeal_dish表中的关联信息Long setmealId = setmeal.getId();// 2.1 先删除原有的关联信息setmealDishMapper.deleteBySetmealIds(List.of(setmealId));// 2.2 再重新插入新的关联信息List<SetmealDish> setmealDishes = setmealDTO.getSetmealDishes();// 设置套餐idsetmealDishes.forEach(setmealDish -> setmealDish.setSetmealId(setmealId));// 批量插入套餐和菜品的关联关系setmealDishMapper.insertBatch(setmealDishes);}

SetmealMapper.java

/*** 根据id动态修改套餐信息* @param setmeal 套餐信息*/@AutoFill(value = OperationType.UPDATE)void updateById(Setmeal setmeal);

SetmealMapper.xml

<update id="updateById">update setmeal<set><if test="categoryId != null">category_id = #{categoryId},</if><if test="name != null">name = #{name},</if><if test="price != null">price = #{price},</if><if test="status != null">status = #{status},</if><if test="description != null">description = #{description},</if><if test="image != null">image = #{image},</if>update_time = #{updateTime},update_user = #{updateUser}</set>where id = #{id}</update>

SetmealDishMapper.java

/*** 根据套餐id查询对应的套餐和菜品的关联关系* @param setmealId 套餐id* @return 套餐和菜品的关联关系*/@Select("select * from setmeal_dish where setmeal_id = #{setmealId}")List<SetmealDish> getByIdWithDishes(Long setmealId);

功能测试

5. 起售停售套餐

需求分析与接口设计

业务规则:

  • 可以对状态为起售的套餐进行停售操作,可以对状态为停售的套餐进行起售操作

  • 起售的套餐可以展示在用户端,停售的套餐不能展示在用户端

  • 起售套餐时,如果套餐内包含停售的菜品,则不能起售

接口设计:

代码编写

SetmealController

/*** 起售/停售套餐* @param status 状态* @param id 套餐id集合* @return 结果*/@PostMapping("/status/{status}")@ApiOperation("起售/停售套餐")public Result<Void> startOrStop(@PathVariable Integer status, Long id) {log.info("起售/停售套餐: {}, {}", status, id);setmealService.startOrStop(status, id);return Result.success();}

SetmealServiceImpl

/*** 起售/停售套餐* @param status 套餐状态* @param id 套餐id*/@Overridepublic void startOrStop(Integer status, Long id) {// 如果要设置为起售,需要检查套餐内的菜品是否都为起售状态if (Objects.equals(status, StatusConstant.ENABLE)) { // 要起售// select a.* from dish a// left join setmeal_dish b on a.id = b.dish_id where b.setmeal_id = ?;List<Dish> dishList = dishMapper.getBySetmealId(id); // 查询套餐内的菜品dishList.forEach(dish -> {if (dish.getStatus().equals(StatusConstant.DISABLE)) {throw new SetmealEnableFailedException(MessageConstant.SETMEAL_ENABLE_FAILED);}}); // 只要有一个菜品是停售状态, 就不能起售}// 可以起售,更新套餐状态Setmeal setmeal = Setmeal.builder().id(id).status(status).build();setmealMapper.updateById(setmeal);}

DishMapper

/*** 根据套餐id查询对应的菜品* @param id 套餐id* @return 菜品列表*/@Select("select d.* from dish d " +"left join setmeal_dish sd on d.id = sd.dish_id " +"where sd.setmeal_id = #{id} ")List<Dish> getBySetmealId(Long id);

功能测试

http://www.dtcms.com/a/477710.html

相关文章:

  • 初识redis(分布式系统, redis的特性, 基本命令)
  • [特殊字符] Avalonia + Silk.NET 加载 3D 模型时 GenBuffer 返回 0?这是个底层兼容性陷阱!
  • 学习threejs,打造交互式花卉生成器
  • Redis 学习笔记(二)
  • 北京展览馆网站建设wordpress插件排列
  • 北京做网站优化多少钱最基本最重要的网站推广工具是
  • 每日算法刷题Day70:10.13:leetcode 二叉树10道题,用时2h
  • MySQL 设置远程 IP 连接方式(含自动检测授权脚本)
  • flash型网站网址高校思政课网站建设
  • 网站建设费做什么会计科目硬件开发外包平台
  • 【SpringBoot从初学者到专家的成长15】MVC、Spring MVC与Spring Boot:理解其差异与联系
  • Docker 存储与数据共享
  • k8s storageclasses nfs-provisioner 部署
  • Linux(Samba服务)
  • 电商智能客服进化论:多轮对话+意图识别+知识推荐系统开发
  • 算法198. 打家劫舍
  • 刚学做网站怎么划算全栈网站开发工程师
  • 长春网站优化公司wordpress目录遍历漏洞
  • 华为OD-23届考研-Java面经
  • 10.9 鸿蒙创建和运行项目
  • delphi调用C#编写的DLL
  • 从API调用到智能体编排:GPT-5时代的AI开发新模式
  • C++学习录(1):C++入门简介,从零开始
  • 电力专用多功能微气象监测装置在电网安全运维中的核心价值是什么?
  • 科研快报 |声波“听”见火灾温度:混合深度学习重构三维温度场
  • 从超级大脑到智能毛细血管:四大技术重构智慧园区生态版图
  • 旅游网站建设方案书制作一个网站平台需要多少钱
  • SQL入门:集合运算实战指南
  • Docker 网络类型与容器通信
  • Oracle 21C 部署ogg踩过的坑