操作日誌的內容記錄及比對

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章