基于注解和aspect记录修改操作日志

白昼怎懂夜的黑 提交于 2020-01-10 09:53:18

工作中遇到一个需求,管理员修改用户个人信息后,要通过邮件通知给超级管理员本次修改的列以及修改前后对应的值。首先想到的方法是遍历每个字段,逐一比较,后面发现工作量巨大,直接放弃。后来经过思考,参考swagger的实现方式,自己造轮子实现了一套基于注解和切面的操作日志记录功能。

关键maven依赖:

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

展示效果

在这里插入图片描述

具体实现

创建自定义注解

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface OperateLogParam {

    /**
     *  名称
     * @return
     */
    String name() default "";

    /**
     *  连接字符
     * @return
     */
    String limit() default "";

    /**
     * 映射关系
     * @see 1-转正;0-试用期
     * @return
     */
    String map() default "";

    /**
     * 类型   可以根据需要制定默认值 处理逻辑见OperateLogEvent
     * @return
     */
    OperateLogEnum operateType() default OperateLogEnum.UPDATE_AFTER;


}

操作类型枚举类

public enum OperateLogEnum {

    UPDATE_AROUND,UPDATE_BEFORE,UPDATE_AFTER;

}

日志信息

@Data
@NoArgsConstructor
public class OperateLog {

    @TableId(value = "id",type = IdType.AUTO)
    private Long id;

    @JsonFormat(pattern="yyyy-MM-dd",timezone="GMT+8")
    @ApiModelProperty(name = "optTime",value = "操作时间")
    private Date optTime;

    @ApiModelProperty(name = "field",value = "操作列")
    private String field;

    @ApiModelProperty(name = "value",value = "操作内容")
    private String value;

    @ApiModelProperty(name = "operator",value = "操作人")
    private String operator;

    private Long employeeId;

    public OperateLog(String field, String value, String operator, Long employeeId) {
        this.field = field;
        this.value = value;
        this.operator = operator;
        this.employeeId = employeeId;
    }
}

处理事件

@Component
@Slf4j
//@EnableAsync 根据业务逻辑判断是否开启异步
public class OperateLogEvent {

//    @Async
    public <T> List<OperateLog> compare(T srcInfo, T destInfo, String operator, Long employeeId) {
        JSONObject srcEmployeeInfo = JSONObject.parseObject(JSONObject.toJSONString(srcInfo));
        JSONObject destEmployeeInfo = JSONObject.parseObject(JSONObject.toJSONString(destInfo));

        Field[] fields = srcInfo.getClass().getDeclaredFields();
        ArrayList<OperateLog> operateLogList = Lists.newArrayList();
        for (Field field : fields) {
            OperateLogParam annotation = field.getAnnotation(OperateLogParam.class);
            if (ObjectUtil.isNotEmpty(annotation)) {
                try {
                    if (srcEmployeeInfo.getString(field.getName()).equals(destEmployeeInfo.getString(field.getName()))) {
                        continue;
                    }
                    switch (annotation.operateType()) {
                        case UPDATE_BEFORE:
                            String[] kvStr = annotation.map().split(";");
                            Map<String, String> map = Maps.newHashMap();
                            for (String kv : kvStr) {
                                String[] kvArr = kv.split("-");
                                map.put(kvArr[0], kvArr[1]);
                            }
                            operateLogList.add(new OperateLog(map.get(destEmployeeInfo.getString(field.getName())), null, operator, employeeId));
                            break;
                        case UPDATE_AROUND:
                            operateLogList.add(new OperateLog(annotation.name(), srcEmployeeInfo.getString(field.getName()) + annotation.limit() + destEmployeeInfo.getString(field.getName()), operator, employeeId));
                            break;
                        case UPDATE_AFTER:
                            operateLogList.add(new OperateLog(annotation.name(), destEmployeeInfo.getString(field.getName()), operator, employeeId));
                            break;
                        default:
                            break;
                    }
                } catch (Exception e) {
                    log.error("解析日志注解失败,原因:",e);
                }
            }
        }
        return operateLogList;
    }
}

切面方法

@Aspect
@Component
@Slf4j
public class OperateLogAspect {

    @Autowired
    private EmployeeInfoService employeeInfoService;
    @Autowired
    private OperateLogEvent operateLogEvent;


    @Around(value = "execution(* com.my.modifyUserInfo(..))")
    @Transactional(rollbackFor = Exception.class)
    public ResultEnums modifyBaseDataOperation(ProceedingJoinPoint joinPoint) {

		//从modify方法中获取参数,我们在实现的时候在这里传入了当前登录用户的信息,所以可以接收到操作人
        EmployeeBaseDataDto employeeBaseDataDto = (EmployeeBaseDataDto) joinPoint.getArgs()[0];//修改内容封装类,在这里主要用于获取主键
        UserInfoDto userInfoDto = (UserInfoDto) joinPoint.getArgs()[1];//操作人
        EmployeeDetailInfoVo srcInfo = employeeInfoService.getById(employeeBaseDataDto.getEmployeeId());//获取修改前的用户信息
        ResultEnums resultObject;
        try {
            resultObject = (ResultEnums) joinPoint.proceed();//执行修改操作
            if (ResultEnums.SUCCESS.getCode().equals(resultObject.getCode())) {
                EmployeeDetailInfoVo destInfo = employeeInfoService.employeeInfoService.getById(employeeId);//获取修改后的用户信息
                List<OperateLog> operateLogList = operateLogEvent.compare(srcInfo, destInfo,userInfoDto.getRealName(), employeeId);
                //日志信息入库
                //doSomething
            } else {
                log.error("员工信息修改失败,不记录操作日志");
            }
        } catch (Throwable throwable) {
            log.error("修改员工信息切面处理失败,原因:", throwable);
            resultObject = ResultEnums.DB_UPDATE_ERROR;
        }
        return resultObject;


    }


}

注解使用示例

注意:注解要写在获取用户信息返回的model类上

@Data
public class userInfo{

    @ApiModelProperty("工号")
    @OperateLogParam(name = "更新员工工号为")
    private String jobNumber;

    @OperateLogParam(name = "更新部门信息为",limit = "到",operateType = OperateLogEnum.UPDATE_AROUND)
    private String depName;

    @OperateLogParam(map = "1-试用期;2-转正",operateType = OperateLogEnum.UPDATE_BEFORE)
    private Integer isDuringProbation;
}

响应示例

{
  "code": 0,
  "message": "成功",
  "result": [
    {
      "id": 47,
      "optTime": "2020-01-08",
      "field": "更新员工工号为",
      "value": "10086",
      "operator": "管理员",
      "employeeId": 727
    },
    {
      "id": 48,
      "optTime": "2020-01-08",
      "field": "更新部门信息为",
      "value": "互联网平台事业部到市场部",
      "operator": "管理员",
      "employeeId": 727
    }
    {
      "id": 50,
      "optTime": "2020-01-08",
      "field": "试用期",
      "value": null,
      "operator": "管理员",
      "employeeId": 727
    }
  ]
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!