当前位置: 首页 > news >正文

二刷苍穹外卖 day03

公共字段自动填充

通过AOP切面编程,实现功能增强,完成对公共字段的自动填充

实现思路:对以下四个字段进行统一处理
![[Pasted image 20250613222608.png]]

实现步骤:
1).自定义注解AutoFill,用于表示需要进行公共字段自动填充的方法
2)自定义切面类AutoFillAspect,统一拦截加入了AutoFill注解的方法,通过反射为公共字段赋值
3).在Mapper的方法上加入AutoFill注解

代码实现

自定义注解AutoFill

@Target(ElementType.METHOD)  
@Retention(RetentionPolicy.RUNTIME)  
public @interface AutoFill {  OperationType value();  
}

@Target(ElementType.MethOD)表明这个注解只能应用在方法上
@Retention(RetentionPolicy.RUNTIME)表明该注解在运行时依然存在,可通过反射获取
OperationType value();定义了该注解有一个名为value的属性,类型为OperationType ,使用该注解时需为value属性赋值。

自定义切面AutoFillAspect

 /*** 切入点*/@Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill)")public void autoFillPointCut(){}/*** 前置通知,在通知中进行公共字段的赋值*/@Before("autoFillPointCut()")public void autoFill(JoinPoint joinPoint){/重要//可先进行调试,是否能进入该方法 提前在mapper方法添加AutoFill注解log.info("开始进行公共字段自动填充...");}

execution(* com.sky.mapper.*.*(..))表示匹配com.sky.mapper包下所有类的所有方法;&& @annotation(com.sky.annotation.AutoFill)表示被com.sky.annotation.AutoFill注解标记的方法才会被匹配。整体意思是,当com.sky.mapper包下的方法被AutoFill注解标记时,该切入点匹配。

  • public void autoFillPointCut() { } 是切入点方法,它本身没有实际的方法体,只是作为切入点的标识。

总结:

AutoFill是“标记”,而AutoFillAspect是"实现逻辑"
AutoFill用于标记目标方法(@Target(ElementType.METHOD)声明注解只能标注在方法上)
通过OperationType value()声明注解需要携带一个OperationType类型的参数
AutoFillAspect是一个切面类:
通过AOP的切点@Pointcut筛选出对应包下能被@AutoFill注解标记的方法
通过注解的操作类型自动填充字段

通过这种方式,只需要在Mapper中确定是插入操作还是更新操作即可统一管理公共字段的赋值逻辑,开发者只需在 Mapper 方法上声明 @AutoFill(OperationType.INSERT)@AutoFill(OperationType.UPDATE),切面会自动完成公共字段的赋值,无需手动编写重复代码。

AOP

面向切面编程,在Spring AOP中可以认为是面向方法编程
在不修改目标方法源代码的前提下,底层通过动态代理机制实现对目标方法的编程,动态代理是目前面向方法编程最主流的实现技术
![[Pasted image 20250622192431.png]]

文件上传

定义OSS相关配置

在这里插入图片描述

读取OSS配置

![[Pasted image 20250622194454.png]]

  • @ConfigurationProperties(prefix = "sky.alioss"):这是 Spring Boot 提供的注解,用于将配置文件中以sky.alioss为前缀的属性值绑定到该类的对应字段上。比如在application.propertiesapplication.yml等配置文件中,sky.alioss.endpoint的值会被绑定到类中的endpoint字段。

生成OSS工具类对象

![[Pasted image 20250622194610.png]]

定义文件上传接口

package com.sky.controller.admin;  import com.sky.constant.MessageConstant;  
import com.sky.result.Result;  
import com.sky.utils.AliOssUtil;  
import io.swagger.annotations.Api;  
import io.swagger.annotations.ApiOperation;  
import lombok.extern.slf4j.Slf4j;  
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.web.bind.annotation.PostMapping;  
import org.springframework.web.bind.annotation.RequestMapping;  
import org.springframework.web.bind.annotation.RestController;  
import org.springframework.web.multipart.MultipartFile;  import java.io.IOException;  
import java.util.UUID;  @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);  }  return Result.error(MessageConstant.UPLOAD_FAILED);  }  
}
  1. String originalFilename = file.getOriginalFilename();:获取文件的原始文件名,并将其存储在originalFilename变量中。
  2. String extension = originalFilename.substring(originalFilename.lastIndexOf("."));:通过substring方法从原始文件名中截取文件后缀。lastIndexOf(".")用于找到文件名中最后一个点号(.)的位置,以此来确定后缀的起始位置,截取的后缀存储在extension变量中。
  3. String objectName = UUID.randomUUID().toString() + extension;:生成一个唯一的通用唯一识别码(UUID),并将其与文件后缀拼接起来,作为上传文件的名称存储在objectName变量中。这样做可以确保上传文件的名称具有唯一性,避免文件名冲突。
  4. String filePath = aliOssUtil.upload(file.getBytes(), objectName);:调用aliOssUtil工具类中的upload方法,将文件的字节数组(file.getBytes())和生成的上传文件名(objectName)作为参数传入,该方法返回文件上传后的请求路径,并存储在filePath变量中。这一步实现了将文件上传到指定的存储位置(可能是阿里云对象存储 OSS,由aliOssUtil工具类决定)。

新增菜品

service层

 @Override  @Transactionalpublic void saveWithFlavor(DishDTO dishDTO) {  Dish dish = new Dish();  BeanUtils.copyProperties(dishDTO, dish);  dishMapper.insert(dish);  Long dishId = dish.getId();  List<DishFlavor> flavors = dishDTO.getFlavors();  if (flavors != null && flavors.size() > 0) {  /**  * 如果直接调用 flavors.size() 而不先检查 flavors 是否为 null,  * 当 flavors 是 null 时会抛出 NullPointerException(空指针异常),  * 导致程序崩溃。  */  
//            for (DishFlavor flavor : flavors) {  
//                flavor.setDishId(dishId);  
//            }  flavors.forEach(dishFlavor -> {  dishFlavor.setDishId(dishId);  });  dishFlavorMapper.insertBatch(flavors);  }  }

Mapper

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.sky.mapper.DishMapper"><insert id="insert" useGeneratedKeys="true" keyProperty="id">insert into dish (name, category_id, price, image, description, create_time, update_time, create_user,update_user, status)values (#{name}, #{categoryId}, #{price}, #{image}, #{description}, #{createTime}, #{updateTime}, #{createUser}, #{updateUser}, #{status})</insert>
</mapper>

useGeneratedKeys=“true” 表示使用数据库自动生成的主键值。当插入数据时,如果数据库支持自动生成主键(如 MySQL 的自增主键),设置此属性为 true 后,MyBatis 会在插入操作执行完毕后,获取数据库生成的主键值。
keyProperty=“id” 表示将获取到的自动生成的主键值,赋值给 Java 对象中的 id 属性。也就是说,假设在 Java 代码中有一个 JavaBean 对象,其中包含 id 这个属性,插入操作完成后,数据库生成的主键值会自动填充到该 JavaBean 对象的 id 属性中。

菜品分页查询

service层

	/*** 菜品分页查询** @param dishPageQueryDTO* @return*/public PageResult pageQuery(DishPageQueryDTO dishPageQueryDTO) {PageHelper.startPage(dishPageQueryDTO.getPage(), dishPageQueryDTO.getPageSize());Page<DishVO> page = dishMapper.pageQuery(dishPageQueryDTO);//后绪步骤实现return new PageResult(page.getTotal(), page.getResult());}

page.getResult()实际上返回的是List集合,DishVO是单个菜品数据的 “数据模板”,而page.getResult()是当前页所有菜品数据的 “容器”(列表),两者共同完成了 “分页查询结果的结构化封装”。

删除菜品

Service

    @Autowiredprivate SetmealDishMapper setmealDishMapper;/*** 菜品批量删除** @param ids*/@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.size() > 0) {//当前菜品被套餐关联了,不能删除throw new DeletionNotAllowedException(MessageConstant.DISH_BE_RELATED_BY_SETMEAL);}//删除菜品表中的菜品数据for (Long id : ids) {dishMapper.deleteById(id);//后绪步骤实现//删除菜品关联的口味数据dishFlavorMapper.deleteByDishId(id);//后绪步骤实现}}

逻辑:查询所有菜品,是否存在启售?没有启售的再去查询这些菜品有没有被套餐关联,没有才能逐个删除

Mapper

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.sky.mapper.SetmealDishMapper"><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>
</mapper>

resultType="java.lang.Long" 定义了单条记录的映射类型,而 MyBatis 会自动将多条记录的Long对象封装为List<Long>,因此接口方法返回List<Long>是合理且 MyBatis 支持的

修改菜品

service

	/*** 根据id修改菜品基本信息和对应的口味信息** @param dishDTO*/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.size() > 0) {flavors.forEach(dishFlavor -> {dishFlavor.setDishId(dishDTO.getId());});//向口味表插入n条数据dishFlavorMapper.insertBatch(flavors);}}

先删除原有口味
再插入口味数据

总结

本节主要涉及公共字段自动填充、文件上传以及联表增删改查功能

1.AOP切面编程是什么?
在Spring AOP中可视作面向方法编程

2.@AutoFill的作用是什么?
自定义注解,用于标记需要填充公共字段的Mapper方法,通过在方法上标注并制定操作类型,AOP 切面会拦截并自动完成字段赋值,避免重复代码的书写

3.AOP切面AutoFillAspect如何识别需要自动填充的方法
通过@Pointcut定义切入点表达式:execution(* com.sky.mapper.*.*(..)) && @annotation(AutoFill),表示拦截com.sky.mapper包下所有被@AutoFill注解标记的方法,从而触发公共字段填充逻辑。

4.useGeneratedKeys="true"的作用
用于获取数据库自动生成的主键,设置后,MyBatis会在插入操作完成后将生成的主键赋值给keyProperty制定的对象方便后续使用

5.OSS 配置类如何与 application.yml 中的参数绑定?
通过@ConfigurationProperties(prefix = "sky.alioss")注解,Spring 会自动将配置文件中以sky.alioss为前缀的参数(如endpointaccessKeyId)绑定到配置类的对应字段上。

6.在pageQuery分页查询方法中,Page和page.getResult()是什么含义?
page.getResult()返回的实际上是List集合,DishVo是单个菜品数据,而page.getResult()会返回当前页所有菜品数据以容器(list)的形式实现

http://www.lryc.cn/news/573976.html

相关文章:

  • Unity2D 街机风太空射击游戏 学习记录 #12QFramework引入
  • 链接脚本基础语法
  • 国产12537穿甲弹侵彻仿真(显式动力学)
  • 抽象工厂设计模式
  • webpack+vite前端构建工具 - 9 webpack技巧性配置
  • Python商务数据分析——Python 入门基础知识学习笔记
  • Python打卡训练营Day56
  • 今日推荐:data-engineer-handbook
  • ICML 2025 | 时空数据(Spatial-Temporal)论文总结
  • 【RocketMQ 生产者和消费者】- 消费者的订阅关系一致性
  • Unity3D仿星露谷物语开发69之动作声音
  • 统计用户本月的连续登录天数
  • 系列一、windows中安装RabbitMQ
  • [论文阅读] 软件工程 + 教学 | 软件工程项目管理课程改革:从传统教学到以学生为中心的混合式学习实践
  • Linux——6.检测磁盘空间、处理数据文件
  • 爬虫入门练习(文字数据的爬取)
  • JavaScript 的 “==” 存在的坑
  • 跨域视角下强化学习重塑大模型推理:GURU框架与多领域推理新突破
  • TypeScript类型定义:Interface与Type的全面对比与使用场景
  • 线程池异步处理
  • 分布式ID生成方式及优缺点详解
  • 【Datawhale组队学习202506】YOLO-Master task03 IOU总结
  • uni-app项目实战笔记23--解决首次加载额外图片带来的网络消耗问题
  • 人工智能、机器人最容易取哪些体力劳动和脑力劳动
  • day03-微服务01
  • 《Nature Commun》(中科院1区, IF17.694): CITE-seq+空间转录组解析SSc免疫异质性
  • MySQL学习(1)——基础库操作
  • 【C++开发】CMake构建工具
  • 系统思考:救火先放火
  • (线性代数最小二乘问题)Normal Equation(正规方程)