苍穹外卖项目笔记day03

发布于:2025-09-06 ⋅ 阅读:(19) ⋅ 点赞:(0)

公共字段的自动填充

在业务接口实现中,数据库中不断重复出现的字段被我们叫做公共字段,例如菜品种类表和员工表里面都有修改人/时间 以及 创建人/时间 四个公共的字段,而且代码也基本一致,都是给对象赋值然后传递到mapper层进行数据库操作,而大量相似的代码会造成代码的冗余.因此我们通过AOP切面编程,来实现其的自动填充

实现步骤:

1). 自定义注解 AutoFill,用于标识需要进行公共字段自动填充的方法

2). 自定义切面类 AutoFillAspect,统一拦截加入了 AutoFill 注解的方法,通过反射为公共字段赋值

package com.sky.aspect;

import com.sky.annotation.AutoFill;
import com.sky.constant.AutoFillConstant;
import com.sky.context.BaseContext;
import com.sky.enumeration.OperationType;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.time.LocalDateTime;

/**
 * 自定义切面,实现公共字段自动填充处理逻辑
 */
@Aspect
@Slf4j
@Component
public class AutoFillAspect {
    /**
     * 切入点
     */
    @Pointcut("execution(* com.sky.mapper.*.*(..))&&@annotation(com.sky.annotation.AutoFill) ")
    public void autoFillPointcut() {
    }

    @Before("autoFillPointcut()")
    public void autoFill(JoinPoint joinPoint) {
        log.info("开始进行公共字段自动填充...");

        //获取当前被拦截到方法的数据库操作类型-->如是insert还是update insert四个公共字段,update则两个
        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];
        //准备赋值数据 时间和id
        LocalDateTime now = LocalDateTime.now();
        Long currentId = BaseContext.getCurrentId();

        //根据当前不同的操作类型,为对应的属性通过反射来赋值
        if (operationType == OperationType.INSERT) {//为四个字段赋值
            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) {
            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();
            }

        }
    }
}

3). 在 Mapper 的方法上加入 AutoFill 注解 -->拦截我们这里采用的方法是自定义注解,自定义一个方法注解,被该注解标记的方法会被拦截器拦截

在上面的代码中,我们主要是对创建时间,修改时间,创建人,修改人这四个公共字段进行自动填充,实现原理:对使用了@AutoFill注解的mapper方法进行拦截,然后根据使用@Aspect注解的类中通知对这四个字段进行赋值,然后再进行被拦截的方法(由@before我们可知这是一个前置通知)

OSS文件上传接口实现文件上传服务,需要有存储的支持,那么我们的解决方案将以下几种:

  1. 直接将图片保存到服务的硬盘(springmvc中的文件上传)
    1. 优点:开发便捷,成本低
    2. 缺点:扩容困难
  2. 使用分布式文件系统进行存储
    1. 优点:容易实现扩容
    2. 缺点:开发复杂度稍大(有成熟的产品可以使用,比如:FastDFS,MinIO)
  3. 使用第三方的存储服务(例如OSS)
    1. 优点:开发简单,拥有强大功能,免维护
    2. 缺点:付费

显示思路:本项目利用阿里云OSS服务器,把图片上传到阿里云的OSS服务器后,利用后端接收OSS服务器返回的图片URL,然后再返回给前端,前端调用URL进行图片回显

配置类的设置:别把配置类写死,因为我们的项目会经历开发,测试,维护三个阶段,而这三个阶段可能使用的数据库等等配置类不一致,所以我们选择在另外书写一个配置类如:dev,让目标配置类引用开发配置类

接口实现的部分代码

  • 生成OSS工具类对象

@Data
@AllArgsConstructor
@Slf4j
public class AliOssUtil {

    private String endpoint;
    private String accessKeyId;
    private String accessKeySecret;
    private String bucketName;

    /**
     * 文件上传
     *
     * @param bytes
     * @param objectName
     * @return
     */
    public String upload(byte[] bytes, String objectName) {

        // 创建OSSClient实例。
        OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);

        try {
            // 创建PutObject请求。
            ossClient.putObject(bucketName, objectName, new ByteArrayInputStream(bytes));
        } catch (OSSException oe) {
            System.out.println("Caught an OSSException, which means your request made it to OSS, "
                               + "but was rejected with an error response for some reason.");
            System.out.println("Error Message:" + oe.getErrorMessage());
            System.out.println("Error Code:" + oe.getErrorCode());
            System.out.println("Request ID:" + oe.getRequestId());
            System.out.println("Host ID:" + oe.getHostId());
        } catch (ClientException ce) {
            System.out.println("Caught an ClientException, which means the client encountered "
                               + "a serious internal problem while trying to communicate with OSS, "
                               + "such as not being able to access the network.");
            System.out.println("Error Message:" + ce.getMessage());
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }

        //文件访问路径规则 https://BucketName.Endpoint/ObjectName
        StringBuilder stringBuilder = new StringBuilder("https://");
        stringBuilder
        .append(bucketName)
        .append(".")
        .append(endpoint)
        .append("/")
        .append(objectName);

        log.info("文件上传到:{}", stringBuilder.toString());

        return stringBuilder.toString();
    }
}
  • 通用接口
/**
 * 通用接口
 */
@RestController
@RequestMapping("/admin/common")
@Api(tags = "通用接口")
@Slf4j
public class CommonController {

    @Autowired
    private AliOssUtil aliOssUtil;

    /**
     * 文件上传
     * @param file
     * @return
     */
    @PostMapping("/upload")
    @ApiOperation("文件上传")
    public Result<String> upload(MultipartFile file){
        log.info("文件上传:{}",file);

        try {
            //原始文件名
            String originalFilename = file.getOriginalFilename();
            //截取原始文件名的后缀   dfdfdf.png
            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);
    }
}
其中我们对两个AliOssUtil进行说明

它们的关系是:一个作为配置类(工厂),负责生产另一个(工具类实例)。

com.sky.config.OssConfiguration 中的 AliOssUtil

角色:这是一个 配置类 中的一个 Bean定义方法。
目的:它的作用不是定义一个类,而是告诉Spring如何创建一个 AliOssUtil 工具类的实例(Bean)。

执行时机:当Spring容器启动时,它会读取这个配置。因为方法上加了 @Bean 注解,Spring会调用这个 aliOssUtil() 方法。

工作流程:

方法接收一个已经配置好的 AliOssProperties 参数(这个参数的值通常来自 application.yml 配置文件)。

方法打印日志:“开始创建阿里云文件上传工具类对象…”。

方法 new 了一个 com.sky.utils.AliOssUtil 类的对象,并将 AliOssProperties 中的配置值(endpoint, accessKeyId等)作为构造参数传入。

最终,它将这个新创建的工具类对象返回给Spring容器管理。之后,在项目的任何地方,你都可以通过 @Autowired 注入这个 AliOssUtil Bean来使用文件上传功能。

@ConditionalOnMissingBean 注解的作用是:如果Spring容器中已经存在一个 AliOssUtil 类型的Bean了,则这个配置方法就不执行。这确保了只有一个工具类实例被创建。

com.sky.utils.AliOssUtil

角色:这是一个具体的工具类,包含了实际的业务逻辑。
目的:封装阿里云OSS SDK的调用细节,提供一个简单的 upload 方法供业务代码使用。

工作流程:

它本身是一个普通的Java类(使用了Lombok的 @Data 和 @AllArgsConstructor),定义了4个属性并通过构造方法初始化。

它提供了 upload(byte[] bytes, String objectName) 方法,该方法内部使用阿里云的 OSSClient 来执行文件上传操作。

它处理了上传过程中可能出现的异常。

它拼接并返回了上传成功的文件的访问URL


网站公告

今日签到

点亮在社区的每一天
去签到