一、配置多租户
TenantLineInnerInterceptor
是 MyBatis-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:觉得可以请帮忙点赞收藏,万分感谢🫡🫡🫡