延边朝鲜族自治州网站建设_网站建设公司_导航易用性_seo优化
2026/1/16 3:42:02 网站建设 项目流程

一、回顾与概述

Day2我们已经完成了员工模块与分类模块,相信大家已经对于最基本的CRUD业务有了一定的掌握了,那么今天我们将会在菜品模块上提升难度,利用AOP与反射设置公共字段填充增强、加入OSS上传文件、联表查询逻辑、参数为集合时如何处理以及批量删除时的动态SQL语句等等。

二、公共字段填充

1.需求分析

在我们各个模块的新增操作和修改操作的Service层中,由于DTO模型中无法传输创建时间、更新时间、创建人、更新人这些字段,难免就会涉及到补充属性的相关操作,但几乎很多表中都有这四个公共属性,补充属性的代码又相对固定,所以我们需要把这些公共字段的填充抽象出来,那么就要用到AOP技术面向切面编程,选择性针对那些需要增强功能的方法。

2.切点和切点表达式

(1)确定切点

我们知道切点就是需要实现该增强功能的方法,我们的增强功能本质上是补充属性,需要调用相关的set方法,我们是Controller层、Service层、Mapper层,那么需要在哪里进行增强呢。那这就取决于我们在哪里可以取到这个set方法。我们用来取set方法的方式是通过反射加上切面类的joinPoint,反射可以通过getDeclaredMethod方法得到这个对象的方法,而joinPoint则负责拿到这个对象,那我们的目标就是获取到具备setUpdateTime这些方法的类的对象,而这些类基本上都是entity实体类,即它们的字段往往就跟表里面一一对应,而我们只能在Mapper层设置切点,然后通过joinPoint拿到entity参数对象,再用反射机制获取到方法进行动态代理。

以新增员工方法举例:

p1.Controller层的方法参数是EmployeeDTO,里面压根没有这些公共字段

p2.Service层同上

p3.Mapper层的方法参数就是Employee这种entity实体类了,里面有公共字段的set方法,确定为切点位置

(2)确定切点表达式

在确定切点时我们已经明确了是在各个模块的Mapper层的新增和修改方法上切入。我们知道切点表达式有两种常见方法,一种使用execution进行匹配,但是不同模块的新增和修改方法往往所在接口、方法名和参数各不相同,所以如果用execution需要写多个匹配语句用 || 逻辑连接。另一种常用方法就是使用特定注解,这一种方法更加灵活,可以在需要增强的切点上自习添加注解。

3.编写特定注解

在自定义特定注解上需要加上两个元注解@Target和@Retention@Target是用来表明注解的适用位置、@Retention用来表明注解的生命周期,因为注解我们要放在Mapper层的新增修改方法上,所以就是ElementType.METHOD,生命周期就设置为常见的运行时RetentionPolicy.RUNTIME即可。然后还有一点需要注意那就是我们在修改和新增两种方法公共字段填充的逻辑是不一样的,前者不需要补充创建人和创建时间两个字段,后者四个四段都要填充,所以我们需要在增强前进行区别,那就可以用注解的属性进行区分。这里的value属性作用就是用来区分修改还是新增操作,OperationType就有UPDATE和INSERT两种取值。这样我们就可以通过切面类的参数joinPoint取到注解的value值,进而判断采取哪种增强逻辑。

4.编写切面类

1.注意添加注解@Component交给IOC容器管理,@Aspect表明切面类

2.必须是@Before,因为Mapper层的修改、新增方法执行时已经需要用到公共属性字段了,所以必须在方法执行前就进行增强

3.获取目标方法上的注解,拿到属性值,判断是执行修改操作的增强逻辑还是新增操作的。具体要使用增强方法的参数JoinPoint对象,调用getSignature然后必须强转成MethodSignature(不强转就会得到Signature,它是调不了getMethod方法获得Method对象的),调用getMethod方法得到Method对象后再调用getAnnotation拿到注解。

4.拿到目标方法的参数对象,还是需要用到JoinPoint对象,调用getArgs方法,前提可以判断Args不为空增强代码健壮性。一般参数都只有一个所以调用args[0]即可。

5.判断注解中的属性值执行不同的增强逻辑。接下来就是反射逻辑,首先需要使用刚刚拿到的参数对象调用getClass拿到该对象的类,然后调用getDeclaredMethod方法,里面需要传方法名(自定义常量)和返回值.class,最后就是把拿到的方法调用invoke动态代理,目标对象就填之前拿到的参数对象,值照常写就行。

6.在需要增强的方法上加上@AutoFill注解,标注上对应value属性即可。

三、菜品模块

1.产品模型与接口概览

根据分类id查询菜品这个接口在昨天已经完成了,从相关接口概览字面上看只有一个批量删除好像是不同的,其他都跟昨天的模块没什么区别。但是仔细看产品模型还是能发现挺多细节有所不同。分页查询这里的需求可以通过菜品名称、菜品分类、菜品状态模糊查询。新增操作里面还有上传图片需求就需要使用阿里云提供的OSS上传云服务。批量删除的时候传入的是id的集合,参数的注解也有不同。

2.新增菜品

(1)接口文档

新增菜品的请求参数是json,需要封装到DishDTO中,然后还要image,这个就涉及到文件上传了。文件上传就需要另外编写一个Controller类,实现upload方法,请求参数就是file,返回文件上传路径。

(2)文件上传功能实现

1.登录阿里云创建Bucket,记得取消阻止公共访问,然后把读写权限改为公共读写。同时通过AccessKey获取到AccessKeyId和AccessKeySecret。在IDEA导入阿里云OSS依赖

2.在application-dev.yml文件里面配置好自己私密的endpoint,access-key-id,access-key-secret,bucket-name四个值。然后在application.yml文件里面利用${}传输私密配置的值。

3.编写AliOssUtil工具类实现upload方法

4.声明阿里Oss配置类,用来生成AliOssUtil对象,配合@Bean将它注册到IOC容器里面。关于SpringBoot引入第三方jar的Bean的三种方式可以移步博客:Click Here

5.根据接口文档编写专门的CommonController实现文件上传功能(不存在Service层和Mapper层)。刚刚配合@Bean已经注册到的aliOssUtil对象在这里通过自动注入获取到。请求参数是MultiPartFile,调用它的getOriginalFilename可以获取原始文件名,我们需要通过substring方法取到它的后缀类似于.jpg,以此确定文件类型,但不能直接用原始文件名直接作为上传文件名,原因是如果原始文件名重复了,那么后上传的就会把先上传的文件覆盖。因此前缀文件名objectName必须唯一。此时就可以使用UUID.randomUUID方法来拼接处唯一的上传文件名。随后调用aliOssUtil的upload方法即可,第一个参数是源文件的字节数据file.getBytes()即可,第二个参数就是上传文件名设置成什么。返回值url直接作为data返回即可。

(3)Controller层

(4)Service层

由于DishDTO传输的数据不全所以需要新建一个DIsh然后利用BeanUtils浅拷贝到DIsh上,补充属性的操作我们在此之前已经放到AOP里面了。这里就可以先调用dishMapper把dish的数据插入到dish表了,但是新增菜品操作还涉及到了口味,前端传来的DTO里面包含了Flavors集合,需要把这个集合里面的所有flavor批量插入到dish_flavor表里面,但需要注意的是因为是新增操作,我们压根不知道菜品的id是什么,所以前端传来的DishFlavor中也没有dish_id这个字段,因此直接把数据插入到dish_flavor会因为找不到这个口味对应的菜品而无法插入报错。所以我们需要事先取到这个id封装到新建的这个dish里面然后利用lambda表达式让每个口味都setDishId补充dish_id字段后再插入。另外凡是涉及到了多表的增删改操作都要开启事务,由于该Service层的方法涉及到两个表的插入操作所以要加@Transactional注解开启事务,关于如何取到这个id放在Mapper层讲解。

(5)Mapper层

大家可以发现除了@Insert和我们编写的公共字段填充的特定注解@AutoFill,还多了一个注解@Options。在上一层我们提到了在新增操作的时候由于我们压根不知道菜品id是多少,在我们写SQL语句的时候id字段也是用null借助数据库内部的主键自增帮我们填入。但是我们在Service业务逻辑层当中进行新增对应菜品的各种味道的时候,插入语句又需要用到菜品的id。所以这就是@Options的作用,通过参数useGeneratedKeys = true, keyProperty = "id"来取到插入的这一条数据的(主键)菜品id,然后封装回Service层new出来的dish对象。这样dish里面的id字段就有值了。当然如果你要把SQL语句写到xml文件里面也可以进行这样的配置,需要注意此时@Options注解和@Insert就都不用写了,直接在xml文件里面的<insert>内部配置上Options的这两个属性即可。

dishFlavorMapper里面批量插入语句因为不确定dishFlavorList集合里面有多少条flavor,所以要插入的数据是不确定的,因此需要编写动态SQL语句。这里就是用到了<foreach>这个标签。collection参数里面写的是集合的变量名,item可以随便写主要代指集合中每个对象,后面#{}语句中就需要item.类的属性名进行值的替换。关于#{}和${}两种占位符的区别可以移步:Click Here

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询