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