工作中遇到一个需求,管理员修改用户个人信息后,要通过邮件通知给超级管理员本次修改的列以及修改前后对应的值。首先想到的方法是遍历每个字段,逐一比较,后面发现工作量巨大,直接放弃。后来经过思考,参考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
}
]
}
来源:CSDN
作者:yma
链接:https://blog.csdn.net/qq_39384387/article/details/103892092