├── .gitignore
├── LICENSE
├── README.md
├── architecture.png
├── build.gradle
├── buildSrc
├── build.gradle
├── settings.gradle
└── src
│ └── main
│ └── java
│ └── cc
│ └── sofastframework
│ └── gradle
│ ├── JavaConventionsPlugin.java
│ ├── SofastModulePlugin.java
│ └── optional
│ └── OptionalDependenciesPlugin.java
├── dependencies
└── build.gradle
├── document
├── .gitkeep
└── v1.0.md
├── example
├── build.gradle
└── src
│ ├── main
│ ├── java
│ │ └── cc
│ │ │ └── sofast
│ │ │ └── example
│ │ │ ├── ExampleApp.java
│ │ │ ├── bean
│ │ │ └── Account.java
│ │ │ ├── extension
│ │ │ ├── AccountExtPt.java
│ │ │ ├── DefaultAccountExtPt.java
│ │ │ ├── TenantT2AccountExtPt.java
│ │ │ └── TenantT3AccountExtPt.java
│ │ │ ├── mapper
│ │ │ └── AccountMapper.java
│ │ │ ├── rest
│ │ │ ├── AccountCacheController.java
│ │ │ ├── AccountController.java
│ │ │ ├── CustomizationController.java
│ │ │ ├── DynamicScriptController.java
│ │ │ ├── ExtensionController.java
│ │ │ ├── SchemaSyncController.java
│ │ │ ├── TenantAccountConverter.groovy
│ │ │ └── TenantEventController.java
│ │ │ └── service
│ │ │ ├── AccountService.java
│ │ │ ├── AccountServiceImpl.java
│ │ │ └── SqlSync.java
│ └── resources
│ │ ├── application.yml
│ │ └── example.md
│ └── test
│ └── java
│ └── cc
│ └── sofast
│ └── example
│ └── ExampleAppTest.java
├── extend
├── dynamic-rest-sql
│ ├── README.md
│ ├── build.gradle
│ ├── img.png
│ └── src
│ │ └── main
│ │ ├── java
│ │ └── cc
│ │ │ └── sofast
│ │ │ └── infrastructure
│ │ │ └── restsql
│ │ │ └── RestSqlConfiguration.java
│ │ └── resources
│ │ └── META-INF
│ │ ├── spring.factories
│ │ └── spring
│ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports
├── extension-point-plugin
│ ├── README.md
│ ├── build.gradle
│ └── src
│ │ ├── main
│ │ ├── java
│ │ │ └── cc
│ │ │ │ └── sofast
│ │ │ │ └── infrastructure
│ │ │ │ ├── customization
│ │ │ │ ├── ApplicationContextHelper.java
│ │ │ │ ├── ComposeCustomizationLoader.java
│ │ │ │ ├── Cons.java
│ │ │ │ ├── CustomizationLoader.java
│ │ │ │ ├── CustomizationProperties.java
│ │ │ │ ├── DynamicScriptModel.java
│ │ │ │ ├── PersistentCustomizationLoader.java
│ │ │ │ ├── ScriptExecutor.java
│ │ │ │ ├── TCustomizationModel.java
│ │ │ │ ├── TKey.java
│ │ │ │ ├── TenantCustomization.java
│ │ │ │ ├── TenantCustomizationConfiguration.java
│ │ │ │ ├── TenantDynamicScriptExecutor.java
│ │ │ │ ├── TenantDynamicScriptExecutorConfiguration.java
│ │ │ │ ├── customization.sql
│ │ │ │ ├── db
│ │ │ │ │ ├── JdbcCustomizationLoader.java
│ │ │ │ │ └── StandardSQLCustomizationLoader.java
│ │ │ │ ├── file
│ │ │ │ │ └── FileCustomizationLoader.java
│ │ │ │ ├── mem
│ │ │ │ │ └── MemCustomizationLoader.java
│ │ │ │ ├── notify
│ │ │ │ │ ├── TenantCustomizationEventChanger.java
│ │ │ │ │ ├── TenantCustomizationEventConfiguration.java
│ │ │ │ │ └── TenantCustomizationEventListener.java
│ │ │ │ ├── redis
│ │ │ │ │ └── RedisCustomizationLoader.java
│ │ │ │ └── script
│ │ │ │ │ ├── EngineExecutorResult.java
│ │ │ │ │ ├── ExecutionStatus.java
│ │ │ │ │ ├── Script.java
│ │ │ │ │ ├── aviator
│ │ │ │ │ └── Aviator.java
│ │ │ │ │ └── groovy
│ │ │ │ │ ├── Groovy.java
│ │ │ │ │ ├── GroovyDemo.groovy
│ │ │ │ │ ├── GroovyKey.java
│ │ │ │ │ ├── GroovyScriptEntry.java
│ │ │ │ │ ├── Md5Script.java
│ │ │ │ │ └── ScriptTemplate.java
│ │ │ │ ├── extension
│ │ │ │ ├── BizScenario.java
│ │ │ │ ├── Extension.java
│ │ │ │ ├── ExtensionAutoConfiguration.java
│ │ │ │ ├── ExtensionCoordinate.java
│ │ │ │ ├── ExtensionException.java
│ │ │ │ ├── ExtensionPointI.java
│ │ │ │ ├── ExtensionRepository.java
│ │ │ │ ├── Extensions.java
│ │ │ │ ├── TenantExtensionExecutor.java
│ │ │ │ └── register
│ │ │ │ │ ├── AbstractComponentExecutor.java
│ │ │ │ │ ├── ExtensionBootstrap.java
│ │ │ │ │ └── ExtensionRegister.java
│ │ │ │ └── func
│ │ │ │ ├── Fun.java
│ │ │ │ ├── FunctionContextUtils.java
│ │ │ │ ├── FunctionTypeUtils.java
│ │ │ │ ├── TenantBizFun.java
│ │ │ │ ├── TenantBizFuncConfiguration.java
│ │ │ │ ├── TenantBizFuncExecutor.java
│ │ │ │ ├── TenantFunctionInvocationWrapper.java
│ │ │ │ └── TenantFunctionRegistration.java
│ │ └── resources
│ │ │ └── META-INF
│ │ │ ├── spring.factories
│ │ │ └── spring
│ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports
│ │ └── test
│ │ ├── java
│ │ └── cc
│ │ │ └── sofast
│ │ │ └── infrastructure
│ │ │ └── customization
│ │ │ └── TenantDynamicScriptExecutorTest.java
│ │ └── resources
│ │ └── t_script.json
├── template-schema-sync
│ ├── README.md
│ ├── build.gradle
│ └── src
│ │ ├── main
│ │ ├── java
│ │ │ └── cc
│ │ │ │ └── sofast
│ │ │ │ └── infrastructure
│ │ │ │ └── jdbc
│ │ │ │ └── schema
│ │ │ │ ├── Schema.java
│ │ │ │ ├── SchemaInfo.java
│ │ │ │ ├── TenantSchemaAutoconfiguration.java
│ │ │ │ ├── TenantSchemaProperties.java
│ │ │ │ ├── diff
│ │ │ │ ├── Diff.java
│ │ │ │ ├── SchemaDiff.java
│ │ │ │ └── diff.md
│ │ │ │ ├── exec
│ │ │ │ ├── Exec.java
│ │ │ │ └── JdbcTemplateExec.java
│ │ │ │ ├── postgresql
│ │ │ │ ├── PostgresqlCommand.java
│ │ │ │ ├── pgdump
│ │ │ │ │ └── PgDump.java
│ │ │ │ └── pgrestore
│ │ │ │ │ └── PgRestore.java
│ │ │ │ ├── shell
│ │ │ │ ├── AbstractShell.java
│ │ │ │ └── ShellExecutor.java
│ │ │ │ ├── sync
│ │ │ │ └── TenantDDLSync.java
│ │ │ │ └── utils
│ │ │ │ ├── OSUtils.java
│ │ │ │ ├── PgDumpUtils.java
│ │ │ │ └── TzxUtils.java
│ │ └── resources
│ │ │ ├── META-INF
│ │ │ ├── spring.factories
│ │ │ └── spring
│ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports
│ │ │ └── postgresql
│ │ │ ├── postgres-darwin-x86_64.txz
│ │ │ └── postgres-linux-x86_64.txz
│ │ └── test
│ │ └── java
│ │ └── cc
│ │ └── sofast
│ │ └── infrastructure
│ │ └── jdbc
│ │ └── schema
│ │ ├── SchemaTest.java
│ │ ├── diff
│ │ └── SchemaDiffTest.java
│ │ └── postgresql
│ │ └── pgdump
│ │ └── PgDumpTest.java
└── tenant-data-validator
│ ├── README.md
│ └── build.gradle
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── settings.gradle
└── starter
├── build.gradle
└── src
├── main
├── java
│ └── cc
│ │ └── sofast
│ │ └── infrastructure
│ │ └── tenant
│ │ ├── TenantBizExecutor.java
│ │ ├── TenantBizService.java
│ │ ├── TenantContextHolder.java
│ │ ├── TenantHelper.java
│ │ ├── aot
│ │ └── TenantStarterRuntimeHints.java
│ │ ├── autoconfigure
│ │ └── TenantAutoconfiguration.java
│ │ ├── datasource
│ │ ├── DataSourceCreatorAutoconfiguration.java
│ │ ├── DataSourceProperty.java
│ │ ├── DsType.java
│ │ ├── SeataMode.java
│ │ ├── TenantDataSourceProperties.java
│ │ ├── TenantDataSourcePropertiesCustomizer.java
│ │ ├── TenantDataSourceProxy.java
│ │ ├── TenantDataSourceRegister.java
│ │ ├── TenantDynamicRoutingDataSource.java
│ │ ├── connection
│ │ │ ├── TenantConnectionProxy.java
│ │ │ └── TenantDatabaseMetaDataPorxy.java
│ │ ├── creator
│ │ │ ├── AbstractDataSourceCreator.java
│ │ │ ├── DataSourceCreator.java
│ │ │ ├── DefaultDataSourceCreator.java
│ │ │ ├── HikariDataSourceCreator.java
│ │ │ └── JndiDataSourceCreator.java
│ │ └── provider
│ │ │ ├── AbstractTenantDataSourceProvider.java
│ │ │ ├── JdbcTenantDataSourceProvider.java
│ │ │ ├── PropertiesTenantDataSourceProvider.java
│ │ │ ├── RestTenantDataSourceProvider.java
│ │ │ └── TenantDataSourceProvider.java
│ │ ├── exception
│ │ ├── TenantException.java
│ │ └── TenantNotFoundException.java
│ │ ├── notify
│ │ ├── TenancyNotifyAutoConfiguration.java
│ │ ├── TenantEvent.java
│ │ ├── TenantEventListenerErrorHandler.java
│ │ ├── TenantEventNotifyProperties.java
│ │ ├── TenantEventProcess.java
│ │ ├── TenantEventType.java
│ │ └── TenantNotify.java
│ │ ├── propagation
│ │ ├── FeignTenantPropagationInterceptor.java
│ │ ├── PropagationConfiguration.java
│ │ ├── PropagationProperties.java
│ │ ├── RestTemplateTenantPropagationInterceptor.java
│ │ └── RestTemplateTenantPropagationInterceptorAfterPropertiesSet.java
│ │ ├── redis
│ │ ├── RedisTemplateCustomizer.java
│ │ ├── TenantPrefixStringRedisSerializer.java
│ │ └── TenantRedisConfiguration.java
│ │ ├── resolver
│ │ ├── FixedTenantResolver.java
│ │ ├── SystemPropertiesTenantResolver.java
│ │ ├── TenantResolver.java
│ │ ├── TenantResolverConfiguration.java
│ │ ├── TenantResolverProperties.java
│ │ ├── Type.java
│ │ ├── WebType.java
│ │ ├── http
│ │ │ ├── AbstractHttpRequestTenantResolver.java
│ │ │ ├── CookieTenantResolver.java
│ │ │ ├── HeaderTenantResolver.java
│ │ │ ├── HttpRequestTenantResolver.java
│ │ │ ├── RequestPathTenantResolver.java
│ │ │ └── SubdomainTenantResolver.java
│ │ └── webfilter
│ │ │ ├── TenantContextFilter.java
│ │ │ └── match
│ │ │ ├── HttpPathPatterRequestIgnoreMatcher.java
│ │ │ └── TenantRequestIgnoreMatcher.java
│ │ └── support
│ │ ├── Const.java
│ │ └── ResolverEnvironmentPropertyUtils.java
└── resources
│ └── META-INF
│ ├── spring.factories
│ └── spring
│ ├── aot.factories
│ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports
└── test
├── java
└── cc
│ └── sofast
│ └── infrastructure
│ └── tenant
│ ├── ApplicationTests.java
│ └── StartupApp.java
└── resources
└── application.yml
/.gitignore:
--------------------------------------------------------------------------------
1 | HELP.md
2 | .gradle
3 | build/
4 | !gradle/wrapper/gradle-wrapper.jar
5 | !**/src/main/**/build/
6 | !**/src/test/**/build/
7 |
8 | ### STS ###
9 | .apt_generated
10 | .classpath
11 | .factorypath
12 | .project
13 | .settings
14 | .springBeans
15 | .sts4-cache
16 | bin/
17 | !**/src/main/**/bin/
18 | !**/src/test/**/bin/
19 |
20 | ### IntelliJ IDEA ###
21 | .idea
22 | *.iws
23 | *.iml
24 | *.ipr
25 | out/
26 | !**/src/main/**/out/
27 | !**/src/test/**/out/
28 |
29 | ### NetBeans ###
30 | /nbproject/private/
31 | /nbbuild/
32 | /dist/
33 | /nbdist/
34 | /.nb-gradle/
35 |
36 | ### VS Code ###
37 | .vscode/
38 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## 简介
2 |
3 | `multi-tenancy-datasource-spring-boot-starter` 是一个基于`springboot`的多租户的开发神器. 包含了多租户场景下业务封装和组件抽象。
4 |
5 | ## 特性
6 |
7 | - 数据库隔离模型: 基于`Postgresql`的 `schema`,`database` 级别隔离
8 | - Redis隔离模型: 为每个key增加租户前缀
9 | - 租户标识解析: 从`Cookie`, `Header`, `Domain`,`URL Path`,`Env`中解析租户标识
10 | - 租户标识传递: 支持`Seata`, `Feign`, `RestTemplate`
11 | - 数据源: 多个租户复用数据库`Connection`减少资源消耗,也可使用独立数据源
12 | - 动态注册租户: 基于`Redis Stream`在运行期注册租户
13 | - `postgresql` 中表DDL对比和模版`Schema`,方便新增租户和查看其差异
14 | - 租户业务执行器: 设置和释放当前线程的租户上下文,执行定制业务功能. 满足定制化业务处理场景
15 | - 扩展cola组件,使用扩展点的方式扩展租户业务. 满足定制化业务处理场景
16 | - 抽象租户定制化配置类`TenantCustomization`,租户动态脚本引擎`.TenantDynamicScriptExecutor`满足定制化业务逻辑的开发。
17 | - 动态定义接口组件,满足定制化报表类,简单CRUD类,定制化数据列表类业务
18 |
19 | ## Redis
20 |
21 | - 为每个租户增加不同的key前缀来做数据隔离
22 |
23 | ## DB
24 |
25 | 
26 |
27 | ## 使用方式
28 |
29 | - Maven
30 |
31 | ```
32 |
33 | cc.sofast.infrastructure
34 | multi-tenancy-datasource-spring-boot-starter
35 | {lastverion}
36 |
37 | ```
38 |
39 | - Gradle
40 |
41 | ```
42 | implementation 'cc.sofast.infrastructure:multi-tenancy-datasource-spring-boot-starter:{lastverion}'
43 | ```
44 |
45 | - jdk17 启动时需要的参数
46 |
47 | ```
48 | --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.lang.invoke=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED --add-opens java.base/java.text=ALL-UNNAMED --add-opens java.desktop/java.awt.font=ALL-UNNAMED --add-opens java.base/java.nio=ALL-UNNAMED
49 | ```
--------------------------------------------------------------------------------
/architecture.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ccwxl/multi-tenancy-spring-boot-starter/ff2fcc0521d70f8706b59016ceeedd6856b09094/architecture.png
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | allprojects {
2 | group = 'cc.sofast.infrastructure'
3 | configurations.all {
4 | resolutionStrategy.cacheChangingModulesFor 0, "minutes"
5 | }
6 | }
--------------------------------------------------------------------------------
/buildSrc/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id('java-gradle-plugin')
3 | }
4 |
5 | description = "sofast dependency manager"
6 |
7 | group 'cc.sofast.dependency'
8 | version '1.0.0'
9 |
10 | repositories {
11 | mavenLocal()
12 | mavenCentral()
13 | maven { url 'https://maven.aliyun.com/nexus/content/groups/public/' }
14 | maven { url 'https://repo.spring.io/release' }
15 | maven { url 'https://repo.spring.io/milestone' }
16 | }
17 |
18 | dependencies {
19 | implementation('org.springframework.boot:org.springframework.boot.gradle.plugin:3.1.2')
20 | implementation('org.graalvm.buildtools.native:org.graalvm.buildtools.native.gradle.plugin:0.9.23')
21 | implementation('io.spring.gradle:dependency-management-plugin:1.1.0')
22 | implementation('io.freefair.lombok:io.freefair.lombok.gradle.plugin:8.0.1')
23 | }
24 |
25 | gradlePlugin {
26 | plugins {
27 | version {
28 | id = 'sofast.module.plugin'
29 | implementationClass = 'cc.sofastframework.gradle.SofastModulePlugin'
30 | displayName = "Dependency Manager Plugin"
31 | description = "Manage Dependent Versions"
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/buildSrc/settings.gradle:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | mavenCentral()
4 | gradlePluginPortal()
5 | }
6 | }
--------------------------------------------------------------------------------
/buildSrc/src/main/java/cc/sofastframework/gradle/optional/OptionalDependenciesPlugin.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2012-2021 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package cc.sofastframework.gradle.optional;
18 |
19 | import org.gradle.api.Plugin;
20 | import org.gradle.api.Project;
21 | import org.gradle.api.artifacts.Configuration;
22 | import org.gradle.api.plugins.JavaPlugin;
23 | import org.gradle.api.plugins.JavaPluginExtension;
24 | import org.gradle.api.tasks.SourceSetContainer;
25 |
26 | /**
27 | * A {@code Plugin} that adds support for Maven-style optional dependencies. Creates a new
28 | * {@code optional} configuration. The {@code optional} configuration is part of the
29 | * project's compile and runtime classpaths but does not affect the classpath of dependent
30 | * projects.
31 | *
32 | * @author Andy Wilkinson
33 | */
34 | public class OptionalDependenciesPlugin implements Plugin {
35 |
36 | /**
37 | * Name of the {@code optional} configuration.
38 | */
39 | public static final String OPTIONAL_CONFIGURATION_NAME = "optional";
40 |
41 | @Override
42 | public void apply(Project project) {
43 | Configuration optional = project.getConfigurations().create("optional");
44 | optional.setCanBeConsumed(false);
45 | optional.setCanBeResolved(false);
46 | project.getPlugins().withType(JavaPlugin.class, (javaPlugin) -> {
47 | SourceSetContainer sourceSets = project.getExtensions().getByType(JavaPluginExtension.class)
48 | .getSourceSets();
49 | sourceSets.all((sourceSet) -> {
50 | project.getConfigurations().getByName(sourceSet.getCompileClasspathConfigurationName())
51 | .extendsFrom(optional);
52 | project.getConfigurations().getByName(sourceSet.getRuntimeClasspathConfigurationName())
53 | .extendsFrom(optional);
54 | });
55 | });
56 | }
57 |
58 | }
59 |
--------------------------------------------------------------------------------
/dependencies/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'java-platform'
3 | }
4 |
5 | javaPlatform {
6 | allowDependencies()
7 | }
8 |
9 | ext {
10 | p6spyVersion = '3.9.1'
11 | seataVersion = '1.6.1'
12 | feignVersion = '12.1'
13 | mybatisPlusVersion = '3.5.3.1'
14 | }
15 |
16 | dependencies {
17 | constraints {
18 | api "p6spy:p6spy:$p6spyVersion"
19 | api "io.seata:seata-rm-datasource:$seataVersion"
20 | api "io.github.openfeign:feign-core:$feignVersion"
21 | api "com.baomidou:mybatis-plus-boot-starter:$mybatisPlusVersion"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/document/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ccwxl/multi-tenancy-spring-boot-starter/ff2fcc0521d70f8706b59016ceeedd6856b09094/document/.gitkeep
--------------------------------------------------------------------------------
/document/v1.0.md:
--------------------------------------------------------------------------------
1 | - 工程结构优化
2 |
3 | ```groovy
4 | rootProject.name = 'multi-tenancy-framework'
5 | include("dependencies")
6 | include("multi-tenancy-example") //综合案例,整合一下各个组件
7 | include("multi-tenancy-common") //异常,ThreadLocal
8 | include("multi-tenancy-trace") //trace 将tenant 信息放入到当前的日志trace中
9 | include("multi-tenancy-customization") //租户个性化配置项
10 | include("multi-tenancy-dynamic-script") //租户动态脚本执行器
11 | include("multi-tenancy-function") //租户定制化业务业务实现使用Function
12 | include("multi-tenancy-extension") //租户定制化业务扩展点
13 | include("multi-tenancy-schema-toolkit") // 对postgresql的schema 封装工具类
14 | include("multi-tenancy-resolver") //租户标识解析
15 | include("multi-tenancy-propagation") //租户传播
16 | include("multi-tenancy-iso-redis") //租户redis 中key隔离
17 | include("multi-tenancy-iso-database") //租户数据库隔离,行级别(PG RLS),schema,database
18 | include("multi-tenancy-authorization-server") //定制化authorization-server支持多租户认证,授权
19 | include("multi-tenancy-security") //多租户安全组件
20 | include("multi-tenancy-event-notify") //租户事件通知
21 | include("multi-tenancy-distributed-transaction") //微服务下多租户分布式事务问题
22 | include("multi-tenancy-spring-boot-starter") //starter 自动配置
23 | ```
24 |
--------------------------------------------------------------------------------
/example/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'sofast.module.plugin'
3 | }
4 |
5 | processAot {
6 | enabled(false)
7 | }
8 |
9 | processTestAot {
10 | enabled(false)
11 | }
12 |
13 | bootJar {
14 | enabled(false)
15 | }
16 |
17 | dependencies {
18 | implementation(project(":starter"))
19 | implementation(project(":extend:extension-point-plugin"))
20 | implementation(project(":extend:template-schema-sync"))
21 |
22 | implementation 'org.springframework.boot:spring-boot-starter-web'
23 | implementation 'org.springframework.boot:spring-boot-starter-jdbc'
24 | implementation 'org.springframework.boot:spring-boot-starter-data-redis'
25 | implementation 'org.springframework.boot:spring-boot-starter-actuator'
26 | implementation 'io.github.openfeign:feign-core'
27 | implementation 'org.postgresql:postgresql'
28 | implementation 'com.baomidou:mybatis-plus-boot-starter'
29 |
30 | annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
31 |
32 | testImplementation 'org.springframework.boot:spring-boot-starter-test'
33 | }
34 |
35 | tasks.named('test') {
36 | enabled(false)
37 | useJUnitPlatform()
38 | }
39 |
--------------------------------------------------------------------------------
/example/src/main/java/cc/sofast/example/ExampleApp.java:
--------------------------------------------------------------------------------
1 | package cc.sofast.example;
2 |
3 | import org.springframework.boot.SpringApplication;
4 | import org.springframework.boot.autoconfigure.SpringBootApplication;
5 | import org.springframework.cache.annotation.EnableCaching;
6 |
7 | /**
8 | * @author apple
9 | */
10 | @EnableCaching
11 | @SpringBootApplication
12 | public class ExampleApp {
13 |
14 | public static void main(String[] args) {
15 |
16 | SpringApplication.run(ExampleApp.class, args);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/example/src/main/java/cc/sofast/example/bean/Account.java:
--------------------------------------------------------------------------------
1 | package cc.sofast.example.bean;
2 |
3 | import com.baomidou.mybatisplus.annotation.IdType;
4 | import com.baomidou.mybatisplus.annotation.TableId;
5 | import com.baomidou.mybatisplus.annotation.TableName;
6 | import lombok.Data;
7 |
8 | /**
9 | * @author apple
10 | */
11 | @Data
12 | @TableName("account")
13 | public class Account {
14 |
15 | @TableId(type = IdType.AUTO)
16 | private Long id;
17 | public String nickname;
18 | public Long age;
19 | public String address;
20 |
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/example/src/main/java/cc/sofast/example/extension/AccountExtPt.java:
--------------------------------------------------------------------------------
1 | package cc.sofast.example.extension;
2 |
3 |
4 | import cc.sofast.example.bean.Account;
5 | import cc.sofast.infrastructure.extension.ExtensionPointI;
6 |
7 | public interface AccountExtPt extends ExtensionPointI {
8 |
9 | String BIZ_ID="account";
10 |
11 | String SCENARIO="converter";
12 |
13 | Account accountCalculate(Account account);
14 | }
15 |
--------------------------------------------------------------------------------
/example/src/main/java/cc/sofast/example/extension/DefaultAccountExtPt.java:
--------------------------------------------------------------------------------
1 | package cc.sofast.example.extension;
2 |
3 | import cc.sofast.example.bean.Account;
4 | import cc.sofast.infrastructure.extension.Extension;
5 | import org.springframework.context.ApplicationContext;
6 |
7 | @Extension(bizId = AccountExtPt.BIZ_ID, scenario = AccountExtPt.SCENARIO)
8 | public class DefaultAccountExtPt implements AccountExtPt {
9 |
10 | @Override
11 | public Account accountCalculate(Account account) {
12 |
13 | return null;
14 | }
15 |
16 | @Override
17 | public void registerBefore(ApplicationContext applicationContext) {
18 |
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/example/src/main/java/cc/sofast/example/extension/TenantT2AccountExtPt.java:
--------------------------------------------------------------------------------
1 | package cc.sofast.example.extension;
2 |
3 | import cc.sofast.example.bean.Account;
4 | import cc.sofast.infrastructure.extension.Extension;
5 | import org.springframework.context.ApplicationContext;
6 |
7 | @Extension(tenant = "t2", bizId = AccountExtPt.BIZ_ID, scenario = AccountExtPt.SCENARIO)
8 | public class TenantT2AccountExtPt implements AccountExtPt {
9 |
10 | @Override
11 | public Account accountCalculate(Account account) {
12 |
13 | return null;
14 | }
15 |
16 | @Override
17 | public void registerBefore(ApplicationContext applicationContext) {
18 |
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/example/src/main/java/cc/sofast/example/extension/TenantT3AccountExtPt.java:
--------------------------------------------------------------------------------
1 | package cc.sofast.example.extension;
2 |
3 | import cc.sofast.example.bean.Account;
4 | import cc.sofast.infrastructure.extension.Extension;
5 | import org.slf4j.Logger;
6 | import org.slf4j.LoggerFactory;
7 | import org.springframework.context.ApplicationContext;
8 |
9 | @Extension(tenant = "t3", bizId = AccountExtPt.BIZ_ID, scenario = AccountExtPt.SCENARIO)
10 | public class TenantT3AccountExtPt implements AccountExtPt {
11 |
12 | private static final Logger log = LoggerFactory.getLogger(TenantT3AccountExtPt.class);
13 |
14 | @Override
15 | public Account accountCalculate(Account account) {
16 | log.info("call t3 ext point.");
17 | account.setNickname("t3: " + account.getNickname());
18 | return account;
19 | }
20 |
21 | @Override
22 | public void registerBefore(ApplicationContext applicationContext) {
23 |
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/example/src/main/java/cc/sofast/example/mapper/AccountMapper.java:
--------------------------------------------------------------------------------
1 | package cc.sofast.example.mapper;
2 |
3 | import cc.sofast.example.bean.Account;
4 | import com.baomidou.mybatisplus.core.mapper.BaseMapper;
5 | import org.apache.ibatis.annotations.Mapper;
6 |
7 | /**
8 | * @author apple
9 | */
10 | @Mapper
11 | public interface AccountMapper extends BaseMapper {
12 |
13 | }
14 |
--------------------------------------------------------------------------------
/example/src/main/java/cc/sofast/example/rest/AccountCacheController.java:
--------------------------------------------------------------------------------
1 | package cc.sofast.example.rest;
2 |
3 | import cc.sofast.example.bean.Account;
4 | import cc.sofast.example.service.AccountService;
5 | import org.springframework.cache.annotation.CacheConfig;
6 | import org.springframework.cache.annotation.Cacheable;
7 | import org.springframework.web.bind.annotation.GetMapping;
8 | import org.springframework.web.bind.annotation.PathVariable;
9 | import org.springframework.web.bind.annotation.RequestMapping;
10 | import org.springframework.web.bind.annotation.RestController;
11 |
12 | /**
13 | * @author apple
14 | */
15 | @RestController
16 | @RequestMapping("cache")
17 | @CacheConfig(cacheNames = "account")
18 | public class AccountCacheController {
19 |
20 | private final AccountService accountService;
21 |
22 | public AccountCacheController(AccountService accountService) {
23 | this.accountService = accountService;
24 | }
25 |
26 |
27 | @Cacheable(value = "account", key = "#account", sync = true)
28 | @GetMapping("{account}")
29 | public Account getAccount(@PathVariable Long account) {
30 | Account accountServiceById = accountService.getById(account);
31 | return accountServiceById;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/example/src/main/java/cc/sofast/example/rest/AccountController.java:
--------------------------------------------------------------------------------
1 | package cc.sofast.example.rest;
2 |
3 | import cc.sofast.example.bean.Account;
4 | import cc.sofast.example.service.AccountService;
5 | import org.springframework.web.bind.annotation.*;
6 |
7 | /**
8 | * @author apple
9 | */
10 | @RestController
11 | @RequestMapping("account")
12 | public class AccountController {
13 |
14 | private final AccountService accountService;
15 |
16 | public AccountController(AccountService accountService) {
17 | this.accountService = accountService;
18 | }
19 |
20 | @PostMapping
21 | public Account addAccount(@RequestBody Account account) {
22 | accountService.save(account);
23 | return account;
24 | }
25 |
26 | @GetMapping("{account}")
27 | public Account getAccount(@PathVariable Long account) {
28 |
29 | return accountService.getById(account);
30 | }
31 |
32 | @PutMapping("{accountId}")
33 | public Account updateAccount(@PathVariable Long accountId, @RequestBody Account account) {
34 |
35 | return accountService.getById(accountId);
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/example/src/main/java/cc/sofast/example/rest/CustomizationController.java:
--------------------------------------------------------------------------------
1 | package cc.sofast.example.rest;
2 |
3 | import cc.sofast.example.bean.Account;
4 | import cc.sofast.infrastructure.customization.TKey;
5 | import cc.sofast.infrastructure.customization.TenantCustomization;
6 | import cc.sofast.infrastructure.tenant.TenantContextHolder;
7 | import org.springframework.beans.factory.annotation.Autowired;
8 | import org.springframework.web.bind.annotation.*;
9 |
10 | @RestController
11 | @RequestMapping("/customization")
12 | public class CustomizationController {
13 |
14 | @Autowired
15 | private TenantCustomization tenantCustomization;
16 |
17 | private static final String MACHINE_PROFILE = "MACHINE_PROFILE";
18 |
19 | @GetMapping()
20 | public Account getConfigItem() {
21 | return tenantCustomization.getVal(TKey.of(TenantContextHolder.peek(), MACHINE_PROFILE), Account.class);
22 | }
23 |
24 | @PutMapping
25 | public String updateConfigItem(@RequestBody Account account) {
26 | boolean b = tenantCustomization.saveOrUpdate(TKey.of(TenantContextHolder.peek(), MACHINE_PROFILE), account);
27 | return b ? "ok" : "failed";
28 | }
29 |
30 | @DeleteMapping
31 | public String deleteConfigItem() {
32 | boolean remove = tenantCustomization.remove(TKey.of(TenantContextHolder.peek(), MACHINE_PROFILE));
33 | return remove ? "ok" : "failed";
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/example/src/main/java/cc/sofast/example/rest/DynamicScriptController.java:
--------------------------------------------------------------------------------
1 | package cc.sofast.example.rest;
2 |
3 | import cc.sofast.example.bean.Account;
4 | import cc.sofast.infrastructure.customization.DynamicScriptModel;
5 | import cc.sofast.infrastructure.customization.TKey;
6 | import cc.sofast.infrastructure.customization.TenantCustomization;
7 | import cc.sofast.infrastructure.customization.TenantDynamicScriptExecutor;
8 | import cc.sofast.infrastructure.customization.script.EngineExecutorResult;
9 | import cc.sofast.infrastructure.tenant.TenantContextHolder;
10 | import org.springframework.beans.factory.annotation.Autowired;
11 | import org.springframework.web.bind.annotation.GetMapping;
12 | import org.springframework.web.bind.annotation.PostMapping;
13 | import org.springframework.web.bind.annotation.RequestMapping;
14 | import org.springframework.web.bind.annotation.RestController;
15 |
16 | import javax.tools.JavaCompiler;
17 | import java.util.HashMap;
18 | import java.util.Map;
19 |
20 | /**
21 | * @author apple
22 | */
23 | @RestController
24 | @RequestMapping("/script")
25 | public class DynamicScriptController {
26 |
27 | @Autowired
28 | private TenantDynamicScriptExecutor tenantDynamicScriptExecutor;
29 |
30 | @Autowired
31 | private TenantCustomization tenantCustomization;
32 |
33 | private static final String ACCOUNT_CONVERTER = "ACCOUNT_CONVERTER";
34 |
35 | @GetMapping
36 | public EngineExecutorResult script() {
37 | Account account = new Account();
38 | account.setAddress("济南");
39 | account.setAge(30L);
40 | account.setNickname("wxl");
41 | TKey tKey = TKey.of(TenantContextHolder.peek(), ACCOUNT_CONVERTER);
42 | Map accountParam = new HashMap<>(1);
43 | accountParam.put("account", account);
44 | return tenantDynamicScriptExecutor.eval(tKey, accountParam);
45 | }
46 |
47 | @PostMapping
48 | public DynamicScriptModel register(DynamicScriptModel dynamicScriptModel) {
49 | TKey tKey = TKey.of(TenantContextHolder.peek(), ACCOUNT_CONVERTER);
50 | tenantCustomization.saveOrUpdate(tKey, dynamicScriptModel);
51 | return tenantCustomization.getVal(tKey, DynamicScriptModel.class);
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/example/src/main/java/cc/sofast/example/rest/ExtensionController.java:
--------------------------------------------------------------------------------
1 | package cc.sofast.example.rest;
2 |
3 | import cc.sofast.example.bean.Account;
4 | import cc.sofast.example.extension.AccountExtPt;
5 | import cc.sofast.infrastructure.extension.BizScenario;
6 | import cc.sofast.infrastructure.extension.TenantExtensionExecutor;
7 | import cc.sofast.infrastructure.tenant.TenantContextHolder;
8 | import org.springframework.beans.factory.annotation.Autowired;
9 | import org.springframework.web.bind.annotation.GetMapping;
10 | import org.springframework.web.bind.annotation.RequestMapping;
11 | import org.springframework.web.bind.annotation.RestController;
12 |
13 | /**
14 | * @author apple
15 | */
16 | @RestController
17 | @RequestMapping("/extension")
18 | public class ExtensionController {
19 |
20 | @Autowired
21 | TenantExtensionExecutor tenantExtensionExecutor;
22 |
23 | @GetMapping
24 | public Account extension() {
25 | Account account = new Account();
26 | account.setAddress("济南");
27 | account.setAge(30L);
28 | account.setNickname("wxl");
29 |
30 | BizScenario scenario = BizScenario.valueOf(AccountExtPt.BIZ_ID, TenantContextHolder.peek(), AccountExtPt.SCENARIO);
31 |
32 | return tenantExtensionExecutor.execute(AccountExtPt.class, scenario,
33 | accountExtPt -> accountExtPt.accountCalculate(account));
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/example/src/main/java/cc/sofast/example/rest/SchemaSyncController.java:
--------------------------------------------------------------------------------
1 | package cc.sofast.example.rest;
2 |
3 | import cc.sofast.example.service.SqlSync;
4 | import cc.sofast.infrastructure.tenant.TenantBizExecutor;
5 | import cc.sofast.infrastructure.tenant.TenantBizService;
6 | import cc.sofast.infrastructure.tenant.TenantHelper;
7 | import org.springframework.beans.factory.annotation.Autowired;
8 | import org.springframework.web.bind.annotation.GetMapping;
9 | import org.springframework.web.bind.annotation.RequestMapping;
10 | import org.springframework.web.bind.annotation.RestController;
11 |
12 | import java.util.List;
13 | import java.util.function.Predicate;
14 |
15 | @RestController
16 | @RequestMapping("/schema")
17 | public class SchemaSyncController {
18 |
19 | @Autowired
20 | private TenantHelper tenantHelper;
21 |
22 | @Autowired
23 | private TenantBizExecutor tenantBizExecutor;
24 |
25 | @GetMapping
26 | public String schema(String sql) {
27 | List tenantList = tenantHelper.getTenantList(Predicate.not(String::isEmpty));
28 | for (String tenant : tenantList) {
29 | tenantBizExecutor.execute(tenant, SqlSync.class, s -> { s.exec(sql); });
30 | }
31 | return "ok";
32 | }
33 | }
--------------------------------------------------------------------------------
/example/src/main/java/cc/sofast/example/rest/TenantAccountConverter.groovy:
--------------------------------------------------------------------------------
1 | package cc.sofast.example.rest
2 |
3 | import cc.sofast.example.bean.Account
4 | import cc.sofast.infrastructure.customization.Cons;
5 | import cc.sofast.infrastructure.customization.script.groovy.ScriptTemplate;
6 | import org.springframework.context.ApplicationContext;
7 |
8 | class TenantAccountConverter extends ScriptTemplate {
9 |
10 | @Override
11 | Object toRun(ApplicationContext applicationContext, String tenant, String key, Binding binding) {
12 | Account account = binding.getVariable("account") as Account
13 | account.setAge(account.getAge() + 10)
14 | return Cons.SUCCESS
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/example/src/main/java/cc/sofast/example/rest/TenantEventController.java:
--------------------------------------------------------------------------------
1 | package cc.sofast.example.rest;
2 |
3 | import cc.sofast.infrastructure.tenant.datasource.DataSourceProperty;
4 | import cc.sofast.infrastructure.tenant.notify.TenantEvent;
5 | import cc.sofast.infrastructure.tenant.notify.TenantEventType;
6 | import cc.sofast.infrastructure.tenant.notify.TenantNotify;
7 | import org.springframework.web.bind.annotation.PostMapping;
8 | import org.springframework.web.bind.annotation.RequestMapping;
9 | import org.springframework.web.bind.annotation.RestController;
10 |
11 | import java.util.Collections;
12 |
13 | /**
14 | * @author apple
15 | */
16 | @RestController
17 | @RequestMapping("tenant")
18 | public class TenantEventController {
19 |
20 | private final TenantNotify tenantNotify;
21 |
22 | public TenantEventController(TenantNotify tenantNotify) {
23 | this.tenantNotify = tenantNotify;
24 | }
25 |
26 | @PostMapping("/event")
27 | public String addTenant() {
28 | TenantEvent tenantEvent = new TenantEvent();
29 | tenantEvent.setType(TenantEventType.CREATE);
30 | tenantEvent.setTenants(Collections.singletonList("t1"));
31 | tenantEvent.setDsType("hikacp");
32 | tenantNotify.pubTenantEvent(tenantEvent);
33 | return "ok";
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/example/src/main/java/cc/sofast/example/service/AccountService.java:
--------------------------------------------------------------------------------
1 | package cc.sofast.example.service;
2 |
3 | import cc.sofast.example.bean.Account;
4 | import com.baomidou.mybatisplus.extension.service.IService;
5 | import org.springframework.stereotype.Service;
6 |
7 | /**
8 | * @author apple
9 | */
10 |
11 | public interface AccountService extends IService {
12 |
13 |
14 | }
15 |
--------------------------------------------------------------------------------
/example/src/main/java/cc/sofast/example/service/AccountServiceImpl.java:
--------------------------------------------------------------------------------
1 | package cc.sofast.example.service;
2 |
3 | import cc.sofast.example.bean.Account;
4 | import cc.sofast.example.mapper.AccountMapper;
5 | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
6 | import org.springframework.stereotype.Service;
7 |
8 | /**
9 | * @author apple
10 | */
11 | @Service
12 | public class AccountServiceImpl extends ServiceImpl implements AccountService {
13 |
14 | }
15 |
--------------------------------------------------------------------------------
/example/src/main/java/cc/sofast/example/service/SqlSync.java:
--------------------------------------------------------------------------------
1 | package cc.sofast.example.service;
2 |
3 | import cc.sofast.infrastructure.jdbc.schema.Schema;
4 | import cc.sofast.infrastructure.jdbc.schema.exec.Exec;
5 | import cc.sofast.infrastructure.tenant.TenantContextHolder;
6 | import org.springframework.beans.factory.annotation.Autowired;
7 | import org.springframework.stereotype.Service;
8 |
9 | import javax.sql.DataSource;
10 |
11 | @Service
12 | public class SqlSync {
13 |
14 | @Autowired
15 | private Schema schema;
16 |
17 | @Autowired
18 | private DataSource dataSource;
19 |
20 | public void exec(String sql) {
21 | Exec exec = schema.getExec();
22 | exec.execSQL(dataSource, TenantContextHolder.peek(), sql);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/example/src/main/resources/application.yml:
--------------------------------------------------------------------------------
1 | spring:
2 | multitenancy:
3 | resolver:
4 | type: web
5 | web:
6 | web-type: header
7 | ignore-path:
8 | - /tenant/**
9 | - /actuator/**
10 | - /schema/**
11 | enabled: true
12 | primary: def
13 | datasource:
14 | def:
15 | pool-name: def
16 | type: com.zaxxer.hikari.HikariDataSource
17 | driver-class-name: org.postgresql.Driver
18 | url: jdbc:postgresql://127.0.0.1:5432/saas_shared_db
19 | username: share_user
20 | password: share_user
21 | shared:
22 | pool-name: pool
23 | type: com.zaxxer.hikari.HikariDataSource
24 | driver-class-name: org.postgresql.Driver
25 | url: jdbc:postgresql://127.0.0.1:5432/saas_shared_db
26 | username: share_user
27 | password: share_user
28 | tenant-ds-type: share
29 | tenants:
30 | - t1
31 | - t2
32 | hikari:
33 | maximum-pool-size: 10
34 | t3:
35 | pool-name: pool
36 | type: com.zaxxer.hikari.HikariDataSource
37 | driver-class-name: org.postgresql.Driver
38 | url: jdbc:postgresql://127.0.0.1:5432/t3
39 | username: t3
40 | password: t3
41 | tenant-ds-type: share
42 | tenants:
43 | - t3
44 | hikari:
45 | maximum-pool-size: 10
46 | data:
47 | redis:
48 | host: 127.0.0.1
49 | password: redis
50 | cache:
51 | type: redis
52 |
53 | mybatis-plus:
54 | global-config:
55 | banner: false
56 |
--------------------------------------------------------------------------------
/example/src/main/resources/example.md:
--------------------------------------------------------------------------------
1 | - 共享连接+独立schema
2 |
3 | ```postgresql
4 | CREATE ROLE share_user WITH
5 | LOGIN
6 | NOSUPERUSER
7 | NOINHERIT--设置角色为不继承
8 | NOCREATEDB
9 | NOCREATEROLE
10 | NOREPLICATION;
11 |
12 | CREATE ROLE t1 WITH
13 | LOGIN
14 | NOSUPERUSER
15 | NOINHERIT--设置角色为不继承
16 | NOCREATEDB
17 | NOCREATEROLE
18 | NOREPLICATION;
19 |
20 | CREATE ROLE t2 WITH
21 | LOGIN
22 | NOSUPERUSER
23 | NOINHERIT--设置角色为不继承
24 | NOCREATEDB
25 | NOCREATEROLE
26 | NOREPLICATION;
27 |
28 | GRANT t1, t2 TO share_user;
29 |
30 | CREATE DATABASE saas_shared_db
31 | OWNER = share_user
32 | ENCODING = 'UTF8'
33 | TABLESPACE = pg_default
34 | CONNECTION LIMIT = -1
35 | IS_TEMPLATE = False;
36 |
37 | CREATE SCHEMA IF NOT EXISTS t1
38 | AUTHORIZATION t1;
39 |
40 |
41 | CREATE SCHEMA IF NOT EXISTS t2
42 | AUTHORIZATION t2;
43 | ```
44 |
45 | - 独立db,schema
46 |
47 | ```postgresql
48 | CREATE ROLE t3 WITH
49 | LOGIN
50 | NOSUPERUSER
51 | NOINHERIT--设置角色为不继承
52 | NOCREATEDB
53 | NOCREATEROLE
54 | NOREPLICATION;
55 |
56 | CREATE DATABASE t3
57 | OWNER = t3
58 | ENCODING = 'UTF8'
59 | TABLESPACE = pg_default
60 | CONNECTION LIMIT = -1
61 | IS_TEMPLATE = False;
62 |
63 | CREATE SCHEMA IF NOT EXISTS t3
64 | AUTHORIZATION t3;
65 | ```
66 |
67 | - 分别在各个租户下创建表
68 |
69 | ```postgresql
70 | CREATE TABLE IF NOT EXISTS account
71 | (
72 | id bigserial primary key,
73 | nickname text,
74 | age bigint,
75 | address text
76 | );
77 | ```
--------------------------------------------------------------------------------
/example/src/test/java/cc/sofast/example/ExampleAppTest.java:
--------------------------------------------------------------------------------
1 | package cc.sofast.example;
2 |
3 | import org.slf4j.Logger;
4 | import org.slf4j.LoggerFactory;
5 |
6 | import static org.junit.jupiter.api.Assertions.*;
7 |
8 | class ExampleAppTest {
9 |
10 | private static final Logger logger = LoggerFactory.getLogger(ExampleAppTest.class);
11 |
12 | @org.junit.jupiter.api.Test
13 | void main() {
14 | logger.atInfo().addKeyValue("oldT", 11).addKeyValue("newT", 11).log("Temperature changed.");
15 | }
16 | }
--------------------------------------------------------------------------------
/extend/dynamic-rest-sql/README.md:
--------------------------------------------------------------------------------
1 | ### 动态接口
2 |
3 | 以SQL为基础,实现租户的定制化的业务. 适用于的场景包含: 定制列表, 数据报表, 定制独立的crud的功能.
4 |
5 | ### 主要思路
6 |
7 | - 注册接口,在线写SQL为租户扩展。
8 | - 数据库字段下划线转驼峰的映射
9 | - cache 是否加入到redis cache中,
10 | - cache key
11 | - cache 更新
12 | - cache 失效
13 | - Get
14 | - 分页查询
15 | - 列表查询
16 | - 详情查询
17 | - Post
18 | - 增加
19 | - Put
20 | - 更新
21 | - 局部更新
22 | - Delete
23 | - 删除
24 |
25 | ### 实现方式
26 |
27 | - 接口层
28 | - 动态注册restful接口
29 | - 解析参数,通用校验规则
30 | - 处理层
31 | - 使用脚本引擎对数据进行二次处理,转换等操作
32 | - SQL执行层
33 | - 基于mybatis-plus(mybatis)实现
34 | - 抽取公共的Mapper ,使用SQLProvider的注解的方式来达到定制化sql
35 | - 对字段进行驼峰转换
36 |
37 | ### 数据库设计
38 |
39 | - 分类表
40 | - id
41 | - label
42 | - parent_id
43 |
44 | - 定义表
45 | - id
46 | - tenant text
47 | - catalog_id bigint
48 | - rest_url text
49 | - rest_param json ? 校验
50 | - function_script text sandbox?
51 | - exec_sql text sql注入校验
52 | - active smallint
53 |
54 | ### 参考
55 |
56 | - https://gitee.com/ssssssss-team/magic-api
57 | - https://github.com/ClouGence/hasor
58 | - https://gitee.com/Tencent/APIJSON
59 | - https://gitee.com/linebyte/crabc
60 |
61 |
--------------------------------------------------------------------------------
/extend/dynamic-rest-sql/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'sofast.module.plugin'
3 | }
4 |
5 | processAot {
6 | enabled(false)
7 | }
8 |
9 | processTestAot {
10 | enabled(false)
11 | }
12 |
13 | bootJar {
14 | enabled(false)
15 | }
16 |
17 | repositories {
18 | mavenCentral()
19 | }
20 |
21 | dependencies {
22 | implementation 'org.postgresql:postgresql'
23 | implementation 'org.springframework.boot:spring-boot-starter-jdbc'
24 |
25 |
26 | annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
27 | testImplementation 'org.springframework.boot:spring-boot-starter-test'
28 | }
29 |
30 | tasks.named('test') {
31 | enabled(false)
32 | useJUnitPlatform()
33 | jvmArgs["--add-opens java.base/java.lang=ALL-UNNAMED"]
34 | }
35 |
--------------------------------------------------------------------------------
/extend/dynamic-rest-sql/img.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ccwxl/multi-tenancy-spring-boot-starter/ff2fcc0521d70f8706b59016ceeedd6856b09094/extend/dynamic-rest-sql/img.png
--------------------------------------------------------------------------------
/extend/dynamic-rest-sql/src/main/java/cc/sofast/infrastructure/restsql/RestSqlConfiguration.java:
--------------------------------------------------------------------------------
1 | package cc.sofast.infrastructure.restsql;
2 |
3 | /**
4 | * @author xielong.wang
5 | * @since 1.0.0
6 | */
7 | public class RestSqlConfiguration {
8 | }
9 |
--------------------------------------------------------------------------------
/extend/dynamic-rest-sql/src/main/resources/META-INF/spring.factories:
--------------------------------------------------------------------------------
1 | org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
2 | cc.sofast.infrastructure.restsql.RestSqlConfiguration
--------------------------------------------------------------------------------
/extend/dynamic-rest-sql/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports:
--------------------------------------------------------------------------------
1 | cc.sofast.infrastructure.restsql.RestSqlConfiguration
--------------------------------------------------------------------------------
/extend/extension-point-plugin/README.md:
--------------------------------------------------------------------------------
1 | ## extension-point-plugin
2 |
3 | - 扩展点
4 | - 自定义配置
5 | - 执行动态脚本
6 | - TODO 缓存配置后,如果多节点,则需要多个节点更新。
--------------------------------------------------------------------------------
/extend/extension-point-plugin/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'sofast.module.plugin'
3 | }
4 |
5 | processAot {
6 | enabled(false)
7 | }
8 |
9 | processTestAot {
10 | enabled(false)
11 | }
12 |
13 | bootJar {
14 | enabled(false)
15 | }
16 |
17 | repositories {
18 | mavenCentral()
19 | }
20 |
21 | dependencies {
22 | compileOnly 'org.springframework.boot:spring-boot-starter'
23 | compileOnly 'org.springframework.boot:spring-boot-starter-data-redis'
24 | compileOnly 'org.springframework.boot:spring-boot-starter-jdbc'
25 | compileOnly 'org.springframework.boot:spring-boot-starter-web'
26 | compileOnly group: 'com.googlecode.aviator', name: 'aviator', version: '5.3.3'
27 | compileOnly group: 'org.apache.groovy', name: 'groovy', version: '4.0.13'
28 | implementation group: 'net.jodah', name: 'typetools', version: '0.6.3'
29 | compileOnly 'com.google.auto.service:auto-service:1.1.1'
30 | annotationProcessor 'com.google.auto.service:auto-service:1.1.1'
31 |
32 | annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
33 | testImplementation 'org.springframework.boot:spring-boot-starter-test'
34 | testImplementation 'org.springframework.boot:spring-boot-starter-web'
35 | testImplementation 'org.springframework.boot:spring-boot-starter-data-redis'
36 | testImplementation 'org.springframework.boot:spring-boot-starter-jdbc'
37 | // testImplementation group: 'com.googlecode.aviator', name: 'aviator', version: '5.3.3'
38 | testImplementation group: 'org.apache.groovy', name: 'groovy', version: '4.0.13'
39 | }
40 |
41 | tasks.named('test') {
42 | enabled(false)
43 | useJUnitPlatform()
44 | jvmArgs["--add-opens java.base/java.lang=ALL-UNNAMED"]
45 | }
46 |
--------------------------------------------------------------------------------
/extend/extension-point-plugin/src/main/java/cc/sofast/infrastructure/customization/ApplicationContextHelper.java:
--------------------------------------------------------------------------------
1 | package cc.sofast.infrastructure.customization;
2 |
3 | import org.springframework.beans.BeansException;
4 | import org.springframework.beans.factory.support.DefaultListableBeanFactory;
5 | import org.springframework.context.ApplicationContext;
6 | import org.springframework.context.ApplicationContextAware;
7 | import org.springframework.context.support.AbstractRefreshableApplicationContext;
8 | import org.springframework.context.support.GenericApplicationContext;
9 |
10 | /**
11 | * @author xielong.wang
12 | */
13 | public class ApplicationContextHelper implements ApplicationContextAware {
14 |
15 | private static ApplicationContext context;
16 |
17 | private static DefaultListableBeanFactory springFactory;
18 |
19 | @Override
20 | public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
21 | ApplicationContextHelper.setContext(applicationContext);
22 | if (applicationContext instanceof AbstractRefreshableApplicationContext) {
23 | AbstractRefreshableApplicationContext springContext =
24 | (AbstractRefreshableApplicationContext) applicationContext;
25 | ApplicationContextHelper.setFactory((DefaultListableBeanFactory) springContext.getBeanFactory());
26 | } else if (applicationContext instanceof GenericApplicationContext) {
27 | GenericApplicationContext springContext = (GenericApplicationContext) applicationContext;
28 | ApplicationContextHelper.setFactory(springContext.getDefaultListableBeanFactory());
29 | }
30 | }
31 |
32 | private static void setContext(ApplicationContext applicationContext) {
33 |
34 | ApplicationContextHelper.context = applicationContext;
35 | }
36 |
37 | private static void setFactory(DefaultListableBeanFactory springFactory) {
38 |
39 | ApplicationContextHelper.springFactory = springFactory;
40 | }
41 |
42 | public static ApplicationContext getContext() {
43 |
44 | return context;
45 | }
46 |
47 | public static DefaultListableBeanFactory getSpringFactory() {
48 |
49 | return springFactory;
50 | }
51 |
52 | }
53 |
54 |
--------------------------------------------------------------------------------
/extend/extension-point-plugin/src/main/java/cc/sofast/infrastructure/customization/ComposeCustomizationLoader.java:
--------------------------------------------------------------------------------
1 | package cc.sofast.infrastructure.customization;
2 |
3 |
4 | import cc.sofast.infrastructure.customization.mem.MemCustomizationLoader;
5 | import org.springframework.util.StringUtils;
6 |
7 | /**
8 | * @author wxl
9 | * 组合缓存和持久化存储加载器。实现多级加载
10 | */
11 | public class ComposeCustomizationLoader implements CustomizationLoader {
12 |
13 | private final MemCustomizationLoader memCustomizationLoader;
14 |
15 | private final PersistentCustomizationLoader persistentCustomizationLoader;
16 |
17 |
18 | public ComposeCustomizationLoader(MemCustomizationLoader memCustomizationLoader,
19 | PersistentCustomizationLoader persistentCustomizationLoader) {
20 | this.memCustomizationLoader = memCustomizationLoader;
21 | this.persistentCustomizationLoader = persistentCustomizationLoader;
22 | }
23 |
24 |
25 | @Override
26 | public String val(TKey key) {
27 | String val = memCustomizationLoader.val(key);
28 | if (StringUtils.hasLength(val)) {
29 | return val;
30 | }
31 | String dbVal = persistentCustomizationLoader.val(key);
32 | memCustomizationLoader.saveOrUpdate(key, dbVal);
33 | return dbVal;
34 | }
35 |
36 | @Override
37 | public boolean remove(TKey key) {
38 | memCustomizationLoader.remove(key);
39 | persistentCustomizationLoader.remove(key);
40 | return false;
41 | }
42 |
43 | @Override
44 | public boolean saveOrUpdate(TKey key, String valJson) {
45 |
46 | return memCustomizationLoader.saveOrUpdate(key, valJson) && persistentCustomizationLoader.saveOrUpdate(key, valJson);
47 | }
48 | }
--------------------------------------------------------------------------------
/extend/extension-point-plugin/src/main/java/cc/sofast/infrastructure/customization/Cons.java:
--------------------------------------------------------------------------------
1 | package cc.sofast.infrastructure.customization;
2 |
3 | public class Cons {
4 |
5 | public static final String TENANT = "tenant";
6 |
7 | public static final String KEY = "key";
8 |
9 | public static final String SUCCESS = "ok";
10 |
11 | public static final String APPLICATION_CONTEXT = "applicationContext";
12 |
13 | }
14 |
--------------------------------------------------------------------------------
/extend/extension-point-plugin/src/main/java/cc/sofast/infrastructure/customization/CustomizationLoader.java:
--------------------------------------------------------------------------------
1 | package cc.sofast.infrastructure.customization;
2 |
3 | /**
4 | * @author wxl
5 | * 租户自定义配置加载器
6 | */
7 | public interface CustomizationLoader {
8 |
9 | String val(TKey key);
10 |
11 | boolean remove(TKey key);
12 |
13 | boolean saveOrUpdate(TKey key, String valJson);
14 | }
15 |
--------------------------------------------------------------------------------
/extend/extension-point-plugin/src/main/java/cc/sofast/infrastructure/customization/CustomizationProperties.java:
--------------------------------------------------------------------------------
1 | package cc.sofast.infrastructure.customization;
2 |
3 | import org.springframework.boot.context.properties.ConfigurationProperties;
4 |
5 | @ConfigurationProperties(prefix = CustomizationProperties.PREFIX)
6 | public class CustomizationProperties {
7 |
8 | public static final String PREFIX = "spring.multitenancy.customization";
9 |
10 | private Stream stream = new Stream();
11 |
12 | public static class Stream {
13 |
14 | private int poolSize = 10;
15 |
16 | private int pollTimeout = 100;
17 |
18 | private String key = "tenant_customization_event_stream";
19 |
20 | private long maxLen = 1000;
21 |
22 | public int getPoolSize() {
23 | return poolSize;
24 | }
25 |
26 | public void setPoolSize(int poolSize) {
27 | this.poolSize = poolSize;
28 | }
29 |
30 | public int getPollTimeout() {
31 | return pollTimeout;
32 | }
33 |
34 | public void setPollTimeout(int pollTimeout) {
35 | this.pollTimeout = pollTimeout;
36 | }
37 |
38 | public String getKey() {
39 | return key;
40 | }
41 |
42 | public void setKey(String key) {
43 | this.key = key;
44 | }
45 |
46 | public long getMaxLen() {
47 |
48 | return maxLen;
49 | }
50 |
51 | public void setMaxLen(long maxLen) {
52 | this.maxLen = maxLen;
53 | }
54 | }
55 |
56 | public Stream getStream() {
57 | return stream;
58 | }
59 |
60 | public void setStream(Stream stream) {
61 | this.stream = stream;
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/extend/extension-point-plugin/src/main/java/cc/sofast/infrastructure/customization/DynamicScriptModel.java:
--------------------------------------------------------------------------------
1 | package cc.sofast.infrastructure.customization;
2 |
3 | /**
4 | * @author wxl
5 | */
6 | public class DynamicScriptModel {
7 |
8 | private String type;
9 |
10 | private String describe;
11 |
12 | private String script;
13 |
14 | public String getType() {
15 | return type;
16 | }
17 |
18 | public void setType(String type) {
19 | this.type = type;
20 | }
21 |
22 | public String getDescribe() {
23 | return describe;
24 | }
25 |
26 | public void setDescribe(String describe) {
27 | this.describe = describe;
28 | }
29 |
30 | public String getScript() {
31 | return script;
32 | }
33 |
34 | public void setScript(String script) {
35 | this.script = script;
36 | }
37 | }
38 |
39 |
--------------------------------------------------------------------------------
/extend/extension-point-plugin/src/main/java/cc/sofast/infrastructure/customization/PersistentCustomizationLoader.java:
--------------------------------------------------------------------------------
1 | package cc.sofast.infrastructure.customization;
2 |
3 | /**
4 | * @author xielong.wang
5 | */
6 | public abstract class PersistentCustomizationLoader implements CustomizationLoader {
7 |
8 | }
9 |
--------------------------------------------------------------------------------
/extend/extension-point-plugin/src/main/java/cc/sofast/infrastructure/customization/ScriptExecutor.java:
--------------------------------------------------------------------------------
1 | package cc.sofast.infrastructure.customization;
2 |
3 |
4 | import cc.sofast.infrastructure.customization.script.EngineExecutorResult;
5 | import cc.sofast.infrastructure.customization.script.Script;
6 |
7 | import java.util.HashMap;
8 | import java.util.Map;
9 | import java.util.ServiceLoader;
10 |
11 | /**
12 | * @author wxl
13 | * 脚本执行器,同步执行
14 | */
15 | public class ScriptExecutor {
16 |
17 | Map scripts = new HashMap<>();
18 |
19 | public ScriptExecutor() {
20 | init();
21 | }
22 |
23 | public void init() {
24 | //扫描脚本执行器。进行注册。使用SPI的方式
25 | ServiceLoader