24282 2 жил өмнө
parent
commit
a876504d6d

+ 1 - 1
ruoyi-admin/src/main/resources/application.yml

@@ -89,7 +89,7 @@ token:
 # MyBatis Plus配置
 mybatis-plus:
   # 搜索指定包别名
-  typeAliasesPackage: com.ruoyi.**.domain
+  typeAliasesPackage: com.ruoyi.**.domain,com.fjhx.**.entity.po
   # 配置mapper的扫描,找到所有的mapper.xml映射文件
   mapperLocations: classpath*:mapper/**/*Mapper.xml
   # 加载全局的配置文件

+ 25 - 0
ruoyi-common/src/main/java/com/ruoyi/common/annotation/LogicIgnore.java

@@ -0,0 +1,25 @@
+package com.ruoyi.common.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * 排除逻辑删除逻辑
+ * <p>
+ * 当排除表名和排除表别名都为空时,排除所有表逻辑删除拼接
+ */
+@Target({ElementType.TYPE, ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface LogicIgnore {
+
+    /**
+     * 排除表名
+     */
+    String[] tableName() default {};
+
+    /**
+     * 排除表别名
+     */
+    String[] alias() default {};
+
+}

+ 34 - 0
ruoyi-framework/src/main/java/com/ruoyi/framework/aspect/LogicAspect.java

@@ -0,0 +1,34 @@
+package com.ruoyi.framework.aspect;
+
+import com.ruoyi.common.annotation.LogicIgnore;
+import com.ruoyi.framework.mybatis.holder.LogicHolder;
+import com.ruoyi.framework.mybatis.holder.TenantHolder;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+
+/**
+ * 租户拦截器
+ */
+@Aspect
+public class LogicAspect {
+
+    @Around("@annotation(logicIgnore)")
+    public Object around(ProceedingJoinPoint point, LogicIgnore logicIgnore) throws Throwable {
+        try {
+            String[] alias = logicIgnore.alias();
+            String[] tableName = logicIgnore.tableName();
+            LogicHolder logicHolder = new LogicHolder();
+            logicHolder.setAliases(alias);
+            logicHolder.setTableNames(tableName);
+            // 开启忽略
+            LogicHolder.setLogicHolder(logicHolder);
+            // 执行方法
+            return point.proceed();
+        } finally {
+            // 关闭忽略
+            LogicHolder.clear();
+        }
+    }
+
+}

+ 1 - 1
ruoyi-framework/src/main/java/com/ruoyi/framework/mybatis/aspectj/TenantAspect.java → ruoyi-framework/src/main/java/com/ruoyi/framework/aspect/TenantAspect.java

@@ -1,4 +1,4 @@
-package com.ruoyi.framework.mybatis.aspectj;
+package com.ruoyi.framework.aspect;
 
 import com.ruoyi.common.annotation.TenantIgnore;
 import com.ruoyi.framework.mybatis.holder.TenantHolder;

+ 3 - 2
ruoyi-framework/src/main/java/com/ruoyi/framework/config/ApplicationConfig.java

@@ -1,12 +1,13 @@
 package com.ruoyi.framework.config;
 
-import java.util.TimeZone;
 import org.mybatis.spring.annotation.MapperScan;
 import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.context.annotation.EnableAspectJAutoProxy;
 
+import java.util.TimeZone;
+
 /**
  * 程序注解配置
  *
@@ -16,7 +17,7 @@ import org.springframework.context.annotation.EnableAspectJAutoProxy;
 // 表示通过aop框架暴露该代理对象,AopContext能够访问
 @EnableAspectJAutoProxy(exposeProxy = true)
 // 指定要扫描的Mapper类的包的路径
-@MapperScan("com.ruoyi.**.mapper")
+@MapperScan({"com.ruoyi.**.mapper", "com.fjhx.**.mapper"})
 public class ApplicationConfig
 {
     /**

+ 26 - 15
ruoyi-framework/src/main/java/com/ruoyi/framework/mybatis/MybatisPlusConfig.java

@@ -1,10 +1,15 @@
 package com.ruoyi.framework.mybatis;
 
 import com.baomidou.mybatisplus.annotation.DbType;
+import com.baomidou.mybatisplus.autoconfigure.ConfigurationCustomizer;
+import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
+import com.baomidou.mybatisplus.extension.MybatisMapWrapperFactory;
 import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
 import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
 import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
-import com.ruoyi.framework.mybatis.interceptor.MyTenantInterceptor;
+import com.ruoyi.framework.mybatis.holder.CustomMetaObjectHandler;
+import com.ruoyi.framework.mybatis.interceptor.LogicInterceptor;
+import com.ruoyi.framework.mybatis.interceptor.TenantInterceptor;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.transaction.annotation.EnableTransactionManagement;
@@ -25,12 +30,30 @@ public class MybatisPlusConfig {
         interceptor.addInnerInterceptor(paginationInnerInterceptor());
         // 乐观锁插件
         interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
+        // 拼接租户
+        interceptor.addInnerInterceptor(new TenantInterceptor());
+        // 拼接逻辑删除
+        interceptor.addInnerInterceptor(new LogicInterceptor());
+        return interceptor;
+    }
 
+    /**
+     * 自定义公共字段自动注入
+     */
+    @Bean
+    public MetaObjectHandler metaObjectHandler() {
+        return new CustomMetaObjectHandler();
+    }
 
-        interceptor.addInnerInterceptor(new MyTenantInterceptor());
-        return interceptor;
+    /**
+     * map返回查询结果时,自动转驼峰
+     */
+    @Bean
+    public ConfigurationCustomizer configurationCustomizer() {
+        return i -> i.setObjectWrapperFactory(new MybatisMapWrapperFactory());
     }
 
+    // 分页优化
     public PaginationInnerInterceptor paginationInnerInterceptor() {
         PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor();
         // 设置数据库类型为mysql
@@ -42,16 +65,4 @@ public class MybatisPlusConfig {
         return paginationInnerInterceptor;
     }
 
-    // public TenantLineInnerInterceptor tenantLineInnerInterceptor() {
-    //     TenantLineInnerInterceptor tenantLineInnerInterceptor = new TenantLineInnerInterceptor(new TenantLineHandler() {
-    //         @Override
-    //         public Expression getTenantId() {
-    //             return null;
-    //         }
-    //     });
-    //
-    //
-    //     return yt
-    // }
-
 }

+ 74 - 0
ruoyi-framework/src/main/java/com/ruoyi/framework/mybatis/holder/CustomMetaObjectHandler.java

@@ -0,0 +1,74 @@
+package com.ruoyi.framework.mybatis.holder;
+
+import cn.hutool.core.util.ObjectUtil;
+import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
+import com.ruoyi.common.utils.SecurityUtils;
+import org.apache.ibatis.reflection.MetaObject;
+
+import java.util.Date;
+
+/**
+ * 自定义sql字段填充器,自动填充创建修改相关字段
+ */
+public class CustomMetaObjectHandler implements MetaObjectHandler {
+
+    private static final String CREATE_USER = "createUser";
+
+    private static final String CREATE_TIME = "createTime";
+
+    private static final String UPDATE_USER = "updateUser";
+
+    private static final String UPDATE_TIME = "updateTime";
+
+    private static final String VERSION_FLAG = "version";
+
+    /**
+     * 新增模块时,需要添加的字段
+     */
+    @Override
+    public void insertFill(MetaObject metaObject) {
+        Long userUniqueId = this.getUserUniqueId();
+        Date date = new Date();
+
+        if (ObjectUtil.isNotEmpty(metaObject.hasSetter(CREATE_USER))) {
+            setFieldValByName(CREATE_USER, userUniqueId, metaObject);
+        }
+        if (ObjectUtil.isNotEmpty(metaObject.hasSetter(CREATE_TIME))) {
+            setFieldValByName(CREATE_TIME, date, metaObject);
+        }
+        if (ObjectUtil.isNotEmpty(metaObject.hasSetter(UPDATE_USER))) {
+            setFieldValByName(UPDATE_USER, userUniqueId, metaObject);
+        }
+        if (ObjectUtil.isNotEmpty(metaObject.hasSetter(UPDATE_TIME))) {
+            setFieldValByName(UPDATE_TIME, date, metaObject);
+        }
+        if (ObjectUtil.isNotEmpty(metaObject.hasSetter(VERSION_FLAG))) {
+            setFieldValByName(VERSION_FLAG, 0, metaObject);
+        }
+    }
+
+    /**
+     * 编辑模块时,需要添加的字段
+     */
+    @Override
+    public void updateFill(MetaObject metaObject) {
+        if (ObjectUtil.isNotEmpty(metaObject.hasSetter(UPDATE_USER))) {
+            setFieldValByName(UPDATE_USER, this.getUserUniqueId(), metaObject);
+        }
+        if (ObjectUtil.isNotEmpty(metaObject.hasSetter(UPDATE_TIME))) {
+            setFieldValByName(UPDATE_TIME, new Date(), metaObject);
+        }
+    }
+
+    /**
+     * 获取用户id
+     */
+    private Long getUserUniqueId() {
+        try {
+            return SecurityUtils.getUserId();
+        } catch (Exception e) {
+            return -1L;
+        }
+    }
+
+}

+ 38 - 0
ruoyi-framework/src/main/java/com/ruoyi/framework/mybatis/holder/LogicHolder.java

@@ -0,0 +1,38 @@
+package com.ruoyi.framework.mybatis.holder;
+
+import lombok.Getter;
+import lombok.Setter;
+import org.springframework.core.NamedThreadLocal;
+
+/**
+ * 租户线程处理
+ */
+@Getter
+@Setter
+public class LogicHolder {
+
+    /**
+     * 排除表名
+     */
+    private String[] tableNames;
+
+    /**
+     * 排除表别名
+     */
+    private String[] aliases;
+
+    private static final ThreadLocal<LogicHolder> LOGIC_KEY_HOLDER = new NamedThreadLocal<>("saas-delFlag");
+
+    public static void setLogicHolder(LogicHolder logicHolder) {
+        LOGIC_KEY_HOLDER.set(logicHolder);
+    }
+
+    public static LogicHolder getLogicHolder() {
+        return LOGIC_KEY_HOLDER.get();
+    }
+
+    public static void clear() {
+        LOGIC_KEY_HOLDER.remove();
+    }
+
+}

+ 305 - 0
ruoyi-framework/src/main/java/com/ruoyi/framework/mybatis/interceptor/LogicInterceptor.java

@@ -0,0 +1,305 @@
+package com.ruoyi.framework.mybatis.interceptor;
+
+import cn.hutool.extra.spring.SpringUtil;
+import com.alibaba.druid.pool.DruidDataSource;
+import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
+import com.baomidou.mybatisplus.core.toolkit.ExceptionUtils;
+import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
+import com.baomidou.mybatisplus.core.toolkit.StringPool;
+import com.baomidou.mybatisplus.extension.plugins.inner.BaseMultiTableInnerInterceptor;
+import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
+import com.ruoyi.common.enums.DataSourceType;
+import com.ruoyi.framework.datasource.DynamicDataSourceContextHolder;
+import com.ruoyi.framework.mybatis.holder.LogicHolder;
+import lombok.extern.slf4j.Slf4j;
+import net.sf.jsqlparser.JSQLParserException;
+import net.sf.jsqlparser.expression.Alias;
+import net.sf.jsqlparser.expression.Expression;
+import net.sf.jsqlparser.expression.LongValue;
+import net.sf.jsqlparser.expression.StringValue;
+import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
+import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
+import net.sf.jsqlparser.expression.operators.relational.ItemsList;
+import net.sf.jsqlparser.expression.operators.relational.MultiExpressionList;
+import net.sf.jsqlparser.parser.CCJSqlParserUtil;
+import net.sf.jsqlparser.schema.Column;
+import net.sf.jsqlparser.schema.Table;
+import net.sf.jsqlparser.statement.delete.Delete;
+import net.sf.jsqlparser.statement.insert.Insert;
+import net.sf.jsqlparser.statement.select.*;
+import net.sf.jsqlparser.statement.update.Update;
+import org.apache.ibatis.executor.Executor;
+import org.apache.ibatis.executor.statement.StatementHandler;
+import org.apache.ibatis.mapping.BoundSql;
+import org.apache.ibatis.mapping.MappedStatement;
+import org.apache.ibatis.mapping.SqlCommandType;
+import org.apache.ibatis.session.ResultHandler;
+import org.apache.ibatis.session.RowBounds;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.stereotype.Component;
+
+import java.sql.Connection;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@Slf4j
+@Component
+public class LogicInterceptor extends BaseMultiTableInnerInterceptor implements InnerInterceptor {
+
+    /**
+     * 逻辑删除字段名
+     */
+    private static final String delFlagName = "del_flag";
+
+    /**
+     * 未删除值
+     */
+    private static final Expression notDelValue = new LongValue(0);
+
+    /**
+     * 已删除值
+     */
+    private static final Expression delValue = new LongValue(1);
+
+    /**
+     * 包含逻辑删除字段的库表集合
+     * key: 数据源名称
+     * value: 包含逻辑删除字段的表名集合
+     */
+    private static final Map<String, List<String>> notIncludeLogicIdTableNameMap = new HashMap<>();
+
+    public LogicInterceptor() {
+        // 获取主库数据源
+        DruidDataSource dataSource = SpringUtil.getBean("masterDataSource");
+        putTableMap(dataSource, DataSourceType.MASTER);
+
+    }
+
+    /**
+     * 添加不存在租户字段的表map
+     *
+     * @param dataSource     数据源
+     * @param dataSourceType 数据源类型
+     */
+    private void putTableMap(DruidDataSource dataSource, DataSourceType dataSourceType) {
+        // 获取链接url
+        String url = dataSource.getUrl();
+        // 获取数据库名
+        String dbName = url.split("/")[3].split("\\?")[0];
+        // 查询不包含租户字段的表名
+        String sql = "SELECT DISTINCT table_name FROM information_schema.COLUMNS WHERE table_schema = ? AND column_name != ?";
+        // 获取jdbc
+        JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
+        List<String> notIncludeTenantIdTableNameList = jdbcTemplate.queryForList(sql, String.class, dbName, delFlagName);
+        notIncludeLogicIdTableNameMap.put(dataSourceType.name(), notIncludeTenantIdTableNameList);
+    }
+
+    @Override
+    public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
+        PluginUtils.MPBoundSql mpBs = PluginUtils.mpBoundSql(boundSql);
+        mpBs.sql(parserSingle(mpBs.sql(), null));
+    }
+
+    @Override
+    public void beforePrepare(StatementHandler sh, Connection connection, Integer transactionTimeout) {
+        PluginUtils.MPStatementHandler mpSh = PluginUtils.mpStatementHandler(sh);
+        MappedStatement ms = mpSh.mappedStatement();
+        SqlCommandType sct = ms.getSqlCommandType();
+        // 处理新增编辑
+        if (sct == SqlCommandType.INSERT || sct == SqlCommandType.UPDATE) {
+            PluginUtils.MPBoundSql mpBs = mpSh.mPBoundSql();
+            mpBs.sql(parserMulti(mpBs.sql(), null));
+        }
+        // 处理删除
+        if (sct == SqlCommandType.DELETE) {
+            PluginUtils.MPBoundSql mpBs = mpSh.mPBoundSql();
+            String sql = mpBs.sql();
+            try {
+                Delete delete = (Delete) CCJSqlParserUtil.parse(sql);
+                Table table = delete.getTable();
+                if (isSkip(table)) {
+                    return;
+                }
+
+                sql = "UPDATE " + table.getName() + " SET " + delFlagName + " = " + delValue;
+
+                Expression where = delete.getWhere();
+                if (where != null) {
+                    sql += " WHERE " + where;
+                }
+                mpBs.sql(sql);
+            } catch (JSQLParserException e) {
+                throw new RuntimeException(e);
+            }
+        }
+    }
+
+    @Override
+    protected void processSelect(Select select, int index, String sql, Object obj) {
+        final String whereSegment = (String) obj;
+        processSelectBody(select.getSelectBody(), whereSegment);
+        List<WithItem> withItemsList = select.getWithItemsList();
+        if (!CollectionUtils.isEmpty(withItemsList)) {
+            withItemsList.forEach(withItem -> processSelectBody(withItem, whereSegment));
+        }
+    }
+
+    @Override
+    protected void processInsert(Insert insert, int index, String sql, Object obj) {
+        if (isSkip(insert.getTable())) {
+            // 过滤退出执行
+            return;
+        }
+        List<Column> columns = insert.getColumns();
+        if (CollectionUtils.isEmpty(columns)) {
+            // 针对不给列名的insert 不处理
+            return;
+        }
+        if (columns.stream().map(Column::getColumnName).anyMatch(i -> i.equalsIgnoreCase(delFlagName))) {
+            // 针对已给出逻辑删除列的insert 不处理
+            return;
+        }
+        columns.add(new Column(delFlagName));
+
+        // fixed gitee pulls/141 duplicate update
+        List<Expression> duplicateUpdateColumns = insert.getDuplicateUpdateExpressionList();
+        if (CollectionUtils.isNotEmpty(duplicateUpdateColumns)) {
+            EqualsTo equalsTo = new EqualsTo();
+            equalsTo.setLeftExpression(new StringValue(delFlagName));
+            equalsTo.setRightExpression(notDelValue);
+            duplicateUpdateColumns.add(equalsTo);
+        }
+
+        Select select = insert.getSelect();
+        if (select != null) {
+            this.processInsertSelect(select.getSelectBody(), (String) obj);
+        } else if (insert.getItemsList(ItemsList.class) != null) {
+            ItemsList itemsList = insert.getItemsList(ItemsList.class);
+            if (itemsList instanceof MultiExpressionList) {
+                ((MultiExpressionList) itemsList).getExpressionLists().forEach(el -> el.getExpressions().add(notDelValue));
+            } else {
+                ((ExpressionList) itemsList).getExpressions().add(notDelValue);
+            }
+        } else {
+            throw ExceptionUtils.mpe("Failed to process multiple-table update, please exclude the tableName or statementId");
+        }
+    }
+
+    /**
+     * update 语句处理
+     */
+    @Override
+    protected void processUpdate(Update update, int index, String sql, Object obj) {
+        final Table table = update.getTable();
+        if (isSkip(table)) {
+            // 过滤退出执行
+            return;
+        }
+        update.setWhere(this.andExpression(table, update.getWhere(), (String) obj));
+    }
+
+    @Override
+    public Expression buildTableExpression(Table table, Expression where, String whereSegment) {
+        if (isSkip(table)) {
+            return null;
+        }
+        return new EqualsTo(new Column(getAliasColumn(table)), notDelValue);
+    }
+
+    /**
+     * 处理 insert into select
+     * <p>
+     * 进入这里表示需要 insert 的表启用了多租户,则 select 的表都启动了
+     *
+     * @param selectBody SelectBody
+     */
+    protected void processInsertSelect(SelectBody selectBody, final String whereSegment) {
+        PlainSelect plainSelect = (PlainSelect) selectBody;
+        FromItem fromItem = plainSelect.getFromItem();
+        if (fromItem instanceof Table) {
+            processPlainSelect(plainSelect, whereSegment);
+            appendSelectItem(plainSelect.getSelectItems());
+        } else if (fromItem instanceof SubSelect) {
+            SubSelect subSelect = (SubSelect) fromItem;
+            appendSelectItem(plainSelect.getSelectItems());
+            processInsertSelect(subSelect.getSelectBody(), whereSegment);
+        }
+    }
+
+    /**
+     * 追加 SelectItem
+     *
+     * @param selectItems SelectItem
+     */
+    protected void appendSelectItem(List<SelectItem> selectItems) {
+        if (CollectionUtils.isEmpty(selectItems)) {
+            return;
+        }
+        if (selectItems.size() == 1) {
+            SelectItem item = selectItems.get(0);
+            if (item instanceof AllColumns || item instanceof AllTableColumns) {
+                return;
+            }
+        }
+        selectItems.add(new SelectExpressionItem(new Column(delFlagName)));
+    }
+
+    /**
+     * 逻辑删除字段别名设置
+     * <p>
+     * del_flag 或 tableAlias.del_flag
+     * </p>
+     *
+     * @param table 表对象
+     * @return 字段
+     */
+    private String getAliasColumn(Table table) {
+        StringBuilder column = new StringBuilder();
+        if (table.getAlias() != null) {
+            column.append(table.getAlias().getName()).append(StringPool.DOT);
+        }
+        column.append(delFlagName);
+        return column.toString();
+    }
+
+    /**
+     * 是否跳过执行
+     */
+    private boolean isSkip(Table table) {
+
+        LogicHolder logicHolder = LogicHolder.getLogicHolder();
+        String name = table.getName();
+
+        if (logicHolder != null) {
+
+            String[] tableNames = logicHolder.getTableNames();
+            String[] aliases = logicHolder.getAliases();
+
+            if (tableNames.length == 0 && aliases.length == 0) {
+                return true;
+            }
+
+            if (Arrays.asList(tableNames).contains(name)) {
+                return true;
+            }
+
+            Alias alias = table.getAlias();
+            if (alias != null) {
+                if (Arrays.asList(aliases).contains(alias.getName())) {
+                    return true;
+                }
+            }
+
+        }
+
+        // 执行sql的数据源名称
+        String dataSourceType = DynamicDataSourceContextHolder.getDataSourceType();
+        // 获取数据源中不包含逻辑删除字段的表名
+        List<String> tableNameList = notIncludeLogicIdTableNameMap.get(dataSourceType);
+        // 如果包涵则跳过拼接逻辑删除
+        return tableNameList.contains(name);
+    }
+
+}

+ 125 - 0
ruoyi-framework/src/main/java/com/ruoyi/framework/mybatis/interceptor/SqlLogInterceptor.java

@@ -0,0 +1,125 @@
+package com.ruoyi.framework.mybatis.interceptor;
+
+import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.ibatis.executor.statement.StatementHandler;
+import org.apache.ibatis.mapping.BoundSql;
+import org.apache.ibatis.mapping.MappedStatement;
+import org.apache.ibatis.mapping.ParameterMapping;
+import org.apache.ibatis.mapping.ParameterMode;
+import org.apache.ibatis.plugin.Interceptor;
+import org.apache.ibatis.plugin.Intercepts;
+import org.apache.ibatis.plugin.Invocation;
+import org.apache.ibatis.plugin.Signature;
+import org.apache.ibatis.reflection.MetaObject;
+import org.apache.ibatis.reflection.SystemMetaObject;
+import org.apache.ibatis.session.Configuration;
+import org.apache.ibatis.session.ResultHandler;
+import org.apache.ibatis.type.TypeHandlerRegistry;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+import java.sql.Statement;
+import java.text.DateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * 打印执行的sql日志
+ */
+@Intercepts({
+        @Signature(type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class}),
+        @Signature(type = StatementHandler.class, method = "update", args = {Statement.class}),
+        @Signature(type = StatementHandler.class, method = "batch", args = {Statement.class})
+})
+@Slf4j
+@Component
+public class SqlLogInterceptor implements Interceptor {
+
+    @Value("${spring.profiles.active}")
+    private String active;
+
+    @Override
+    public Object intercept(Invocation invocation) throws Throwable {
+        if (!"dev".equals(active)) {
+            return invocation.proceed();
+        }
+
+        long start = System.currentTimeMillis();
+        Object result = invocation.proceed();
+        long end = System.currentTimeMillis();
+
+        StatementHandler statementHandler = PluginUtils.realTarget(invocation.getTarget());
+        MetaObject metaObject = SystemMetaObject.forObject(statementHandler);
+        MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
+        BoundSql boundSql = (BoundSql) metaObject.getValue("delegate.boundSql");
+        String sql = boundSql.getSql().replaceAll("\\s+", " ").toLowerCase();
+
+        List<ParameterMapping> parameterMappings = new ArrayList<>(boundSql.getParameterMappings());
+        Object parameterObject = boundSql.getParameterObject();
+        if (parameterMappings.isEmpty() && parameterObject == null) {
+            return result;
+        }
+
+        Configuration configuration = mappedStatement.getConfiguration();
+        TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
+
+        try {
+            String parameter = "null";
+            MetaObject newMetaObject = configuration.newMetaObject(parameterObject);
+            for (ParameterMapping parameterMapping : parameterMappings) {
+                if (parameterMapping.getMode() == ParameterMode.OUT) {
+                    continue;
+                }
+                String propertyName = parameterMapping.getProperty();
+                if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
+                    parameter = getParameterValue(parameterObject);
+                } else if (newMetaObject.hasGetter(propertyName)) {
+                    parameter = getParameterValue(newMetaObject.getValue(propertyName));
+                } else if (boundSql.hasAdditionalParameter(propertyName)) {
+                    parameter = getParameterValue(boundSql.getAdditionalParameter(propertyName));
+                }
+
+                sql = sql.replaceFirst("\\?", parameter);
+            }
+
+            // 打印 sql
+            log.info("\n====================  Sql Start  ===================="
+                            + "\n mapper       : {}"
+                            + "\n execute sql  : {}"
+                            + "\n execute time : {} ms"
+                            + "\n====================  Sql  End   ====================\n",
+                    mappedStatement.getId(), sql, end - start);
+        } catch (Exception e) {
+            log.error(String.format("intercept sql error: [%s]", sql), e);
+        }
+
+        return result;
+    }
+
+    /**
+     * 获取参数
+     *
+     * @param obj Object类型参数
+     * @return 转换之后的参数
+     */
+    private String getParameterValue(Object obj) {
+        String value;
+        if (obj instanceof String) {
+            value = "'" + obj + "'";
+        } 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.replace("$", "\\$");
+    }
+
+}

+ 2 - 2
ruoyi-framework/src/main/java/com/ruoyi/framework/mybatis/interceptor/MyTenantInterceptor.java → ruoyi-framework/src/main/java/com/ruoyi/framework/mybatis/interceptor/TenantInterceptor.java

@@ -18,13 +18,13 @@ import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 
 @Component
-public class MyTenantInterceptor extends TenantLineInnerInterceptor {
+public class TenantInterceptor extends TenantLineInnerInterceptor {
 
     private static final String TenantName = "tenant_id";
 
     private static final Map<String, List<String>> notIncludeTenantIdTableNameMap = new ConcurrentHashMap<>();
 
-    public MyTenantInterceptor() {
+    public TenantInterceptor() {
 
         // 获取主库数据源
         DruidDataSource dataSource = SpringUtil.getBean("masterDataSource");