菜品管理
一、公共字段的内容填充
在开始本章节之前,我们不妨想一个问题,上一章我们在新增员工和新增菜品分类时,需要设置以下字段:
- 创建时间(
createTime) - 创建人(
createUser) - 修改时间(
updateTime) - 修改人(
updateUser)
在编辑员工和编辑菜品分类时,需要设置以下字段:
- 修改时间(
updateTime) - 修改人(
updateUser)
这些字段属于公共字段,在一个具有完整CRUD的模块中许多数据表都会包含的通用字段。
1.1 常见处理方式
- 新增数据
createTime、updateTime:设置为当前时间createUser、updateUser:设置为当前登录用户 ID
- 更新数据
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-common 的 com.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 注释上一章中的冗余代码
- 员工管理中新增、编辑方法的公共字段
- 菜品分类管理中新增、修改方法的公共字段
这样公共字段赋值将完全由切面统一处理。
二、文件上传
因为菜品需要上传图片,所以我们先来完成前置模块,通用功能之文件上传
实现文件上传功能,需要依赖存储服务。常见的解决方案包括:
- 直接将文件保存到服务器本地硬盘
- 优点:开发简单、成本低
- 缺点:扩容困难,受服务器磁盘容量限制
- 使用分布式文件系统(如 FastDFS、MinIO)
- 优点:易于扩容,适合大规模存储
- 缺点:开发复杂度相对较高
- 使用第三方存储服务(如阿里云 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 产品原型
新增菜品功能用于在后台系统中添加菜品信息,包括基本信息和口味信息。
操作流程:
- 管理员点击“新增菜品”按钮
- 填写菜品名称、分类、价格、图片、描述等基本信息
- 可选填写口味信息
实现要点:
- 菜品数据分为两部分:
- 基本数据(保存到
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 实现类
新增菜品步骤:
- 创建
Dish实体对象 - 使用
BeanUtils.copyProperties()复制DishDTO属性 - 插入菜品数据到
dish表 - 获取新插入菜品的
id - 为口味数据设置
dishId - 插入口味数据到
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 产品原型
在菜品列表展示时,除了基本信息(名称、售价、售卖状态、最后操作时间)外,还有两个特殊字段需要处理:
- 图片字段:数据库中保存图片路径,页面展示需要下载图片文件。
- 菜品分类字段:数据库保存的是分类 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 联调测试
- 在菜品列表页面选择多个停售菜品,执行删除

- 删除起售中的菜品时,会提示失败

六、修改菜品
6.1 需求分析
6.1.1 产品原型
在菜品管理列表页面点击 修改 按钮,跳转到修改菜品页面,在该页面回显菜品的相关信息并进行修改,最后点击 保存 按钮完成修改操作。
6.1.2 接口设计
通过对原型图进行分析,该页面共涉及 4 个接口:
- 根据 id 查询菜品
- 根据类型查询分类(已实现)
- 文件上传(已实现)
- 修改菜品
- 根据 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": ""
}
- 修改菜品
接口地址:/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 功能测试
点击 修改,回显成功。
进入 菜品列表查询页面,对某个菜品的价格进行修改。

关于菜品管理就基本实现了,下一章小柒味来 - 店铺营业状态接口实现
欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 1701220998@qq.com