.增强了系统的可扩展性和可伸缩性,支持水平扩展,每个租户的数据和资源均可管理和控制。
@RestController public class TenantController { @GetMapping("/hello") public String hello(@RequestHeader("tenant-id") String tenantId) { return "Hello, " + tenantId; } }1.2 Spring Cloud
.为每个租户创建单独的数据库,每个数据库内的表结构相同,但数据相互隔离。
CREATE TABLE tenant ( id BIGINT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(50) NOT NULL UNIQUE, description VARCHAR(255), created_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP );4.2 租户权限控制
@EnableGlobalMethodSecurity(prePostEnabled = true) @Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/api/tenant/**").hasRole("ADMIN") .anyRequest().authenticated() .and() .formLogin(); } @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService()) .passwordEncoder(new BCryptPasswordEncoder()) .and() .inMemoryAuthentication() .withUser("admin") .password(new BCryptPasswordEncoder().encode("123456")) .roles("ADMIN"); } }三、技术实现
多数据源是指为不同的租户配置不同的数据源,使得每个租户都可以访问自己的独立数据。具体实现方法如下:
// 堆代码 duidaima.com @Configuration public class DataSourceConfig { @Bean(name = "dataSourceA") @ConfigurationProperties(prefix = "spring.datasource.a") public DataSource dataSourceA() { return DataSourceBuilder.create().build(); } @Bean(name = "dataSourceB") @ConfigurationProperties(prefix = "spring.datasource.b") public DataSource dataSourceB() { return DataSourceBuilder.create().build(); } @Bean(name = "dataSourceC") @ConfigurationProperties(prefix = "spring.datasource.c") public DataSource dataSourceC() { return DataSourceBuilder.create().build(); } }以上代码是配置了三个数据源分别对应三个租户。然后在使用时,可以使用注解标记需要连接的数据源。
@Service public class ProductService { @Autowired @Qualifier("dataSourceA") private DataSource dataSource; // ... }1.2 动态路由实现
public class DynamicDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { return TenantContextHolder.getTenantId(); } } @Configuration public class DataSourceConfig { @Bean(name = "dataSource") @ConfigurationProperties(prefix = "spring.datasource") public DataSource dataSource() { return DataSourceBuilder.create().type(DynamicDataSource.class).build(); } }以上是动态路由的核心代码DynamicDataSource继承自AbstractRoutingDataSource,通过determineCurrentLookupKey()方法动态获得租户ID,然后切换到对应的数据源。
<!-- Spring Boot --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- Spring Cloud --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>2020.0.3</version> <type>pom</type> <scope>import</scope> </dependency>然后需要在application.yml中配置相应的参数,如下所示:
spring: datasource: url: jdbc:mysql://localhost:3306/appdb?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai username: root password: 123456 mybatis: type-aliases-package: com.example.demo.model mapper-locations: classpath:mapper/*.xml server: port: 8080 eureka: client: serviceUrl: defaultZone: http://localhost:8761/eureka/ management: endpoints: web: exposure: include: "*"其中datasource.url为数据库连接的URL,username和password为数据库连接的账号和密码;server.port为Spring Boot应用启动的端口;eureka.client.serviceUrl.defaultZone为Eureka服务注册中心的URL。
@Configuration public class MultiTenantConfig { // 提供对应租户的数据源 @Bean public DataSource dataSource(TenantRegistry tenantRegistry) { return new TenantAwareDataSource(tenantRegistry); } // 多租户Session工厂 @Bean(name = "sqlSessionFactory") public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception { SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean(); sessionFactory.setDataSource(dataSource); return sessionFactory.getObject(); } // 动态切换租户 @Bean public MultiTenantInterceptor multiTenantInterceptor(TenantResolver tenantResolver) { MultiTenantInterceptor interceptor = new MultiTenantInterceptor(); interceptor.setTenantResolver(tenantResolver); return interceptor; } // 注册拦截器 @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(multiTenantInterceptor()); } // 注册租户信息 @Bean public TenantRegistry tenantRegistry() { return new TenantRegistryImpl(); } // 解析租户ID @Bean public TenantResolver tenantResolver() { return new HeaderTenantResolver(); } }其中MultiTenantConfig是多租户部署的核心配置类,它提供了对应租户数据源、多租户Session工厂、动态切换租户等功能。