mirror of
https://github.com/continew-org/continew-starter.git
synced 2025-11-13 04:57:13 +08:00
Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c0c449870f | |||
| 67edb0828d | |||
| e05e0de7b8 | |||
| 2f2aae08ab | |||
| 3f7f118d3e | |||
| 17272a7809 | |||
| 07e1637363 | |||
| a0b64b81d5 | |||
| 778a861fa9 | |||
| d32c05166d | |||
| 4719a349dd | |||
| b4cb147a77 | |||
| aefd61b855 | |||
| 3be0d90018 | |||
| 34deff959a | |||
| 3dad27df3f | |||
| dcd185f532 |
25
CHANGELOG.md
25
CHANGELOG.md
@@ -1,3 +1,28 @@
|
|||||||
|
## [v2.13.1](https://github.com/continew-org/continew-starter/compare/v2.13.0...v2.13.1) (2025-07-17)
|
||||||
|
|
||||||
|
### ✨ 新特性
|
||||||
|
|
||||||
|
- 【validation】增强 EnumValue 枚举校验器,支持枚举值的数组和集合校验,增加对 BaseEnum 接口的支持 ([3dad27d](https://github.com/continew-org/continew-starter/commit/3dad27df3f747d60f47c1504286c86f6636e7242))
|
||||||
|
- 【extension/tenant】新增 TenantIgnoreAspect 切面,完善定时任务等需要忽略租户的场景 ([07e1637](https://github.com/continew-org/continew-starter/commit/07e1637363bce4cac3f215384c8bbf6235a30778))
|
||||||
|
- 【core】SpringUtils 工具类新增 getBean(Class<T> clazz, boolean ignoreNoSuchBeanEx) 方法 ([17272a7](https://github.com/continew-org/continew-starter/commit/17272a780905b554b1fb47e52667a51be0af7bbe))
|
||||||
|
- 【core】新增集合工具类 CollUtils(mapToList、mapToSet) ([3f7f118](https://github.com/continew-org/continew-starter/commit/3f7f118d3e6b38c2cb13a2661a37eda3325894a7))
|
||||||
|
- 【extension/tenant】新增 TenantUtils 替换 TenantHandler 接口及其实现类 DefaultTenantHandler ([2f2aae0](https://github.com/continew-org/continew-starter/commit/2f2aae08ab934009cd39bbe7ec3823c594fa48f8))
|
||||||
|
- 【core】ServletUtils 新增应 JSON 数据给客户端方法 ([67edb08](https://github.com/continew-org/continew-starter/commit/67edb0828dee355ff46d055935a76a42a5a6ebd8))
|
||||||
|
|
||||||
|
### 💎 功能优化
|
||||||
|
|
||||||
|
- 【extension/crud】完善树配置相关注释 ([3be0d90](https://github.com/continew-org/continew-starter/commit/3be0d900180e4ed528f32f2350b150552aee0420))
|
||||||
|
- 【extension/crud】移除 Crane4j 依赖方便使用者自定义实现 ([aefd61b](https://github.com/continew-org/continew-starter/commit/aefd61b855e376b0f509d34d441b1d1d5b831a39))
|
||||||
|
- 【extension/tenant】将"多租户"描述统一为"租户" ([d32c051](https://github.com/continew-org/continew-starter/commit/d32c05166d297d1665436eae63f41277f6dca2af))
|
||||||
|
- 【extension/tenant】将 dynamic-datasource 依赖设置为 optional ([778a861](https://github.com/continew-org/continew-starter/commit/778a861fa9f97a99a7014b72a37e984145872421))
|
||||||
|
- 【extension/datapermission】UserContext、RoleContext 重命名为 UserData、RoleData,以避免和应用冲突 ([a0b64b8](https://github.com/continew-org/continew-starter/commit/a0b64b81d560c3c4e7175685f66ab98406a31dcc))
|
||||||
|
- 使用 CollUtils 替代部分 Stream 集合转换 ([e05e0de](https://github.com/continew-org/continew-starter/commit/e05e0de7b81329512ea1f0ad5e9ed3c04bdfe752))
|
||||||
|
|
||||||
|
### 🐛 问题修复
|
||||||
|
|
||||||
|
- 【security/mask】修复部分注释错误 ([34deff9](https://github.com/continew-org/continew-starter/commit/34deff959aa9ba817d05b552b173b4cbaebd289a))
|
||||||
|
- 【dependencies】指定 Apache POI 依赖版本(解决版本冲突)并移除冗余包 ([b4cb147](https://github.com/continew-org/continew-starter/commit/b4cb147a77cdfeb754c061eab888eb10314231be))
|
||||||
|
|
||||||
## [v2.13.0](https://github.com/continew-org/continew-starter/compare/v2.12.2...v2.13.0) (2025-07-05)
|
## [v2.13.0](https://github.com/continew-org/continew-starter/compare/v2.12.2...v2.13.0) (2025-07-05)
|
||||||
|
|
||||||
### ✨ 新特性
|
### ✨ 新特性
|
||||||
|
|||||||
@@ -197,7 +197,7 @@ continew-starter
|
|||||||
├─ continew-starter-extension-datapermission(数据权限模块)
|
├─ continew-starter-extension-datapermission(数据权限模块)
|
||||||
│ ├─ continew-starter-extension-datapermission-core(核心模块)
|
│ ├─ continew-starter-extension-datapermission-core(核心模块)
|
||||||
│ └─ continew-starter-extension-datapermission-mp(MyBatis Plus)
|
│ └─ continew-starter-extension-datapermission-mp(MyBatis Plus)
|
||||||
├─ continew-starter-extension-tenant(多租户模块)
|
├─ continew-starter-extension-tenant(租户模块)
|
||||||
│ ├─ continew-starter-extension-tenant-core(核心模块)
|
│ ├─ continew-starter-extension-tenant-core(核心模块)
|
||||||
│ └─ continew-starter-extension-tenant-mp(MyBatis Plus)
|
│ └─ continew-starter-extension-tenant-mp(MyBatis Plus)
|
||||||
└─ continew-starter-extension-crud(CRUD 模块)
|
└─ continew-starter-extension-crud(CRUD 模块)
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
|||||||
import top.continew.starter.apidoc.handler.BaseEnumParameterHandler;
|
import top.continew.starter.apidoc.handler.BaseEnumParameterHandler;
|
||||||
import top.continew.starter.apidoc.handler.OpenApiHandler;
|
import top.continew.starter.apidoc.handler.OpenApiHandler;
|
||||||
import top.continew.starter.core.autoconfigure.application.ApplicationProperties;
|
import top.continew.starter.core.autoconfigure.application.ApplicationProperties;
|
||||||
|
import top.continew.starter.core.util.CollUtils;
|
||||||
import top.continew.starter.core.util.GeneralPropertySourceFactory;
|
import top.continew.starter.core.util.GeneralPropertySourceFactory;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -104,7 +105,7 @@ public class SpringDocAutoConfiguration implements WebMvcConfigurer {
|
|||||||
Map<String, SecurityScheme> securitySchemeMap = components.getSecuritySchemes();
|
Map<String, SecurityScheme> securitySchemeMap = components.getSecuritySchemes();
|
||||||
if (MapUtil.isNotEmpty(securitySchemeMap)) {
|
if (MapUtil.isNotEmpty(securitySchemeMap)) {
|
||||||
SecurityRequirement securityRequirement = new SecurityRequirement();
|
SecurityRequirement securityRequirement = new SecurityRequirement();
|
||||||
List<String> list = securitySchemeMap.values().stream().map(SecurityScheme::getName).toList();
|
List<String> list = CollUtils.mapToList(securitySchemeMap.values(), SecurityScheme::getName);
|
||||||
list.forEach(securityRequirement::addList);
|
list.forEach(securityRequirement::addList);
|
||||||
openApi.addSecurityItem(securityRequirement);
|
openApi.addSecurityItem(securityRequirement);
|
||||||
}
|
}
|
||||||
@@ -127,10 +128,8 @@ public class SpringDocAutoConfiguration implements WebMvcConfigurer {
|
|||||||
Map<String, SecurityScheme> securitySchemeMap = components.getSecuritySchemes();
|
Map<String, SecurityScheme> securitySchemeMap = components.getSecuritySchemes();
|
||||||
pathItem.readOperations().forEach(operation -> {
|
pathItem.readOperations().forEach(operation -> {
|
||||||
SecurityRequirement securityRequirement = new SecurityRequirement();
|
SecurityRequirement securityRequirement = new SecurityRequirement();
|
||||||
List<String> list = securitySchemeMap.values()
|
List<String> list = CollUtils.mapToList(securitySchemeMap
|
||||||
.stream()
|
.values(), SecurityScheme::getName);
|
||||||
.map(SecurityScheme::getName)
|
|
||||||
.toList();
|
|
||||||
list.forEach(securityRequirement::addList);
|
list.forEach(securityRequirement::addList);
|
||||||
operation.addSecurityItem(securityRequirement);
|
operation.addSecurityItem(securityRequirement);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -37,11 +37,11 @@ import org.springdoc.core.utils.PropertyResolverUtils;
|
|||||||
import org.springframework.context.ApplicationContext;
|
import org.springframework.context.ApplicationContext;
|
||||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||||
import org.springframework.web.method.HandlerMethod;
|
import org.springframework.web.method.HandlerMethod;
|
||||||
|
import top.continew.starter.core.util.CollUtils;
|
||||||
|
|
||||||
import java.io.StringReader;
|
import java.io.StringReader;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.function.Function;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
@@ -176,11 +176,8 @@ public class OpenApiHandler extends OpenAPIService {
|
|||||||
buildTagsFromClass(handlerMethod.getBeanType(), tags, tagsStr, locale);
|
buildTagsFromClass(handlerMethod.getBeanType(), tags, tagsStr, locale);
|
||||||
|
|
||||||
if (CollUtil.isNotEmpty(tagsStr)) {
|
if (CollUtil.isNotEmpty(tagsStr)) {
|
||||||
tagsStr = tagsStr.stream()
|
tagsStr = CollUtils.mapToSet(tagsStr, str -> propertyResolverUtils.resolve(str, locale));
|
||||||
.map(str -> propertyResolverUtils.resolve(str, locale))
|
|
||||||
.collect(Collectors.toSet());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (springdocTags.containsKey(handlerMethod)) {
|
if (springdocTags.containsKey(handlerMethod)) {
|
||||||
Tag tag = springdocTags.get(handlerMethod);
|
Tag tag = springdocTags.get(handlerMethod);
|
||||||
tagsStr.add(tag.getName());
|
tagsStr.add(tag.getName());
|
||||||
@@ -256,7 +253,7 @@ public class OpenApiHandler extends OpenAPIService {
|
|||||||
methodTags.addAll(AnnotatedElementUtils
|
methodTags.addAll(AnnotatedElementUtils
|
||||||
.findAllMergedAnnotations(method, io.swagger.v3.oas.annotations.tags.Tag.class));
|
.findAllMergedAnnotations(method, io.swagger.v3.oas.annotations.tags.Tag.class));
|
||||||
if (CollUtil.isNotEmpty(methodTags)) {
|
if (CollUtil.isNotEmpty(methodTags)) {
|
||||||
tagsStr.addAll(toSet(methodTags, tag -> propertyResolverUtils.resolve(tag.name(), locale)));
|
tagsStr.addAll(CollUtils.mapToSet(methodTags, tag -> propertyResolverUtils.resolve(tag.name(), locale)));
|
||||||
List<io.swagger.v3.oas.annotations.tags.Tag> allTags = new ArrayList<>(methodTags);
|
List<io.swagger.v3.oas.annotations.tags.Tag> allTags = new ArrayList<>(methodTags);
|
||||||
addTags(allTags, tags, locale);
|
addTags(allTags, tags, locale);
|
||||||
}
|
}
|
||||||
@@ -275,22 +272,4 @@ public class OpenApiHandler extends OpenAPIService {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 将collection转化为Set集合,但是两者的泛型不同<br>
|
|
||||||
* <B>{@code Collection<E> ------> Set<T> } </B>
|
|
||||||
*
|
|
||||||
* @param collection 需要转化的集合
|
|
||||||
* @param function collection中的泛型转化为set泛型的lambda表达式
|
|
||||||
* @param <E> collection中的泛型
|
|
||||||
* @param <T> Set中的泛型
|
|
||||||
* @return 转化后的Set
|
|
||||||
*/
|
|
||||||
public static <E, T> Set<T> toSet(Collection<E> collection, Function<E, T> function) {
|
|
||||||
if (CollUtil.isEmpty(collection) || function == null) {
|
|
||||||
return CollUtil.newHashSet();
|
|
||||||
}
|
|
||||||
return collection.stream().map(function).filter(Objects::nonNull).collect(Collectors.toSet());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
<description>ContiNew Starter BOM</description>
|
<description>ContiNew Starter BOM</description>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<revision>2.13.0</revision>
|
<revision>2.13.1</revision>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<dependencyManagement>
|
<dependencyManagement>
|
||||||
@@ -292,13 +292,13 @@
|
|||||||
<version>${revision}</version>
|
<version>${revision}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- 扩展模块 - 多租户 - MyBatis Plus ORM 模块 -->
|
<!-- 扩展模块 - 租户 - MyBatis Plus ORM 模块 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>top.continew.starter</groupId>
|
<groupId>top.continew.starter</groupId>
|
||||||
<artifactId>continew-starter-extension-tenant-mp</artifactId>
|
<artifactId>continew-starter-extension-tenant-mp</artifactId>
|
||||||
<version>${revision}</version>
|
<version>${revision}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<!-- 扩展模块 - 多租户 - 核心模块 -->
|
<!-- 扩展模块 - 租户 - 核心模块 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>top.continew.starter</groupId>
|
<groupId>top.continew.starter</groupId>
|
||||||
<artifactId>continew-starter-extension-tenant-core</artifactId>
|
<artifactId>continew-starter-extension-tenant-core</artifactId>
|
||||||
|
|||||||
@@ -19,9 +19,10 @@ package top.continew.starter.core.constant;
|
|||||||
/**
|
/**
|
||||||
* 字符相关常量
|
* 字符相关常量
|
||||||
*
|
*
|
||||||
|
* @see cn.hutool.core.text.CharPool
|
||||||
|
*
|
||||||
* @author looly(<a href="https://gitee.com/dromara/hutool">Hutool</a>)
|
* @author looly(<a href="https://gitee.com/dromara/hutool">Hutool</a>)
|
||||||
* @author Charles7c
|
* @author Charles7c
|
||||||
* @see cn.hutool.core.text.CharPool
|
|
||||||
* @since 2.7.3
|
* @since 2.7.3
|
||||||
*/
|
*/
|
||||||
public class CharConstants {
|
public class CharConstants {
|
||||||
|
|||||||
@@ -150,7 +150,7 @@ public class PropertiesConstants {
|
|||||||
public static final String DATA_PERMISSION = CONTINEW_STARTER + StringConstants.DOT + "data-permission";
|
public static final String DATA_PERMISSION = CONTINEW_STARTER + StringConstants.DOT + "data-permission";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 多租户配置
|
* 租户配置
|
||||||
*/
|
*/
|
||||||
public static final String TENANT = CONTINEW_STARTER + StringConstants.DOT + "tenant";
|
public static final String TENANT = CONTINEW_STARTER + StringConstants.DOT + "tenant";
|
||||||
|
|
||||||
|
|||||||
@@ -19,9 +19,10 @@ package top.continew.starter.core.constant;
|
|||||||
/**
|
/**
|
||||||
* 字符串相关常量
|
* 字符串相关常量
|
||||||
*
|
*
|
||||||
|
* @see cn.hutool.core.text.StrPool
|
||||||
|
*
|
||||||
* @author looly(<a href="https://gitee.com/dromara/hutool">Hutool</a>)
|
* @author looly(<a href="https://gitee.com/dromara/hutool">Hutool</a>)
|
||||||
* @author Charles7c
|
* @author Charles7c
|
||||||
* @see cn.hutool.core.text.StrPool
|
|
||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
public class StringConstants {
|
public class StringConstants {
|
||||||
|
|||||||
@@ -0,0 +1,116 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||||
|
* <p>
|
||||||
|
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* <p>
|
||||||
|
* http://www.gnu.org/licenses/lgpl.html
|
||||||
|
* <p>
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package top.continew.starter.core.util;
|
||||||
|
|
||||||
|
import cn.hutool.core.collection.CollUtil;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 集合相关工具类
|
||||||
|
*
|
||||||
|
* @author Charles7c
|
||||||
|
* @since 2.13.1
|
||||||
|
*/
|
||||||
|
public class CollUtils {
|
||||||
|
|
||||||
|
private CollUtils() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过 func 自定义一个规则,此规则将原集合中的元素转换成新的元素,生成新的列表返回<br>
|
||||||
|
* 例如:提供一个 Bean 列表,通过 Function 接口实现获取某个字段值,返回这个字段值组成的新列表
|
||||||
|
*
|
||||||
|
* @param <T> 集合元素类型
|
||||||
|
* @param <R> 返回集合元素类型
|
||||||
|
* @param collection 原集合
|
||||||
|
* @param func 编辑函数
|
||||||
|
* @return 抽取后的新列表(默认去除 null 值)
|
||||||
|
* @see CollUtil#map(Iterable, Function, boolean)
|
||||||
|
*/
|
||||||
|
public static <T, R> List<R> mapToList(Collection<T> collection, Function<? super T, ? extends R> func) {
|
||||||
|
return mapToList(collection, func, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过 func 自定义一个规则,此规则将原集合中的元素转换成新的元素,生成新的列表返回<br>
|
||||||
|
* 例如:提供一个 Bean 列表,通过 Function 接口实现获取某个字段值,返回这个字段值组成的新列表
|
||||||
|
*
|
||||||
|
* @param <T> 集合元素类型
|
||||||
|
* @param <R> 返回集合元素类型
|
||||||
|
* @param collection 原集合
|
||||||
|
* @param func 编辑函数
|
||||||
|
* @param ignoreNull 是否忽略空值,这里的空值包括函数处理前和处理后的 null 值
|
||||||
|
* @return 抽取后的新列表
|
||||||
|
* @see CollUtil#map(Iterable, Function, boolean)
|
||||||
|
*/
|
||||||
|
public static <T, R> List<R> mapToList(Collection<T> collection,
|
||||||
|
Function<? super T, ? extends R> func,
|
||||||
|
boolean ignoreNull) {
|
||||||
|
if (CollUtil.isEmpty(collection)) {
|
||||||
|
return new ArrayList<>(0);
|
||||||
|
}
|
||||||
|
Stream<T> stream = collection.stream();
|
||||||
|
if (ignoreNull) {
|
||||||
|
return stream.filter(Objects::nonNull).map(func).filter(Objects::nonNull).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
return stream.map(func).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过 func 自定义一个规则,此规则将原集合中的元素转换成新的元素,生成新的集合返回<br>
|
||||||
|
* 例如:提供一个 Bean 集合,通过 Function 接口实现获取某个字段值,返回这个字段值组成的新集合
|
||||||
|
*
|
||||||
|
* @param <T> 集合元素类型
|
||||||
|
* @param <R> 返回集合元素类型
|
||||||
|
* @param collection 原集合
|
||||||
|
* @param func 编辑函数
|
||||||
|
* @return 抽取后的新集合(默认去除 null 值)
|
||||||
|
* @see CollUtil#map(Iterable, Function, boolean)
|
||||||
|
*/
|
||||||
|
public static <T, R> Set<R> mapToSet(Collection<T> collection, Function<? super T, ? extends R> func) {
|
||||||
|
return mapToSet(collection, func, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过 func 自定义一个规则,此规则将原集合中的元素转换成新的元素,生成新的集合返回<br>
|
||||||
|
* 例如:提供一个 Bean 集合,通过 Function 接口实现获取某个字段值,返回这个字段值组成的新集合
|
||||||
|
*
|
||||||
|
* @param <T> 集合元素类型
|
||||||
|
* @param <R> 返回集合元素类型
|
||||||
|
* @param collection 原集合
|
||||||
|
* @param func 编辑函数
|
||||||
|
* @param ignoreNull 是否忽略空值,这里的空值包括函数处理前和处理后的 null 值
|
||||||
|
* @return 抽取后的新集合
|
||||||
|
* @see CollUtil#map(Iterable, Function, boolean)
|
||||||
|
*/
|
||||||
|
public static <T, R> Set<R> mapToSet(Collection<T> collection,
|
||||||
|
Function<? super T, ? extends R> func,
|
||||||
|
boolean ignoreNull) {
|
||||||
|
if (CollUtil.isEmpty(collection)) {
|
||||||
|
return new HashSet<>(0);
|
||||||
|
}
|
||||||
|
Stream<T> stream = collection.stream();
|
||||||
|
if (ignoreNull) {
|
||||||
|
return stream.filter(Objects::nonNull).map(func).filter(Objects::nonNull).collect(Collectors.toSet());
|
||||||
|
}
|
||||||
|
return stream.map(func).collect(Collectors.toSet());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -45,7 +45,7 @@ public class ReflectUtils {
|
|||||||
*/
|
*/
|
||||||
public static List<String> getNonStaticFieldsName(Class<?> beanClass) throws SecurityException {
|
public static List<String> getNonStaticFieldsName(Class<?> beanClass) throws SecurityException {
|
||||||
List<Field> nonStaticFields = getNonStaticFields(beanClass);
|
List<Field> nonStaticFields = getNonStaticFields(beanClass);
|
||||||
return nonStaticFields.stream().map(Field::getName).collect(Collectors.toList());
|
return CollUtils.mapToList(nonStaticFields, Field::getName);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import cn.hutool.json.JSONUtil;
|
|||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
import jakarta.servlet.http.HttpSession;
|
import jakarta.servlet.http.HttpSession;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.web.context.request.RequestAttributes;
|
import org.springframework.web.context.request.RequestAttributes;
|
||||||
import org.springframework.web.context.request.RequestContextHolder;
|
import org.springframework.web.context.request.RequestContextHolder;
|
||||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||||
@@ -36,7 +37,10 @@ import top.continew.starter.core.wrapper.RepeatReadResponseWrapper;
|
|||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.*;
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Servlet 工具类
|
* Servlet 工具类
|
||||||
@@ -334,6 +338,18 @@ public class ServletUtils extends JakartaServletUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 响应 JSON 数据给客户端
|
||||||
|
*
|
||||||
|
* @param response 响应对象
|
||||||
|
* @param data 响应数据
|
||||||
|
* @since 2.13.1
|
||||||
|
* @see #write(HttpServletResponse, String, String)
|
||||||
|
*/
|
||||||
|
public static void writeJSON(HttpServletResponse response, Object data) {
|
||||||
|
write(response, JSONUtil.toJsonStr(data), MediaType.APPLICATION_JSON_VALUE);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 追加查询字符串
|
* 追加查询字符串
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
package top.continew.starter.core.util;
|
package top.continew.starter.core.util;
|
||||||
|
|
||||||
import cn.hutool.extra.spring.SpringUtil;
|
import cn.hutool.extra.spring.SpringUtil;
|
||||||
|
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Spring 工具类
|
* Spring 工具类
|
||||||
@@ -40,4 +41,25 @@ public class SpringUtils {
|
|||||||
public static <T> T getProxy(T target) {
|
public static <T> T getProxy(T target) {
|
||||||
return (T)SpringUtil.getBean(target.getClass());
|
return (T)SpringUtil.getBean(target.getClass());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过 class 获取 Bean
|
||||||
|
*
|
||||||
|
* @param <T> Bean类型
|
||||||
|
* @param clazz Bean类
|
||||||
|
* @param ignoreNoSuchBeanEx 是否忽略 {@link NoSuchBeanDefinitionException}
|
||||||
|
* @return Bean对象
|
||||||
|
* @see SpringUtil#getBean(Class)
|
||||||
|
* @since 2.13.1
|
||||||
|
*/
|
||||||
|
public static <T> T getBean(Class<T> clazz, boolean ignoreNoSuchBeanEx) {
|
||||||
|
try {
|
||||||
|
return SpringUtil.getBean(clazz);
|
||||||
|
} catch (NoSuchBeanDefinitionException e) {
|
||||||
|
if (ignoreNoSuchBeanEx) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,8 +25,9 @@ import java.util.function.BooleanSupplier;
|
|||||||
/**
|
/**
|
||||||
* 业务参数校验工具类(抛出 500 ServiceException)
|
* 业务参数校验工具类(抛出 500 ServiceException)
|
||||||
*
|
*
|
||||||
* @author Charles7c
|
|
||||||
* @see BusinessException
|
* @see BusinessException
|
||||||
|
*
|
||||||
|
* @author Charles7c
|
||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
public class CheckUtils extends Validator {
|
public class CheckUtils extends Validator {
|
||||||
|
|||||||
@@ -24,8 +24,9 @@ import java.util.function.BooleanSupplier;
|
|||||||
/**
|
/**
|
||||||
* 基本参数校验工具类(抛出 400 BadRequestException)
|
* 基本参数校验工具类(抛出 400 BadRequestException)
|
||||||
*
|
*
|
||||||
|
* @see top.continew.starter.core.exception.BadRequestException
|
||||||
|
*
|
||||||
* @author Charles7c
|
* @author Charles7c
|
||||||
* @see BadRequestException
|
|
||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
public class ValidationUtils extends Validator {
|
public class ValidationUtils extends Validator {
|
||||||
|
|||||||
@@ -33,9 +33,10 @@ import java.util.concurrent.ConcurrentHashMap;
|
|||||||
/**
|
/**
|
||||||
* 复合枚举类型处理器(扩展 BaseEnum 支持)
|
* 复合枚举类型处理器(扩展 BaseEnum 支持)
|
||||||
*
|
*
|
||||||
|
* @see com.baomidou.mybatisplus.core.handlers.CompositeEnumTypeHandler
|
||||||
|
*
|
||||||
* @author miemie(<a href="https://gitee.com/baomidou/mybatis-plus">MyBatis Plus</a>)
|
* @author miemie(<a href="https://gitee.com/baomidou/mybatis-plus">MyBatis Plus</a>)
|
||||||
* @author Charles7c
|
* @author Charles7c
|
||||||
* @see com.baomidou.mybatisplus.core.handlers.CompositeEnumTypeHandler
|
|
||||||
* @since 2.7.3
|
* @since 2.7.3
|
||||||
*/
|
*/
|
||||||
public class CompositeBaseEnumTypeHandler<E extends Enum<E>> implements TypeHandler<E> {
|
public class CompositeBaseEnumTypeHandler<E extends Enum<E>> implements TypeHandler<E> {
|
||||||
|
|||||||
@@ -45,9 +45,10 @@ import java.util.concurrent.ConcurrentHashMap;
|
|||||||
/**
|
/**
|
||||||
* 枚举类型处理器(扩展 BaseEnum 支持)
|
* 枚举类型处理器(扩展 BaseEnum 支持)
|
||||||
*
|
*
|
||||||
|
* @see com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler
|
||||||
|
*
|
||||||
* @author hubin(<a href="https://gitee.com/baomidou/mybatis-plus">MyBatis Plus</a>)
|
* @author hubin(<a href="https://gitee.com/baomidou/mybatis-plus">MyBatis Plus</a>)
|
||||||
* @author Charles7c
|
* @author Charles7c
|
||||||
* @see com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler
|
|
||||||
* @since 2.4.0
|
* @since 2.4.0
|
||||||
*/
|
*/
|
||||||
public class MybatisBaseEnumTypeHandler<E extends Enum<E>> extends BaseTypeHandler<E> {
|
public class MybatisBaseEnumTypeHandler<E extends Enum<E>> extends BaseTypeHandler<E> {
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ import java.util.function.Function;
|
|||||||
* <p>将 MP 的 {@link CrudRepository} 迁移至本类中,减少两层继承,解决层级过多出现 Sonar 警告的问题</p>
|
* <p>将 MP 的 {@link CrudRepository} 迁移至本类中,减少两层继承,解决层级过多出现 Sonar 警告的问题</p>
|
||||||
*
|
*
|
||||||
* @see CrudRepository
|
* @see CrudRepository
|
||||||
|
*
|
||||||
* @param <M> Mapper 接口
|
* @param <M> Mapper 接口
|
||||||
* @param <T> 实体类型
|
* @param <T> 实体类型
|
||||||
* @author hubin (<a href="https://gitee.com/baomidou/mybatis-plus">MyBatis Plus</a>)
|
* @author hubin (<a href="https://gitee.com/baomidou/mybatis-plus">MyBatis Plus</a>)
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<!-- 项目版本号 -->
|
<!-- 项目版本号 -->
|
||||||
<revision>2.13.0</revision>
|
<revision>2.13.1</revision>
|
||||||
<spring-boot.version>3.3.12</spring-boot.version>
|
<spring-boot.version>3.3.12</spring-boot.version>
|
||||||
<spring-cloud.version>2023.0.5</spring-cloud.version>
|
<spring-cloud.version>2023.0.5</spring-cloud.version>
|
||||||
<redisson.version>3.49.0</redisson.version>
|
<redisson.version>3.49.0</redisson.version>
|
||||||
@@ -217,10 +217,31 @@
|
|||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- Apache POI(适用于 Microsoft 文档的 Java API) -->
|
<!-- Apache POI(适用于 Microsoft 文档的 Java API) -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.poi</groupId>
|
||||||
|
<artifactId>poi</artifactId>
|
||||||
|
<version>${poi.version}</version>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>commons-io</groupId>
|
||||||
|
<artifactId>commons-io</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>commons-codec</groupId>
|
||||||
|
<artifactId>commons-codec</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.poi</groupId>
|
<groupId>org.apache.poi</groupId>
|
||||||
<artifactId>poi-ooxml</artifactId>
|
<artifactId>poi-ooxml</artifactId>
|
||||||
<version>${poi.version}</version>
|
<version>${poi.version}</version>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>commons-io</groupId>
|
||||||
|
<artifactId>commons-io</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- X File Storage(一行代码将文件存储到本地、FTP、SFTP、WebDAV、阿里云 OSS、华为云 OBS...等其它兼容 S3 协议的存储平台) -->
|
<!-- X File Storage(一行代码将文件存储到本地、FTP、SFTP、WebDAV、阿里云 OSS、华为云 OBS...等其它兼容 S3 协议的存储平台) -->
|
||||||
|
|||||||
@@ -28,8 +28,9 @@ import top.continew.starter.core.enums.BaseEnum;
|
|||||||
/**
|
/**
|
||||||
* Easy Excel 枚举接口转换器
|
* Easy Excel 枚举接口转换器
|
||||||
*
|
*
|
||||||
* @author Charles7c
|
|
||||||
* @see BaseEnum
|
* @see BaseEnum
|
||||||
|
*
|
||||||
|
* @author Charles7c
|
||||||
* @since 1.2.0
|
* @since 1.2.0
|
||||||
*/
|
*/
|
||||||
public class ExcelBaseEnumConverter implements Converter<BaseEnum<?>> {
|
public class ExcelBaseEnumConverter implements Converter<BaseEnum<?>> {
|
||||||
|
|||||||
@@ -39,11 +39,5 @@
|
|||||||
<groupId>top.continew.starter</groupId>
|
<groupId>top.continew.starter</groupId>
|
||||||
<artifactId>continew-starter-excel-fastexcel</artifactId>
|
<artifactId>continew-starter-excel-fastexcel</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- Crane4j(基于注解的,用于完成一切 “根据 A 的 key 值拿到 B,再把 B 的属性映射到 A” 这类需求的字段填充框架) -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>cn.crane4j</groupId>
|
|
||||||
<artifactId>crane4j-spring-boot-starter</artifactId>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</project>
|
||||||
@@ -16,13 +16,21 @@
|
|||||||
|
|
||||||
package top.continew.starter.extension.crud.annotation;
|
package top.continew.starter.extension.crud.annotation;
|
||||||
|
|
||||||
|
import top.continew.starter.extension.crud.autoconfigure.CrudTreeProperties;
|
||||||
|
|
||||||
import java.lang.annotation.*;
|
import java.lang.annotation.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 树结构字段
|
* 树结构字段
|
||||||
*
|
*
|
||||||
* @author Charles7c
|
* <p>
|
||||||
|
* 用于复杂树场景,例如:表格
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
* @see cn.hutool.core.lang.tree.TreeNodeConfig
|
* @see cn.hutool.core.lang.tree.TreeNodeConfig
|
||||||
|
* @see CrudTreeProperties
|
||||||
|
*
|
||||||
|
* @author Charles7c
|
||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
@Target(ElementType.TYPE)
|
@Target(ElementType.TYPE)
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ public class CrudProperties {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 树配置
|
* 树配置
|
||||||
|
* <p>用于简单树场景,例如:树选择(下拉)</p>
|
||||||
*/
|
*/
|
||||||
@NestedConfigurationProperty
|
@NestedConfigurationProperty
|
||||||
private CrudTreeProperties tree = new CrudTreeProperties();
|
private CrudTreeProperties tree = new CrudTreeProperties();
|
||||||
|
|||||||
@@ -23,6 +23,12 @@ import top.continew.starter.extension.crud.annotation.TreeField;
|
|||||||
/**
|
/**
|
||||||
* CRUD 树列表配置属性
|
* CRUD 树列表配置属性
|
||||||
*
|
*
|
||||||
|
* <p>
|
||||||
|
* 用于简单树场景,例如:树选择(下拉)
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @see TreeField
|
||||||
|
*
|
||||||
* @author Charles7c
|
* @author Charles7c
|
||||||
* @since 2.7.2
|
* @since 2.7.2
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -16,7 +16,6 @@
|
|||||||
|
|
||||||
package top.continew.starter.extension.crud.service;
|
package top.continew.starter.extension.crud.service;
|
||||||
|
|
||||||
import cn.crane4j.core.support.OperateTemplate;
|
|
||||||
import cn.hutool.core.bean.BeanUtil;
|
import cn.hutool.core.bean.BeanUtil;
|
||||||
import cn.hutool.core.bean.copier.CopyOptions;
|
import cn.hutool.core.bean.copier.CopyOptions;
|
||||||
import cn.hutool.core.collection.CollUtil;
|
import cn.hutool.core.collection.CollUtil;
|
||||||
@@ -214,19 +213,6 @@ public abstract class CrudServiceImpl<M extends BaseMapper<T>, T extends BaseIdD
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 填充数据
|
|
||||||
*
|
|
||||||
* @param obj 待填充信息
|
|
||||||
*/
|
|
||||||
protected void fill(Object obj) {
|
|
||||||
if (obj == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
OperateTemplate operateTemplate = SpringUtil.getBean(OperateTemplate.class);
|
|
||||||
operateTemplate.execute(obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 构建 QueryWrapper
|
* 构建 QueryWrapper
|
||||||
*
|
*
|
||||||
@@ -239,6 +225,14 @@ public abstract class CrudServiceImpl<M extends BaseMapper<T>, T extends BaseIdD
|
|||||||
return QueryWrapperHelper.build(query, queryFields, queryWrapper);
|
return QueryWrapperHelper.build(query, queryFields, queryWrapper);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 填充数据
|
||||||
|
*
|
||||||
|
* @param obj 待填充信息
|
||||||
|
*/
|
||||||
|
protected void fill(Object obj) {
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 新增前置处理
|
* 新增前置处理
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -16,7 +16,6 @@
|
|||||||
|
|
||||||
package top.continew.starter.extension.crud.service;
|
package top.continew.starter.extension.crud.service;
|
||||||
|
|
||||||
import cn.crane4j.core.support.OperateTemplate;
|
|
||||||
import cn.hutool.core.bean.BeanUtil;
|
import cn.hutool.core.bean.BeanUtil;
|
||||||
import cn.hutool.core.bean.copier.CopyOptions;
|
import cn.hutool.core.bean.copier.CopyOptions;
|
||||||
import cn.hutool.core.collection.CollUtil;
|
import cn.hutool.core.collection.CollUtil;
|
||||||
@@ -304,19 +303,6 @@ public abstract class CrudServiceImpl<M extends BaseMapper<T>, T extends BaseIdD
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 填充数据
|
|
||||||
*
|
|
||||||
* @param obj 待填充信息
|
|
||||||
*/
|
|
||||||
protected void fill(Object obj) {
|
|
||||||
if (obj == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
OperateTemplate operateTemplate = SpringUtil.getBean(OperateTemplate.class);
|
|
||||||
operateTemplate.execute(obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 构建 QueryWrapper
|
* 构建 QueryWrapper
|
||||||
*
|
*
|
||||||
@@ -329,6 +315,14 @@ public abstract class CrudServiceImpl<M extends BaseMapper<T>, T extends BaseIdD
|
|||||||
return QueryWrapperHelper.build(query, this.getQueryFields(), queryWrapper);
|
return QueryWrapperHelper.build(query, this.getQueryFields(), queryWrapper);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 填充数据
|
||||||
|
*
|
||||||
|
* @param obj 待填充信息
|
||||||
|
*/
|
||||||
|
protected void fill(Object obj) {
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 新增前置处理
|
* 新增前置处理
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -16,15 +16,15 @@
|
|||||||
|
|
||||||
package top.continew.starter.extension.datapermission.filter;
|
package top.continew.starter.extension.datapermission.filter;
|
||||||
|
|
||||||
import top.continew.starter.extension.datapermission.model.UserContext;
|
import top.continew.starter.extension.datapermission.model.UserData;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 数据权限用户上下文提供者
|
* 数据权限用户数据提供者
|
||||||
*
|
*
|
||||||
* @author Charles7c
|
* @author Charles7c
|
||||||
* @since 1.1.0
|
* @since 1.1.0
|
||||||
*/
|
*/
|
||||||
public interface DataPermissionUserContextProvider {
|
public interface DataPermissionUserDataProvider {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 是否过滤
|
* 是否过滤
|
||||||
@@ -34,9 +34,9 @@ public interface DataPermissionUserContextProvider {
|
|||||||
boolean isFilter();
|
boolean isFilter();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取用户上下文
|
* 获取用户数据
|
||||||
*
|
*
|
||||||
* @return 用户上下文
|
* @return 用户数据
|
||||||
*/
|
*/
|
||||||
UserContext getUserContext();
|
UserData getUserData();
|
||||||
}
|
}
|
||||||
@@ -19,12 +19,12 @@ package top.continew.starter.extension.datapermission.model;
|
|||||||
import top.continew.starter.extension.datapermission.enums.DataScope;
|
import top.continew.starter.extension.datapermission.enums.DataScope;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 角色上下文
|
* 角色数据
|
||||||
*
|
*
|
||||||
* @author Charles7c
|
* @author Charles7c
|
||||||
* @since 1.1.0
|
* @since 1.1.0
|
||||||
*/
|
*/
|
||||||
public class RoleContext {
|
public class RoleData {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 角色 ID
|
* 角色 ID
|
||||||
@@ -36,10 +36,10 @@ public class RoleContext {
|
|||||||
*/
|
*/
|
||||||
private DataScope dataScope;
|
private DataScope dataScope;
|
||||||
|
|
||||||
public RoleContext() {
|
public RoleData() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public RoleContext(String roleId, DataScope dataScope) {
|
public RoleData(String roleId, DataScope dataScope) {
|
||||||
this.roleId = roleId;
|
this.roleId = roleId;
|
||||||
this.dataScope = dataScope;
|
this.dataScope = dataScope;
|
||||||
}
|
}
|
||||||
@@ -59,4 +59,20 @@ public class RoleContext {
|
|||||||
public void setDataScope(DataScope dataScope) {
|
public void setDataScope(DataScope dataScope) {
|
||||||
this.dataScope = dataScope;
|
this.dataScope = dataScope;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (o == null || getClass() != o.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
RoleData roleData = (RoleData)o;
|
||||||
|
return roleId.equals(roleData.roleId) && dataScope == roleData.dataScope;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
int result = roleId.hashCode();
|
||||||
|
result = 31 * result + dataScope.hashCode();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -19,12 +19,12 @@ package top.continew.starter.extension.datapermission.model;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用户上下文
|
* 用户数据
|
||||||
*
|
*
|
||||||
* @author Charles7c
|
* @author Charles7c
|
||||||
* @since 1.1.0
|
* @since 1.1.0
|
||||||
*/
|
*/
|
||||||
public class UserContext {
|
public class UserData {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用户 ID
|
* 用户 ID
|
||||||
@@ -34,7 +34,7 @@ public class UserContext {
|
|||||||
/**
|
/**
|
||||||
* 角色列表
|
* 角色列表
|
||||||
*/
|
*/
|
||||||
private Set<RoleContext> roles;
|
private Set<RoleData> roles;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 部门 ID
|
* 部门 ID
|
||||||
@@ -49,11 +49,11 @@ public class UserContext {
|
|||||||
this.userId = userId;
|
this.userId = userId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Set<RoleContext> getRoles() {
|
public Set<RoleData> getRoles() {
|
||||||
return roles;
|
return roles;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setRoles(Set<RoleContext> roles) {
|
public void setRoles(Set<RoleData> roles) {
|
||||||
this.roles = roles;
|
this.roles = roles;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,7 +28,7 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties
|
|||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.core.ResolvableType;
|
import org.springframework.core.ResolvableType;
|
||||||
import top.continew.starter.core.constant.PropertiesConstants;
|
import top.continew.starter.core.constant.PropertiesConstants;
|
||||||
import top.continew.starter.extension.datapermission.filter.DataPermissionUserContextProvider;
|
import top.continew.starter.extension.datapermission.filter.DataPermissionUserDataProvider;
|
||||||
import top.continew.starter.extension.datapermission.handler.DefaultDataPermissionHandler;
|
import top.continew.starter.extension.datapermission.handler.DefaultDataPermissionHandler;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -61,21 +61,21 @@ public class DataPermissionAutoConfiguration {
|
|||||||
*/
|
*/
|
||||||
@Bean
|
@Bean
|
||||||
@ConditionalOnMissingBean
|
@ConditionalOnMissingBean
|
||||||
public DataPermissionHandler dataPermissionHandler(DataPermissionUserContextProvider dataPermissionUserContextProvider) {
|
public DataPermissionHandler dataPermissionHandler(DataPermissionUserDataProvider dataPermissionUserDataProvider) {
|
||||||
return new DefaultDataPermissionHandler(dataPermissionUserContextProvider);
|
return new DefaultDataPermissionHandler(dataPermissionUserDataProvider);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 数据权限用户上下文提供者
|
* 数据权限用户数据提供者
|
||||||
*/
|
*/
|
||||||
@Bean
|
@Bean
|
||||||
@ConditionalOnMissingBean
|
@ConditionalOnMissingBean
|
||||||
public DataPermissionUserContextProvider dataPermissionUserContextProvider() {
|
public DataPermissionUserDataProvider dataPermissionUserDataProvider() {
|
||||||
if (log.isErrorEnabled()) {
|
if (log.isErrorEnabled()) {
|
||||||
log.error("Consider defining a bean of type '{}' in your configuration.", ResolvableType
|
log.error("Consider defining a bean of type '{}' in your configuration.", ResolvableType
|
||||||
.forClass(DataPermissionUserContextProvider.class));
|
.forClass(DataPermissionUserDataProvider.class));
|
||||||
}
|
}
|
||||||
throw new NoSuchBeanDefinitionException(DataPermissionUserContextProvider.class);
|
throw new NoSuchBeanDefinitionException(DataPermissionUserDataProvider.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
static {
|
static {
|
||||||
|
|||||||
@@ -47,9 +47,9 @@ import top.continew.starter.data.enums.DatabaseType;
|
|||||||
import top.continew.starter.data.util.MetaUtils;
|
import top.continew.starter.data.util.MetaUtils;
|
||||||
import top.continew.starter.extension.datapermission.annotation.DataPermission;
|
import top.continew.starter.extension.datapermission.annotation.DataPermission;
|
||||||
import top.continew.starter.extension.datapermission.enums.DataScope;
|
import top.continew.starter.extension.datapermission.enums.DataScope;
|
||||||
import top.continew.starter.extension.datapermission.filter.DataPermissionUserContextProvider;
|
import top.continew.starter.extension.datapermission.filter.DataPermissionUserDataProvider;
|
||||||
import top.continew.starter.extension.datapermission.model.RoleContext;
|
import top.continew.starter.extension.datapermission.model.RoleData;
|
||||||
import top.continew.starter.extension.datapermission.model.UserContext;
|
import top.continew.starter.extension.datapermission.model.UserData;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 默认数据权限处理器
|
* 默认数据权限处理器
|
||||||
@@ -61,11 +61,11 @@ import top.continew.starter.extension.datapermission.model.UserContext;
|
|||||||
public class DefaultDataPermissionHandler implements DataPermissionHandler {
|
public class DefaultDataPermissionHandler implements DataPermissionHandler {
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(DefaultDataPermissionHandler.class);
|
private static final Logger log = LoggerFactory.getLogger(DefaultDataPermissionHandler.class);
|
||||||
private final DataPermissionUserContextProvider dataPermissionUserContextProvider;
|
private final DataPermissionUserDataProvider dataPermissionUserDataProvider;
|
||||||
private static final DataSource dataSource = SpringUtil.getBean(DataSource.class);
|
private static final DataSource dataSource = SpringUtil.getBean(DataSource.class);
|
||||||
|
|
||||||
public DefaultDataPermissionHandler(DataPermissionUserContextProvider dataPermissionUserContextProvider) {
|
public DefaultDataPermissionHandler(DataPermissionUserDataProvider dataPermissionUserDataProvider) {
|
||||||
this.dataPermissionUserContextProvider = dataPermissionUserContextProvider;
|
this.dataPermissionUserDataProvider = dataPermissionUserDataProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -81,7 +81,7 @@ public class DefaultDataPermissionHandler implements DataPermissionHandler {
|
|||||||
if (dataPermission == null || !CharSequenceUtil.equalsAny(methodName, name, name + "_COUNT")) {
|
if (dataPermission == null || !CharSequenceUtil.equalsAny(methodName, name, name + "_COUNT")) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (dataPermissionUserContextProvider.isFilter()) {
|
if (dataPermissionUserDataProvider.isFilter()) {
|
||||||
return buildDataScopeFilter(dataPermission, where);
|
return buildDataScopeFilter(dataPermission, where);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -100,19 +100,19 @@ public class DefaultDataPermissionHandler implements DataPermissionHandler {
|
|||||||
*/
|
*/
|
||||||
private Expression buildDataScopeFilter(DataPermission dataPermission, Expression where) {
|
private Expression buildDataScopeFilter(DataPermission dataPermission, Expression where) {
|
||||||
Expression expression = null;
|
Expression expression = null;
|
||||||
UserContext userContext = dataPermissionUserContextProvider.getUserContext();
|
UserData userData = dataPermissionUserDataProvider.getUserData();
|
||||||
Set<RoleContext> roles = userContext.getRoles();
|
Set<RoleData> roles = userData.getRoles();
|
||||||
for (RoleContext roleContext : roles) {
|
for (RoleData roleData : roles) {
|
||||||
DataScope dataScope = roleContext.getDataScope();
|
DataScope dataScope = roleData.getDataScope();
|
||||||
if (DataScope.ALL.equals(dataScope)) {
|
if (DataScope.ALL.equals(dataScope)) {
|
||||||
return where;
|
return where;
|
||||||
}
|
}
|
||||||
switch (dataScope) {
|
switch (dataScope) {
|
||||||
case DEPT_AND_CHILD -> expression = this
|
case DEPT_AND_CHILD -> expression = this
|
||||||
.buildDeptAndChildExpression(dataPermission, userContext, expression);
|
.buildDeptAndChildExpression(dataPermission, userData, expression);
|
||||||
case DEPT -> expression = this.buildDeptExpression(dataPermission, userContext, expression);
|
case DEPT -> expression = this.buildDeptExpression(dataPermission, userData, expression);
|
||||||
case SELF -> expression = this.buildSelfExpression(dataPermission, userContext, expression);
|
case SELF -> expression = this.buildSelfExpression(dataPermission, userData, expression);
|
||||||
case CUSTOM -> expression = this.buildCustomExpression(dataPermission, roleContext, expression);
|
case CUSTOM -> expression = this.buildCustomExpression(dataPermission, roleData, expression);
|
||||||
default -> throw new IllegalArgumentException("暂不支持 [%s] 数据权限".formatted(dataScope));
|
default -> throw new IllegalArgumentException("暂不支持 [%s] 数据权限".formatted(dataScope));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -128,12 +128,12 @@ public class DefaultDataPermissionHandler implements DataPermissionHandler {
|
|||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param dataPermission 数据权限
|
* @param dataPermission 数据权限
|
||||||
* @param userContext 用户上下文
|
* @param userData 用户数据
|
||||||
* @param expression 处理前的表达式
|
* @param expression 处理前的表达式
|
||||||
* @return 处理完后的表达式
|
* @return 处理完后的表达式
|
||||||
*/
|
*/
|
||||||
private Expression buildDeptAndChildExpression(DataPermission dataPermission,
|
private Expression buildDeptAndChildExpression(DataPermission dataPermission,
|
||||||
UserContext userContext,
|
UserData userData,
|
||||||
Expression expression) {
|
Expression expression) {
|
||||||
ParenthesedSelect subSelect = new ParenthesedSelect();
|
ParenthesedSelect subSelect = new ParenthesedSelect();
|
||||||
PlainSelect select = new PlainSelect();
|
PlainSelect select = new PlainSelect();
|
||||||
@@ -142,14 +142,14 @@ public class DefaultDataPermissionHandler implements DataPermissionHandler {
|
|||||||
|
|
||||||
EqualsTo equalsTo = new EqualsTo();
|
EqualsTo equalsTo = new EqualsTo();
|
||||||
equalsTo.setLeftExpression(new Column(dataPermission.id()));
|
equalsTo.setLeftExpression(new Column(dataPermission.id()));
|
||||||
equalsTo.setRightExpression(new LongValue(userContext.getDeptId()));
|
equalsTo.setRightExpression(new LongValue(userData.getDeptId()));
|
||||||
|
|
||||||
DatabaseType databaseType = MetaUtils.getDatabaseType(dataSource);
|
DatabaseType databaseType = MetaUtils.getDatabaseType(dataSource);
|
||||||
Expression inSetExpression;
|
Expression inSetExpression;
|
||||||
if (DatabaseType.MYSQL.getDatabase().equalsIgnoreCase(databaseType.getDatabase())) {
|
if (DatabaseType.MYSQL.getDatabase().equalsIgnoreCase(databaseType.getDatabase())) {
|
||||||
Function findInSetFunction = new Function();
|
Function findInSetFunction = new Function();
|
||||||
findInSetFunction.setName("find_in_set");
|
findInSetFunction.setName("find_in_set");
|
||||||
findInSetFunction.setParameters(new ExpressionList(new LongValue(userContext
|
findInSetFunction.setParameters(new ExpressionList(new LongValue(userData
|
||||||
.getDeptId()), new Column("ancestors")));
|
.getDeptId()), new Column("ancestors")));
|
||||||
inSetExpression = findInSetFunction;
|
inSetExpression = findInSetFunction;
|
||||||
} else if (DatabaseType.POSTGRE_SQL.getDatabase().equalsIgnoreCase(databaseType.getDatabase())) {
|
} else if (DatabaseType.POSTGRE_SQL.getDatabase().equalsIgnoreCase(databaseType.getDatabase())) {
|
||||||
@@ -160,7 +160,7 @@ public class DefaultDataPermissionHandler implements DataPermissionHandler {
|
|||||||
// 创建 LIKE 函数
|
// 创建 LIKE 函数
|
||||||
LikeExpression likeExpression = new LikeExpression();
|
LikeExpression likeExpression = new LikeExpression();
|
||||||
likeExpression.setLeftExpression(concatFunction);
|
likeExpression.setLeftExpression(concatFunction);
|
||||||
likeExpression.setRightExpression(new StringValue("%," + userContext.getDeptId() + ",%"));
|
likeExpression.setRightExpression(new StringValue("%," + userData.getDeptId() + ",%"));
|
||||||
inSetExpression = likeExpression;
|
inSetExpression = likeExpression;
|
||||||
} else {
|
} else {
|
||||||
throw new IllegalArgumentException("暂不支持 [%s] 数据权限".formatted(""));
|
throw new IllegalArgumentException("暂不支持 [%s] 数据权限".formatted(""));
|
||||||
@@ -183,16 +183,14 @@ public class DefaultDataPermissionHandler implements DataPermissionHandler {
|
|||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param dataPermission 数据权限
|
* @param dataPermission 数据权限
|
||||||
* @param userContext 用户上下文
|
* @param userData 用户数据
|
||||||
* @param expression 处理前的表达式
|
* @param expression 处理前的表达式
|
||||||
* @return 处理完后的表达式
|
* @return 处理完后的表达式
|
||||||
*/
|
*/
|
||||||
private Expression buildDeptExpression(DataPermission dataPermission,
|
private Expression buildDeptExpression(DataPermission dataPermission, UserData userData, Expression expression) {
|
||||||
UserContext userContext,
|
|
||||||
Expression expression) {
|
|
||||||
EqualsTo equalsTo = new EqualsTo();
|
EqualsTo equalsTo = new EqualsTo();
|
||||||
equalsTo.setLeftExpression(this.buildColumn(dataPermission.tableAlias(), dataPermission.deptId()));
|
equalsTo.setLeftExpression(this.buildColumn(dataPermission.tableAlias(), dataPermission.deptId()));
|
||||||
equalsTo.setRightExpression(new LongValue(userContext.getDeptId()));
|
equalsTo.setRightExpression(new LongValue(userData.getDeptId()));
|
||||||
return expression != null ? new OrExpression(expression, equalsTo) : equalsTo;
|
return expression != null ? new OrExpression(expression, equalsTo) : equalsTo;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,16 +202,14 @@ public class DefaultDataPermissionHandler implements DataPermissionHandler {
|
|||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param dataPermission 数据权限
|
* @param dataPermission 数据权限
|
||||||
* @param userContext 用户上下文
|
* @param userData 用户数据
|
||||||
* @param expression 处理前的表达式
|
* @param expression 处理前的表达式
|
||||||
* @return 处理完后的表达式
|
* @return 处理完后的表达式
|
||||||
*/
|
*/
|
||||||
private Expression buildSelfExpression(DataPermission dataPermission,
|
private Expression buildSelfExpression(DataPermission dataPermission, UserData userData, Expression expression) {
|
||||||
UserContext userContext,
|
|
||||||
Expression expression) {
|
|
||||||
EqualsTo equalsTo = new EqualsTo();
|
EqualsTo equalsTo = new EqualsTo();
|
||||||
equalsTo.setLeftExpression(this.buildColumn(dataPermission.tableAlias(), dataPermission.userId()));
|
equalsTo.setLeftExpression(this.buildColumn(dataPermission.tableAlias(), dataPermission.userId()));
|
||||||
equalsTo.setRightExpression(new LongValue(userContext.getUserId()));
|
equalsTo.setRightExpression(new LongValue(userData.getUserId()));
|
||||||
return expression != null ? new OrExpression(expression, equalsTo) : equalsTo;
|
return expression != null ? new OrExpression(expression, equalsTo) : equalsTo;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -226,20 +222,18 @@ public class DefaultDataPermissionHandler implements DataPermissionHandler {
|
|||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param dataPermission 数据权限
|
* @param dataPermission 数据权限
|
||||||
* @param roleContext 角色上下文
|
* @param roleData 角色上下文
|
||||||
* @param expression 处理前的表达式
|
* @param expression 处理前的表达式
|
||||||
* @return 处理完后的表达式
|
* @return 处理完后的表达式
|
||||||
*/
|
*/
|
||||||
private Expression buildCustomExpression(DataPermission dataPermission,
|
private Expression buildCustomExpression(DataPermission dataPermission, RoleData roleData, Expression expression) {
|
||||||
RoleContext roleContext,
|
|
||||||
Expression expression) {
|
|
||||||
ParenthesedSelect subSelect = new ParenthesedSelect();
|
ParenthesedSelect subSelect = new ParenthesedSelect();
|
||||||
PlainSelect select = new PlainSelect();
|
PlainSelect select = new PlainSelect();
|
||||||
select.setSelectItems(Collections.singletonList(new SelectItem<>(new Column(dataPermission.deptId()))));
|
select.setSelectItems(Collections.singletonList(new SelectItem<>(new Column(dataPermission.deptId()))));
|
||||||
select.setFromItem(new Table(dataPermission.roleDeptTableAlias()));
|
select.setFromItem(new Table(dataPermission.roleDeptTableAlias()));
|
||||||
EqualsTo equalsTo = new EqualsTo();
|
EqualsTo equalsTo = new EqualsTo();
|
||||||
equalsTo.setLeftExpression(new Column(dataPermission.roleId()));
|
equalsTo.setLeftExpression(new Column(dataPermission.roleId()));
|
||||||
equalsTo.setRightExpression(new LongValue(roleContext.getRoleId()));
|
equalsTo.setRightExpression(new LongValue(roleData.getRoleId()));
|
||||||
select.setWhere(equalsTo);
|
select.setWhere(equalsTo);
|
||||||
subSelect.setSelect(select);
|
subSelect.setSelect(select);
|
||||||
// 构建父查询
|
// 构建父查询
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
<packaging>jar</packaging>
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
<name>${project.artifactId}</name>
|
<name>${project.artifactId}</name>
|
||||||
<description>ContiNew Starter 扩展模块 - 多租户 - 核心模块</description>
|
<description>ContiNew Starter 扩展模块 - 租户 - 核心模块</description>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<!-- TTL(线程间传递 ThreadLocal,异步执行时上下文传递的解决方案) -->
|
<!-- TTL(线程间传递 ThreadLocal,异步执行时上下文传递的解决方案) -->
|
||||||
|
|||||||
@@ -57,4 +57,9 @@ public interface TenantDataSourceHandler {
|
|||||||
* @param dataSourceName 数据源名称
|
* @param dataSourceName 数据源名称
|
||||||
*/
|
*/
|
||||||
void removeDataSource(String dataSourceName);
|
void removeDataSource(String dataSourceName);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 轮询数据源
|
||||||
|
*/
|
||||||
|
void poll();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,21 +14,22 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package top.continew.starter.extension.tenant;
|
package top.continew.starter.extension.tenant.annotation;
|
||||||
|
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
|
import top.continew.starter.core.constant.PropertiesConstants;
|
||||||
|
|
||||||
|
import java.lang.annotation.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 租户处理器
|
* 是否启用租户判断注解
|
||||||
*
|
*
|
||||||
* @author 小熊
|
* @author Charles7c
|
||||||
* @since 2.8.0
|
* @since 2.13.1
|
||||||
*/
|
*/
|
||||||
public interface TenantHandler {
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Target({ElementType.TYPE, ElementType.METHOD})
|
||||||
/**
|
@Documented
|
||||||
* 在指定租户中执行
|
@ConditionalOnProperty(prefix = PropertiesConstants.TENANT, name = PropertiesConstants.ENABLED, havingValue = "true", matchIfMissing = true)
|
||||||
*
|
public @interface ConditionalOnEnabledTenant {
|
||||||
* @param tenantId 租户 ID
|
|
||||||
* @param runnable 方法
|
|
||||||
*/
|
|
||||||
void execute(Long tenantId, Runnable runnable);
|
|
||||||
}
|
}
|
||||||
@@ -19,7 +19,11 @@ package top.continew.starter.extension.tenant.annotation;
|
|||||||
import java.lang.annotation.*;
|
import java.lang.annotation.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 多租户忽略注解
|
* 租户忽略注解
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* 例如:定时任务等需要忽略租户的场景
|
||||||
|
* </p>
|
||||||
*
|
*
|
||||||
* @author Charles7c
|
* @author Charles7c
|
||||||
* @since 2.7.0
|
* @since 2.7.0
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||||
|
* <p>
|
||||||
|
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* <p>
|
||||||
|
* http://www.gnu.org/licenses/lgpl.html
|
||||||
|
* <p>
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package top.continew.starter.extension.tenant.aop;
|
||||||
|
|
||||||
|
import org.aspectj.lang.ProceedingJoinPoint;
|
||||||
|
import org.aspectj.lang.annotation.Around;
|
||||||
|
import org.aspectj.lang.annotation.Aspect;
|
||||||
|
import top.continew.starter.extension.tenant.annotation.TenantIgnore;
|
||||||
|
import top.continew.starter.extension.tenant.context.TenantContextHolder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 租户忽略注解切面
|
||||||
|
*
|
||||||
|
* @author Charles7c
|
||||||
|
* @since 2.13.1
|
||||||
|
*/
|
||||||
|
@Aspect
|
||||||
|
public class TenantIgnoreAspect {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 忽略租户
|
||||||
|
*
|
||||||
|
* @param joinPoint 切点
|
||||||
|
* @return 返回结果
|
||||||
|
* @throws Throwable 异常
|
||||||
|
*/
|
||||||
|
@Around("@annotation(tenantIgnore)")
|
||||||
|
public Object around(ProceedingJoinPoint joinPoint, TenantIgnore tenantIgnore) throws Throwable {
|
||||||
|
boolean oldIgnore = TenantContextHolder.isIgnore();
|
||||||
|
try {
|
||||||
|
TenantContextHolder.setIgnore(true);
|
||||||
|
return joinPoint.proceed();
|
||||||
|
} finally {
|
||||||
|
TenantContextHolder.setIgnore(oldIgnore);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -52,12 +52,12 @@ public class TenantProperties {
|
|||||||
private String tenantIdHeader = "X-Tenant-Id";
|
private String tenantIdHeader = "X-Tenant-Id";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 超级租户 ID
|
* 超级/默认租户 ID(超管用户所在租户)
|
||||||
*/
|
*/
|
||||||
private Long superTenantId = 1L;
|
private Long superTenantId = 0L;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 忽略表(忽略拼接多租户条件)
|
* 忽略表(忽略拼接租户条件)
|
||||||
*/
|
*/
|
||||||
private List<String> ignoreTables;
|
private List<String> ignoreTables;
|
||||||
|
|
||||||
|
|||||||
@@ -23,9 +23,10 @@ import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
|||||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||||
import top.continew.starter.core.constant.PropertiesConstants;
|
import top.continew.starter.core.constant.PropertiesConstants;
|
||||||
import top.continew.starter.extension.tenant.config.TenantProvider;
|
import top.continew.starter.extension.tenant.config.TenantProvider;
|
||||||
|
import top.continew.starter.extension.tenant.interceptor.TenantInterceptor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 多租户 Web MVC 自动配置
|
* 租户 Web MVC 自动配置
|
||||||
*
|
*
|
||||||
* @author Charles7c
|
* @author Charles7c
|
||||||
* @since 2.7.0
|
* @since 2.7.0
|
||||||
|
|||||||
@@ -30,9 +30,9 @@ public interface TenantProvider {
|
|||||||
/**
|
/**
|
||||||
* 根据租户 ID 获取租户上下文
|
* 根据租户 ID 获取租户上下文
|
||||||
*
|
*
|
||||||
* @param tenantId 租户 ID
|
* @param tenantIdAsString 租户 ID 字符串
|
||||||
* @param isVerify 是否验证有效性
|
* @param isVerify 是否验证有效性
|
||||||
* @return 租户上下文
|
* @return 租户上下文
|
||||||
*/
|
*/
|
||||||
TenantContext getByTenantId(String tenantId, boolean isVerify);
|
TenantContext getByTenantId(String tenantIdAsString, boolean isVerify);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ package top.continew.starter.extension.tenant.context;
|
|||||||
|
|
||||||
import cn.hutool.extra.spring.SpringUtil;
|
import cn.hutool.extra.spring.SpringUtil;
|
||||||
import com.alibaba.ttl.TransmittableThreadLocal;
|
import com.alibaba.ttl.TransmittableThreadLocal;
|
||||||
|
import top.continew.starter.core.util.SpringUtils;
|
||||||
import top.continew.starter.extension.tenant.autoconfigure.TenantProperties;
|
import top.continew.starter.extension.tenant.autoconfigure.TenantProperties;
|
||||||
import top.continew.starter.extension.tenant.config.TenantDataSource;
|
import top.continew.starter.extension.tenant.config.TenantDataSource;
|
||||||
import top.continew.starter.extension.tenant.enums.TenantIsolationLevel;
|
import top.continew.starter.extension.tenant.enums.TenantIsolationLevel;
|
||||||
@@ -32,8 +33,16 @@ import java.util.Optional;
|
|||||||
*/
|
*/
|
||||||
public class TenantContextHolder {
|
public class TenantContextHolder {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 租户上下文
|
||||||
|
*/
|
||||||
private static final TransmittableThreadLocal<TenantContext> CONTEXT_HOLDER = new TransmittableThreadLocal<>();
|
private static final TransmittableThreadLocal<TenantContext> CONTEXT_HOLDER = new TransmittableThreadLocal<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否忽略租户
|
||||||
|
*/
|
||||||
|
private static final TransmittableThreadLocal<Boolean> IGNORE_HOLDER = new TransmittableThreadLocal<>();
|
||||||
|
|
||||||
private TenantContextHolder() {
|
private TenantContextHolder() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,10 +65,29 @@ public class TenantContextHolder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 清除上下文
|
* 设置是否忽略租户
|
||||||
|
*
|
||||||
|
* @param ignore 是否忽略租户
|
||||||
*/
|
*/
|
||||||
public static void clearContext() {
|
public static void setIgnore(boolean ignore) {
|
||||||
|
IGNORE_HOLDER.set(ignore);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否忽略租户
|
||||||
|
*
|
||||||
|
* @return 是否忽略租户
|
||||||
|
*/
|
||||||
|
public static boolean isIgnore() {
|
||||||
|
return Boolean.TRUE.equals(IGNORE_HOLDER.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清除
|
||||||
|
*/
|
||||||
|
public static void clear() {
|
||||||
CONTEXT_HOLDER.remove();
|
CONTEXT_HOLDER.remove();
|
||||||
|
IGNORE_HOLDER.remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -90,4 +118,23 @@ public class TenantContextHolder {
|
|||||||
public static TenantDataSource getDataSource() {
|
public static TenantDataSource getDataSource() {
|
||||||
return Optional.ofNullable(getContext()).map(TenantContext::getDataSource).orElse(null);
|
return Optional.ofNullable(getContext()).map(TenantContext::getDataSource).orElse(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否启用了租户
|
||||||
|
*
|
||||||
|
* @return 是否启用了租户
|
||||||
|
*/
|
||||||
|
public static boolean isTenantEnabled() {
|
||||||
|
TenantProperties tenantProperties = SpringUtils.getBean(TenantProperties.class, true);
|
||||||
|
return tenantProperties != null && tenantProperties.isEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否禁用了租户
|
||||||
|
*
|
||||||
|
* @return 是否禁用了租户
|
||||||
|
*/
|
||||||
|
public static boolean isTenantDisabled() {
|
||||||
|
return !isTenantEnabled();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,14 +14,16 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package top.continew.starter.extension.tenant.autoconfigure;
|
package top.continew.starter.extension.tenant.interceptor;
|
||||||
|
|
||||||
|
import cn.hutool.core.annotation.AnnotationUtil;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
import org.springframework.core.Ordered;
|
import org.springframework.core.Ordered;
|
||||||
import org.springframework.web.method.HandlerMethod;
|
import org.springframework.web.method.HandlerMethod;
|
||||||
import org.springframework.web.servlet.HandlerInterceptor;
|
import org.springframework.web.servlet.HandlerInterceptor;
|
||||||
import top.continew.starter.extension.tenant.annotation.TenantIgnore;
|
import top.continew.starter.extension.tenant.annotation.TenantIgnore;
|
||||||
|
import top.continew.starter.extension.tenant.autoconfigure.TenantProperties;
|
||||||
import top.continew.starter.extension.tenant.config.TenantProvider;
|
import top.continew.starter.extension.tenant.config.TenantProvider;
|
||||||
import top.continew.starter.extension.tenant.context.TenantContextHolder;
|
import top.continew.starter.extension.tenant.context.TenantContextHolder;
|
||||||
|
|
||||||
@@ -43,17 +45,30 @@ public class TenantInterceptor implements HandlerInterceptor, Ordered {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
|
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
|
||||||
|
// 忽略租户拦截
|
||||||
if (handler instanceof HandlerMethod handlerMethod) {
|
if (handler instanceof HandlerMethod handlerMethod) {
|
||||||
TenantIgnore tenantIgnore = handlerMethod.getMethodAnnotation(TenantIgnore.class);
|
TenantIgnore methodAnnotation = handlerMethod.getMethodAnnotation(TenantIgnore.class);
|
||||||
if (tenantIgnore != null) {
|
if (methodAnnotation != null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
TenantIgnore classAnnotation = AnnotationUtil.getAnnotation(handlerMethod
|
||||||
|
.getBeanType(), TenantIgnore.class);
|
||||||
|
if (classAnnotation != null) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// 设置上下文
|
||||||
String tenantId = request.getHeader(tenantProperties.getTenantIdHeader());
|
String tenantId = request.getHeader(tenantProperties.getTenantIdHeader());
|
||||||
TenantContextHolder.setContext(tenantProvider.getByTenantId(tenantId, true));
|
TenantContextHolder.setContext(tenantProvider.getByTenantId(tenantId, true));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception e) {
|
||||||
|
// 清除上下文
|
||||||
|
TenantContextHolder.clear();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getOrder() {
|
public int getOrder() {
|
||||||
return Integer.MIN_VALUE;
|
return Integer.MIN_VALUE;
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||||
|
* <p>
|
||||||
|
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* <p>
|
||||||
|
* http://www.gnu.org/licenses/lgpl.html
|
||||||
|
* <p>
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package top.continew.starter.extension.tenant.util;
|
||||||
|
|
||||||
|
import cn.hutool.extra.spring.SpringUtil;
|
||||||
|
import top.continew.starter.extension.tenant.TenantDataSourceHandler;
|
||||||
|
import top.continew.starter.extension.tenant.config.TenantProvider;
|
||||||
|
import top.continew.starter.extension.tenant.context.TenantContext;
|
||||||
|
import top.continew.starter.extension.tenant.context.TenantContextHolder;
|
||||||
|
import top.continew.starter.extension.tenant.enums.TenantIsolationLevel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 租户工具类
|
||||||
|
*
|
||||||
|
* @author 小熊
|
||||||
|
* @author Charles7c
|
||||||
|
* @since 2.13.1
|
||||||
|
*/
|
||||||
|
public class TenantUtils {
|
||||||
|
|
||||||
|
private TenantUtils() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使用指定租户执行业务逻辑
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* 强制设置 {@code TenantContextHolder.setIgnore(false)},执行完恢复原值。<br>
|
||||||
|
* 适用于在非租户逻辑中执行有租户逻辑的业务逻辑。
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param tenantId 租户 ID
|
||||||
|
* @param runnable 业务逻辑
|
||||||
|
*/
|
||||||
|
public static void execute(Long tenantId, Runnable runnable) {
|
||||||
|
// 未启用租户,直接执行业务逻辑
|
||||||
|
if (TenantContextHolder.isTenantDisabled()) {
|
||||||
|
runnable.run();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 原租户上下文
|
||||||
|
TenantContext oldContext = TenantContextHolder.getContext();
|
||||||
|
boolean oldIgnore = TenantContextHolder.isIgnore();
|
||||||
|
boolean isPush = false;
|
||||||
|
try {
|
||||||
|
TenantContext newContext = SpringUtil.getBean(TenantProvider.class)
|
||||||
|
.getByTenantId(tenantId.toString(), false);
|
||||||
|
// 设置新租户上下文
|
||||||
|
TenantContextHolder.setContext(newContext);
|
||||||
|
TenantContextHolder.setIgnore(false);
|
||||||
|
// 数据源级隔离:切换数据源
|
||||||
|
if (TenantIsolationLevel.DATASOURCE.equals(newContext.getIsolationLevel())) {
|
||||||
|
SpringUtil.getBean(TenantDataSourceHandler.class).changeDataSource(newContext.getDataSource());
|
||||||
|
isPush = true;
|
||||||
|
}
|
||||||
|
// 执行业务逻辑
|
||||||
|
runnable.run();
|
||||||
|
} finally {
|
||||||
|
// 恢复原租户上下文
|
||||||
|
TenantContextHolder.setContext(oldContext);
|
||||||
|
TenantContextHolder.setIgnore(oldIgnore);
|
||||||
|
if (isPush) {
|
||||||
|
SpringUtil.getBean(TenantDataSourceHandler.class).poll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 忽略租户执行业务逻辑
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* 适用于在租户逻辑中执行非租户逻辑的业务逻辑。
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param runnable 业务逻辑
|
||||||
|
*/
|
||||||
|
public void executeIgnore(Runnable runnable) {
|
||||||
|
// 未启用租户,直接执行业务逻辑
|
||||||
|
if (TenantContextHolder.isTenantDisabled()) {
|
||||||
|
runnable.run();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
boolean oldIgnore = TenantContextHolder.isIgnore();
|
||||||
|
try {
|
||||||
|
TenantContextHolder.setIgnore(true);
|
||||||
|
// 执行业务逻辑
|
||||||
|
runnable.run();
|
||||||
|
} finally {
|
||||||
|
TenantContextHolder.setIgnore(oldIgnore);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,10 +13,10 @@
|
|||||||
<packaging>jar</packaging>
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
<name>${project.artifactId}</name>
|
<name>${project.artifactId}</name>
|
||||||
<description>ContiNew Starter 扩展模块 - 多租户 - MyBatis Plus ORM 模块</description>
|
<description>ContiNew Starter 扩展模块 - 租户 - MyBatis Plus ORM 模块</description>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<!-- 多租户 - 核心模块 -->
|
<!-- 租户 - 核心模块 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>top.continew.starter</groupId>
|
<groupId>top.continew.starter</groupId>
|
||||||
<artifactId>continew-starter-extension-tenant-core</artifactId>
|
<artifactId>continew-starter-extension-tenant-core</artifactId>
|
||||||
@@ -32,6 +32,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.baomidou</groupId>
|
<groupId>com.baomidou</groupId>
|
||||||
<artifactId>dynamic-datasource-spring-boot3-starter</artifactId>
|
<artifactId>dynamic-datasource-spring-boot3-starter</artifactId>
|
||||||
|
<optional>true</optional>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</project>
|
||||||
@@ -16,8 +16,6 @@
|
|||||||
|
|
||||||
package top.continew.starter.extension.tenant.autoconfigure;
|
package top.continew.starter.extension.tenant.autoconfigure;
|
||||||
|
|
||||||
import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
|
|
||||||
import com.baomidou.dynamic.datasource.creator.DefaultDataSourceCreator;
|
|
||||||
import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
|
import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
|
||||||
import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
|
import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
|
||||||
import jakarta.annotation.PostConstruct;
|
import jakarta.annotation.PostConstruct;
|
||||||
@@ -25,16 +23,15 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
|
||||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.core.ResolvableType;
|
import org.springframework.core.ResolvableType;
|
||||||
import top.continew.starter.core.constant.PropertiesConstants;
|
import top.continew.starter.extension.tenant.annotation.ConditionalOnEnabledTenant;
|
||||||
|
import top.continew.starter.extension.tenant.aop.TenantIgnoreAspect;
|
||||||
import top.continew.starter.extension.tenant.config.TenantProvider;
|
import top.continew.starter.extension.tenant.config.TenantProvider;
|
||||||
import top.continew.starter.extension.tenant.handler.DefaultTenantHandler;
|
|
||||||
import top.continew.starter.extension.tenant.TenantDataSourceHandler;
|
import top.continew.starter.extension.tenant.TenantDataSourceHandler;
|
||||||
import top.continew.starter.extension.tenant.TenantHandler;
|
|
||||||
import top.continew.starter.extension.tenant.handler.datasource.DefaultTenantDataSourceHandler;
|
import top.continew.starter.extension.tenant.handler.datasource.DefaultTenantDataSourceHandler;
|
||||||
import top.continew.starter.extension.tenant.handler.datasource.TenantDataSourceAdvisor;
|
import top.continew.starter.extension.tenant.handler.datasource.TenantDataSourceAdvisor;
|
||||||
import top.continew.starter.extension.tenant.handler.datasource.TenantDataSourceInterceptor;
|
import top.continew.starter.extension.tenant.handler.datasource.TenantDataSourceInterceptor;
|
||||||
@@ -49,8 +46,8 @@ import javax.sql.DataSource;
|
|||||||
* @since 2.7.0
|
* @since 2.7.0
|
||||||
*/
|
*/
|
||||||
@AutoConfiguration
|
@AutoConfiguration
|
||||||
|
@ConditionalOnEnabledTenant
|
||||||
@EnableConfigurationProperties(TenantProperties.class)
|
@EnableConfigurationProperties(TenantProperties.class)
|
||||||
@ConditionalOnProperty(prefix = PropertiesConstants.TENANT, name = PropertiesConstants.ENABLED, havingValue = "true", matchIfMissing = true)
|
|
||||||
public class TenantAutoConfiguration {
|
public class TenantAutoConfiguration {
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(TenantAutoConfiguration.class);
|
private static final Logger log = LoggerFactory.getLogger(TenantAutoConfiguration.class);
|
||||||
@@ -60,6 +57,15 @@ public class TenantAutoConfiguration {
|
|||||||
this.tenantProperties = tenantProperties;
|
this.tenantProperties = tenantProperties;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 租户忽略切面
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
@ConditionalOnMissingBean
|
||||||
|
public TenantIgnoreAspect tenantIgnoreAspect() {
|
||||||
|
return new TenantIgnoreAspect();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 租户行级隔离拦截器
|
* 租户行级隔离拦截器
|
||||||
*/
|
*/
|
||||||
@@ -83,6 +89,7 @@ public class TenantAutoConfiguration {
|
|||||||
*/
|
*/
|
||||||
@Bean
|
@Bean
|
||||||
@ConditionalOnMissingBean
|
@ConditionalOnMissingBean
|
||||||
|
@ConditionalOnClass(name = "com.baomidou.dynamic.datasource.DynamicRoutingDataSource")
|
||||||
public TenantDataSourceAdvisor tenantDataSourceAdvisor(TenantDataSourceInterceptor tenantDataSourceInterceptor) {
|
public TenantDataSourceAdvisor tenantDataSourceAdvisor(TenantDataSourceInterceptor tenantDataSourceInterceptor) {
|
||||||
return new TenantDataSourceAdvisor(tenantDataSourceInterceptor);
|
return new TenantDataSourceAdvisor(tenantDataSourceInterceptor);
|
||||||
}
|
}
|
||||||
@@ -92,6 +99,7 @@ public class TenantAutoConfiguration {
|
|||||||
*/
|
*/
|
||||||
@Bean
|
@Bean
|
||||||
@ConditionalOnMissingBean
|
@ConditionalOnMissingBean
|
||||||
|
@ConditionalOnClass(name = "com.baomidou.dynamic.datasource.DynamicRoutingDataSource")
|
||||||
public TenantDataSourceInterceptor tenantDataSourceInterceptor(TenantDataSourceHandler tenantDataSourceHandler) {
|
public TenantDataSourceInterceptor tenantDataSourceInterceptor(TenantDataSourceHandler tenantDataSourceHandler) {
|
||||||
return new TenantDataSourceInterceptor(tenantDataSourceHandler);
|
return new TenantDataSourceInterceptor(tenantDataSourceHandler);
|
||||||
}
|
}
|
||||||
@@ -101,9 +109,9 @@ public class TenantAutoConfiguration {
|
|||||||
*/
|
*/
|
||||||
@Bean
|
@Bean
|
||||||
@ConditionalOnMissingBean
|
@ConditionalOnMissingBean
|
||||||
public TenantDataSourceHandler tenantDataSourceHandler(DataSource dataSource,
|
@ConditionalOnClass(name = "com.baomidou.dynamic.datasource.DynamicRoutingDataSource")
|
||||||
DefaultDataSourceCreator dataSourceCreator) {
|
public TenantDataSourceHandler tenantDataSourceHandler(DataSource dataSource) {
|
||||||
return new DefaultTenantDataSourceHandler((DynamicRoutingDataSource)dataSource, dataSourceCreator);
|
return new DefaultTenantDataSourceHandler(dataSource);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -119,15 +127,6 @@ public class TenantAutoConfiguration {
|
|||||||
throw new NoSuchBeanDefinitionException(TenantProvider.class);
|
throw new NoSuchBeanDefinitionException(TenantProvider.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 租户处理器
|
|
||||||
*/
|
|
||||||
@Bean
|
|
||||||
@ConditionalOnMissingBean
|
|
||||||
public TenantHandler tenantHandler(TenantDataSourceHandler tenantDataSourceHandler, TenantProvider tenantProvider) {
|
|
||||||
return new DefaultTenantHandler(tenantProperties, tenantDataSourceHandler, tenantProvider);
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
public void postConstruct() {
|
public void postConstruct() {
|
||||||
log.debug("[ContiNew Starter] - Auto Configuration 'Tenant' completed initialization.");
|
log.debug("[ContiNew Starter] - Auto Configuration 'Tenant' completed initialization.");
|
||||||
|
|||||||
@@ -1,79 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
|
||||||
* <p>
|
|
||||||
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* <p>
|
|
||||||
* http://www.gnu.org/licenses/lgpl.html
|
|
||||||
* <p>
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package top.continew.starter.extension.tenant.handler;
|
|
||||||
|
|
||||||
import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
|
|
||||||
import top.continew.starter.extension.tenant.TenantDataSourceHandler;
|
|
||||||
import top.continew.starter.extension.tenant.TenantHandler;
|
|
||||||
import top.continew.starter.extension.tenant.autoconfigure.TenantProperties;
|
|
||||||
import top.continew.starter.extension.tenant.config.TenantProvider;
|
|
||||||
import top.continew.starter.extension.tenant.context.TenantContext;
|
|
||||||
import top.continew.starter.extension.tenant.context.TenantContextHolder;
|
|
||||||
import top.continew.starter.extension.tenant.enums.TenantIsolationLevel;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 租户处理器
|
|
||||||
*
|
|
||||||
* @author 小熊
|
|
||||||
* @since 2.8.0
|
|
||||||
*/
|
|
||||||
public class DefaultTenantHandler implements TenantHandler {
|
|
||||||
|
|
||||||
private final TenantProperties tenantProperties;
|
|
||||||
private final TenantDataSourceHandler dataSourceHandler;
|
|
||||||
private final TenantProvider tenantProvider;
|
|
||||||
|
|
||||||
public DefaultTenantHandler(TenantProperties tenantProperties,
|
|
||||||
TenantDataSourceHandler dataSourceHandler,
|
|
||||||
TenantProvider tenantProvider) {
|
|
||||||
this.tenantProperties = tenantProperties;
|
|
||||||
this.dataSourceHandler = dataSourceHandler;
|
|
||||||
this.tenantProvider = tenantProvider;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void execute(Long tenantId, Runnable runnable) {
|
|
||||||
if (!tenantProperties.isEnabled()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
TenantContext tenantHandler = tenantProvider.getByTenantId(tenantId.toString(), false);
|
|
||||||
// 保存当前的租户上下文
|
|
||||||
TenantContext originalContext = TenantContextHolder.getContext();
|
|
||||||
boolean isPush = false;
|
|
||||||
try {
|
|
||||||
// 设置新的租户上下文
|
|
||||||
TenantContextHolder.setContext(tenantHandler);
|
|
||||||
// 切换数据源
|
|
||||||
if (TenantIsolationLevel.DATASOURCE.equals(tenantHandler.getIsolationLevel())) {
|
|
||||||
dataSourceHandler.changeDataSource(tenantHandler.getDataSource());
|
|
||||||
isPush = true;
|
|
||||||
}
|
|
||||||
// 执行业务逻辑
|
|
||||||
runnable.run();
|
|
||||||
} finally {
|
|
||||||
// 恢复原始的租户上下文
|
|
||||||
if (originalContext != null) {
|
|
||||||
TenantContextHolder.setContext(originalContext);
|
|
||||||
} else {
|
|
||||||
TenantContextHolder.clearContext();
|
|
||||||
}
|
|
||||||
if (isPush) {
|
|
||||||
DynamicDataSourceContextHolder.poll();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -17,6 +17,7 @@
|
|||||||
package top.continew.starter.extension.tenant.handler.datasource;
|
package top.continew.starter.extension.tenant.handler.datasource;
|
||||||
|
|
||||||
import cn.hutool.core.text.CharSequenceUtil;
|
import cn.hutool.core.text.CharSequenceUtil;
|
||||||
|
import cn.hutool.extra.spring.SpringUtil;
|
||||||
import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
|
import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
|
||||||
import com.baomidou.dynamic.datasource.creator.DataSourceProperty;
|
import com.baomidou.dynamic.datasource.creator.DataSourceProperty;
|
||||||
import com.baomidou.dynamic.datasource.creator.DefaultDataSourceCreator;
|
import com.baomidou.dynamic.datasource.creator.DefaultDataSourceCreator;
|
||||||
@@ -40,10 +41,9 @@ public class DefaultTenantDataSourceHandler implements TenantDataSourceHandler {
|
|||||||
private final DynamicRoutingDataSource dynamicRoutingDataSource;
|
private final DynamicRoutingDataSource dynamicRoutingDataSource;
|
||||||
private final DefaultDataSourceCreator dataSourceCreator;
|
private final DefaultDataSourceCreator dataSourceCreator;
|
||||||
|
|
||||||
public DefaultTenantDataSourceHandler(DynamicRoutingDataSource dynamicRoutingDataSource,
|
public DefaultTenantDataSourceHandler(DataSource dataSource) {
|
||||||
DefaultDataSourceCreator dataSourceCreator) {
|
this.dynamicRoutingDataSource = (DynamicRoutingDataSource)dataSource;
|
||||||
this.dynamicRoutingDataSource = dynamicRoutingDataSource;
|
this.dataSourceCreator = SpringUtil.getBean(DefaultDataSourceCreator.class);
|
||||||
this.dataSourceCreator = dataSourceCreator;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -82,4 +82,9 @@ public class DefaultTenantDataSourceHandler implements TenantDataSourceHandler {
|
|||||||
public void removeDataSource(String dataSourceName) {
|
public void removeDataSource(String dataSourceName) {
|
||||||
dynamicRoutingDataSource.removeDataSource(dataSourceName);
|
dynamicRoutingDataSource.removeDataSource(dataSourceName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void poll() {
|
||||||
|
DynamicDataSourceContextHolder.poll();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -67,7 +67,6 @@ public class TenantDataSourceAdvisor extends AbstractPointcutAdvisor implements
|
|||||||
AspectJExpressionPointcut cut = new AspectJExpressionPointcut();
|
AspectJExpressionPointcut cut = new AspectJExpressionPointcut();
|
||||||
cut.setExpression("""
|
cut.setExpression("""
|
||||||
execution(* *..controller..*(..))
|
execution(* *..controller..*(..))
|
||||||
&& !@annotation(top.continew.starter.extension.tenant.annotation.TenantIgnore)
|
|
||||||
""");
|
""");
|
||||||
return new ComposablePointcut((Pointcut)cut);
|
return new ComposablePointcut((Pointcut)cut);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,6 +39,11 @@ public class TenantDataSourceInterceptor implements MethodInterceptor {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object invoke(MethodInvocation invocation) throws Throwable {
|
public Object invoke(MethodInvocation invocation) throws Throwable {
|
||||||
|
// 忽略租户
|
||||||
|
if (TenantContextHolder.isIgnore()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// 忽略行级隔离
|
||||||
if (TenantIsolationLevel.LINE.equals(TenantContextHolder.getIsolationLevel())) {
|
if (TenantIsolationLevel.LINE.equals(TenantContextHolder.getIsolationLevel())) {
|
||||||
return invocation.proceed();
|
return invocation.proceed();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,9 @@ import cn.hutool.core.collection.CollUtil;
|
|||||||
import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
|
import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
|
||||||
import net.sf.jsqlparser.expression.Expression;
|
import net.sf.jsqlparser.expression.Expression;
|
||||||
import net.sf.jsqlparser.expression.LongValue;
|
import net.sf.jsqlparser.expression.LongValue;
|
||||||
|
import net.sf.jsqlparser.expression.NullValue;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
import top.continew.starter.extension.tenant.autoconfigure.TenantProperties;
|
import top.continew.starter.extension.tenant.autoconfigure.TenantProperties;
|
||||||
import top.continew.starter.extension.tenant.context.TenantContextHolder;
|
import top.continew.starter.extension.tenant.context.TenantContextHolder;
|
||||||
import top.continew.starter.extension.tenant.enums.TenantIsolationLevel;
|
import top.continew.starter.extension.tenant.enums.TenantIsolationLevel;
|
||||||
@@ -32,6 +35,7 @@ import top.continew.starter.extension.tenant.enums.TenantIsolationLevel;
|
|||||||
*/
|
*/
|
||||||
public class DefaultTenantLineHandler implements TenantLineHandler {
|
public class DefaultTenantLineHandler implements TenantLineHandler {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(DefaultTenantLineHandler.class);
|
||||||
private final TenantProperties tenantProperties;
|
private final TenantProperties tenantProperties;
|
||||||
|
|
||||||
public DefaultTenantLineHandler(TenantProperties tenantProperties) {
|
public DefaultTenantLineHandler(TenantProperties tenantProperties) {
|
||||||
@@ -44,7 +48,8 @@ public class DefaultTenantLineHandler implements TenantLineHandler {
|
|||||||
if (tenantId != null) {
|
if (tenantId != null) {
|
||||||
return new LongValue(tenantId);
|
return new LongValue(tenantId);
|
||||||
}
|
}
|
||||||
return null;
|
log.warn("Tenant ID not found in current context.");
|
||||||
|
return new NullValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -54,13 +59,20 @@ public class DefaultTenantLineHandler implements TenantLineHandler {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean ignoreTable(String tableName) {
|
public boolean ignoreTable(String tableName) {
|
||||||
Long tenantId = TenantContextHolder.getTenantId();
|
// 忽略租户
|
||||||
if (tenantId != null && tenantId.equals(tenantProperties.getSuperTenantId())) {
|
if (TenantContextHolder.isIgnore()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
// 忽略超级租户
|
||||||
|
Long tenantId = TenantContextHolder.getTenantId();
|
||||||
|
if (tenantId == null || tenantId.equals(tenantProperties.getSuperTenantId())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// 忽略数据源级隔离
|
||||||
if (TenantIsolationLevel.DATASOURCE.equals(TenantContextHolder.getIsolationLevel())) {
|
if (TenantIsolationLevel.DATASOURCE.equals(TenantContextHolder.getIsolationLevel())) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
// 忽略指定表
|
||||||
return CollUtil.contains(tenantProperties.getIgnoreTables(), tableName);
|
return CollUtil.contains(tenantProperties.getIgnoreTables(), tableName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
<packaging>pom</packaging>
|
<packaging>pom</packaging>
|
||||||
|
|
||||||
<name>${project.artifactId}</name>
|
<name>${project.artifactId}</name>
|
||||||
<description>ContiNew Starter 扩展模块 - 多租户</description>
|
<description>ContiNew Starter 扩展模块 - 租户</description>
|
||||||
|
|
||||||
<modules>
|
<modules>
|
||||||
<module>continew-starter-extension-tenant-core</module>
|
<module>continew-starter-extension-tenant-core</module>
|
||||||
|
|||||||
@@ -32,8 +32,9 @@ import java.util.Objects;
|
|||||||
/**
|
/**
|
||||||
* 枚举接口 BaseEnum 反序列化器
|
* 枚举接口 BaseEnum 反序列化器
|
||||||
*
|
*
|
||||||
* @author Charles7c
|
|
||||||
* @see BaseEnum
|
* @see BaseEnum
|
||||||
|
*
|
||||||
|
* @author Charles7c
|
||||||
* @since 2.4.0
|
* @since 2.4.0
|
||||||
*/
|
*/
|
||||||
@JacksonStdImpl
|
@JacksonStdImpl
|
||||||
|
|||||||
@@ -27,8 +27,9 @@ import java.io.IOException;
|
|||||||
/**
|
/**
|
||||||
* 枚举接口 BaseEnum 序列化器
|
* 枚举接口 BaseEnum 序列化器
|
||||||
*
|
*
|
||||||
* @author Charles7c
|
|
||||||
* @see BaseEnum
|
* @see BaseEnum
|
||||||
|
*
|
||||||
|
* @author Charles7c
|
||||||
* @since 2.4.0
|
* @since 2.4.0
|
||||||
*/
|
*/
|
||||||
@JacksonStdImpl
|
@JacksonStdImpl
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import cn.hutool.core.io.IoUtil;
|
|||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import top.continew.starter.core.util.CollUtils;
|
||||||
import top.continew.starter.license.exception.LicenseException;
|
import top.continew.starter.license.exception.LicenseException;
|
||||||
import top.continew.starter.license.model.LicenseExtraModel;
|
import top.continew.starter.license.model.LicenseExtraModel;
|
||||||
|
|
||||||
@@ -33,7 +34,6 @@ import java.net.SocketException;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Enumeration;
|
import java.util.Enumeration;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 服务器信息工具类
|
* 服务器信息工具类
|
||||||
@@ -264,10 +264,7 @@ public class ServerInfoUtils {
|
|||||||
// 获取所有网络接口
|
// 获取所有网络接口
|
||||||
Set<InetAddress> inetAddresses = getLocalAllInetAddress();
|
Set<InetAddress> inetAddresses = getLocalAllInetAddress();
|
||||||
if (CollectionUtil.isNotEmpty(inetAddresses)) {
|
if (CollectionUtil.isNotEmpty(inetAddresses)) {
|
||||||
return inetAddresses.stream()
|
return CollUtils.mapToSet(inetAddresses, ServerInfoUtils::getMacByInetAddress);
|
||||||
.map(ServerInfoUtils::getMacByInetAddress)
|
|
||||||
.distinct()
|
|
||||||
.collect(Collectors.toSet());
|
|
||||||
}
|
}
|
||||||
return Collections.emptySet();
|
return Collections.emptySet();
|
||||||
}
|
}
|
||||||
@@ -282,11 +279,7 @@ public class ServerInfoUtils {
|
|||||||
// 获取所有网络接口
|
// 获取所有网络接口
|
||||||
Set<InetAddress> inetAddresses = getLocalAllInetAddress();
|
Set<InetAddress> inetAddresses = getLocalAllInetAddress();
|
||||||
if (CollectionUtil.isNotEmpty(inetAddresses)) {
|
if (CollectionUtil.isNotEmpty(inetAddresses)) {
|
||||||
return inetAddresses.stream()
|
return CollUtils.mapToSet(inetAddresses, InetAddress::getHostAddress);
|
||||||
.map(InetAddress::getHostAddress)
|
|
||||||
.distinct()
|
|
||||||
.map(String::toLowerCase)
|
|
||||||
.collect(Collectors.toSet());
|
|
||||||
}
|
}
|
||||||
return Collections.emptySet();
|
return Collections.emptySet();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ public @interface JsonMask {
|
|||||||
/**
|
/**
|
||||||
* 左侧保留位数
|
* 左侧保留位数
|
||||||
* <p>
|
* <p>
|
||||||
* 仅在脱敏类型为 {@code DesensitizedType.CUSTOM } 时使用
|
* 仅在脱敏类型为 {@code MaskType.CUSTOM } 时使用
|
||||||
* </p>
|
* </p>
|
||||||
*/
|
*/
|
||||||
int left() default 0;
|
int left() default 0;
|
||||||
@@ -64,7 +64,7 @@ public @interface JsonMask {
|
|||||||
/**
|
/**
|
||||||
* 右侧保留位数
|
* 右侧保留位数
|
||||||
* <p>
|
* <p>
|
||||||
* 仅在脱敏类型为 {@code DesensitizedType.CUSTOM } 时使用
|
* 仅在脱敏类型为 {@code MaskType.CUSTOM } 时使用
|
||||||
* </p>
|
* </p>
|
||||||
*/
|
*/
|
||||||
int right() default 0;
|
int right() default 0;
|
||||||
|
|||||||
@@ -23,9 +23,10 @@ package top.continew.starter.trace.autoconfigure;
|
|||||||
* 重写 TLog 配置以适配 Spring Boot 3.x
|
* 重写 TLog 配置以适配 Spring Boot 3.x
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
|
* @see com.yomahub.tlog.springboot.property.TLogProperty
|
||||||
|
*
|
||||||
* @author Bryan.Zhang
|
* @author Bryan.Zhang
|
||||||
* @author Jasmine
|
* @author Jasmine
|
||||||
* @see com.yomahub.tlog.springboot.property.TLogProperty
|
|
||||||
* @since 1.3.0
|
* @since 1.3.0
|
||||||
*/
|
*/
|
||||||
public class TLogProperties {
|
public class TLogProperties {
|
||||||
|
|||||||
@@ -33,9 +33,10 @@ import java.io.IOException;
|
|||||||
* 重写 TLog 配置以适配 Spring Boot 3.x
|
* 重写 TLog 配置以适配 Spring Boot 3.x
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
|
* @see com.yomahub.tlog.web.filter.TLogServletFilter
|
||||||
|
*
|
||||||
* @author Bryan.Zhang
|
* @author Bryan.Zhang
|
||||||
* @author Jasmine
|
* @author Jasmine
|
||||||
* @see com.yomahub.tlog.web.filter.TLogServletFilter
|
|
||||||
* @since 1.3.0
|
* @since 1.3.0
|
||||||
*/
|
*/
|
||||||
public class TLogServletFilter implements Filter {
|
public class TLogServletFilter implements Filter {
|
||||||
|
|||||||
@@ -28,9 +28,10 @@ import jakarta.servlet.http.HttpServletRequest;
|
|||||||
* 重写 TLog 配置以适配 Spring Boot 3.x
|
* 重写 TLog 配置以适配 Spring Boot 3.x
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
|
* @see com.yomahub.tlog.web.common.TLogWebCommon
|
||||||
|
*
|
||||||
* @author Bryan.Zhang
|
* @author Bryan.Zhang
|
||||||
* @author Jasmine
|
* @author Jasmine
|
||||||
* @see com.yomahub.tlog.web.common.TLogWebCommon
|
|
||||||
* @since 1.3.0
|
* @since 1.3.0
|
||||||
*/
|
*/
|
||||||
public class TLogWebCommon extends TLogRPCHandler {
|
public class TLogWebCommon extends TLogRPCHandler {
|
||||||
|
|||||||
@@ -18,26 +18,24 @@ package top.continew.starter.validation.autoconfigure;
|
|||||||
|
|
||||||
import jakarta.annotation.PostConstruct;
|
import jakarta.annotation.PostConstruct;
|
||||||
import jakarta.validation.Validator;
|
import jakarta.validation.Validator;
|
||||||
import org.hibernate.validator.HibernateValidator;
|
import org.hibernate.validator.BaseHibernateValidatorConfiguration;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
|
||||||
import org.springframework.context.MessageSource;
|
import org.springframework.context.MessageSource;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
|
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
|
||||||
|
|
||||||
import java.util.Properties;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* JSR 303 校验器自动配置
|
* JSR 303 校验器自动配置
|
||||||
*
|
*
|
||||||
* @author Charles7c
|
* @author Charles7c
|
||||||
* @since 2.3.0
|
* @since 2.3.0
|
||||||
*/
|
*/
|
||||||
@AutoConfiguration
|
@AutoConfigureBefore
|
||||||
public class ValidatorAutoConfiguration {
|
public class ValidationAutoConfiguration {
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(ValidatorAutoConfiguration.class);
|
private static final Logger log = LoggerFactory.getLogger(ValidationAutoConfiguration.class);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validator 失败立即返回模式配置
|
* Validator 失败立即返回模式配置
|
||||||
@@ -51,10 +49,9 @@ public class ValidatorAutoConfiguration {
|
|||||||
try (LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean()) {
|
try (LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean()) {
|
||||||
// 国际化
|
// 国际化
|
||||||
factoryBean.setValidationMessageSource(messageSource);
|
factoryBean.setValidationMessageSource(messageSource);
|
||||||
factoryBean.setProviderClass(HibernateValidator.class);
|
// 快速失败
|
||||||
Properties properties = new Properties();
|
factoryBean.getValidationPropertyMap()
|
||||||
properties.setProperty("hibernate.validator.fail_fast", "true");
|
.put(BaseHibernateValidatorConfiguration.FAIL_FAST, Boolean.TRUE.toString());
|
||||||
factoryBean.setValidationProperties(properties);
|
|
||||||
factoryBean.afterPropertiesSet();
|
factoryBean.afterPropertiesSet();
|
||||||
return factoryBean.getValidator();
|
return factoryBean.getValidator();
|
||||||
}
|
}
|
||||||
@@ -62,6 +59,6 @@ public class ValidatorAutoConfiguration {
|
|||||||
|
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
public void postConstruct() {
|
public void postConstruct() {
|
||||||
log.debug("[ContiNew Starter] - Auto Configuration 'Validator' completed initialization.");
|
log.debug("[ContiNew Starter] - Auto Configuration 'Validation' completed initialization.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -22,10 +22,10 @@ import jakarta.validation.ConstraintValidator;
|
|||||||
import jakarta.validation.ConstraintValidatorContext;
|
import jakarta.validation.ConstraintValidatorContext;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import top.continew.starter.core.enums.BaseEnum;
|
||||||
|
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.function.Function;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 枚举校验器
|
* 枚举校验器
|
||||||
@@ -53,17 +53,53 @@ public class EnumValueValidator implements ConstraintValidator<EnumValue, Object
|
|||||||
if (value == null) {
|
if (value == null) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 处理数组场景
|
||||||
|
if (value.getClass().isArray()) {
|
||||||
|
Object[] array = (Object[])value;
|
||||||
|
for (Object element : array) {
|
||||||
|
if (!isValidElement(element)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理集合场景
|
||||||
|
if (value instanceof Iterable<?> iterable) {
|
||||||
|
for (Object element : iterable) {
|
||||||
|
if (!isValidElement(element)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理单个值场景
|
||||||
|
return isValidElement(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验单个元素是否有效
|
||||||
|
*
|
||||||
|
* @param value 待校验的值
|
||||||
|
* @return 是否有效
|
||||||
|
*/
|
||||||
|
private boolean isValidElement(Object value) {
|
||||||
// 优先校验 enumValues
|
// 优先校验 enumValues
|
||||||
if (enumValues.length > 0) {
|
if (enumValues.length > 0) {
|
||||||
return Arrays.asList(enumValues).contains(Convert.toStr(value));
|
return Arrays.asList(enumValues).contains(Convert.toStr(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
Enum[] enumConstants = enumClass.getEnumConstants();
|
Enum[] enumConstants = enumClass.getEnumConstants();
|
||||||
if (enumConstants.length == 0) {
|
if (enumConstants.length == 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (CharSequenceUtil.isBlank(enumMethod)) {
|
if (CharSequenceUtil.isBlank(enumMethod)) {
|
||||||
return findEnumValue(enumConstants, Enum::toString, Convert.toStr(value));
|
return findEnumValue(enumConstants, Convert.toStr(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 枚举类指定了方法名,则调用指定方法获取枚举值
|
// 枚举类指定了方法名,则调用指定方法获取枚举值
|
||||||
Method method = enumClass.getMethod(enumMethod);
|
Method method = enumClass.getMethod(enumMethod);
|
||||||
@@ -82,13 +118,16 @@ public class EnumValueValidator implements ConstraintValidator<EnumValue, Object
|
|||||||
* 遍历枚举类,判断是否包含指定值
|
* 遍历枚举类,判断是否包含指定值
|
||||||
*
|
*
|
||||||
* @param enumConstants 枚举类数组
|
* @param enumConstants 枚举类数组
|
||||||
* @param function 获取枚举值的函数
|
|
||||||
* @param value 待校验的值
|
* @param value 待校验的值
|
||||||
* @return 是否包含指定值
|
* @return 是否包含指定值
|
||||||
*/
|
*/
|
||||||
private boolean findEnumValue(Enum[] enumConstants, Function<Enum, Object> function, Object value) {
|
private boolean findEnumValue(Enum[] enumConstants, Object value) {
|
||||||
for (Enum enumConstant : enumConstants) {
|
for (Enum enumConstant : enumConstants) {
|
||||||
if (function.apply(enumConstant).equals(value)) {
|
if (enumConstant instanceof BaseEnum<?> baseEnum) {
|
||||||
|
if (baseEnum.getValue().toString().equals(value)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} else if (enumConstant.toString().equals(value)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
top.continew.starter.validation.autoconfigure.ValidatorAutoConfiguration
|
top.continew.starter.validation.autoconfigure.ValidationAutoConfiguration
|
||||||
@@ -31,8 +31,7 @@ import io.undertow.Undertow;
|
|||||||
import io.undertow.server.handlers.DisallowedMethodsHandler;
|
import io.undertow.server.handlers.DisallowedMethodsHandler;
|
||||||
import io.undertow.util.HttpString;
|
import io.undertow.util.HttpString;
|
||||||
import top.continew.starter.core.constant.PropertiesConstants;
|
import top.continew.starter.core.constant.PropertiesConstants;
|
||||||
|
import top.continew.starter.core.util.CollUtils;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Undertow 自动配置
|
* Undertow 自动配置
|
||||||
@@ -57,11 +56,8 @@ public class UndertowAutoConfiguration {
|
|||||||
public WebServerFactoryCustomizer<UndertowServletWebServerFactory> customize(ServerExtensionProperties properties) {
|
public WebServerFactoryCustomizer<UndertowServletWebServerFactory> customize(ServerExtensionProperties properties) {
|
||||||
return factory -> {
|
return factory -> {
|
||||||
factory.addDeploymentInfoCustomizers(deploymentInfo -> deploymentInfo
|
factory.addDeploymentInfoCustomizers(deploymentInfo -> deploymentInfo
|
||||||
.addInitialHandlerChainWrapper(handler -> new DisallowedMethodsHandler(handler, properties
|
.addInitialHandlerChainWrapper(handler -> new DisallowedMethodsHandler(handler, CollUtils
|
||||||
.getDisallowedMethods()
|
.mapToSet(properties.getDisallowedMethods(), HttpString::tryFromString))));
|
||||||
.stream()
|
|
||||||
.map(HttpString::tryFromString)
|
|
||||||
.collect(Collectors.toSet()))));
|
|
||||||
log.debug("[ContiNew Starter] - Disallowed HTTP methods on Server Undertow: {}.", properties
|
log.debug("[ContiNew Starter] - Disallowed HTTP methods on Server Undertow: {}.", properties
|
||||||
.getDisallowedMethods());
|
.getDisallowedMethods());
|
||||||
log.debug("[ContiNew Starter] - Auto Configuration 'Web-Server Undertow' completed initialization.");
|
log.debug("[ContiNew Starter] - Auto Configuration 'Web-Server Undertow' completed initialization.");
|
||||||
|
|||||||
Reference in New Issue
Block a user