springboot 自定义注解实现操作日志记录

springboot 自定义注解实现操作日志记录

1.添加依赖

		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        
        <!--hutool工具类-->
		<dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.6.3</version>
        </dependency>

2.自定义注解

import com.micgoo.lezystockmanage.core.enums.OperateBehaviorEnum;
import com.micgoo.lezystockmanage.core.enums.OperateModuleEnum;
import com.micgoo.lezystockmanage.core.enums.ServiceEnum;

import java.lang.annotation.*;

/***
 * 操作日志注解
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface OperateLog {

    /**
     * 业务名称,例如: "修改商品"
     */
    String title() default "";

    /***
     * 操作类型
     */
    OperateBehaviorEnum behavior() default OperateBehaviorEnum.OTHER;

    /***
     * 模块名称
     */

    OperateModuleEnum module() default OperateModuleEnum.OTHER;

    /***
     * service 名称
     */

    ServiceEnum serviceName() default ServiceEnum.OTHER;
}

3.日志类、日志类builder、操作类型枚举、模块类型枚举类、service名枚举类

3.1 日志类

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

@Data
@TableName(value = "operate")
public class OperateEntity {

    @TableId(value = "pk_Id",type = IdType.AUTO)
    private long pk_Id;
    /**
     * 操作人userName
     */
    private String userName;
    /**
     * 日志标题
     */
    private String title;
    /***
     * 日志模块
     */
    private String module;
    /**
     * 操作类型
     */
    private String behavior;
    /**
     * 原始数据json
     */
    private String oldJSON;
    /**
     * 新数据json
     */
    private String newJSON;
    /**
     * 操作日期
     */
    private String createTime;

}

3.2 日志类builder (使用builder设计模式是为了美化 new 日志对象的操作 当然也可以不用builder 直接 new 对象 set属性)

import cn.hutool.core.bean.BeanUtil;
import com.micgoo.lezystockmanage.entity.db.mysql.OperateEntity;

public class OperateBuilder {

    /**
     * 操作人userName
     */
    private final String userName;
    /**
     * 日志标题
     */
    private final String title;
    /***
     * 日志模块
     */
    private final String module;
    /**
     * 操作类型
     */
    private final String behavior;
    /**
     * 原始数据json
     */
    private final String oldJSON;
    /**
     * 新数据json
     */
    private final String newJSON;


    public OperateBuilder(Builder builder) {
        this.userName = builder.userName;
        this.title = builder.title;
        this.module = builder.module;
        this.behavior = builder.behavior;
        this.oldJSON = builder.oldJSON;
        this.newJSON = builder.newJSON;
    }


    public String getUserName() {
        return userName;
    }

    public String getTitle() {
        return title;
    }

    public String getModule() {
        return module;
    }

    public String getBehavior() {
        return behavior;
    }

    public String getOldJSON() {
        return oldJSON;
    }

    public String getNewJSON() {
        return newJSON;
    }


    /**
     *静态内部类 Builder
     */

    public static class Builder{

        private long pk_Id;

        private String userName;

        private String title;

        private String module;

        private String behavior;

        private String oldJSON;

        private String newJSON;

        private String createTime;

        //构造方法
        public Builder() {
            userName = userName;
            title = title;
            module = module;
            behavior = behavior;
            oldJSON = oldJSON;
            newJSON = newJSON;

        }

        public Builder setUserName(String userName) {
            this.userName = userName;
            return this;
        }

        public Builder setTitle(String title) {
            this.title = title;
            return this;
        }

        public Builder setModule(String module) {
            this.module = module;
            return this;
        }

        public Builder setBehavior(String behavior) {
            this.behavior = behavior;
            return this;
        }

        public Builder setOldJSON(String oldJSON) {
            this.oldJSON = oldJSON;
            return this;
        }

        public Builder setNewJSON(String newJSON) {
            this.newJSON = newJSON;
            return this;
        }


        //构建一个实体
        public OperateEntity build() {
            OperateBuilder operateBuilder = new OperateBuilder(this);
            OperateEntity operateEntity = BeanUtil.toBean(operateBuilder, OperateEntity.class);
            return operateEntity;
        }
    }

}

3.3 操作类型枚举

import com.micgoo.lezystockmanage.core.enums.abs.MicgooEnumInterface;

/***
 * enum
 * 操作日志行为类枚举
 */
public enum OperateBehaviorEnum {

    ADD("新增"),
    DELETE("删除"),
    UPDATE("更新"),
    EXPORT("导出"),
    OTHER("其他")
    ;

    private String behavior;

    OperateBehaviorEnum(String behavior) {
        this.behavior = behavior;
    }

    public String getBehavior() {
        return behavior;
    }

}

3.4 模块类型枚举类

import com.micgoo.lezystockmanage.core.enums.abs.MicgooEnumInterface;

/***
 * enum
 * 模块枚举类
 */
public enum OperateModuleEnum implements MicgooEnumInterface {
    OTHER("other","未知"),
    POST("post","岗位"),
    SYSUSER("sysUser","系统用户")
    ;
    /**
     * code值
     */
    private String resultCode;
    /**
     * 描述
     */
    private String resultMsg;

    OperateModuleEnum(String resultCode, String resultMsg) {
        this.resultCode = resultCode;
        this.resultMsg = resultMsg;
    }


    @Override
    public String getResultCode() {
        return resultCode;
    }

    @Override
    public String getResultMsg() {
        return resultMsg;
    }
}

3.5 service名枚举类

/***
 * enum
 * service名称枚举类
 */
public enum ServiceEnum{

    OTHER(""),
    USERPOSTSERVICE("UserPostService"),
    POSTSSERVICE("PostsService")
    ;

    private String serviceName;

    ServiceEnum(String serviceName) {
        this.serviceName = serviceName;
    }

    ServiceEnum() {
    }

    public String getServiceName() {
        return serviceName;
    }

    public void setServiceName(String serviceName) {
        this.serviceName = serviceName;
    }
}

4.aop切面

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.hutool.json.JSONUtil;
import com.micgoo.lezystockmanage.core.annotion.OperateLog;
import com.micgoo.lezystockmanage.core.enums.MicgooEnum;
import com.micgoo.lezystockmanage.core.enums.OperateBehaviorEnum;
import com.micgoo.lezystockmanage.core.enums.OperateModuleEnum;
import com.micgoo.lezystockmanage.core.enums.ServiceEnum;
import com.micgoo.lezystockmanage.core.exception.MicgooException;
import com.micgoo.lezystockmanage.entity.OperateBuilder;
import com.micgoo.lezystockmanage.entity.db.mysql.OperateEntity;
import com.micgoo.lezystockmanage.service.OperateService;
import lombok.extern.log4j.Log4j2;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/***
 * 操作日志切面
 */
@Aspect
@Log4j2
@Component
public class OperateLogAop {

    @Autowired
    private OperateService operateService;
    /***
     * 切点
     */
    @Pointcut("@annotation(com.micgoo.lezystockmanage.core.annotion.OperateLog)")
    public void operateLogPointCut(){

    }

    /***
     *  环绕通知
     */
    @Around( value = "operateLogPointCut()")
    public void recordOperateLog(ProceedingJoinPoint proceedingJoinPoint){
        ServletRequestAttributes  requestAttributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();//从RequestContextHolder获得requestAttributes
        HttpServletRequest request = requestAttributes.getRequest();
        HttpSession session = request.getSession();
        Object userName = session.getAttribute("userName");//操作人userName
        if(Objects.isNull(userName)){
            log.error("userName为空! 请求URL为 {} ",request.getServletPath());
            throw new MicgooException(MicgooEnum.FAIL.getResultCode(),"操作日志userName为空!");
        }
        MethodSignature methodSignature = (MethodSignature)proceedingJoinPoint.getSignature();
        Method method = methodSignature.getMethod();
        OperateLog annotation = method.getAnnotation(OperateLog.class);
        String title = annotation.title();//业务名称
        OperateBehaviorEnum behavior = annotation.behavior();//操作类型
        OperateModuleEnum module = annotation.module();//操作模块
        ServiceEnum serviceName = annotation.serviceName();//serviceName
        if(StrUtil.isBlank(title)){
            log.debug("业务名称为空! 请求URL为 {} ",request.getServletPath());
            throw new MicgooException(MicgooEnum.FAIL.getResultCode(),"操作日志业务名称为空,请联系管理员修改!");
        }
        if(behavior==OperateBehaviorEnum.OTHER){
            log.debug("操作类型为空! 请求URL为 {}",request.getServletPath());
            throw new MicgooException(MicgooEnum.FAIL.getResultCode(),"操作日志操作类型为空,请联系管理员修改!");
        }
        if(module==OperateModuleEnum.OTHER){
            log.debug("模块类型为空! 请求URL为 {}",request.getServletPath());
            throw new MicgooException(MicgooEnum.FAIL.getResultCode(),"操作日志模块类型为空,请联系管理员修改!");
        }
        if(serviceName==ServiceEnum.OTHER){
            log.debug("service为空! 请求URL为 {}",request.getServletPath());
            throw new MicgooException(MicgooEnum.FAIL.getResultCode(),"操作日志service为空,请联系管理员修改!");
        }
        //获得参数名和参数
        Object[] args = proceedingJoinPoint.getArgs();
        String[] parameterNames = methodSignature.getParameterNames();
        //定义一个map存放参数
        Map<String, Object> argsMap = new HashMap<>();
        for (int i = 0; i < args.length; i++) {
            argsMap.put(parameterNames[i],args[i]);
        }
        //参数是否为空
        if(CollUtil.isEmpty(argsMap)){
            log.debug("请求参数为空!, 请求URL为:{}",request.getServletPath());
        }
        OperateEntity operateEntity = null;
        switch (behavior){
            case ADD_OR_UPDATE:

                if(Long.parseLong(argsMap.get("id").toString())==0){
                    operateEntity = new OperateBuilder.Builder()
                            .setUserName(String.valueOf(userName))
                            .setBehavior(OperateBehaviorEnum.ADD.getBehavior())
                            .setModule(module.getResultCode())
                            .setTitle(title)
                            .setOldJSON("[]")
                            .setNewJSON(JSONUtil.toJsonStr(argsMap))
                            .build();
                }else{
                    operateEntity = new OperateBuilder.Builder()
                            .setUserName(String.valueOf(userName))
                            .setBehavior(OperateBehaviorEnum.UPDATE.getBehavior())
                            .setModule(module.getResultCode())
                            .setTitle(title)
                            .setOldJSON(getOldJSON(serviceName.getServiceName(),Long.parseLong(argsMap.get("id").toString())))
                            .setNewJSON(JSONUtil.toJsonStr(argsMap))
                            .build();
                }

                break;
            case ADD:
                operateEntity = new OperateBuilder.Builder()
                        .setUserName(String.valueOf(userName))
                        .setBehavior(behavior.getBehavior())
                        .setModule(module.getResultCode())
                        .setTitle(title)
                        .setOldJSON("[]")
                        .setNewJSON(JSONUtil.toJsonStr(argsMap))
                        .build();
                break;
            case DELETE:
                operateEntity = new OperateBuilder.Builder()
                        .setUserName(String.valueOf(userName))
                        .setBehavior(behavior.getBehavior())
                        .setModule(module.getResultCode())
                        .setTitle(title)
                        .setOldJSON(getOldJSON( serviceName.getServiceName(), Long.parseLong(argsMap.get("id").toString())))
                        .setNewJSON("[]")
                        .build();
                break;
            case UPDATE:
                operateEntity = new OperateBuilder.Builder()
                        .setUserName(String.valueOf(userName))
                        .setBehavior(behavior.getBehavior())
                        .setModule(module.getResultCode())
                        .setTitle(title)
                        .setOldJSON(getOldJSON(serviceName.getServiceName(), Long.parseLong(argsMap.get("id").toString())))
                        .setNewJSON(JSONUtil.toJsonStr(argsMap))
                        .build();
                break;
            case EXPORT:
                //TODO
                break;
            default:
                throw new MicgooException(MicgooEnum.FAIL.getResultCode(),"操作日志未知操作类型!");
        }

        try {
            /*
             * 执行此方法前,为环绕通知的前置操作
             */
            proceedingJoinPoint.proceed();//执行被环绕的方法
            /*
             * 执行后为环绕通知的后置操作
             */
            if(ObjectUtil.isNotNull(operateEntity)){
                operateService.save(operateEntity);
            }
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            log.debug("操作日志环绕通知执行proceed方法失败! 请求URL为 "+request.getServletPath());
            throw new MicgooException(MicgooEnum.FAIL.getResultCode(),"操作日志环绕通知执行proceed方法失败!");
        }

    }

    //为了获取要修改数据的修改前数据
    private String getOldJSON(String servicelName, long id){
        Class<?> clazz = null;
        Field service = null;
        Method getById = null;
        Object obj = null;
        try {
            clazz = Class.forName("com.micgoo.lezystockmanage.service."+servicelName);
        } catch (ClassNotFoundException e) {
            throw new MicgooException(MicgooEnum.FAIL.getResultCode(),"获取class失败!");
        }
        try {
            getById = clazz.getMethod("getById", Serializable.class);
        } catch (NoSuchMethodException e) {
            throw new MicgooException(MicgooEnum.FAIL.getResultCode(),"获取getById方法失败!");
        }
        try {
            /*
             *  注意:
             *  这里之所以用 SpringUtil.getBean(clazz) 来获取对象 是因为反射的对象为接口是没有构造方法的,不能被实例化,所以采用从spring容器中获取
             *  通过id获取,值得注意的是这里是通过反射获取的,为了达到代码的通用性,这里每个被反射的service都应继承mybatis的ServiceImpl,接口都应继承IService
             */
            obj = getById.invoke(SpringUtil.getBean(clazz),id);
        } catch (Exception e) {
            e.printStackTrace();
            throw new MicgooException(MicgooEnum.FAIL.getResultCode(),"调用getById方法失败!");
        }
        if(Objects.isNull(obj)){
            throw new MicgooException(MicgooEnum.FAIL.getResultCode(),"调用getById方法获取数据为null!");
        }
        return JSONUtil.toJsonStr(obj);
    }


}

5.使用

    @PostMapping("/DeletePosts")
    @OperateLog(title = "删除岗位",behavior = OperateBehaviorEnum.DELETE,module = OperateModuleEnum.POSTS,serviceName = ServiceEnum.POSTSSERVICE)
    public Object DeletePosts(@RequestParam("id") Long id){

        if(postsService.InquirerPosts(id)){
            return new ResultMap("Same");
        }else{
            postsService.DelePosts(id);
            return new ResultMap(MicgooEnum.SUCCESS.getResultCode());
        }
    }

写在最后只得注意的一点是,同事在使用自定义注解的时候发现怎么注解都不生效,还以为是注解没有开启的原因,找了许久才发现,使用注解的方法是private的,这里切面类没有实现接口,故是用的cglib的动态代理,是生成一个子类作为代理对象,子类不能继承父类private的方法和属性。改为public就好了。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
SpringBoot中可以自定义注解实现特定的功能。自定义注解的步骤如下: 1. 使用`@interface`关键字来定义注解,可以在注解中设置属性。 2. 可以通过注解的属性来传递参数,比如设置注解中的属性值。 3. 可以通过判断某个类是否有特定注解来进行相应的操作。 在SpringBoot中,自定义注解可以用于实现日志记录、定时器等功能。通过使用注解,可以简化代码,并提高开发效率。同时,自定义注解也是Spring框架中广泛应用的一种方式,可以在SpringMVC框架中使用注解来配置各种功能。而在SpringBoot框架中,更是将注解的使用推向了极致,几乎将传统的XML配置都替换为了注解。因此,对于SpringBoot来说,自定义注解是非常重要的一部分。123 #### 引用[.reference_title] - *1* *3* [springboot定义注解(含源码)](https://blog.csdn.net/yb546822612/article/details/88116654)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}} ] [.reference_item] - *2* [SpringBoot-自定义注解](https://blog.csdn.net/weixin_44809337/article/details/124366325)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值