美文网首页vueSpringbootJWT
dao层日志拦截记录 mybatis拦截器的使用

dao层日志拦截记录 mybatis拦截器的使用

作者: KICHUN | 来源:发表于2019-05-21 11:58 被阅读111次

近期需求中需要做一个系统日志,需求为记录到每次操作类型,操作的模块,IP,操作者昵称,操作者用户名,日志描述,操作时间等信息

首先,设计一下数据表


image.png

为了获取用户信息,写了一个拦截器,拦截jwt_token获取用户信息
向线程变量中封装了用户名、昵称、模块名(从配置文件注入)、IP地址四个字段

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,并获取其中的用户名设置到用户信息上下文线程变量中
 *
 * @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)) {
            //验签
            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("当前用户未登录");
        }*/
        contextInfo.setModuleName(moduleName);
        contextInfo.setIp(getIp());
        CustomContext.setContextInfo(contextInfo);
        return true;
    }


    /**
     * @return 返回当前请求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拦截器如下
封装日志对象,主要为获取操作类型(增删改查)、操作表实体

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 拦截器 功能为拦截数据库操作,获取操作类型及操作表格,并从CustomContext获取线程变量中的数据封装日志
 * 比较粗糙,下次优化
 * Signature 对Executor 的 update 及query 方法进行拦截,后面为参数
 *
 * @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 = "操作日志";

    /**
     * 这个是日志服务,使用feign 调用
     */
    @Autowired
    private LogService logService;

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        //仅做日志,不能影响正常业务,故try住
        try {
            //获取第一个参数,就是Signature中args的下标
            MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
            LogDto logDto = new LogDto();
            //从自定义线程中copy出日志所需的信息,我放了用户名,ip,模块名这些
            ContextInfo info = CustomContext.current();
            BeanUtil.copyProperties(info, logDto);
            Date date = new Date();
            //获取第二个参数Object,这个对象可能是实体也可能是map,比如自己用@Param注入参数,那就是map对象
            Object object = invocation.getArgs()[1];
            if (!(object instanceof Map)) {
                //直接记录操作的实体
                logDto.setOperationEntity(object.getClass().getName());
            } else {
                //如果是注入的map参数,则使用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(" 对 ").append(logDto.getModuleName()).append(" 模块进行了 ").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("日志记录出错,错误信息:{}", 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

代码不易,勿忘点赞

相关文章

网友评论

    本文标题:dao层日志拦截记录 mybatis拦截器的使用

    本文链接:https://www.haomeiwen.com/subject/rpvdzqtx.html