美文网首页
使用Druid解析SQL实现血缘关系计算,获取sql执行语句、表

使用Druid解析SQL实现血缘关系计算,获取sql执行语句、表

作者: JeffreyTaiT | 来源:发表于2022-03-28 17:07 被阅读0次

    项目中使用的MybatisPlus,要做一个基于Elastic的日志查看功能,需要统计sql相关信息。

    定义一个ExecuteSqlPrintInnerInterceptor,继承 InnerInterceptor。

    package com.xxxx.interceptor;

    import com.alibaba.druid.sql.SQLUtils;

    import com.alibaba.druid.sql.ast.SQLStatement;

    import com.alibaba.druid.sql.visitor.SchemaStatVisitor;

    import com.alibaba.druid.stat.TableStat;

    import com.alibaba.druid.util.JdbcConstants;

    import com.alibaba.fastjson.JSON;

    import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;

    import lombok.extern.slf4j.Slf4j;

    import org.apache.ibatis.executor.Executor;

    import org.apache.ibatis.mapping.BoundSql;

    import org.apache.ibatis.mapping.MappedStatement;

    import org.apache.ibatis.mapping.ParameterMapping;

    import org.apache.ibatis.reflection.MetaObject;

    import org.apache.ibatis.session.Configuration;

    import org.apache.ibatis.session.ResultHandler;

    import org.apache.ibatis.session.RowBounds;

    import org.apache.ibatis.type.TypeHandlerRegistry;

    import org.springframework.web.context.request.RequestContextHolder;

    import org.springframework.web.context.request.ServletRequestAttributes;

    import javax.servlet.http.HttpServletRequest;

    import java.io.BufferedReader;

    import java.io.IOException;

    import java.io.InputStream;

    import java.io.InputStreamReader;

    import java.nio.charset.Charset;

    import java.sql.SQLException;

    import java.text.DateFormat;

    import java.util.*;

    /**

    * @description:

    * @author: zhanglao

    * @date: 2022/3/28 11:03 上午

    */

    @Slf4j

    public class ExecuteSqlPrintInnerInterceptorimplements InnerInterceptor{

        @Override

        public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException{

            Configuration configuration = ms.getConfiguration();

            handleSql(configuration, boundSql, ms.getId());

        }

        @Override

        public void beforeUpdate(Executor executor, MappedStatement ms, Object parameter) throws SQLException{

            Configuration configuration = ms.getConfiguration();

            handleSql(configuration, ms.getBoundSql(parameter), ms.getId());

        }

        private static void handleSql(Configuration configuration, BoundSql boundSql, String sqlId) {

            Object parameterObject = boundSql.getParameterObject();

            List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();

            //替换空格、换行、tab缩进等

            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));

                        }

    }

    }

    }

            logs(sql, sqlId);

        }

        private static StringgetParameterValue(Object obj) {

            String value;

            if (objinstanceof String) {

                value ="'" + obj.toString() +"'";

            } else if (objinstanceof 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.replace("$", "\\$");

        }

        /**

    * log打印

    */

        private static void logs(String sql, String sqlId) {

            ExecuteParseEntity executeParseEntity =new ExecuteParseEntity();

            HttpServletRequest request =((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();

            Enumeration<String> params = request.getAttributeNames();

            executeParseEntity.setMethodType(request.getMethod());

            executeParseEntity.setRequestAddress(getRemoteHost(request));

            executeParseEntity.setRequestURI(request.getRequestURI());

            executeParseEntity.setSqlId(sqlId);

            executeParseEntity.setSql(sql);

            parseSql(executeParseEntity);

            log.info(JSON.toJSONString(executeParseEntity));

        }

        private static void parseSql(ExecuteParseEntity executeParseEntity) {

            List<SQLStatement> sqlStatements = SQLUtils.parseStatements(executeParseEntity.getSql(), JdbcConstants.MYSQL);

            for (SQLStatement sqlStatement : sqlStatements) {

                SchemaStatVisitor schemaStatVisitor = SQLUtils.createSchemaStatVisitor(JdbcConstants.MYSQL);

                sqlStatement.accept(schemaStatVisitor);

                Map<TableStat.Name, TableStat> tables = schemaStatVisitor.getTables();

                Collection<TableStat.Column> columns = schemaStatVisitor.getColumns();

                if (Objects.nonNull(tables)) {

                    List<Map<String, List<String>>> tableInfo =new ArrayList<>();

                    tables.forEach(((name, tableStat) ->{

                        Map<String, List<String>> tabMap =new HashMap<>();

                        List<String> columsList =new ArrayList<>();

                        if (tableStat.getCreateCount() >0 || tableStat.getInsertCount() >0) {

                            log.info("update to: table[{}]", name.getName().toLowerCase());

                            columns.stream().filter(column -> Objects.equals(column.getTable().toLowerCase(), name.getName().toLowerCase())).forEach(column ->{

                                columsList.add(column.getName());

    //                            log.info("to: table[{}] column[{}]", column.getTable().toLowerCase(), column.getName().toLowerCase());

                            });

                            tabMap.put(name.getName(), columsList);

                        } else  if (tableStat.getSelectCount() >0) {

                            log.info("select from: table[{}]", name.getName().toLowerCase());

                            columns.stream().filter(column -> Objects.equals(column.getTable().toLowerCase(), name.getName().toLowerCase())).forEach(column ->{

                                columsList.add(column.getName());

    //                            log.info("from: table[{}] column[{}]", column.getTable().toLowerCase(), column.getName().toLowerCase());

                            });

                            tabMap.put(name.getName(), columsList);

                        }

                        tableInfo.add(tabMap);

                    }));

                    executeParseEntity.setTableInfo(tableInfo);

                }

    }

    }

        /**

    * 获取请求body内容

    */

        public static StringgetBodyString(HttpServletRequest request) {

            StringBuilder sb =new StringBuilder();

            InputStream inputStream =null;

            BufferedReader reader =null;

            try {

                inputStream = request.getInputStream();

                reader =new BufferedReader(

                        new InputStreamReader(inputStream, Charset.forName("UTF-8")));

                char[] bodyCharBuffer =new char[1024];

                int len =0;

                while ((len = reader.read(bodyCharBuffer)) != -1) {

                    sb.append(new String(bodyCharBuffer, 0, len));

                }

            } catch (IOException e) {

                e.printStackTrace();

            } finally {

                if (inputStream !=null) {

                    try {

                        inputStream.close();

                    } catch (IOException e) {

                        e.printStackTrace();

                    }

    }

                if (reader !=null) {

                    try {

                        reader.close();

                    } catch (IOException e) {

                        e.printStackTrace();

                    }

    }

    }

            return sb.toString();

        }

        /**

    * 获取目标主机的ip

        * @param request

        * @return

        */

        private static StringgetRemoteHost(HttpServletRequest request) {

            String ip = request.getHeader("x-forwarded-for");

            if (ip ==null || ip.length() ==0 ||"unknown".equalsIgnoreCase(ip)) {

                ip = request.getHeader("Proxy-Client-IP");

            }

            if (ip ==null || ip.length() ==0 ||"unknown".equalsIgnoreCase(ip)) {

                ip = request.getHeader("WL-Proxy-Client-IP");

            }

            if (ip ==null || ip.length() ==0 ||"unknown".equalsIgnoreCase(ip)) {

                ip = request.getRemoteAddr();

            }

            return ip.contains("0:0:0:0:0:0:0:1") ?"127.0.0.1" : ip;

        }

    }

    ExecuteParseEntity就是个实体接收,方便查看参数含义。

    package com.xxxx.interceptor;

    import com.sun.javafx.collections.MappingChange;

    import lombok.Data;

    import java.io.Serializable;

    import java.util.List;

    import java.util.Map;

    /**

    * @description:

    * @author: zhanglao

    * @date: 2022/3/28 3:38 下午

    */

    @Data

    public class ExecuteParseEntity implements Serializable {

        private static final long serialVersionUID = -1673663624670778651L;

        /**

        * 方法类型

        */

        private String methodType;

        /**

        * 请求域名

        */

        private String requestAddress;

        /**

        * 请求api

        */

        private String requestURI;

        /**

        * sql方法

        */

        private String sqlId;

        /**

        * sql语句

        */

        private String sql;

        /**

        * 表相关信息

        */

        private List<Map<String, List<String>>> tableInfo;

        @Override

        public String toString() {

            return "ExecuteParseEntity{" +

                    "requestAddress='" + requestAddress + '\'' +

                    ", requestURI='" + requestURI + '\'' +

                    ", sqlId='" + sqlId + '\'' +

                    ", sql='" + sql + '\'' +

                    ", tableInfo=" + tableInfo +

                    '}';

        }

    }

    最后需要在**MybatisPlusConfig**将拦截器添加

    @Bean

        public MybatisPlusInterceptor mybatisPlusInterceptor() {

            MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();

            interceptor.addInnerInterceptor(new ExecuteSqlPrintInnerInterceptor());

            return interceptor;

        }

    结果:

    > {

    > "requestAddress":"127.0.0.1",

    > "requestURI":"/admin/role/permission/list",

    > "sql":"SELECT id,role_id,permission_id,gmt_create,create_nick,gmt_modified,modified_nick

    > FROM role_permission WHERE (role_id IN (1))",

    "sqlId":"com.xxxx.dal.mapper.RolePermissionMapper.selectList",

    "tableInfo":[{"role_permission":["id","role_id","permission_id","gmt_create","create_nick","gmt_modified","modified_nick"]}]

    }

    相关文章

      网友评论

          本文标题:使用Druid解析SQL实现血缘关系计算,获取sql执行语句、表

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