1 Star 0 Fork 29

柚子 / notebook

forked from JustryDeng / notebook 
加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
[06]mybatis-plus多租户.md 31.78 KB
一键复制 编辑 原始数据 按行查看 历史
JustryDeng 提交于 2023-02-23 17:03 . mybatis-plus多租户

mybatis-plus多租

简述

注意:

  • 默认情况下,在insert时,若主动指定了租户列的值,那么租户插件将不会自动填充,最终生效的是主动指定的租户值
  • 默认情况下,在delete、update、select时,where条件之后无论是否主动设置了租户的筛选值,mybatis-plus的租户条件都会再追加一个租户的条件筛选

使用非常简单,只需要在注册MybatisPlusInterceptor的时候,设置添加mybatis-plus的内部多租户插件TenantLineInnerInterceptor即可

TenantLineInnerInterceptor配置示例

import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.LongValue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author miemie
 * @since 2018-08-10
 */
@Configuration
public class MybatisPlusConfig {

    /**
     * 新多租户插件配置,一缓和二缓遵循mybatis的规则,需要设置 MybatisConfiguration#useDeprecatedExecutor = false 避免缓存万一出现问题
     * 
     * 注:高版本mybatis-plus移除了useDeprecatedExecutor字段
     */
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(new TenantLineHandler() {
            @Override
            public Expression getTenantId() {
                // 返回租户id
                return new LongValue(1);
            }
    
            @Override
            public String getTenantIdColumn() {
                return "tenant_id";
            }
    
            // 这是 default 方法,默认返回 false 表示所有表都需要拼多租户条件
            @Override
            public boolean ignoreTable(String tableName) {
                return !"user".equalsIgnoreCase(tableName);
            }
        }));
        // 如果用了分页插件注意先 add TenantLineInnerInterceptor 再 add PaginationInnerInterceptor
        // interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
        return interceptor;
    }
}

TenantLineHandler说明

我们在配置TenantLineInnerInterceptor时,需要构造TenantLineHandler对象,这里对TenantLineHandler进行方法说明

接口说明

import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.schema.Column;

import java.util.List;

/**
 * 租户处理器( TenantId 行级 )
 *
 * @author hubin
 * @since 3.4.0
 */
public interface TenantLineHandler {

    /**
     * 获取租户 ID 值表达式,只支持单个 ID 值
     * <p>
     *
     * @return 租户 ID 值表达式
     */
    Expression getTenantId();

    /**
     * 获取租户字段名
     * <p>
     * 默认字段名叫: tenant_id
     *
     * @return 租户字段名
     */
    default String getTenantIdColumn() {
        return "tenant_id";
    }

    /**
     * 根据表名判断是否忽略拼接多租户条件
     * <p>
     * 默认都要进行解析并拼接多租户条件
     *
     * @param tableName 表名
     * @return 是否忽略, true:表示忽略,false:需要解析并拼接多租户条件
     */
    default boolean ignoreTable(String tableName) {
        return false;
    }

    /**
     * 忽略插入租户字段逻辑
     *
     * @param columns        插入字段
     * @param tenantIdColumn 租户 ID 字段
     * @return
     */
    default boolean ignoreInsert(List<Column> columns, String tenantIdColumn) {
        return columns.stream().map(Column::getColumnName).anyMatch(i -> i.equalsIgnoreCase(tenantIdColumn));
    }
}

实现示例

@Override
public Expression getTenantId() {
    // 租户id应该根据当前用户,动态获取; 这里简单演示,写死为1
    return new LongValue(1);
}

@Override
public String getTenantIdColumn() {
    // 数据库表中,代表租户id的列名
    return "jd_tenant_id";
}

@Override
public boolean ignoreTable(String tableName) {
    // 只要表名不是user的表,都不做多租户区分, 都忽略
    return !"user".equalsIgnoreCase(tableName);
}

使用多租户插件后的CURD说明

这里通过代码上的注释,进行说明

import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
import com.baomidou.mybatisplus.samples.tenant.entity.User;
import com.baomidou.mybatisplus.samples.tenant.mapper.UserMapper;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import javax.annotation.Resource;
import java.util.List;

/**
 * <p>
 * 多租户 Tenant 演示
 * </p>
 *
 * @author hubin
 * @since 2018-08-11
 */
@SpringBootTest
public class TenantTest {
    @Resource
    private UserMapper mapper;
    
    /**
     * 新增的时候,如果该表 没有被
     * {@link TenantLineHandler#ignoreTable(java.lang.String)}
     * 忽略,那么会自动插入租户id
     */
    @Test
    public void aInsert() {
        User user = new User();
        user.setName("一一");
        Assertions.assertTrue(mapper.insert(user) > 0);
        user = mapper.selectById(user.getId());
        Assertions.assertTrue(1 == user.getTenantId());
    }
    
    
    /**
     * 删除的时候,如果该表 没有被
     * {@link TenantLineHandler#ignoreTable(java.lang.String)}
     * 忽略,那么会在where后自动追加租户id判等条件
     */
    @Test
    public void bDelete() {
        Assertions.assertTrue(mapper.deleteById(3L) > 0);
    }
    
    /**
     * 更新的时候,如果该表 没有被
     * {@link TenantLineHandler#ignoreTable(java.lang.String)}
     * 忽略,那么会在where后自动追加租户id判等条件
     */
    @Test
    public void cUpdate() {
        Assertions.assertTrue(mapper.updateById(new User().setId(1L).setName("mp")) > 0);
    }
    
    /**
     * 查询的时候,如果该表 没有被
     * {@link TenantLineHandler#ignoreTable(java.lang.String)}
     * 忽略,那么会在where后自动追加租户id判等条件
     */
    @Test
    public void dSelect() {
        List<User> userList = mapper.selectList(null);
        userList.forEach(u -> Assertions.assertTrue(1 == u.getTenantId()));
    }

    /**
     * 自定义SQL:默认也会增加多租户条件
     * 参考打印的SQL
     */
    @Test
    public void manualSqlTenantFilterTest() {
        System.out.println(mapper.myCount());
    }

    /**
     *
     * 1. 多张表联表查询的时候,如果from表没有被
     * {@link TenantLineHandler#ignoreTable(java.lang.String)}
     *  略,那么会在where后面 and加 租户过滤 条件
     *  如(addr_name表被忽略, user表没有被忽略): SELECT u.id, u.name, a.name AS addr_name FROM user u LEFT JOIN user_addr a ON a.user_id = u.id WHERE u.name LIKE concat(concat('%', ?), '%') AND u.tenant_id = 1
     *
     * 2.多张表联表查询的时候,如果被join表没有被
     * {@link TenantLineHandler#ignoreTable(java.lang.String)}
     *  略,那么会在在 joins...on..后面加on后面 and加 租户过滤 条件
     * 如(addr_name表被忽略, user表没有被忽略): SELECT a.name AS addr_name, u.id, u.name FROM user_addr a LEFT JOIN user u ON u.id = a.user_id AND u.tenant_id = 1
     */
    @Test
    public void testTenantFilter(){
        mapper.getAddrAndUser(null).forEach(System.out::println);
        mapper.getAddrAndUser("add").forEach(System.out::println);
        mapper.getUserAndAddr(null).forEach(System.out::println);
        mapper.getUserAndAddr("J").forEach(System.out::println);
    }
}

扩展实现租户字段的like查询

默认的,租户字段是通过判等进行过滤的;在有些场景下右模糊查询可能更贴合业务场景写

第一步:自定义TenantLineInnerInterceptor

import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
import net.sf.jsqlparser.expression.BinaryExpression;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.Parenthesis;
import net.sf.jsqlparser.expression.StringValue;
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.expression.operators.conditional.OrExpression;
import net.sf.jsqlparser.expression.operators.relational.LikeExpression;
import net.sf.jsqlparser.schema.Table;

import java.util.List;
import java.util.stream.Collectors;

/**
 * 租户字段的like实现(只需适配update、delete、select)
 *
 * @author JustryDeng
 * @since 2022/9/18 17:41:50
 */
public class LikeTenantLineInnerInterceptor extends TenantLineInnerInterceptor {
    
    TenantLineHandler tenantLineHandler;
    
    public LikeTenantLineInnerInterceptor(TenantLineHandler tenantLineHandler) {
        super(tenantLineHandler);
        this.tenantLineHandler = tenantLineHandler;
    }
    
    /**
     * 适配update和delete
     */
    @Override
    protected BinaryExpression andExpression(Table table, Expression where) {
        final StringValue tenantId = (StringValue)tenantLineHandler.getTenantId();
        final String tenantIdValue = tenantId.getValue();
        LikeExpression likeExpression = new LikeExpression();
        likeExpression.setLeftExpression(this.getAliasColumn(table));
        likeExpression.setRightExpression(new StringValue(tenantIdValue + "%"));
        if (null != where) {
            if (where instanceof OrExpression) {
                return new AndExpression(likeExpression, new Parenthesis(where));
            } else {
                return new AndExpression(likeExpression, where);
            }
        }
        return likeExpression;
    }
    
    
    /**
     * 适配elect
     */
    @Override
    protected Expression builderExpression(Expression currentExpression, List<Table> tables) {
        // 没有表需要处理直接返回
        if (CollectionUtils.isEmpty(tables)) {
            return currentExpression;
        }
        // 构造每张表的条件
        List<Table> tempTables = tables.stream()
                .filter(x -> !tenantLineHandler.ignoreTable(x.getName()))
                .collect(Collectors.toList());
        
        // 没有表需要处理直接返回
        if (CollectionUtils.isEmpty(tempTables)) {
            return currentExpression;
        }
    
        final StringValue tenantIdStrVal = (StringValue)tenantLineHandler.getTenantId();
        final String tenantIdValue = tenantIdStrVal.getValue();
        List<LikeExpression> likeExpressions = tempTables.stream()
                .map(item -> {
                    LikeExpression likeExpression = new LikeExpression();
                    likeExpression.setLeftExpression(this.getAliasColumn(item));
                    likeExpression.setRightExpression(new StringValue(tenantIdValue + "%"));
                    return likeExpression;
                })
                .collect(Collectors.toList());
        
        // 注入的表达式
        Expression injectExpression = likeExpressions.get(0);
        // 如果有多表,则用 and 连接
        if (likeExpressions.size() > 1) {
            for (int i = 1; i < likeExpressions.size(); i++) {
                injectExpression = new AndExpression(injectExpression, likeExpressions.get(i));
            }
        }
        
        if (currentExpression == null) {
            return injectExpression;
        }
        if (currentExpression instanceof OrExpression) {
            return new AndExpression(new Parenthesis(currentExpression), injectExpression);
        } else {
            return new AndExpression(currentExpression, injectExpression);
        }
    }
}

第二步:配置租户插件

import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.StringValue;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@MapperScan("com.baomidou.mybatisplus.samples.tenant.mapper")
public class MybatisPlusConfig {

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new LikeTenantLineInnerInterceptor(new TenantLineHandler() {
            @Override
            public Expression getTenantId() {
                return new StringValue("1,a");
            }
            @Override
            public boolean ignoreTable(String tableName) {
                return !"user".equalsIgnoreCase(tableName);
            }
        }));
        return interceptor;
    }

}

实战示例

总体配置

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
import com.ideaaedi.commonds.function.NoArgConsumer;
import com.ideaaedi.commonds.function.NoArgFunction;
import com.ideaaedi.commonspring.spel.SpelUtil;
import com.ultronsoft.port.acs.oauth2.server.entity.po.SysUserPO;
import com.ultronsoft.port.acs.oauth2.server.util.ClassUtil;
import com.ultronsoft.port.acs.oauth2.server.util.ThreadLocalUtil;
import lombok.Getter;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.expression.BinaryExpression;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.Parenthesis;
import net.sf.jsqlparser.expression.StringValue;
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.expression.operators.conditional.OrExpression;
import net.sf.jsqlparser.expression.operators.relational.LikeExpression;
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.Select;
import net.sf.jsqlparser.statement.update.Update;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.reflection.MetaObject;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;

import javax.annotation.PostConstruct;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.stream.Collectors;

/**
 * mybatis-plus 配置
 *
 * @author JustryDeng
 * @since 2022/9/7 13:32:55
 */
@Slf4j
@Configuration
@EnableAspectJAutoProxy
public class MybatisPlusConfig {
    
    private final Set<String> tenantTables = new HashSet<>();
    
    @PostConstruct
    private void initTenantTables() {
        List<String> poClassLongNameList = new ArrayList<>(64);
        poClassLongNameList.addAll(
                ClassUtil.getClazzName("com.ultronsoft.port.acs.oauth2.server.entity.po", true)
        );
        poClassLongNameList.addAll(
                ClassUtil.getClazzName("com.ultronsoft.port.acs.visitor.entity.po", true)
        );
        try {
            for (String poClassLongName : poClassLongNameList) {
                Class<?> clazz = Class.forName(poClassLongName);
                TableName tableNameAnno = clazz.getDeclaredAnnotation(TableName.class);
                if (tableNameAnno == null) {
                    continue;
                }
                String tableName = tableNameAnno.value();
                boolean existTenantColumn = true;
                try {
                    clazz.getDeclaredField("tenantId");
                } catch (NoSuchFieldException e) {
                    existTenantColumn = false;
                }
                if (existTenantColumn) {
                    tenantTables.add(tableName);
                }
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        log.info("tenantTables -> {}", JSON.toJSONString(tenantTables));
    }
    
    /**
     * 插件配置
     */
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new LikeTenantLineInnerInterceptor(new TenantLineHandler() {
            @Override
            public Expression getTenantId() {
                TenantBO tenantBo = ThreadLocalUtil.TENANT_HOLDER.get();
                if (tenantBo != null && tenantBo.getTenantDeque().size() > 0) {
                    String tenant = tenantBo.getTenantDeque().peekFirst();
                    if (tenant != null) {
                        return new StringValue(tenant);
                    }
                }
                SysUserPO sysUser = ThreadLocalUtil.CURR_USER.get();
                Objects.requireNonNull(sysUser, "LikeTenantLineInnerInterceptor tips: sysUser cannot be null.");
                return new StringValue(sysUser.getCurrTenant());
            }
            
            @Override
            public String getTenantIdColumn() {
                return "tenant_id";
            }
            
            @Override
            public boolean ignoreTable(String tableName) {
                return !tenantTables.contains(tableName);
            }
        }));
        
        // 分页插件
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }
    
    /**
     * 设置字段自动填充 <br /> <br />
     * <a href="https://baomidou.com/pages/4c6bcf/">官网</a>
     *
     * @author JustryDeng
     * @since 2022/9/6 17:10:30
     */
    @Slf4j
    @Component
    public static class MybatisPlusMetaObjectHandler implements MetaObjectHandler {
        
        @Autowired(required = false)
        OperatorIdProvider operatorIdProvider;
        
        @Override
        public void insertFill(MetaObject metaObject) {
            Long userId = operatorIdProvider == null ? null : operatorIdProvider.operatorId(metaObject);
            // 这里是PO的字段名而不是表的列名
            this.strictInsertFill(metaObject, "createdBy", Long.class, userId);
            this.strictInsertFill(metaObject, "createdAt", LocalDateTime.class, LocalDateTime.now());
        }
        
        @Override
        public void updateFill(MetaObject metaObject) {
            Long userId = operatorIdProvider == null ? null : operatorIdProvider.operatorId(metaObject);
            // 这里是PO的字段名而不是表的列名
            this.strictUpdateFill(metaObject, "updatedBy", Long.class, userId);
            this.strictUpdateFill(metaObject, "updatedAt", LocalDateTime.class, LocalDateTime.now());
        }
        
        /**
         * 数据操作人id提供器
         *
         * @author kuoyi
         * @since 1.2.0
         */
        public interface OperatorIdProvider {
            
            /**
             * 获取数据操作人id
             *
             * @param metaObject
             *         当前mybatis sql操作元数据
             *
             * @return 数据操作人id
             */
            @Nullable
            default Long operatorId(MetaObject metaObject) {
                SysUserPO user = ThreadLocalUtil.CURR_USER.get();
                if (user == null) {
                    return null;
                }
                return user.getId();
            }
        }
    }
    
    /**
     * 多租户 like插件
     */
    public static class LikeTenantLineInnerInterceptor extends TenantLineInnerInterceptor {
        
        TenantLineHandler tenantLineHandler;
        
        public LikeTenantLineInnerInterceptor(TenantLineHandler tenantLineHandler) {
            super(tenantLineHandler);
            this.tenantLineHandler = tenantLineHandler;
        }
        @Override
        protected void processInsert(Insert insert, int index, String sql, Object obj) {
            super.processInsert(insert, index, sql, obj);
        }
    
        @Override
        protected void processDelete(Delete delete, int index, String sql, Object obj) {
            if (shouldIgnoreTenant()) {
                return;
            }
            super.processDelete(delete, index, sql, obj);
        }
    
        @Override
        protected void processUpdate(Update update, int index, String sql, Object obj) {
            if (shouldIgnoreTenant()) {
                return;
            }
            super.processUpdate(update, index, sql, obj);
        }
    
        @Override
        protected void processSelect(Select select, int index, String sql, Object obj) {
            if (shouldIgnoreTenant()) {
                return;
            }
            super.processSelect(select, index, sql, obj);
        }
    
        /**
         * 是否忽略租户<br />
         * 逻辑执行在mp自己的判断是否忽略租户的逻辑之前
         */
        private boolean shouldIgnoreTenant() {
            TenantBO tenantBo = ThreadLocalUtil.TENANT_HOLDER.get();
            if (tenantBo != null && tenantBo.getTenantDeque().size() > 0) {
                String tenant = tenantBo.getTenantDeque().peekFirst();
                return TenantBO.IGNORE_URD.equals(tenant);
            }
            return false;
        }
    
        /**
         * 适配update和delete
         */
        @Override
        protected BinaryExpression andExpression(Table table, Expression where) {
            final StringValue tenantId = (StringValue) tenantLineHandler.getTenantId();
            final String tenantIdValue = tenantId.getValue();
            LikeExpression likeExpression = new LikeExpression();
            likeExpression.setLeftExpression(this.getAliasColumn(table));
            likeExpression.setRightExpression(new StringValue(tenantIdValue + "%"));
            if (null != where) {
                if (where instanceof OrExpression) {
                    return new AndExpression(likeExpression, new Parenthesis(where));
                } else {
                    return new AndExpression(likeExpression, where);
                }
            }
            return likeExpression;
        }
        
        
        /**
         * 适配select
         */
        @Override
        protected Expression builderExpression(Expression currentExpression, List<Table> tables) {
            // 没有表需要处理直接返回
            if (CollectionUtils.isEmpty(tables)) {
                return currentExpression;
            }
            // 构造每张表的条件
            List<Table> tempTables = tables.stream()
                    .filter(x -> !tenantLineHandler.ignoreTable(x.getName()))
                    .collect(Collectors.toList());
            
            // 没有表需要处理直接返回
            if (CollectionUtils.isEmpty(tempTables)) {
                return currentExpression;
            }
            
            final StringValue tenantIdStrVal = (StringValue) tenantLineHandler.getTenantId();
            final String tenantIdValue = tenantIdStrVal.getValue();
            List<LikeExpression> likeExpressions = tempTables.stream()
                    .map(item -> {
                        LikeExpression likeExpression = new LikeExpression();
                        likeExpression.setLeftExpression(this.getAliasColumn(item));
                        likeExpression.setRightExpression(new StringValue(tenantIdValue + "%"));
                        return likeExpression;
                    })
                    .collect(Collectors.toList());
            
            // 注入的表达式
            Expression injectExpression = likeExpressions.get(0);
            // 如果有多表,则用 and 连接
            if (likeExpressions.size() > 1) {
                for (int i = 1; i < likeExpressions.size(); i++) {
                    injectExpression = new AndExpression(injectExpression, likeExpressions.get(i));
                }
            }
            
            if (currentExpression == null) {
                return injectExpression;
            }
            if (currentExpression instanceof OrExpression) {
                return new AndExpression(new Parenthesis(currentExpression), injectExpression);
            } else {
                return new AndExpression(currentExpression, injectExpression);
            }
        }
    }
    
    /**
     * 临时租户实体类模型支持
     */
    @Getter
    @ToString
    public static class TenantBO {
        
        /**
         * 特殊标识, 当为CURD中的UPDATE READ DELETE时,忽略租户
         */
        public static final String IGNORE_URD = "<IGNORE_TENANT_WHILE_URD>";
        
        /** 队列 */
        @Getter
        private final ConcurrentLinkedDeque<String> tenantDeque = new ConcurrentLinkedDeque<>();
        
        public TenantBO(String tenant) {
            tenantDeque.addFirst(tenant);
        }
    }
    
    /**
     * 临时租户指定支持
     */
    public static class TemporaryTenantProvider {
        
        public <R> R exec(NoArgFunction<R> function, String tenantId) {
            try {
                Assert.isTrue(StringUtils.isNotBlank(tenantId), "tenantId cannot be blank.");
                TenantBO tenantBO = ThreadLocalUtil.TENANT_HOLDER.get();
                if (tenantBO == null) {
                    ThreadLocalUtil.TENANT_HOLDER.set(new TenantBO(tenantId));
                } else {
                    tenantBO.getTenantDeque().addFirst(tenantId);
                }
                return function.apply();
            } finally {
                TenantBO tenantBO = ThreadLocalUtil.TENANT_HOLDER.get();
                if (tenantBO != null) {
                    tenantBO.getTenantDeque().pollFirst();
                    if (tenantBO.getTenantDeque().size() <= 0) {
                        ThreadLocalUtil.TENANT_HOLDER.remove();
                    }
                }
            }
        }
        
        public void voidExec(NoArgConsumer consumer, String tenantId) {
            try {
                Assert.isTrue(StringUtils.isNotBlank(tenantId), "tenantId cannot be blank.");
                TenantBO tenantBO = ThreadLocalUtil.TENANT_HOLDER.get();
                if (tenantBO == null) {
                    ThreadLocalUtil.TENANT_HOLDER.set(new TenantBO(tenantId));
                } else {
                    tenantBO.getTenantDeque().addFirst(tenantId);
                }
                consumer.accept();
            } finally {
                TenantBO tenantBO = ThreadLocalUtil.TENANT_HOLDER.get();
                if (tenantBO != null) {
                    tenantBO.getTenantDeque().pollFirst();
                    if (tenantBO.getTenantDeque().size() <= 0) {
                        ThreadLocalUtil.TENANT_HOLDER.remove();
                    }
                }
            }
        }
        
        @Target(value = ElementType.METHOD)
        @Retention(value = RetentionPolicy.RUNTIME)
        public @interface Tenant {
            
            /**
             * 租户 (支持spel) <br /> spel示例:"#{#user.id + ':' + #user.username}"
             */
            String value() default "";
            
            /**
             * 租户(当为空时,获取value的值作为租户)
             */
            String tenantSpel() default "";
            
        }
        
        @Slf4j
        @Aspect
        @Component
        @Order(Ordered.HIGHEST_PRECEDENCE + 90)
        public static class TenantAop {
            
            @Around("@annotation(tenantAnno)")
            public Object aroundAdvice(ProceedingJoinPoint thisJoinPoint, TemporaryTenantProvider.Tenant tenantAnno) throws Throwable {
                String tenant = tenantAnno.value();
                Method method = ((MethodSignature) thisJoinPoint.getSignature()).getMethod();
                Object[] arguments = thisJoinPoint.getArgs();
                if (StringUtils.isBlank(tenant)) {
                    String tenantSpel = tenantAnno.tenantSpel();
                    tenant = SpelUtil.parseSpel(method, arguments, String.class, tenantSpel);
                }
                log.info("TenantAop curr tenant is {}", tenant);
                try {
                    Assert.isTrue(StringUtils.isNotBlank(tenant), "tenant cannot be blank.");
                    TenantBO tenantBO = ThreadLocalUtil.TENANT_HOLDER.get();
                    if (tenantBO == null) {
                        ThreadLocalUtil.TENANT_HOLDER.set(new TenantBO(tenant));
                    } else {
                        tenantBO.getTenantDeque().addFirst(tenant);
                    }
                    return thisJoinPoint.proceed();
                } finally {
                    TenantBO tenantBO = ThreadLocalUtil.TENANT_HOLDER.get();
                    if (tenantBO != null) {
                        tenantBO.getTenantDeque().pollFirst();
                        if (tenantBO.getTenantDeque().size() <= 0) {
                            ThreadLocalUtil.TENANT_HOLDER.remove();
                        }
                    }
                }
            }
        }
    }
}

其中ThreadLocal类

import com.ultronsoft.port.unid.oauth2.server.config.MybatisPlusConfig;
import com.ultronsoft.port.unid.oauth2.server.entity.po.SysUserPO;

/**
 * (non-javadoc)
 *
 * @author JustryDeng
 * @since 2022/9/6 22:15:09
 */
public class ThreadLocalUtil {
    
    /** 当前用户 */
    public static final ThreadLocal<SysUserPO> CURR_USER = new ThreadLocal<>();
    
    /**
     * 线程级租户id
     */
    public static final ThreadLocal<MybatisPlusConfig.TenantBO> TENANT_HOLDER = new ThreadLocal<>();
}

相关资料

1
https://gitee.com/WY784755850/notebook.git
git@gitee.com:WY784755850/notebook.git
WY784755850
notebook
notebook
master

搜索帮助