1.创建一个数据表(包含租户ID字段–tenant_id)
CREATE TABLE `t_sys_user` (
`user_id` bigint(50) NOT NULL AUTO_INCREMENT COMMENT '用户ID',
`user_name` varchar(30) NOT NULL COMMENT '用户名',
`user_password` varchar(128) NOT NULL COMMENT '用户密码',
`salt` varchar(64) DEFAULT NULL COMMENT '加密盐',
`user_phone` varchar(20) DEFAULT NULL COMMENT '手机号',
`user_email` varchar(20) DEFAULT NULL COMMENT '邮箱',
`user_title` varchar(20) DEFAULT NULL COMMENT '职称',
`creater_id` bigint(20) DEFAULT NULL COMMENT '创建人ID',
`creater_name` varchar(30) DEFAULT NULL COMMENT '创建人名称',
`creater_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updater_id` bigint(20) DEFAULT NULL COMMENT '更新人ID',
`updater_name` varchar(30) DEFAULT NULL COMMENT '更新人名称',
`updater_time` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`role_ids` varchar(200) DEFAULT NULL,
`role_names` varchar(300) DEFAULT NULL,
`del_tag` int(1) DEFAULT '0' COMMENT '逻辑删除(0 未删除,1 已删除)',
`version` bigint(20) DEFAULT '1',
`tenant_id` bigint(20) NOT NULL COMMENT '服务商ID',
PRIMARY KEY (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=37 DEFAULT CHARSET=utf8;
2.编写一个缓存租户id的类:
@Component
public class ApiContext {
private static final String KEY_CURRENT_TENANT_ID = "KEY_CURRENT_TENANT_ID";
private static final Map<String, Object> mContext = new ConcurrentHashMap<>();
public void setCurrentTenantId(Long providerId) {
mContext.put(KEY_CURRENT_TENANT_ID, providerId);
}
public Long getCurrentTenantId() {
return (Long) mContext.get(KEY_CURRENT_TENANT_ID);
}
}
3.在分页的时候,自动在sql拼接租户id的字段作为where后面的条件,具体在mybatis-plus的配置类里面配置,如下:
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
import com.lz.hehuorenservice.common.component.ApiContext;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.LongValue;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@EnableTransactionManagement
@Configuration
public class MybatisPlusConfig {
@Autowired
private ApiContext apiContext;
// 最新版 分页插件
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
//注册乐观锁插件
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
/**
* 新多租户插件配置,一缓和二缓遵循mybatis的规则,需要设置 MybatisConfiguration#useDeprecatedExecutor = false 避免缓存万一出现问题
*/
interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(new TenantLineHandler() {
@Override
public Expression getTenantId() {
// 从当前系统上下文中取出当前请求的服务商ID,通过解析器注入到SQL中。
Long currentProviderId = apiContext.getCurrentTenantId();
if (null == currentProviderId) {
throw new RuntimeException("Get CurrentProviderId error.");
}
return new LongValue(currentProviderId);
}
// 这是 default 方法,默认返回 false 表示所有表都需要拼多租户条件
@Override
public boolean ignoreTable(String tableName) {
return false;
// return "t_sys_user".equalsIgnoreCase(tableName);
}
}));
return interceptor;
}
}
4.在登录的时候获取到用户的租户ID,并缓存到第二步的类里面,提供给第三步的mybatis-plus的配置类使用,本人是整合在spring security里面,所以在用户的实现类的loadUserByUsername方法里面,获取到用户信息后,把租户id缓存到第二步的类里面。如下配置:
@Service
public class UserServiceImpl extends BaseServiceImpl<User, Long>
implements UserService, UserDetailsService {
@Autowired UserDao userDao;
@Autowired
private ApiContext apiContext;
@Override
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
User user = userDao.getUserByName(userName);
apiContext.setCurrentTenantId(user.getTenantId());
if (user == null) {
throw new UsernameNotFoundException("账户不存在");
}
Set<String> permissions = userDao.getPermissionByUserId(user.getUserId());
user.setPermissions(permissions);
return user;
}
}
5.需要单独排除登录的接口不需要拼接租户id。因为登录之前是无法获取到租户id的,只有在登录成功后,才能获取到租户id,并进行租户id的缓存。所以要在mapper的方法里面的登录方法上,加一个过滤方法的注释@InterceptorIgnore(tenantLine = “1”)。如下:
@Mapper
public interface UserDao extends BaseDao<User, Long> {
@InterceptorIgnore(tenantLine = "1")
User getUserByName(String userName);
}
完成以上5个步骤,就可以实现多租户的功能。
6.mybatis-plus版本说明:
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.3.4</version>
</dependency>
7.期间遇到个问题:
1.使用mybatis-plus的自带的分页的方法查询分页,count(*)的sql统计语句无法拼接上租户id。只有自己实现的分页方法才自动拼接上租户id。(如果路过的高手知道怎么实现,请多多指点)。