苍穹外卖 Day03 :菜品管理模块开发

发布时间:2026/5/27 7:15:46

苍穹外卖 Day03 :菜品管理模块开发 一、公共字段自动填充1.1 问题分析在开发过程中每个业务表都包含公共字段创建时间、修改时间、创建人、修改人。如果在每个插入或更新操作中都手动设置这些字段会导致代码冗余且难以维护。// 冗余代码示例 employee.setCreateTime(LocalDateTime.now()); employee.setUpdateTime(LocalDateTime.now()); employee.setCreateUser(BaseContext.getCurrentId()); employee.setUpdateUser(BaseContext.getCurrentId());1.2 实现思路采用AOP面向切面编程技术实现公共字段的自动填充自定义注解AutoFill标识需要进行自动填充的方法自定义切面AutoFillAspect拦截带注解的方法通过反射为公共字段赋值技术栈枚举、注解、AOP、反射1.3 代码实现1.3.1 自定义注解 AutoFillTarget(ElementType.METHOD) Retention(RetentionPolicy.RUNTIME) public interface AutoFill { // 数据库操作类型INSERT 或 UPDATE OperationType value(); }1.3.2 自定义切面 AutoFillAspectAspect Component Slf4j public class AutoFillAspect { // 切入点匹配 mapper 包下带有 AutoFill 注解的方法 Pointcut(execution(* com.sky.mapper.*.*(..)) annotation(com.sky.annotation.AutoFill)) public void autoFillPointCut() {} // 前置通知在方法执行前进行公共字段填充 Before(autoFillPointCut()) public void autoFill(JoinPoint joinPoint) { // 获取方法签名和注解 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]; // 准备赋值数据 LocalDateTime now LocalDateTime.now(); Long currentId BaseContext.getCurrentId(); // 根据操作类型为不同字段赋值 if (operationType OperationType.INSERT) { // 插入操作设置4个字段 setFieldValue(entity, setCreateTime, now, LocalDateTime.class); setFieldValue(entity, setUpdateTime, now, LocalDateTime.class); setFieldValue(entity, setCreateUser, currentId, Long.class); setFieldValue(entity, setUpdateUser, currentId, Long.class); } else { // 更新操作设置2个字段 setFieldValue(entity, setUpdateTime, now, LocalDateTime.class); setFieldValue(entity, setUpdateUser, currentId, Long.class); } } }1.3.3 在 Mapper 中使用注解Mapper public interface CategoryMapper { AutoFill(OperationType.INSERT) Insert(insert into category(...) values(...)) void insert(Category category); AutoFill(OperationType.UPDATE) void update(Category category); }二、新增菜品2.1 需求分析菜品名称必须唯一菜品必须属于某个分类可根据情况添加菜品口味每个菜品必须有一张图片2.2 数据库设计dish 表菜品基本信息名称、价格、图片、分类等dish_flavor 表菜品口味信息关联菜品 ID2.3 代码实现2.3.1 文件上传接口使用阿里云 OSSRestController RequestMapping(/admin/common) Slf4j public class CommonController { Autowired private AliOssUtil aliOssUtil; PostMapping(/upload) public ResultString upload(MultipartFile file) { // 生成唯一文件名 String originalFilename file.getOriginalFilename(); String extension originalFilename.substring(originalFilename.lastIndexOf(.)); String fileName UUID.randomUUID().toString() extension; try { // 上传到阿里云 OSS String filePath aliOssUtil.upload(file.getBytes(), fileName); return Result.success(filePath); } catch (IOException e) { log.error(文件上传失败: {}, e.getMessage()); return Result.error(上传失败); } } }在这里图片显示不出因为教程里老师有阿里的oss云上查询到图片然后回显的这里我们申请个三个月免费的bucket然后在yml中也要加上相应的阿里oss配置2.3.2 新增菜品接口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 ListDishFlavor flavors; // 口味列表 }Controller 层RestController RequestMapping(/admin/dish) Api(tags 菜品相关接口) public class DishController { Autowired private DishService dishService; PostMapping ApiOperation(新增菜品) public ResultString save(RequestBody DishDTO dishDTO) { dishService.saveWithFlavor(dishDTO); return Result.success(); } }Service 层实现Service Transactional public class DishServiceImpl implements DishService { Autowired private DishMapper dishMapper; Autowired private DishFlavorMapper dishFlavorMapper; Override public void saveWithFlavor(DishDTO dishDTO) { // 1. 插入菜品表 Dish dish new Dish(); BeanUtils.copyProperties(dishDTO, dish); dishMapper.insert(dish); // 2. 获取菜品主键值 Long dishId dish.getId(); // 3. 插入口味表 ListDishFlavor flavors dishDTO.getFlavors(); if (flavors ! null flavors.size() 0) { flavors.forEach(flavor - flavor.setDishId(dishId)); dishFlavorMapper.insertBatch(flavors); } } }Mapper 层// DishMapper.java AutoFill(OperationType.INSERT) void insert(Dish dish); // DishFlavorMapper.java void insertBatch(ListDishFlavor flavors);!-- DishMapper.xml -- insert idinsert useGeneratedKeystrue keyPropertyid insert into dish (status, name, category_id, price, image, description, create_time, update_time, create_user, update_user) values (#{status}, #{name}, #{categoryId}, #{price}, #{image}, #{description}, #{createTime}, #{updateTime}, #{createUser}, #{updateUser}) /insert !-- DishFlavorMapper.xml -- insert idinsertBatch insert into dish_flavor(dish_id, name, value) values foreach collectionflavors itemdishFlavor separator, (#{dishFlavor.dishId}, #{dishFlavor.name}, #{dishFlavor.value}) /foreach /insert三、菜品分页查询3.1 需求分析根据页码展示菜品信息支持按菜品名称、分类、状态进行筛选每页展示指定条数数据3.2 代码实现查询 DTOData public class DishPageQueryDTO implements Serializable { private int page; private int pageSize; private String name; // 菜品名称 private Integer categoryId; // 分类ID private Integer status; // 状态 }返回 VOpublic class DishVO implements Serializable { private Long id; private String name; private Long categoryId; private BigDecimal price; private String image; private String description; private Integer status; private LocalDateTime updateTime; private String categoryName; // 分类名称关联查询 private ListDishFlavor flavors; }Controller 层GetMapping(/page) ApiOperation(菜品分页查询) public ResultPageResult page(DishPageQueryDTO dishPageQueryDTO) { PageResult pageResult dishService.pageQuery(dishPageQueryDTO); return Result.success(pageResult); }Service 层实现Override public PageResult pageQuery(DishPageQueryDTO dishPageQueryDTO) { PageHelper.startPage(dishPageQueryDTO.getPage(), dishPageQueryDTO.getPageSize()); PageDishVO page dishMapper.pageQuery(dishPageQueryDTO); return new PageResult(page.getTotal(), page.getResult()); }Mapper 层 SQLselect idpageQuery resultTypecom.sky.vo.DishVO select d.*, c.name categoryName from dish d left join category c on d.category_id c.id where if testname ! null and d.name like concat(%, #{name}, %) /if if testcategoryId ! null and d.category_id #{categoryId} /if if teststatus ! null and d.status #{status} /if /where order by d.create_time desc /select四、删除菜品4.1 业务规则支持单个删除和批量删除起售中的菜品不能删除被套餐关联的菜品不能删除删除菜品后关联的口味数据也需要删除4.2 代码实现Controller 层DeleteMapping ApiOperation(菜品批量删除) public Result delete(RequestParam ListLong ids) { dishService.deleteBatch(ids); return Result.success(); }Service 层实现Transactional Override public void deleteBatch(ListLong ids) { // 1. 检查菜品状态 ids.forEach(id - { Dish dish dishMapper.getById(id); if (dish.getStatus() StatusConstant.ENABLE) { throw new DeletionNotAllowedException(菜品正在起售中不能删除); } }); // 2. 检查是否被套餐关联 ListLong setmealIds setmealDishMapper.getSetmealIdsByDishIds(ids); if (setmealIds ! null setmealIds.size() 0) { throw new DeletionNotAllowedException(菜品被套餐关联不能删除); } // 3. 删除菜品数据 ids.forEach(id - { dishMapper.deleteById(id); dishFlavorMapper.deleteByDishId(id); }); }Mapper 层// SetmealDishMapper.java ListLong getSetmealIdsByDishIds(ListLong ids);!-- SetmealDishMapper.xml -- select idgetSetmealIdsByDishIds resultTypejava.lang.Long select setmeal_id from setmeal_dish where dish_id in foreach collectionids separator, open( close) itemdishId #{dishId} /foreach /select五、修改菜品5.1 接口设计根据 ID 查询菜品信息包含口味修改菜品基本信息更新菜品口味先删除再新增5.2 代码实现5.2.1 根据 ID 查询菜品GetMapping(/{id}) ApiOperation(根据id查询菜品和关联的口味数据) public ResultDishVO getById(PathVariable Long id) { return Result.success(dishService.getByIdWithFlavor(id)); }Override public DishVO getByIdWithFlavor(Long id) { // 查询菜品信息 Dish dish dishMapper.getById(id); // 查询口味信息 ListDishFlavor flavors dishFlavorMapper.getByDishId(id); // 封装 VO DishVO dishVO new DishVO(); BeanUtils.copyProperties(dish, dishVO); dishVO.setFlavors(flavors); return dishVO; }5.2.2 修改菜品PutMapping ApiOperation(修改菜品) public Result update(RequestBody DishDTO dishDTO) { dishService.updateWithFlavor(dishDTO); return Result.success(); }Transactional Override public void updateWithFlavor(DishDTO dishDTO) { // 1. 修改菜品基本信息 Dish dish new Dish(); BeanUtils.copyProperties(dishDTO, dish); dishMapper.update(dish); // 2. 删除原有口味 dishFlavorMapper.deleteByDishId(dishDTO.getId()); // 3. 插入新口味 ListDishFlavor flavors dishDTO.getFlavors(); if (flavors ! null flavors.size() 0) { flavors.forEach(flavor - flavor.setDishId(dishDTO.getId())); dishFlavorMapper.insertBatch(flavors); } }Mapper 层AutoFill(OperationType.UPDATE) void update(Dish dish);update idupdate update dish set if testname ! nullname #{name},/if if testcategoryId ! nullcategory_id #{categoryId},/if if testprice ! nullprice #{price},/if if testimage ! nullimage #{image},/if if testdescription ! nulldescription #{description},/if if teststatus ! nullstatus #{status},/if if testupdateTime ! nullupdate_time #{updateTime},/if if testupdateUser ! nullupdate_user #{updateUser},/if /set where id #{id} /update六、总结Day03 完成了菜品管理模块的核心功能开发公共字段自动填充通过 AOP 技术解决了代码冗余问题提高了开发效率和代码可维护性新增菜品实现了文件上传和菜品信息保存支持多口味批量插入分页查询结合 PageHelper 实现分页支持多条件筛选删除菜品包含业务校验起售中、被套餐关联和级联删除修改菜品支持菜品信息的完整更新口味采用先删后增策略技术要点AOP 切面编程实现公共字段填充使用枚举定义操作类型反射机制动态调用方法事务管理保证数据一致性批量操作优化性能下一步可以继续完成套餐管理模块的开发。

相关新闻