91名师指路-头部
91名师指路

spring boot aop记录修改前后的值

由于某些原因,现在不支持支付宝支付,如需要购买源码请加博主微信进行购买,微信号:13248254750

效果图:


一:引入jar

<!-- aop功能  -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<version>2.2.7.RELEASE</version>
</dependency>


二:数据库设计。


三:切面类。

package com.mszl.blog.service.impl;

import com.mszl.utils.DataName;
import com.mszl.utils.aop.OperateLog;
import com.mszl.utils.aop.IpAddress;
import com.mszl.utils.aop.OperateType;
import com.mszl.utils.aop.ReflectAnnotationUtil;
import com.mszl.utils.aop.ReflectionUtils;
import lombok.extern.slf4j.Slf4j;
import com.mszl.blog.dao.logMapper;
import com.mszl.blog.service.ContentParser;
import com.mszl.entity.log;
import com.alibaba.fastjson.JSON;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
* 拦截@EnableGameleyLog注解的方法
* 将具体修改存储到数据库中
*/
@Aspect
@Component
@Slf4j
public class OperateAspect {

@Autowired
private logMapper operatelogService;

@Autowired
private DefaultContentParse defaultContentParse;

@Autowired
private ApplicationContext applicationContext;


// 环绕通知
@Around("@annotation(operateLog)")
public void around(ProceedingJoinPoint joinPoint, OperateLog operateLog) throws Throwable{
Map<String, Object> oldMap=new HashMap<>();
log lg = new log();
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
// 当不传默认operateType时 根据Method类型自动匹配
setAnnotationType(request, operateLog);

// fixme 1.0.9开始不再提供自动存入username功能,请在存储实现类中自行存储
// 从切面织入点处通过反射机制获取织入点处的方法
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod(); // 获取切入点所在的方法
String meName = method.getName(); // 获取请求的方法名
String className = joinPoint.getTarget().getClass().getName(); // 获取请求的类名
String methodName=className + "." + meName;
String uri=request.getRequestURL().toString(); // 请求uri
lg.setModuleCode(operateLog.moduleCode()); // 模块code
lg.setModuleName(operateLog.handleName()); // 模块名称
lg.setMethodName(methodName); // 方法名称
lg.setRequestUri(uri); // 请求uri

Object[] args=joinPoint.getArgs(); // 请求参数
String requestParams = JSON.toJSONString(args);
lg.setRequestParams(requestParams);
lg.setIp(IpAddress.getIpAddress(request));
lg.setOperateType(operateLog.operateType().toString());
lg.setCreated(new Date());
if (OperateType.UPDATE.equals(operateLog.operateType())) {
try {
ContentParser contentParser=(ContentParser) applicationContext.getBean(operateLog.parseclass());
Object oldObject = contentParser.getResult(joinPoint, operateLog);
lg.setOldObject(oldObject);
if (operateLog.needDefaultCompare()) {
oldMap = (Map<String, Object>) objectToMap(oldObject); // 存储修改前的对象
}
} catch (Exception e) {
e.printStackTrace();
log.error("service加载失败:", e);
}
}
// joinPoint.proceed()执行前是前置通知,执行后是后置通知
Object object=joinPoint.proceed();
if (OperateType.UPDATE.equals(operateLog.operateType())) {
ContentParser contentParser;
try {
String responseParams=JSON.toJSONString(object);
lg.setResponseParams(responseParams); // 把返回的结果存到log里面
contentParser=(ContentParser) applicationContext.getBean(operateLog.parseclass());
object = contentParser.getResult(joinPoint, operateLog);
} catch (Exception e) {
log.error("service加载失败:", e);
}
// 默认不进行比较,可以自己在logService中自定义实现,降低对性能的影响
if (operateLog.needDefaultCompare()) {
lg.setContent(defaultDealUpdate(object, oldMap));
}
// 如果使用默认缓存 则需要更新到最新的数据
if(operateLog.defaultCache()
&& operateLog.parseclass().equals(DefaultContentParse.class)){
defaultContentParse.updateCache(joinPoint, operateLog,object);
}
} else{
String responseParams=JSON.toJSONString(object);
lg.setResponseParams(responseParams); // 把返回的结果存到log里面
}
int add=operatelogService.insertSelective(lg);
log.info("新增日志:" + add);
}

private String defaultDealUpdate(Object newObject, Map<String, Object> oldMap){
try {
Map<String, Object> newMap = (Map<String, Object>) objectToMap(newObject);
StringBuilder str = new StringBuilder();
Object finalNewObject = newObject;
oldMap.forEach((k, v) -> {
Object newResult = newMap.get(k);
if (null!=v && !v.equals(newResult)) {
Field field = ReflectionUtils.getAccessibleField(finalNewObject, k);
DataName dataName = field.getAnnotation(DataName.class);
if (null!=dataName) {
str.append(dataName.name()).append(",").append("旧值为:").append(v).append(",").append("新值为:").append(newResult).append("。\n");
} else {
str.append(field.getName()).append(",").append("旧值为:").append(v).append(",").append("新值为:").append(newResult).append("。\n");
}
}
});
return str.toString();
} catch (Exception e) {
log.error("比较异常", e);
throw new RuntimeException("比较异常",e);
}
}

private Map<?, ?> objectToMap(Object obj) {
if (obj == null) {
return null;
}
ObjectMapper mapper = new ObjectMapper();
mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
// 如果使用JPA请自己打开这条配置
// mapper.addMixIn(Object.class, IgnoreHibernatePropertiesInJackson.class);
Map<?, ?> mappedObject = mapper.convertValue(obj, Map.class);
return mappedObject;
}

private void setAnnotationType(HttpServletRequest request,OperateLog modifyLog){
if(!modifyLog.operateType().equals(OperateType.NONE)){
return;
}
String method=request.getMethod();
if(RequestMethod.GET.name().equalsIgnoreCase(method)){
ReflectAnnotationUtil.updateValue(modifyLog, "operateType", OperateType.SELECT);
} else if(RequestMethod.POST.name().equalsIgnoreCase(method)){
ReflectAnnotationUtil.updateValue(modifyLog, "operateType", OperateType.ADD);
} else if(RequestMethod.PUT.name().equalsIgnoreCase(method)){
ReflectAnnotationUtil.updateValue(modifyLog, "operateType", OperateType.UPDATE);
} else if(RequestMethod.DELETE.name().equalsIgnoreCase(method)){
ReflectAnnotationUtil.updateValue(modifyLog, "operateType", OperateType.DELETE);
}
}



}


四:自定义日志注解

package com.mszl.utils.aop;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import com.mszl.blog.service.impl.DefaultContentParse;
import com.mszl.utils.aop.IService;

/**
* 记录编辑详细信息的标注
* @author zhang
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface OperateLog {

// 模块名称
String moduleCode() default "";

// 操作的类型 可以直接调用OperateType 不传时根据METHOD自动确定
OperateType operateType() default OperateType.NONE;

// 获取编辑信息的解析类,目前为使用id获取,复杂的解析需要自己实现,默认不填写则使用默认解析类
Class parseclass() default DefaultContentParse.class;

// 查询数据库所调用的class文件
Class serviceclass() default IService.class;

// 具体业务操作名称
String handleName() default "";

// 是否需要默认的改动比较
boolean needDefaultCompare() default false;

// id的类型
Class idType() default String.class;

// 是否使用默认本地缓存
boolean defaultCache() default false;


}


@OperateLog注解使用非常简单,在对应的需要记录日志的地方加上注解即可,如下所示。

@PostMapping("/test/insertUser")
@OperateLog(moduleCode="add_user", operateType=OperateType.ADD, handleName="新增用户信息")
public ReturnMsgUtils insertUser(@RequestBody user ur) {
ReturnMsgUtils msg=testUserService.insertUser(ur);
return msg;
}


五:自定义字段翻译注解。(修改功能时,需要显示如name字段修改前为张三,修改后为李四,就是让别人知道你的name字段对应的中文为姓名)

package com.mszl.utils;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* 写入日志表时,字段对应的中文注释
* @author zhang
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
@Documented
public @interface DataName {

String name() default ""; // 字段名称

}

@DataName的使用很简单,在对应的实体类上加上注解即可。

@DataName(name="姓名")
private String name;


注意在修改功能时,需要实现我们自定义的IService接口,并重写 selectById 方法,因为修改前我们需要根据主键id去数据库查询对应的信息,然后在和修改后的值进行比较。



六:IService接口

package com.mszl.utils.aop;

/**
* 修改时需要记录修改前后的值时,需要根据主键id去查询修改前的值时需要
* @author zhang
*/
public interface IService<T, S> {
T selectById(S id);
}


关键的类都在这里了,其他的一些类在附件里面,如有需要请自行下载。完整的框架截图如下所示:



2020-05-17 12:06:33     阅读(4563)

名师出品,必属精品    https://www.91mszl.com

用户登录遮罩层
x

账号登录

91名师指路-底部