dao層日志攔截記錄 mybatis攔截器的使用

近期需求中需要做一個(gè)系統(tǒng)日志,需求為記錄到每次操作類型,操作的模塊,IP,操作者昵稱,操作者用戶名,日志描述,操作時(shí)間等信息

首先,設(shè)計(jì)一下數(shù)據(jù)表


image.png

為了獲取用戶信息,寫(xiě)了一個(gè)攔截器,攔截jwt_token獲取用戶信息
向線程變量中封裝了用戶名、昵稱、模塊名(從配置文件注入)、IP地址四個(gè)字段

import cn.hutool.core.util.StrUtil;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.jwt.Jwt;
import org.springframework.security.jwt.JwtHelper;
import org.springframework.security.jwt.crypto.sign.MacSigner;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;

/**
 * 用戶信息攔截器
 * 從Header中取出jwttoken,并獲取其中的用戶名設(shè)置到用戶信息上下文線程變量中
 *
 * @author wangqichang
 * @since 2019/4/24
 */
@Component
public class CustomInfoInterceptor implements HandlerInterceptor {
    private ObjectMapper objectMapper = new ObjectMapper();


    @Value("${spring.application.chineseName}")
    private String moduleName;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String authorization = request.getHeader("Authorization");
        authorization = StrUtil.removePrefix(authorization, "Bearer ");
        ContextInfo contextInfo = new ContextInfo();
        if (StrUtil.isNotBlank(authorization)) {
            //驗(yàn)簽
            Jwt decode = JwtHelper.decodeAndVerify(authorization, new MacSigner("secretKey"));
            String claims = decode.getClaims();
            HashMap<String, Object> hashMap = objectMapper.readValue(claims, HashMap.class);
            Object userName = hashMap.get("user_name");
            Object nickName = hashMap.get("nick_name");
            contextInfo.setNickName((String) nickName);
            contextInfo.setUserName((String) userName);
        } /*else {
            throw new ServiceException("當(dāng)前用戶未登錄");
        }*/
        contextInfo.setModuleName(moduleName);
        contextInfo.setIp(getIp());
        CustomContext.setContextInfo(contextInfo);
        return true;
    }


    /**
     * @return 返回當(dāng)前請(qǐng)求IP
     */
    public static String getIp() {
        if (null != RequestContextHolder.currentRequestAttributes()) {
            ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
            if (null != attr) {
                HttpServletRequest request = attr.getRequest();
                String header = request.getHeader("x-forwarded-for");
                String ip = null;
                if (StrUtil.isNotBlank(header) && !"unknown".equalsIgnoreCase(header)) {
                    ip = header.split(",")[0];
                }
                if (StrUtil.isBlank(ip) || "unknown".equalsIgnoreCase(ip)) {
                    ip = request.getHeader("Proxy-Client-IP");
                }
                if (StrUtil.isBlank(ip) || "unknown".equalsIgnoreCase(ip)) {
                    ip = request.getHeader("WL-Proxy-Client-IP");
                }
                if (StrUtil.isBlank(ip) || "unknown".equalsIgnoreCase(ip)) {
                    ip = request.getRemoteAddr();
                }
                return ip.equals("0:0:0:0:0:0:0:1") ? "127.0.0.1" : ip;
            }
        }
        return null;
    }
}

mybatis攔截器如下
封裝日志對(duì)象,主要為獲取操作類型(增刪改查)、操作表實(shí)體

package com.zh.linemanager.interceptor;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import com.zh.backend.common.context.CustomContext;
import com.zh.backend.common.dto.ContextInfo;
import com.zh.backend.common.dto.LogDto;
import com.zh.backend.common.interceptor.CustomInfoInterceptor;
import com.zh.backend.common.service.LogService;
import com.zh.backend.common.util.SqlTableUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
/**
 * 日志mybatis 攔截器 功能為攔截?cái)?shù)據(jù)庫(kù)操作,獲取操作類型及操作表格,并從CustomContext獲取線程變量中的數(shù)據(jù)封裝日志
 * 比較粗糙,下次優(yōu)化
 * Signature 對(duì)Executor 的 update 及query 方法進(jìn)行攔截,后面為參數(shù)
 *
 * @author wangqichang
 * @see CustomInfoInterceptor
 * @since 2019/5/20
 */
@Slf4j
@Intercepts({@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class})
})
public class LogInterceptor implements Interceptor {

    private static String LOG_TYPE = "操作日志";

    /**
     * 這個(gè)是日志服務(wù),使用feign 調(diào)用
     */
    @Autowired
    private LogService logService;

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        //僅做日志,不能影響正常業(yè)務(wù),故try住
        try {
            //獲取第一個(gè)參數(shù),就是Signature中args的下標(biāo)
            MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
            LogDto logDto = new LogDto();
            //從自定義線程中copy出日志所需的信息,我放了用戶名,ip,模塊名這些
            ContextInfo info = CustomContext.current();
            BeanUtil.copyProperties(info, logDto);
            Date date = new Date();
            //獲取第二個(gè)參數(shù)Object,這個(gè)對(duì)象可能是實(shí)體也可能是map,比如自己用@Param注入?yún)?shù),那就是map對(duì)象
            Object object = invocation.getArgs()[1];
            if (!(object instanceof Map)) {
                //直接記錄操作的實(shí)體
                logDto.setOperationEntity(object.getClass().getName());
            } else {
                //如果是注入的map參數(shù),則使用jsqlphaser提取表名
                String sql = mappedStatement.getBoundSql(object).getSql();
                List<String> tableNames = SqlTableUtil.getTableNames(sql);
                String tables = CollUtil.join(tableNames, " ");
                logDto.setOperationEntity(tables);
            }
            String dateTime = DateUtil.formatDateTime(date);
            String operationType = null;
            switch (mappedStatement.getSqlCommandType()) {
                case INSERT:
                    operationType = "新增";
                    break;
                case UPDATE:
                    operationType = "更新";
                    break;
                case DELETE:
                    operationType = "刪除";
                    break;
                case SELECT:
                    operationType = "查詢";
                    break;
                default:
                    operationType = "未知";
                    break;
            }
            StringBuilder logDesc = new StringBuilder().append("用戶 ").append(info.getNickName()).append(" 于 ").append(dateTime).append(" 對(duì) ").append(logDto.getModuleName()).append(" 模塊進(jìn)行了 ").append(operationType).append(" 操作");
            logDto.setOperationType(operationType);
            logDto.setCreateTime(date);
            logDto.setLogType(LOG_TYPE);
            logDto.setLogDesc(logDesc.toString());
            logService.recordLog(logDto);
        } catch (Exception e) {
            log.error("日志記錄出錯(cuò),錯(cuò)誤信息:{}", e.getMessage());
            e.printStackTrace();
        }
        //放行攔截
        return invocation.proceed();
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
    }
}

提取sql表名用到的工具類

import net.sf.jsqlparser.parser.CCJSqlParserManager;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.util.TablesNamesFinder;

/**
 * @author wangqichang
 * @since 2019/5/21
 */
public class SqlTableUtil {
    private static CCJSqlParserManager sqlParserManager = new CCJSqlParserManager();
    private static TablesNamesFinder tablesNamesFinder = new TablesNamesFinder();

    /**
     * detect table names from given table
     * ATTENTION : WE WILL SKIP SCALAR SUBQUERY IN PROJECTION CLAUSE
     */
    public static List<String> getTableNames(String sql) throws Exception {
        Statement statement = sqlParserManager.parse(new StringReader(sql));
        return tablesNamesFinder.getTableList(statement);
    }
}

成功記錄如下


image.png

代碼不易,勿忘點(diǎn)贊

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • ORA-00001: 違反唯一約束條件 (.) 錯(cuò)誤說(shuō)明:當(dāng)在唯一索引所對(duì)應(yīng)的列上鍵入重復(fù)值時(shí),會(huì)觸發(fā)此異常。 O...
    我想起個(gè)好名字閱讀 5,995評(píng)論 0 9
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒(méi)有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對(duì)...
    cosWriter閱讀 11,679評(píng)論 1 32
  • 國(guó)家電網(wǎng)公司企業(yè)標(biāo)準(zhǔn)(Q/GDW)- 面向?qū)ο蟮挠秒娦畔?shù)據(jù)交換協(xié)議 - 報(bào)批稿:20170802 前言: 排版 ...
    庭說(shuō)閱讀 12,494評(píng)論 6 13
  • 請(qǐng)求日志幾乎是所有大型企業(yè)級(jí)項(xiàng)目的必要的模塊,請(qǐng)求日志對(duì)于我們來(lái)說(shuō)后期在項(xiàng)目運(yùn)行上線一段時(shí)間用于排除異常、請(qǐng)求分流...
    恒宇少年閱讀 55,295評(píng)論 40 82
  • iOS網(wǎng)絡(luò)架構(gòu)討論梳理整理中。。。 其實(shí)如果沒(méi)有APIManager這一層是沒(méi)法使用delegate的,畢竟多個(gè)單...
    yhtang閱讀 5,493評(píng)論 1 23

友情鏈接更多精彩內(nèi)容