你在 Day03 真正要练会的 6 件事
Day03 的核心不是“抄代码”,而是把一套后台典型 CRUD 能力做扎实:
- 用 AOP + 注解 + 反射消灭“公共字段重复赋值”(createTime/createUser/updateTime/updateUser)
- 做一个可复用的 文件上传接口(OSS) ,供菜品图片等功能使用
- 完成 新增菜品(含口味表批量插入) ,并用事务保证一致性
- 完成 菜品分页查询(多条件 + 分类名联表回显)
- 完成 删除菜品(规则校验:起售/被套餐关联/级联删除口味)
- 完成 修改菜品(回显 + 基本信息更新 + 口味先删后插)
学完 Day03,你应该能“自己设计并落地一个后台模块”,而不只是跟着敲。
Day03 代码地图(你要在这些地方来回切换)
-
sky-server
controller/admin:接口入口(DishController、CommonController)service/impl:业务编排(事务、校验、组合表操作)aspect:AutoFillAspect(公共字段统一填充)config:OssConfiguration(把 AliOssUtil 交给 Spring 管)
-
sky-pojo
- DTO/VO:DishDTO、DishPageQueryDTO、DishVO 等
-
sky-common
properties:AliOssProperties 读取 yml 配置utils:AliOssUtil 真正上传到 OSS
-
resources/mapper
DishMapper.xml / DishFlavorMapper.xml / SetmealDishMapper.xml:SQL 落点
第一部分:公共字段自动填充(Day03 的“内功”)
1) 为什么要做
你之前在新增/修改员工、分类时都在业务里写 createTime/updateTime 等字段赋值,重复且容易漏。Day03 用 AOP 统一处理:只要是 insert/update 的 mapper 方法,自动补齐公共字段。
2) 设计要点(你必须想清楚这 3 个问题)
- 我拦截谁? —— 拦截
com.sky.mapper.*.*(..)且方法上有@AutoFill的方法 - 我怎么知道这是 INSERT 还是 UPDATE? —— 注解里带枚举
OperationType - 我往谁身上填字段? —— mapper 方法的第一个参数实体对象,用反射调用 setter(setCreateTime 等)
3) 落地步骤(按这个顺序做,少走弯路)
Step A:定义注解 @AutoFill(标记“需要自动填充”的 mapper 方法)
Step B:写切面 AutoFillAspect(前置通知里:取 operationType → 取实体参数 → 反射 set)
Step C:在 mapper 方法上加注解(CategoryMapper、EmployeeMapper、DishMapper 的 insert/update)
Step D:把 service 里手动赋值那坨代码注释掉(否则会重复/混乱)
4) 调试验证清单(你一条条过)
- ✅ 能进
AutoFillAspect.autoFill(),日志出现 “开始进行公共字段自动填充...” - ✅ 执行插入时 SQL 带上 create_time/create_user/update_time/update_user(看控制台 SQL)
- ✅ update 时只改 updateTime/updateUser(符合规则)
5) 高频坑(很容易卡你 1 小时)
- 反射找不到方法:实体类必须有
setCreateTime(LocalDateTime)这种标准 setter,且名字要跟常量一致(别手滑写成 setCreate_time)。 - joinPoint.getArgs()[0] 不是实体:如果你的 mapper 方法第一个参数不是实体(例如多个参数),此方案会失效,需要改切面策略。
- currentId 为空:BaseContext 必须在登录校验/拦截器里提前 set(不然 createUser/updateUser 会是 null)
第二部分:文件上传(CommonController + OSS)
你要的结果
前端选择图片 → 调 /admin/common/upload → 返回图片 URL,后续新增菜品直接把 URL 存数据库。
实现骨架(你要理解“配置怎么流动”)
- yml 配置 endpoint/key/bucket(dev 环境)
AliOssProperties读取配置OssConfiguration生成AliOssUtilBeanCommonController.upload():生成随机文件名、调用aliOssUtil.upload()返回路径
建议你顺手加的 3 个“实战增强”
- 上传前校验后缀(jpg/png/webp),避免乱传
- 限制文件大小(Spring 配置 + 代码兜底)
- OSS key 不要硬编码进仓库(用本地 dev 配置或环境变量)
第三部分:新增菜品(dish + dish_flavor,两张表一次搞定)
1) 关键数据结构
DishDTO 里 flavors 是个 List(可以为空)
2) 关键链路(你要能背出来)
Controller 收 DTO → Service 开事务 → insert dish 拿到主键 → 批量插入 flavors
3) 一次写对的要点
dishMapper.insert(dish)必须能回填主键(useGeneratedKeys="true" keyProperty="id")- flavors 可能为空:空就别 insertBatch(你文档里就是这么处理的)
- 必须加事务:dish 插入成功但 flavors 失败时要整体回滚
第四部分:菜品分页查询(最像真实后台开发)
你要解决的“展示问题”
列表里除了菜品字段,还要回显:
- 图片:数据库存的是 URL/文件名,前端直接展示
- 分类:页面显示的是分类名称,所以要联表查
categoryName
实现套路(通用!以后所有分页都这么写)
- DTO:page/pageSize/name/categoryId/status
- Service:PageHelper.startPage → mapper 查 Page → new PageResult(total, list)
- SQL:left join + 动态 where + order by create_time desc
自测(别只看页面,要会用接口测)
Swagger:/admin/dish/page,注意 token 失效要重新登录拿 token
第五部分:删除菜品(规则校验 + 事务)
业务规则你必须“先校验再删除”
- 起售中的菜品不能删
- 被套餐关联的菜品不能删
- 删除菜品时要删除口味表数据
正确实现顺序(非常重要)
- 遍历 ids:查 dish,若 status==ENABLE → 直接抛异常
- 查 setmeal_dish 是否有关联:有则抛异常
- 真正删除:dish 表 + dish_flavor 表(循环删)
- 事务包住(中途失败要回滚)
第六部分:修改菜品(回显 + 更新 + 口味重建)
1) 回显:根据 id 查菜品 + 查口味
DishVO = dish + flavors
2) 修改:基本信息更新 + 口味先删后插
流程非常典型:update dish → delete flavors → insertBatch new flavors
并且 dishMapper.update 也走 AutoFill,自动补 updateTime/updateUser
建议你这样学 Day03:2 小时“闭环训练”
第 1 轮(30 分钟):只做自动填充闭环
- 给 CategoryMapper / EmployeeMapper / DishMapper 的 insert/update 加
@AutoFill - 跑新增分类/修改分类,看 SQL 是否自动补齐字段
第 2 轮(40 分钟):打通“上传 → 新增菜品”闭环
/admin/common/upload返回 URL- 新增菜品接口保存 dish + dish_flavor(表里能查到)
第 3 轮(50 分钟):分页/删除/修改三连
- 分页:多条件查(name/categoryId/status)
- 删除:分别测“起售不能删”“被套餐关联不能删”“停售能删且口味被删”
- 修改:回显成功 → 改价格/口味 → 保存成功
你做完后,用这 10 个问题自测(答不上来就回头补)
- 为什么 AutoFill 放在 mapper 层方法上而不是 service?
- AOP 里怎么判断 INSERT/UPDATE?
- 为什么 joinPoint 的第一个参数要是实体对象?
useGeneratedKeys的作用是什么?没有会怎样?- 为什么新增菜品必须加
@Transactional? - 分页查询为什么要 PageHelper.startPage 写在 mapper 调用之前?
- 联表查分类名为什么用 left join?
- 删除菜品为什么要先校验再删除?顺序反了会怎样?
- 修改口味为什么是“先删后插”而不是“对比增删改”?
- 修改 dish 时 updateTime/updateUser 是谁填的?在哪里填?
如果你愿意更“像带练”一点:你把你现在卡住的点(比如:AutoFill 进不去、dishId 回填为 null、分页查不到分类名、删除一直提示被套餐关联等)贴出报错/日志/相关方法代码,我就按 Day03 的链路帮你快速定位到具体类、具体 SQL 或具体配置。