mybatis插件配合SpringMVC拦截器实现操作日志统计

在Mybatis的拦截器中,只能统计出最终执行的Sql语句,无法统计出每行语句执行的操作人。


如果想看一个用户主动对数据库的操作日志,则单使用拦截器无法实现。

可以借助SpringMvc的拦截器,将请求头的信息记录下来,这样就能获取到每一个人的操作日志。

新建一个 MyBatisIntercept 类,继承 HandlerInterceptorAdapter 拦截器 并 实现 Mybatis的Interceptor接口
拦截Update和Query操作


@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}),
        }
)
public class MyBatisIntercept extends HandlerInterceptorAdapter implements Interceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        return true;
    }
    @Override
    public Object intercept(Invocation invocation) throws Throwable {

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

    @Override
    public void setProperties(Properties properties) {

    }
}

创建一个全局ThreadLocal对象,用于存储用SpringMvc拦截器进行来的用户身份信息


    private ThreadLocal<Object> threadLocal = new InheritableThreadLocal<>();

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 将请求中可以标识用户信息的数据给塞进去
        objectThreadLocal.set("");
        return true;
    }

如果是同步操作的话,SpringMvc的拦截器和Mybatis的拦截器必然会在一个线程里面。

在拦截器中将用户信息给取出来,然后处理一下Mybatis的Sql语句,这样就能对整个语句进行一个操作人的记录。


    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 能将先前存储的用户信息给获取出来
        threadLocal.get();
    }

这样的方式用于记录操作人是可行的,但是这种方式会使Mybatis拦截器的职责不明确,需要去处理请求里面的内容。

如果有使用日志框架,可以使用MDC对象,MDC对象对ThreadLocal进行了一个优化,可以将request中的信息保存到MDC对象中,
然后配置logback的配置文件,直接将日志通过mq的方式进行存储处理。

最后成了这样:


/**
 * @author : 小咖啡
 * @create : 2018-01-08 10:29
 * mybatis 操作拦截器
 * sql直接拷贝 http://phncz310.iteye.com/blog/2251712
 */
@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}),
        }
)
public class MyBatisIntercept extends HandlerInterceptorAdapter implements Interceptor {

    private static final Logger logger = LoggerFactory.getLogger(MyBatisIntercept.class);

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        MDC.put("operationType", request.getHeader("operationType"));
        return true;
    }


    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object[] args = invocation.getArgs();
        // 传入的对象
        Object obj = args[1];

        MappedStatement mappedStatement = (MappedStatement) args[0];
        // 记录执行结果
        Object resultObj = invocation.proceed();
        String name = mappedStatement.getSqlCommandType().name().toUpperCase();
        //执行的sql
        BoundSql boundSql = mappedStatement.getBoundSql(obj);
        Configuration configuration = mappedStatement.getConfiguration();
        String sql;
        try {
            sql = showSql(configuration, boundSql);
        }catch (Exception e){
            sql = "SQL分析出错";
            logger.warn("SQL分析出错 {}",JSONObject.toJSONString(resultObj));
            return resultObj;
        }
        if (name.startsWith("INSERT")) {
            logger.info("{}||{}", sql, sql.substring(sql.toUpperCase().indexOf("INTO") + 4, sql.toUpperCase().indexOf("(")).trim());
        }
        if (name.startsWith("UPDATE")) {
            // 找where和limit中的参数就是条件
            String keywords = sql.substring(sql.toUpperCase().lastIndexOf("WHERE")).toUpperCase();
            if (keywords.contains("LIMIT")) {
                keywords = keywords.substring("WHERE".length(), sql.toUpperCase().lastIndexOf("LIMIT"));
            }
            StringBuilder sb = new StringBuilder();
            for (String key : keywords.split("AND")) {
                sb.append(key.split("=")[1].trim()).append(",");
            }
            logger.info("{}||{}||{}", sql, sql.substring(name.length(), sql.toUpperCase().lastIndexOf("SET")).trim(), sb.toString());
        }
        if (name.startsWith("DELETE")) {
            String keywords = sql.substring(sql.toUpperCase().lastIndexOf("WHERE")).toUpperCase();
            if (keywords.contains("LIMIT")) {
                keywords = keywords.substring("WHERE".length(), sql.toUpperCase().lastIndexOf("LIMIT"));
            }
            StringBuilder sb = new StringBuilder();
            for (String key : keywords.split("AND")) {
                sb.append(key.split("=")[1].trim()).append(",");
            }
            logger.info("{}||{}||{}", sql, sql.substring(sql.toUpperCase().lastIndexOf("FROM"), sql.toUpperCase().lastIndexOf("WHERE")).trim(), sb.toString());
        }
        if (name.startsWith("SELECT")) {
            logger.info("查询结果 -> {} , {}", sql, JSONObject.toJSONString(resultObj));
        }
        return resultObj;
    }

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

    @Override
    public void setProperties(Properties properties) {

    }

    private static String getParameterValue(Object obj) {
        String value = null;
        if (obj instanceof String) {
            value = "'" + obj.toString() + "'";
        } else if (obj instanceof Date) {
            DateFormat formatter = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.CHINA);
            value = "'" + formatter.format(new Date()) + "'";
        } else {
            if (obj != null) {
                value = obj.toString();
            } else {
                value = "";
            }

        }
        return value;
    }

    public static String showSql(Configuration configuration, BoundSql boundSql) {
        Object parameterObject = boundSql.getParameterObject();
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        String sql = boundSql.getSql().replaceAll("[\\s]+", " ");
        if (parameterMappings.size() > 0 && parameterObject != null) {
            TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
            if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                sql = sql.replaceFirst("\\?", getParameterValue(parameterObject));
            } else {
                MetaObject metaObject = configuration.newMetaObject(parameterObject);
                for (ParameterMapping parameterMapping : parameterMappings) {
                    String propertyName = parameterMapping.getProperty();
                    if (metaObject.hasGetter(propertyName)) {
                        Object obj = metaObject.getValue(propertyName);
                        sql = sql.replaceFirst("\\?", getParameterValue(obj));
                    } else if (boundSql.hasAdditionalParameter(propertyName)) {
                        Object obj = boundSql.getAdditionalParameter(propertyName);
                        sql = sql.replaceFirst("\\?", getParameterValue(obj));
                    } else {
                        Map map = (Map) metaObject;
                        sql = sql.replaceFirst("\\?", getParameterValue(map.get(propertyName)));
                    }
                }
            }
        }
        return sql;
    }
    private String camelToUnderline(String param){
        if (param==null||"".equals(param.trim())){
            return "";
        }
        int len=param.length();
        StringBuilder sb=new StringBuilder(len);
        for (int i = 0; i < len; i++) {
            char c=param.charAt(i);
            if (Character.isUpperCase(c)){
                sb.append("_");
                sb.append(Character.toLowerCase(c));
            }else{
                sb.append(c);
            }
        }
        return sb.toString();
    }

}


logback的配置文件:


    <springProperty  name="host" source="spring.rabbitmq.host"/>
    <springProperty  name="username" source="spring.rabbitmq.username"/>
    <springProperty  name="password" source="spring.rabbitmq.password"/>

    <appender name="AMQP" class="org.springframework.amqp.rabbit.logback.AmqpAppender">
        <layout>
            <pattern>%X{operationType}||%X{operationId}||%X{X-B3-TraceId}||%X{X-B3-SpanId}||%m%n</pattern>
        </layout>
        <filter class="ch.qos.logback.core.filter.EvaluatorFilter">
            <evaluator>
                <!--判断操作人不为空-->
                <expression>
                    mdc.get("operationType") != null
                </expression>
            </evaluator>
            <OnMatch>ACCEPT</OnMatch>
            <OnMismatch>DENY</OnMismatch>
        </filter>
        <host>${host}</host>
        <port>5672</port>
        <username>${username}</username>
        <password>${password}</password>
        <applicationId>AmqpAppenderTest</applicationId>
        <generateId>true</generateId>
        <exchangeName>operationWithParamDestination</exchangeName>
        <charset>UTF-8</charset>
        <durable>false</durable>
        <deliveryMode>NON_PERSISTENT</deliveryMode>
    </appender>

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 225,928评论 6 523
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 97,032评论 3 410
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 173,382评论 0 370
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 61,580评论 1 304
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 70,558评论 6 403
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 54,018评论 1 316
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 42,261评论 3 432
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 41,328评论 0 281
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 47,858评论 1 328
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 39,843评论 3 351
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 41,954评论 1 358
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 37,565评论 5 352
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 43,251评论 3 342
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 33,677评论 0 25
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 34,834评论 1 278
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 50,558评论 3 383
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 47,033评论 2 368

推荐阅读更多精彩内容