毕设项目<<基于微信小程序的餐馆外卖系统的设计后端>>(开发记录(二)菜品管理)
文章预览:
视频传送带
菜品管理
公共字段自动填充
问题分析
代码冗余,不便于后期维护
实现思路
- 自定义注解
AutoFile
,用于标识需要进行公共字段自动填充的方法 - 自定义切面类
AutoFileAspect
,统一加入拦截AutoFile
方法,通过反射为公共字段赋值 - 在
Mapper
的方法上加入AutoFile注解 - 技术点:枚举、注解、
AOP
、反射
代码开发
package com.sky.aspect;
import com.sky.annotation.AutoFile;
import com.sky.constant.AutoFillConstant;
import com.sky.context.BaseContext;
import com.sky.enumeration.OperationType;
import lombok.extern.slf4j.Slf4j;
import org.aopalliance.intercept.Joinpoint;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MemberSignature;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
/**
* projectName: sky-take-out
* package: com.sky.aspect
* author: Amos
* description: 自定义切面,实现公共字段自动填充的逻辑
* date: 2024/1/3 0003 02:18:04
* version: 1.0
*/
@Aspect
@Component
@Slf4j
public class AutoFileAspect {
/**
* 切点表达式:切入点
*/
@Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFile)")
public void autoFillPointCut(){}
/**
* 前置通知,在前置通知中进行公共字段赋值
*/
@Before("autoFillPointCut()")
public void autoFill(JoinPoint joinPoint){
log.info("开始进行公共字段的自动填充...");
//获取到当前被拦截的方法的数据库操作类型
MethodSignature signature = (MethodSignature) joinPoint.getSignature();//方法签名对象
AutoFile autoFile = signature.getMethod().getAnnotation(AutoFile.class);//获得方法上的注解对象
OperationType operationType = autoFile.value();//获得数据库操作类型
//获取被拦截的方法的参数--实体对象
Object[L|400] 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 setCreatTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_TIME, LocalDateTime.class);
Method setCreatUser = 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);
//通过反射为对象属性赋值
setCreatTime.invoke(entity,now);
setCreatUser.invoke(entity,currentId);
setUpdateTime.invoke(entity,now);
setUpdateUser.invoke(entity,currentId);
} catch (Exception e) {
//throw new RuntimeException(e);
e.printStackTrace();
}
} else if (operationType == OperationType.UPDATE) {
//为2个公共字段赋值
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);
e.printStackTrace();
}
}
}
}
新增菜品
需求分析和设计
业务规则
- 菜品名称必须唯一
- 菜品必须属于某个分类下面,不能单独存在
- 新增菜品时可以根据情况选择菜品的口味
- 每个菜品必须对应应一张图片
接口设计
-
根据类型查询分类
-
文件上传
- 新增菜品
数据库设计
dish表
dish_flavor
表
代码开发
阿里云对象存储
配置
alioss:
endpoint: oss-cn-beijing.aliyuncs.com
access-key-id:
access-key-secret:
bucket-name:
文件上传
/**
* 文件上传
* @param file
* @return
*/
@PostMapping("/upload")
@ApiOperation("文件上传")
public Result<String> upload(MultipartFile file){
log.info("文件开始上传:{}",file);
try {
//原始文件名
String fileOriginalFilename = file.getOriginalFilename();
//截取原始文件名后缀 xxxx.pngString extension = fileOriginalFilename.substring(fileOriginalFilename.lastIndexOf("."));
//构造新文件名称
String fileName = UUID.randomUUID() + extension;
String filePath = aliOssUtil.upload(file.getBytes(), fileName);
return Result.success(filePath);
} catch (IOException e) {
log.error("文件上传失败: {}", e.getMessage());
}
return Result.error(MessageConstant.UPLOAD_FAILED);
}
}
新增菜品
/**
* 新增菜品及口味
* @param dishDTO
*/
@Transactional
public void saveWithFlavor(DishDTO dishDTO) {
Dish dish = new Dish();
BeanUtils.copyProperties(dishDTO, dish);
//向菜品表插入1条数据
dishMapper.insert(dish);
// 获取insert语句的主键值
Long dishId = dish.getId();
// 新增n条口味数据
List<DishFlavor> flavors = dishDTO.getFlavors();
if (flavors != null && flavors.size() > 0) {
//向口味表插入n条数据
flavors.forEach(dishFlavor -> {
dishFlavor.setDishId(dishId);
});
// for (DishFlavor flavor : flavors) {
// flavor.setDishId(dishId);
// }
dishFlavorMapper.insertBatch(flavors);
}
}
菜品分页查询
需求分析和设计
产品原型
业务规则
- 根据页码展示菜品信息
- 每页展示10条信息
- 分页查询可以根据需要输入菜品名称、菜品分类、菜品状态进行查询
接口设计
代码开发
-
根据菜品分页查询接口定义设计对应的
DTO
,通过DTO
封装,
-
根据菜品分页查询接口定义设计对应的
VO
,将其转成json
数据传给前端,然后前端进行正常展示
数据库查询
SELECT d.*,c.`name` AS categoryName FROM dish d LEFT OUTER JOIN category c on d.category_id = c.id
/**
*
* 菜品分页查询
* @param dishPageQueryDTO
* @return
*/
@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());
}
代码测试
接口文档测试
前后端联调
删除菜品
需求分析和设计
业务规则
- 可以一次删除一个菜品,也可以批量删除菜品
- 启售中的菜品不能删除
- 被套餐关联的菜品不能删除
- 删除菜品后,关联的口味数据也要删除
接口设计
数据库设计
代码开发
@Transactional
public void deleteBatch(List<Long> ids) {
// 1. 起售中不可删
for (long id:ids
) {
//根据主键查询
Dish dish = dishMapper.getById(id);
if (dish.getStatus() == StatusConstant.ENABLE) {
throw new DeletionNotAllowedException(MessageConstant.DISH_ON_SALE);
}
}
// 2. 被套餐关联的菜品不可删
List<Long> setmealIds = setmealDishMapper.getSetmealIdsByDishIds(ids);
if (setmealIds != null && setmealIds.size() > 0) {
throw new DeletionNotAllowedException(MessageConstant.DISH_BE_RELATED_BY_SETMEAL);
}
// 3. 删除菜品后关联的口味数据也要删除掉
for (Long id : ids) {
dishMapper.deleteById(id);
dishFlavorMapper.deleteByDishIds(id);
}
}
代码测试
修改菜品
需求分析和设计
产品原型
接口设计
- 根据id查询菜品
- 根据类型查询分类
- 文件上传
- 修改菜品
代码开发
根据id查询菜品,用于页面回显
/**
* 根据id查询菜品和对应口味数据
*
* @param id
* @return
*/
public DishVO getByIdWithFlavor(Long id) {
//根据id查询菜品数据
Dish dish = dishMapper.getById(id);
//根据id查询口味数据
List<DishFlavor> dishFlavors = dishFlavorMapper.getByDishId(id);
//将查询数据封装到VO
DishVO dishVO = new DishVO();
BeanUtils.copyProperties(dish,dishVO);
dishVO.setFlavors(dishFlavors);
return dishVO;
}
菜品修改
/**
* 根据id修改菜品
* @param dishDTO
*/
public void updateWithFlafvor(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.size() > 0) {
//向口味表插入n条数据
for (DishFlavor dishFlavor : flavors) {
dishFlavor.setDishId(dishDTO.getId());
}
dishFlavorMapper.insertBatch(flavors);
}
}