MyBatis-Plus如何优雅的配置多租户及分页

一、配置多租户

TenantLineInnerInterceptorMyBatis-Plus 提供的一个插件,用于实现多租户的数据隔离。通过这个插件,可以确保每个租户只能访问自己的数据,从而实现数据的安全隔离。

1、步骤一

通过实现 TenantLineHandler 接口,处理租户相关的逻辑:

import com.baomidou.mybatisplus.core.parser.ISqlParser;
import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.tenant.TenantHandler;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.util.List;

@Component
public class CustomTenantHandler implements TenantHandler {

    @Override
    public Expression getTenantId() {
        // 假设有一个租户上下文,能够从中获取当前用户的租户
         Long tenantId = TenantContextHolder.getCurrentTenantId();
        // 返回租户ID的表达式
        return tenantId;
    }

    @Override
    public String getTenantIdColumn() {
        return "tenant_id";
    }

    @Override
    public boolean ignoreTable(String tableName) {
        // 根据需要返回是否忽略该表
        return false;
    }

    @Override
    public void setProperties(Properties properties) {
        // 可以设置一些属性
    }
}

2、步骤二

但在实际应用场景中会存在部分表不分租户,这时需要忽略租户过滤指定表,常规情况下我们可能会定义一个常量数据来判断,但这样不方便维护,可以通过反射判断当前表是否存在租户id字段,以此忽略租户隔离:

/**
 * <p>
 * 租户处理器
 * </p>
 *
 * @author 瞄你个汪
 * @since 2024/6/2 16:15
 */
@Slf4j
@Component
public class TableTenantLineHandler implements TenantLineHandler {
    @Autowired
    private AppConfiguration appConfig;
    
	...
	
    @Override
    public boolean ignoreTable(String tableName) {
        return Strings.isEmpty(tableName) || appConfig.notHasTenantColumn(tableName, getTenantIdColumn());
    }
    ...
}

3、步骤三

判断表是否存在租户字段:


/**
 * <p>
 *  应用配置类
 * </p>
 * @author 瞄你个汪
 * @since 2024/5/29 12:55
 */
@Data
@Component
public class AppConfiguration {
	...
	// 这里我使用了SpringBoot Cache缓存
    @Cacheable(value="system", key="#methodName + ':' + #tableName")
    public boolean notHasTenantColumn(String tableName, String tenantId) {
    	// TableInfoHelper 为MyBatis-Plus中的工具类
        List<TableInfo> tableInfos = TableInfoHelper.getTableInfos();
        for (TableInfo table : tableInfos) {
            if (tableName.equals(table.getTableName())) {
                return table.getFieldList().stream().noneMatch(x -> tenantId.equals(x.getColumn()));
            }
        }
        return false;
    }
}

步骤四

注入租户处理器

/**
 * <p>
 * MybatisPlus 配置
 * </p>
 * @author 瞄你个汪
 * @since 2024/6/2 16:30
 */
@Configuration
@MapperScan("top.xxxx.mapper")
public class MybatisPlusConfig {
    @Autowired
    private TableTenantLineHandler tableTenantLineHandler;

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        TenantLineInnerInterceptor tenantInterceptor = new TenantLineInnerInterceptor();
        tenantInterceptor.setTenantLineHandler(tableTenantLineHandler);
        interceptor.addInnerInterceptor(tenantInterceptor);

        return interceptor;
    }
}

二、配置分页

1、步骤一

编写通用分页查询接口输入参数类:

/**
 * <p>
 * Http 分页查询输入参数
 * </p>
 * @author 瞄你个汪
 * @since 2024-05-01 12:00:00
 */
@Data
public class PageInput<S> {
    /**
     * 查询条件
     */
    private S search;
    /**
     * 页容量
     */
    @Min(value = 1, message = "页容量不能小于1")
    private long size = 10;
    /**
     * 当前页
     */
    @Min(value = 1, message = "当前页不能小于1")
    private long page = 1;
    /**
     * 排序字段
     */
    private List<OrderItem> orders;

    public <R> IPage<R> getPage() {
        Page<R> page = new Page<>(this.page, size);
        page.setOrders(orders);
        page.setMaxLimit(500L);
        return page;
    }

    /**
     * 传递分页参数并设置查询记录
     */
    public <R> IPage<R> reader(Function<IPage<R>, List<R>> func) {
        IPage<R> page = getPage();
        return page.setRecords(func.apply(page));
    }

    /**
     * 传递分页参数并设置查询记录
     */
    public <R> IPage<R> reader(BiFunction<IPage<R>, QueryWrapper<R>, List<R>> func) {
        IPage<R> page = getPage();
        return page.setRecords(func.apply(page, new QueryWrapper<>()));
    }
}

2、步骤二

注入分页插件:

/**
 * <p>
 * MybatisPlus 配置
 * </p>
 * @author 瞄你个汪
 * @since 2024/6/2 16:30
 */
@Configuration
@MapperScan("top.xxxx.mapper")
public class MybatisPlusConfig {
    @Autowired
    private TableTenantLineHandler tableTenantLineHandler;

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
		...

        // 分页配置, 这行配置必须放最后
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }
}

3、步骤三

使用:

/**
 * <p>
 * 系统用户表 前端控制器
 * </p>
 * @author 瞄你个汪
 * @since 2024-01-01 12:00:00
 */
@Api(tags = "⚙ 系统用户管理")
@RestController()
@RequestMapping("/api/sysUser")
public class SysUserController {
    private final ISysUserService service;

    @Autowired
    public SysUserController(SysUserServiceImpl _service) {
        service = _service;
    }

    /**
     * <p>
     *  分页查询系统用户数据
     * </p>
     * @param page 分页参数
     * @return 查询结果
     */
    @PostMapping("page")
    @ApiOperation(value = "分页查询", notes = "分页查询系统用户数据")
    public Result<IPage<SysUser>> queryByPage(@Validated @RequestBody PageInput<String> page) {
        String search = page.getSearch();
        boolean notEmpty = Strings.isNotEmpty(search);
        return Result.ok(page.reader((fPage, query) -> {
            query.lambda().like(notEmpty, SysUser::getMobile, search)
                    .like(notEmpty, SysUser::getAccount, search)
                    .like(notEmpty, SysUser::getRealName, search)
                    .like(notEmpty, SysUser::getNickName, search);
            return this.service.list(fPage, query);
        }));
    }
    ...
}

没有过滤条件:

	@PostMapping("page")
    @ApiOperation(value = "分页查询", notes = "分页查询系统用户数据")
    public Result<IPage<SysUser>> queryByPage(@Validated @RequestBody PageInput<String> page) {
        return Result.ok(page.reader((_page, query) -> this.service.list(_page, query)));
    }

PS:觉得可以请帮忙点赞收藏,万分感谢🫡🫡🫡

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值