├── .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 | ![architecture.png](architecture.png) 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