小柒味来 - 菜品管理模块开发

菜品管理

一、公共字段的内容填充

在开始本章节之前,我们不妨想一个问题,上一章我们在新增员工新增菜品分类时,需要设置以下字段:

  • 创建时间(createTime
  • 创建人(createUser
  • 修改时间(updateTime
  • 修改人(updateUser

编辑员工编辑菜品分类时,需要设置以下字段:

  • 修改时间(updateTime
  • 修改人(updateUser

这些字段属于公共字段,在一个具有完整CRUD的模块中许多数据表都会包含的通用字段。


1.1 常见处理方式

  1. 新增数据
    • createTimeupdateTime:设置为当前时间
    • createUserupdateUser:设置为当前登录用户 ID
  2. 更新数据
    • updateTime:设置为当前时间
    • updateUser:设置为当前登录用户 ID

按照这种方式,需要在每个业务方法中手动处理这些字段,代码会显得冗余且维护成本高。


1.2 优化方案

为简化开发,可以在统一位置处理这些公共字段,而不是在每个业务方法中重复编写
解决方法是:使用 AOP实现公共字段的内容填充


1.3 功能实现

1.3.1 定义枚举类 OperationType

qi-common模块的com.qi.enumeration包下, 用于标识数据库操作类型。

/**
 * 数据库操作类型
 */
public enum OperationType {
    UPDATE, // 更新
    INSERT  // 插入
}

1.3.2 自定义注解 @AutoFill

qi-commoncom.qi.annotation包下

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoFill {
    //数据库操作类型:INSERT、UPDATE
    OperationType value();
}

1.3.3 在 Mapper 方法上添加@AutoFill注解

CategoryMapper

@Mapper
public interface CategoryMapper {

    @AutoFill
    @Insert("insert into category(type, name, sort, status, create_time, update_time, create_user, update_user) " +
            "VALUES (#{type}, #{name}, #{sort}, #{status}, #{createTime}, #{updateTime}, #{createUser}, #{updateUser})")
    void insert(Category category);

    @AutoFill(OperationType.UPDATE)
    void update(Category category);
}

EmployeeMapper

@Mapper
public interface EmployeeMapper {

    @AutoFill
    @Insert("insert into employee (name, username, password, phone, sex, id_number, create_time, update_time, create_user, update_user, status) " +
            "values (#{name},#{username},#{password},#{phone},#{sex},#{idNumber},#{createTime},#{updateTime},#{createUser},#{updateUser},#{status})")
    void insert(Employee employee);

    @AutoFill(OperationType.UPDATE)
    void update(Employee employee);
}

1.3.4 自定义切面 AutoFillAspect

qi-server模块的 com.qi.aspect包下

@Aspect
@Component
@Slf4j
public class AutoFillAspect {
    /**
     * 切入点方法
     * 定义了需要进行自动填充的切入点
     * 该切入点匹配使用了@AutoFill注解的Mapper接口中的所有方法
     */
    @Pointcut("@annotation(com.qi.annotation.AutoFill) && execution(* com.qi.mapper.*.*(..))")
    public void autoFillPointCut() {
    }

    /**
     * 在执行切入点方法之前进行自动填充
     *
     * @param joinPoint 切入点对象,包含执行方法的信息
     */
    @Before("autoFillPointCut()")
    public void autoFill(JoinPoint joinPoint) {
        // 记录自动填充开始的日志
        log.info("开始进行公共字段自动填充。。。。");

        // 获取方法签名和@AutoFill注解
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        AutoFill autoFill = signature.getMethod().getAnnotation(AutoFill.class);

        // 获取操作类型(插入或更新)
        OperationType operationType = autoFill.value();

        // 获取方法参数
        Object[] args = joinPoint.getArgs();
        if (args == null || args.length == 0) {
            return;
        }
        Object entity = args[0];

        // 获取当前时间和用户ID
        LocalDateTime now = LocalDateTime.now();
        Long currentId = BaseContext.getCurrentId();

        // 根据操作类型进行相应的字段填充
        if (operationType == OperationType.INSERT) {
            try {
                // 对于插入操作,填充创建时间和用户以及更新时间和用户
                Method setCreateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_TIME, LocalDateTime.class);
                Method setCreateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_USER, Long.class);
                Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
                Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);

                setCreateTime.invoke(entity, now);
                setCreateUser.invoke(entity, currentId);
                setUpdateTime.invoke(entity, now);
                setUpdateUser.invoke(entity, currentId);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        } else if (operationType == OperationType.UPDATE) {
            try {
                // 对于更新操作,仅填充更新时间和用户
                Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
                Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);
                setUpdateTime.invoke(entity, now);
                setUpdateUser.invoke(entity, currentId);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }
}

1.3.5 注释上一章中的冗余代码

  • 员工管理中新增、编辑方法的公共字段
  • 菜品分类管理中新增、修改方法的公共字段

这样公共字段赋值将完全由切面统一处理。


二、文件上传

因为菜品需要上传图片,所以我们先来完成前置模块,通用功能之文件上传

实现文件上传功能,需要依赖存储服务。常见的解决方案包括:

  1. 直接将文件保存到服务器本地硬盘
    • 优点:开发简单、成本低
    • 缺点:扩容困难,受服务器磁盘容量限制
  2. 使用分布式文件系统(如 FastDFS、MinIO)
    • 优点:易于扩容,适合大规模存储
    • 缺点:开发复杂度相对较高
  3. 使用第三方存储服务(如阿里云 OSS、腾讯云 COS)
    • 优点:开发简单、免维护
    • 缺点:需要付费

在本项目中,选用 阿里云 OSS 服务 作为文件存储方案。
参考文档:快速使用 OSS SDK - 对象存储 OSS - 阿里云


2.1 引入依赖

父工程 xiao-qi-wei-lai

<dependency>
    <groupId>com.aliyun.oss</groupId>
    <artifactId>aliyun-sdk-oss</artifactId>
    <version>${aliyun.sdk.oss}</version>
</dependency>

公共模块 qi-common

<dependency>
    <groupId>com.aliyun.oss</groupId>
    <artifactId>aliyun-sdk-oss</artifactId>
</dependency>

服务模块 qi-server

<dependency>
    <groupId>com.aliyun.oss</groupId>
    <artifactId>aliyun-sdk-oss</artifactId>
</dependency>

2.2 定义 OSS 配置

qi-server 模块的 application-dev.yml 中配置 OSS 相关参数:

qi:
  alioss:
    endpoint: oss-cn-hangzhou.aliyuncs.com
    access-key-id: <你的 AccessKeyId>
    access-key-secret: <你的 AccessKeySecret>
    bucket-name: xiao-qi-wei-lai

application.yml 中启用环境:

spring:
  profiles:
    active: dev
qi:
  alioss:
    endpoint: ${qi.alioss.endpoint}
    access-key-id: ${qi.alioss.access-key-id}
    access-key-secret: ${qi.alioss.access-key-secret}
    bucket-name: ${qi.alioss.bucket-name}

2.3 读取 OSS 配置

qi-common 模块中创建配置类:

@Component
@ConfigurationProperties(prefix = "qi.alioss")
@Data
public class AliOssProperties {
    private String endpoint;
    private String accessKeyId;
    private String accessKeySecret;
    private String bucketName;
}

2.4 生成 OSS 工具类对象

qi-server 模块中创建配置类:

@Configuration
@Slf4j
public class OssConfiguration {

    @Bean
    @ConditionalOnMissingBean
    public AliOssUtil aliOssUtil(AliOssProperties aliOssProperties){
        log.info("创建阿里云文件上传工具类对象:{}", aliOssProperties);
        return new AliOssUtil(
            aliOssProperties.getEndpoint(),
            aliOssProperties.getAccessKeyId(),
            aliOssProperties.getAccessKeySecret(),
            aliOssProperties.getBucketName()
        );
    }
}

2.5 工具类实现

qi-common 模块中定义 AliOssUtil(直接复制官方示例修改即可):

@Data
@AllArgsConstructor
@Slf4j
public class AliOssUtil {

    private String endpoint;
    private String accessKeyId;
    private String accessKeySecret;
    private String bucketName;

    /**
     * 文件上传
     *
     * @param bytes
     * @param objectName
     * @return
     */
    public String upload(byte[] bytes, String objectName) {

        // 创建OSSClient实例。
        OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);

        try {
            // 创建PutObject请求。
            ossClient.putObject(bucketName, objectName, new ByteArrayInputStream(bytes));
        } catch (OSSException oe) {
            System.out.println("Caught an OSSException, which means your request made it to OSS, "
                    + "but was rejected with an error response for some reason.");
            System.out.println("Error Message:" + oe.getErrorMessage());
            System.out.println("Error Code:" + oe.getErrorCode());
            System.out.println("Request ID:" + oe.getRequestId());
            System.out.println("Host ID:" + oe.getHostId());
        } catch (ClientException ce) {
            System.out.println("Caught an ClientException, which means the client encountered "
                    + "a serious internal problem while trying to communicate with OSS, "
                    + "such as not being able to access the network.");
            System.out.println("Error Message:" + ce.getMessage());
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }

        //文件访问路径规则 https://BucketName.Endpoint/ObjectName
        StringBuilder stringBuilder = new StringBuilder("https://");
        stringBuilder
                .append(bucketName)
                .append(".")
                .append(endpoint)
                .append("/")
                .append(objectName);

        log.info("文件上传到:{}", stringBuilder.toString());

        return stringBuilder.toString();
    }
}

2.6 定义文件上传接口

qi-server 模块中创建 CommonController

@RestController
@RequestMapping("/admin/common")
@Api(tags = "通用接口")
@Slf4j
public class CommonController {

    @Autowired
    private AliOssUtil aliOssUtil;

    @PostMapping("/upload")
    @ApiOperation("文件上传")
    public Result<String> upload(MultipartFile file) {
        log.info("文件上传:{}", file);

        try {
            String originalFilename = file.getOriginalFilename();
            String extension = originalFilename.substring(originalFilename.lastIndexOf("."));
            String objectName = UUID.randomUUID().toString() + extension;

            String filePath = aliOssUtil.upload(file.getBytes(), objectName);
            return Result.success(filePath);
        } catch (IOException e) {
            log.error("文件上传失败:{}", e.getMessage(), e);
        }
        return Result.error(MessageConstant.UPLOAD_FAILED);
    }
}

说明

  • 文件名采用 UUID + 文件后缀,避免重复覆盖
  • 返回的 filePath 即文件在 OSS 上的访问 URL

后续与新增菜品接口一起联调测试

三、新增菜品

3.1 需求分析

3.1.1 产品原型

新增菜品功能用于在后台系统中添加菜品信息,包括基本信息和口味信息。

操作流程:

  1. 管理员点击“新增菜品”按钮
  2. 填写菜品名称、分类、价格、图片、描述等基本信息
  3. 可选填写口味信息

实现要点:

  • 菜品数据分为两部分:
    • 基本数据(保存到 dish 表)
    • 口味数据(保存到 dish_flavor 表)
  • 口味信息可选
  • 支持多口味批量插入
  • 公共字段通过自动填充机制完成

3.1.2 接口设计

接口地址:/admin/dish

请求方式:POST

请求数据类型:application/json

请求示例:

{
  "categoryId": 0,
  "description": "",
  "flavors": [
    {
      "dishId": 0,
      "id": 0,
      "name": "",
      "value": ""
    }
  ],
  "id": 0,
  "image": "",
  "name": "",
  "price": 0,
  "status": 0
}

请求参数:

参数名称 参数说明 请求类型 是否必须 数据类型 schema
dishDTO dishDTO body true DishDTO DishDTO
categoryId false integer(int64)
description false string
flavors false array DishFlavor
dishId false integer
id false integer
name false string
value false string
id false integer(int64)
image false string
name false string
price false number
status false integer(int32)

响应参数:

参数名称 参数说明 类型 schema
code integer(int32) integer(int32)
data object
msg string

响应示例:

{
    "code": 0,
    "data": {},
    "msg": ""
}

3.2 功能实现

3.2.1 设计DTO

@Data
public class DishDTO implements Serializable {
    private Long id;
    private String name;             // 菜品名称
    private Long categoryId;         // 分类ID
    private BigDecimal price;        // 价格
    private String image;            // 图片
    private String description;      // 描述
    private Integer status;          // 0停售 1起售
    private List<DishFlavor> flavors = new ArrayList<>(); // 口味列表
}

3.2.2 Controller 层

@RestController
@Api(tags = "菜品相关接口")
@Slf4j
@CrossOrigin
@RequestMapping("/admin/dish")
public class DishController {

    @Autowired
    private DishService dishService;

    @PostMapping
    @ApiOperation("新增菜品")
    public Result addDish(@RequestBody DishDTO dishDTO){
        log.info("新增菜品:{}", dishDTO);
        dishService.addDish(dishDTO);
        return Result.success();
    }
}

3.2.3 Service 层

public interface DishService {
    void addDish(DishDTO dishDTO);
}

3.2.4 Service 实现类

新增菜品步骤:

  1. 创建 Dish 实体对象
  2. 使用 BeanUtils.copyProperties() 复制 DishDTO 属性
  3. 插入菜品数据到 dish
  4. 获取新插入菜品的 id
  5. 为口味数据设置 dishId
  6. 插入口味数据到 dish_flavor
@Service
public class DishServiceImpl implements DishService {

    @Autowired
    private DishMapper dishMapper;
    @Autowired
    private DishFlavorMapper dishFlavorMapper;

    @Override
    public void addDish(DishDTO dishDTO) {
        Dish dish = new Dish();
        BeanUtils.copyProperties(dishDTO, dish);
        dishMapper.addDish(dish);

        List<DishFlavor> flavorList = dishDTO.getFlavors();
        if (flavorList != null && !flavorList.isEmpty()) {
            for (DishFlavor dishFlavor : flavorList) {
                dishFlavor.setDishId(dish.getId());
                dishFlavorMapper.add(dishFlavor);
            }
        }
    }
}

2.4.5 Mapper 层

@Mapper
public interface DishMapper {

    @Select("select count(id) from dish where category_id = #{categoryId}")
    Integer countByCategoryId(Long categoryId);

    @Insert("insert into dish values (null, #{name}, #{categoryId}, #{price}, #{image}, #{description}, #{status}, #{create_time}, #{update_time}, #{create_user}, #{update_user})")
    void addDish(Dish dish);
}
DishFlavorMapper.java
@Mapper
public interface DishFlavorMapper {
    @Insert("insert into dish_flavor(id, dish_id, name, value) values (default, #{dishId}, #{name}, #{value})")
    void add(DishFlavor dishFlavor);
}

3.3 联调测试

图片上传功能:

新增菜品:


好的,我明白了,你是要我把“3.菜品的分页查询”这一段内容改写成跟你之前给的“新增菜品”那种模板结构,保留图片占位 image.png,并且用条理清晰的分节说明。

我会按照你的模板来输出,包含需求分析、业务规则、代码分块(DTO、VO、Controller、Service、Mapper)、以及测试步骤,像你上一个改写后的那种格式。这样你两个章节在文档里就风格统一。

下面是改写好的版本:


四、菜品的分页查询

4.1 需求分析

4.1.1 产品原型

在菜品列表展示时,除了基本信息(名称、售价、售卖状态、最后操作时间)外,还有两个特殊字段需要处理:

  1. 图片字段:数据库中保存图片路径,页面展示需要下载图片文件。
  2. 菜品分类字段:数据库保存的是分类 ID,页面需要显示分类名称,因此需要根据分类 ID 查询分类表获取名称。

业务规则

  • 根据页码展示菜品信息
  • 每页展示 10 条数据
  • 支持按菜品名称、分类、状态进行条件查询

4.1.2 接口设计

接口地址:/admin/dish/page

请求方式:GET

请求数据类型:application/x-www-form-urlencoded

请求参数:

参数名称 参数说明 请求类型 是否必须 数据类型 schema
categoryId query false integer(int32)
name query false string
page query false integer(int32)
pageSize query false integer(int32)
status query false integer(int32)

响应参数:

参数名称 参数说明 类型 schema
code integer(int32) integer(int32)
data PageResult PageResult
records array object
total integer(int64)
msg string

响应示例:

{
    "code": 0,
    "data": {
        "records": [],
        "total": 0
    },
    "msg": ""
}

4.2 代码实现

4.2.1 设计DTO

qi-pojo 模块中

@Data
public class DishPageQueryDTO implements Serializable {
    private int page;
    private int pageSize;
    private String name;
    // 分类id
    private Integer categoryId;
    // 状态 0表示禁用 1表示启用
    private Integer status;
}

4.2.2 设计VO

qi-pojo 模块中

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class DishVO implements Serializable {
    private Long id;                // 菜品ID
    private String name;            // 菜品名称
    private Long categoryId;        // 分类ID
    private BigDecimal price;       // 价格
    private String image;           // 图片
    private String description;     // 描述信息
    private Integer status;         // 0 停售 1 起售
    private LocalDateTime updateTime; // 更新时间
    private String categoryName;    // 分类名称
    private List<DishFlavor> flavors = new ArrayList<>(); // 菜品口味列表
}

PageResult

@Data
@AllArgsConstructor
@NoArgsConstructor
public class PageResult implements Serializable {
    private long total;     // 总记录数
    private List records;   // 当前页数据集合
}

4.2.3 Controller 层

DishController 新增分页查询接口:

@GetMapping("/page")
@ApiOperation("菜品分页查询")
public Result<PageResult> page(DishPageQueryDTO dishPageQueryDTO) {
    log.info("菜品分页查询:{}", dishPageQueryDTO);
    PageResult pageResult = dishService.pageQuery(dishPageQueryDTO);
    return Result.success(pageResult);
}

4.2.4 Service 层

PageResult pageQuery(DishPageQueryDTO dishPageQueryDTO);

4.2.5 Service 实现类

DishServiceImpl

@Override
public PageResult pageQuery(DishPageQueryDTO dishPageQueryDTO) {
    PageHelper.startPage(dishPageQueryDTO.getPage(), dishPageQueryDTO.getPageSize());
    Page<DishVO> page = dishMapper.pageQuery(dishPageQueryDTO);
    return new PageResult(page.getTotal(), page.getResult());
}

4.2.6 Mapper 层

Page<DishVO> pageQuery(DishPageQueryDTO dishPageQueryDTO);

SQL映射:DishMapper.xml

<select id="pageQuery" resultType="com.sky.vo.DishVO">
  select d.*, c.name as categoryName
  from dish d
  left outer join category c on d.category_id = c.id
  <where>
    <if test="name != null">
      and d.name like concat('%',#{name},'%')
    </if>
    <if test="categoryId != null">
      and d.category_id = #{categoryId}
    </if>
    <if test="status != null">
      and d.status = #{status}
    </if>
  </where>
  order by d.create_time desc
</select>

4.3 联调测试

五、删除菜品

5.1 需求分析

5.1.1 产品原型

  • 支持批量删除菜品
  • 起售中的菜品不能删除
  • 被套餐关联的菜品不能删除
  • 删除菜品后,需要同步删除关联的口味数据

4.1.2 接口设计

接口地址:/admin/dish

请求方式:DELETE

请求数据类型:application/x-www-form-urlencoded

请求参数:

参数名称 参数说明 请求类型 是否必须 数据类型 schema
ids ids query true array integer

响应参数:

参数名称 参数说明 类型 schema
code integer(int32) integer(int32)
data object
msg string

响应示例:

{
    "code": 0,
    "data": {},
    "msg": ""
}

5.2 功能实现

5.2.1 Controller 层

@DeleteMapping
@ApiOperation("菜品批量删除")
public Result delete(@RequestParam List<Long> ids) {
    log.info("菜品批量删除:{}", ids);
    dishService.deleteBatch(ids);
    return Result.success();
}

5.2.2 Service 层

void deleteBatch(List<Long> ids);

5.2.3 Service 实现类

@Autowired
private SetmealDishMapper setmealDishMapper;

@Transactional
public void deleteBatch(List<Long> ids) {
    // 检查是否存在起售中的菜品
    for (Long id : ids) {
        Dish dish = dishMapper.getById(id);
        if (dish.getStatus() == StatusConstant.ENABLE) {
            throw new DeletionNotAllowedException(MessageConstant.DISH_ON_SALE);
        }
    }

    // 检查是否被套餐关联
    List<Long> setmealIds = setmealDishMapper.getSetmealIdsByDishIds(ids);
    if (setmealIds != null && !setmealIds.isEmpty()) {
        throw new DeletionNotAllowedException(MessageConstant.DISH_BE_RELATED_BY_SETMEAL);
    }

    // 删除菜品及口味
    for (Long id : ids) {
        dishMapper.deleteById(id);
        dishFlavorMapper.deleteByDishId(id);
    }
}

5.2.4 Mapper 层

DishMapper

@Select("select * from dish where id = #{id}")
Dish getById(Long id);

@Delete("delete from dish where id = #{id}")
void deleteById(Long id);

SetmealDishMapper

@Mapper
public interface SetmealDishMapper {
    List<Long> getSetmealIdsByDishIds(List<Long> dishIds);
}

SetmealDishMapper.xml

<select id="getSetmealIdsByDishIds" resultType="java.lang.Long">
    select setmeal_id from setmeal_dish where dish_id in
    <foreach collection="dishIds" item="dishId" separator="," open="(" close=")">
        #{dishId}
    </foreach>
</select>

DishFlavorMapper

@Delete("delete from dish_flavor where dish_id = #{dishId}")
void deleteByDishId(Long dishId);

5.5 联调测试

  1. 在菜品列表页面选择多个停售菜品,执行删除
  2. 删除起售中的菜品时,会提示失败

六、修改菜品

6.1 需求分析

6.1.1 产品原型

在菜品管理列表页面点击 修改 按钮,跳转到修改菜品页面,在该页面回显菜品的相关信息并进行修改,最后点击 保存 按钮完成修改操作。


6.1.2 接口设计

通过对原型图进行分析,该页面共涉及 4 个接口

  • 根据 id 查询菜品
  • 根据类型查询分类(已实现)
  • 文件上传(已实现)
  • 修改菜品
  1. 根据 id 查询菜品

接口地址:/admin/dish/{id}

请求方式:GET

请求数据类型:application/x-www-form-urlencoded

请求参数:

参数名称 参数说明 请求类型 是否必须 数据类型 schema
id id path true integer(int64)

响应参数:

参数名称 参数说明 类型 schema
code integer(int32) integer(int32)
data DishVO DishVO
categoryId integer(int64)
categoryName string
description string
flavors array DishFlavor
dishId integer
id integer
name string
value string
id integer(int64)
image string
name string
price number
status integer(int32)
updateTime string(date-time)
msg string

响应示例:

{
    "code": 0,
    "data": {
        "categoryId": 0,
        "categoryName": "",
        "description": "",
        "flavors": [
            {
                "dishId": 0,
                "id": 0,
                "name": "",
                "value": ""
            }
        ],
        "id": 0,
        "image": "",
        "name": "",
        "price": 0,
        "status": 0,
        "updateTime": ""
    },
    "msg": ""
}
  1. 修改菜品

接口地址:/admin/dish

请求方式:PUT

请求数据类型:application/json

请求示例:

{
  "categoryId": 0,
  "description": "",
  "flavors": [
    {
      "dishId": 0,
      "id": 0,
      "name": "",
      "value": ""
    }
  ],
  "id": 0,
  "image": "",
  "name": "",
  "price": 0,
  "status": 0
}

请求参数:

参数名称 参数说明 请求类型 是否必须 数据类型 schema
dishDTO dishDTO body true DishDTO DishDTO
categoryId false integer(int64)
description false string
flavors false array DishFlavor
dishId false integer
id false integer
name false string
value false string
id false integer(int64)
image false string
name false string
price false number
status false integer(int32)

响应状态:

状态码 说明 schema
200 OK Result
201 Created
401 Unauthorized
403 Forbidden
404 Not Found

响应参数:

参数名称 参数说明 类型 schema
code integer(int32) integer(int32)
data object
msg string

响应示例:

{
    "code": 0,
    "data": {},
    "msg": ""
}

5.2 代码开发

5.2.1 根据 id 查询菜品

Controller 层

@GetMapping("/{id}")
@ApiOperation("根据id查询菜品")
public Result<DishVO> getById(@PathVariable Long id) {
    log.info("根据id查询菜品:{}", id);
    DishVO dishVO = dishService.getByIdWithFlavor(id); 
    return Result.success(dishVO);
}

Service 层

DishVO getByIdWithFlavor(Long id);

Service 层实现类

public DishVO getByIdWithFlavor(Long id) {
    // 查询菜品基本信息
    Dish dish = dishMapper.getById(id);

    // 查询菜品口味信息
    List<DishFlavor> dishFlavors = dishFlavorMapper.getByDishId(id);

    // 封装 VO
    DishVO dishVO = new DishVO();
    BeanUtils.copyProperties(dish, dishVO);
    dishVO.setFlavors(dishFlavors);

    return dishVO;
}

Mapper 层

@Select("select * from dish_flavor where dish_id = #{dishId}")
List<DishFlavor> getByDishId(Long dishId);

5.2.2 修改菜品

Controller 层

@PutMapping
@ApiOperation("修改菜品")
public Result update(@RequestBody DishDTO dishDTO) {
    log.info("修改菜品:{}", dishDTO);
    dishService.updateWithFlavor(dishDTO);
    return Result.success();
}

Service 层

void updateWithFlavor(DishDTO dishDTO);

Service 层实现类

public void updateWithFlavor(DishDTO dishDTO) {
    Dish dish = new Dish();
    BeanUtils.copyProperties(dishDTO, dish);
    // 修改菜品基本信息
    dishMapper.update(dish);
    // 删除原有口味数据
    dishFlavorMapper.deleteByDishId(dishDTO.getId());
    // 重新插入口味数据
    List<DishFlavor> flavors = dishDTO.getFlavors();
    if (flavors != null && !flavors.isEmpty()) {
        flavors.forEach(dishFlavor -> dishFlavor.setDishId(dishDTO.getId()));
        dishFlavorMapper.insertBatch(flavors);
    }
}

Mapper 层

@AutoFill(value = OperationType.UPDATE)
void update(Dish dish);

DishMapper.xml

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

5.3 功能测试

  1. 点击 修改,回显成功。

  2. 进入 菜品列表查询页面,对某个菜品的价格进行修改。


关于菜品管理就基本实现了,下一章小柒味来 - 店铺营业状态接口实现


欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 1701220998@qq.com
导航页 GitHub