emmm,还是好久没更新博客了。老样子,懒,哈哈。
ok,回归正题,最近项目需要一个操作日志的内容比对,也就是哪些人在哪个业务的操作了哪些内容,操作前和操作后的内容比对及其他信息做为记录供人查阅。
最先想到的肯定是spring aop 拦截特定方法+自定义注解获取内容,操作前后的内容用json记录入库,比对内容可能就是jsonObject来进行比对。
因项目的特殊性和业务实体里可能会有循环引用等因素,在寻找了一些资料后决定
自定义注解 + MethodInterceptor + fastjson + jsondiffpatch + 消息队列
- 自定义注解用来注明一些内容信息及要做记录的方法
- 实现MethodInterceptor的invoke做方法前后及异常信息的拦截,request通过RequestContextListener的上下文来获取一些请求信息
- fastjson做实体序列化和反序列化(因项目反序列化特殊性fastjson需要1.2.28+版本)
- jsondiffpatch做内容比对 (jsondiffpatch的online demo)
- 消息队列做解析及入库解耦
先建立个自定义注解MyLog
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD })
public @interface MyLog {
// 模块名
String module() default "";
// 操作内容
String operate() default "";
// 备注
String remark() default "";
//所属业务model class baseModel为业务基类
Class<? extends BaseModel> clazz() default BaseModel.class;
}
实现methodInterceptor【也可以自己使用spring aop去设置拦截 请自行查询相关资料】
import com.alibaba.fastjson.JSON;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.lang.StringUtils;
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 java.lang.reflect.Method;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
/**
*
*/
@Component
public class MyInterceptor implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
Method method = methodInvocation.getMethod();
// if(method.isAnnotationPresent(MyLog.class)){
// //未检测到注解则放行
// return methodInvocation.proceed();
// }
MyLog myLog = method.getAnnotation(MyLog.class);
if(myLog != null){
//模块
String module = myLog.module();
//操作
String operation = myLog.operation();
//备注信息
String remark = myLog.remark();
//拦截的类
Class<? extends BaseModel> clazz = myLog.clazz();
}
HttpServletRequest request = ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest();
if (request != null){
//获取请求信息
Map<String,String[]> parameterMap = request.getParameterMap();
//请求头
Map<String,String> headerMap = getHeader(request);
//因项目的拦截方法请求request中会带id
String id = request.getParameter("id");
if(StringUtils.isNotBlank(id)){
// BaseModel model = baseService.findById(id,clazz);
//转json
// String json = JSON.toJSONString(model);
}
}
//异常信息
StringBuilder exceptionMessage = new StringBuilder();
//方法返回值
Object returnObj = null;
//异常
Exception exception = null;
try {
returnObj = methodInvocation.proceed();
}catch (Exception e){
exception = e;
exceptionMessage.append(e.getClass().getName() + " : " + e.getMessage() + "\n");
StackTraceElement[] trace = e.getStackTrace();
for (StackTraceElement s : trace) {
exceptionMessage.append("\tat " + s + "\r\n");
}
}
//记录日志实体
//log.setxxx(xxx);
//发送消息队列
//sendMessageService.send(logObject);
if(exception != null){
throw exception;
}
return returnObj;
}
/**
* 获取header参数内容
*
* @return
*/
private Map<String, String> getHeader(HttpServletRequest request) {
Map<String, String> map = new HashMap<>(0);
Enumeration headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()) {
String key = (String) headerNames.nextElement();
String value = request.getHeader(key);
map.put(key, value);
}
return map;
}
}
配置spring的自动代理
<!-- 通知器或切面 -->
<bean id="operationLogAroundMethodPointcutAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="advice">
<ref bean="operationLogAroundAdvice"/>
</property>
<property name="patterns">
<list>
<!-- 拦截全部方法 -->
<value>.*</value>
</list>
</property>
</bean>
<!-- 自动代理 -->
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="beanNames">
<list>
<!-- 哪些需要代理 -->
<value>*Service</value>
</list>
</property>
<property name="interceptorNames">
<list>
<value>operationLogAroundMethodPointcutAdvisor</value>
</list>
</property>
</bean>
使用方式就是在你的service接口上加入Mylog注解进行信息配置啦~
@MyLog(module = "模块名", operate = "新增操作",clazz = XXXXX.class)
String add(BaseModel model) throws Exception;
业务逻辑就是大概这样:
拦截要记录的接口,获取必要信息,从数据库获取操作前的数据转json存入库,同时记录该业务id和业务类,查看日志的时候从库中读取新的操作后的对象,转成操作后的json 2个json进行比对即可。
至于jsondiffpatch怎么使用官方比我讲的明白
https://www.npmjs.com/package/jsondiffpatch
参考:
https://github.com/alibaba/fastjson/wiki/PropertyNamingStrategy_cn
https://github.com/alibaba/fastjson/wiki/%E5%BE%AA%E7%8E%AF%E5%BC%95%E7%94%A8
https://github.com/alibaba/fastjson/wiki/incompatible_change_list
https://github.com/alibaba/fastjson/wiki/enable_autotype