Day3

公共字段字段填充(AOP)

@Aspect

@Aspect@Component 是 Spring AOP 中用于定义和管理切面的核心注解,二者结合使用才能使切面类生效。

@Aspect:标记类为切面

@Aspect
public class AutoFillAspect { ... }
  • 标记当前类为切面类:告诉 Spring 该类包含 切入点(Pointcut)通知(Advice) 的定义。

  • 启用 AOP 相关功能:Spring 会识别该类中的 @Pointcut@Before@After 等注解,并解析其中的 AOP 逻辑。

  • @Aspect 本身不会将类注册为 Spring 容器的组件,必须配合 @Component 或其他组件注解使用。



@Component

@Component:将切面类纳入 Spring 管理**

@Component
public class AutoFillAspect { ... }
  • 将类注册为 Spring 容器的组件:Spring 会自动扫描并实例化该类,使其成为 IoC 容器中的 Bean。

  • 为 AOP 增强提供基础:只有当切面类是 Spring 管理的 Bean 时,Spring 才能对其进行 AOP 代理增强,使其能够拦截目标方法。

  • @Aspect 定义切面逻辑:负责声明 “拦截哪些方法”(切入点)和 “在何时做什么”(通知)。

  • @Component 注册组件:负责将切面类纳入 Spring 容器,使其能够被 Spring 的 AOP 机制识别和调用。



切入点

@Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill)")
public void autoFillPointCut(){}
  • execution(\* com.sky.mapper.\*.\*(..)):匹配 com.sky.mapper 包下的所有方法。

  • @annotation(com.sky.annotation.AutoFill):匹配带有 @AutoFill 注解的方法。

  • 组合条件:只有同时满足上述两个条件的方法才会被拦截。

    通过这个组合可以与AutoFill注解进行关联



前置通知,在通知中进行公共字段的赋值

@Before("autoFillPointCut()")
public void autoFill(JoinPoint joinPoint){}

在 Spring AOP 中,JoinPoint 是一个核心接口,它提供了被拦截方法的上下文信息

JoinPoint 的核心作用

方法 作用
joinPoint.getSignature() 获取方法签名(如方法名、参数类型)。
joinPoint.getArgs() 获取方法的参数值(如实体对象 User user)。
joinPoint.getTarget() 获取目标对象(如 UserServiceImpl 实例)。
joinPoint.getThis() 获取代理对象(通常用于获取增强后的对象)。


获取到当前被拦截的方法上的数据库操作类型

MethodSignature signature = (MethodSignature) joinPoint.getSignature(); //方法签名对象
AutoFill autoFill = signature.getMethod().getAnnotation(AutoFill.class); //获得方法上的注解对象
OperationType operationType = autoFill.value(); //获得数据库操作类型

通过反射机制从被拦截的方法中获取 @AutoFill 注解及其属性值,是实现自动填充逻辑的关键步骤



取到当前被拦截的方法的参数--实体对象

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个公共字段赋值
    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){
        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){
        e.printStackTrace();
    }
}

在 Java 中,反射机制允许程序在运行时动态获取类的信息(如字段、方法)并调用它们。在 AOP 切面中,我们通过反射实现自动填充字段的核心步骤包括:获取方法参数动态调用 setter 方法注入值



反射的核心 API

类 / 方法 作用
Class.getDeclaredMethod(String name, Class<?>... parameterTypes) 获取类的指定方法(包括私有方法)。
Method.invoke(Object obj, Object... args) 调用方法,传入对象实例和参数值。
Class.getDeclaredFields() 获取类的所有字段(包括私有字段)。
Field.set(Object obj, Object value) 设置字段值。


新增菜品

获取原始文件名并处理后缀

String originalFilename = file.getOriginalFilename();
String extension = originalFilename.substring(originalFilename.lastIndexOf("."));
  • 作用:提取文件后缀(如 .jpg.png),用于生成新文件名,避免文件类型混乱。


生成唯一文件名

String objectName = UUID.randomUUID().toString() + extension;
  • UUID.randomUUID():生成全局唯一 ID(如 550e8400-e29b-41d4-a716-446655440000),避免文件名冲突。
  • 拼接后缀:确保新文件名仍保留原始文件类型(如 550e8400.jpg)。


上传至 OSS 并获取访问路径

String filePath = aliOssUtil.upload(file.getBytes(), objectName);
  • file.getBytes():将上传文件转换为字节数组。
  • upload() 方法:将字节数组上传至 OSS,返回文件的访问 URL(如 https://bucket-name.oss-cn-hangzhou.aliyuncs.com/550e8400.jpg)。

image



常见问题

这里我们要用到阿里云服务器(不能直接用老师的,否则在前后端联调时会加载不出来图片)

首先去阿里云官网创建一个账号,学生可以免费申请3个月的对象存储OSS,然后创建一个Bucket

  • endpoint:与地域有关,如果是北京就是$oss-cn-beijing.aliyuncs.com$,如果是杭州就是$oss-cn-hangzhou.aliyuncs.com$
  • access-key-id与access-key-secret:需要自己申请AccessKey,在右上角头像那,申请后记得保存,后面不可以查看了
  • bucket-name:就是自己的bucket名字
@Service
@Slf4j
public class DishServiceImpl implements DishService {}


@Service

@Service 是一个重要的注解,用于标记一个类为服务层组件。它是 @Component 的一个特定形式,主要用于标识业务逻辑层的类,使 Spring 能够自动扫描并将其注册为 Bean。



获取主键值

<insert id="insert" useGeneratedKeys="true" keyProperty="id">

在 MyBatis 的 XML 映射文件中,<insert>标签的useGeneratedKeys="true"keyProperty="id"是用于获取数据库自动生成的主键值的关键配置,尤其适用于自增主键(如 MySQL 的AUTO_INCREMENT

useGeneratedKeys="true"

  • 作用:告诉 MyBatis,当前插入操作的主键值由数据库自动生成(如自增主键)。
  • 原理:MyBatis 会通过 JDBC 的getGeneratedKeys()方法从数据库获取自动生成的主键值。

keyProperty="id"

  • 作用:指定将数据库生成的主键值赋值给 Java 实体类的哪个属性。
  • 示例keyProperty="id" 表示将生成的主键值赋给实体类的 id 属性(需实体类有对应的 setter 方法)。
flavors.forEach(dishFlavor -> {
    dishFlavor.setDishId(dishId);
});

通过Lambda表达式为集合中的每个口味对象关联菜品 ID



菜品的分页查询

菜品的分页查询与员工的分页查询基本相同,唯一不同的就是SQL语句的书写(多表查询

select d.*, c.name as categoryName from dish d left outer join category c on d.category_id = c.id

通过左外连接进行多表查询LEFT OUTER JOIN连接dish(菜品)表和category(分类)表,确保即使菜品没有分类也会被查询出来

<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>

生成动态查询

每个<if>标签通过test属性判断参数是否有效,若有效则拼接对应的 SQL 片段。



删除菜品

@DeleteMapping
@ApiOperation("菜品批量删除")
public Result delete(@RequestParam List<Long> ids) { //这里也可以用String
    log.info("菜品批量删除:{}", ids);
    dishService.deleteBatch(ids);
    return Result.success();
}

@RequestParam

在 Spring MVC 中,@RequestParam 是一个用于从 HTTP 请求的查询参数(Query Parameters) 中获取参数值的注解。在这个代码中,它的作用是将前端传递的菜品 ID 列表参数绑定到方法ids 参数上。



删除菜品

//方法一:通过for循环---会产生多条SQL语句
for(Long id : ids) {
    //删除菜品表中的菜品数据
    dishMapper.deleteById(id);
    //删除菜品关联的口味数据
    dishFlavorMapper.deleteByDishId(id);
}

//方法二:通过集合删除---只会生成一条SQL语句
//sql :delete from dish where id in(?,?,?)
//根据菜品id集合批量删除菜品数据
dishMapper.deleteByIds(ids);

//根据菜品id集合批量删除关联的口味数据
//sql :delete from dish_flavor where id in(?,?,?)
dishFlavorMapper.deleteByDishIds(ids);
<delete id="deleteByIds">
    delete from dish where id in
    <foreach collection="ids" open="(" close=")" separator="," item="id">
        #{id}
    </foreach>
</delete>

<foreach>是 MyBatis 动态 SQL 的核心标签,用于遍历集合或数组,动态拼接 SQL 片段。其属性含义如下:

属性 作用
collection 指定要遍历的集合参数名。此处collection="ids"对应 Mapper 接口方法的参数List<Long> ids
item 遍历集合时,当前元素的别名。此处item="id"表示每次遍历的元素用id指代(如 1、2、3)。
open 遍历开始时拼接的字符串。此处open="("表示生成(作为IN条件的开头。
close 遍历结束时拼接的字符串。此处close=")"表示生成)作为IN条件的结尾。
separator 元素之间的分隔符。此处separator=","表示用逗号分隔多个 ID(如1, 2, 3)。


修改菜品

@PutMapping
@ApiOperation("修改菜品")
public Result update(@RequestBody DishDTO dishDTO) {}

@PutMapping

@PutMapping 是一个用于映射 HTTP PUT 请求 的注解,通常用于 更新资源 的操作。它是 @RequestMapping(method = RequestMethod.PUT) 的简化形式,在你提供的菜品管理代码中,它用于处理 “修改菜品” 的请求。

posted @ 2025-07-18 20:05  li_hc  阅读(18)  评论(0)    收藏  举报