mirror of
https://github.com/continew-org/continew-starter.git
synced 2025-11-12 06:57:10 +08:00
Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0c606e681a | |||
| a2135374b2 | |||
| 38b6428662 | |||
| 58f9687c58 | |||
| 9d39012f0b | |||
| e64553e620 | |||
| a392fab782 | |||
| 3e822c0b84 | |||
| 0a9027d91f | |||
| 1eb1c2d845 | |||
| c76d777a2e | |||
|
|
55660ba18b | ||
|
|
36c30a20dd |
25
CHANGELOG.md
25
CHANGELOG.md
@@ -1,3 +1,28 @@
|
||||
## [v2.13.3](https://github.com/continew-org/continew-starter/compare/v2.13.2...v2.13.3) (2025-07-22)
|
||||
|
||||
### ✨ 新特性
|
||||
|
||||
- 【core】ReflectUtils 新增 createMethodReference 方法(由 CRUD 模块迁移) ([1eb1c2d](https://github.com/continew-org/continew-starter/commit/1eb1c2d845ded85197a222e492b0afe5bd8da48d))
|
||||
- 【data】Query 注解新增多列查询逻辑关系支持(原来仅支持或者,现在也支持并且) ([3e822c0](https://github.com/continew-org/continew-starter/commit/3e822c0b8442a5a00840a9ae67d7fa03cd5d33b0))
|
||||
- 【core】新增 OrderedConstants 统一登记过滤器和拦截器相关顺序常量,并调整相关过滤器和拦截器顺序 ([a392fab](https://github.com/continew-org/continew-starter/commit/a392fab78222db8f05933e398d8b0541aed07651))
|
||||
- 【security/password】重构密码编码器,新增 PasswordEncoderUtil ([58f9687](https://github.com/continew-org/continew-starter/commit/58f9687c581c121d4688e2ab99678d94d262c60a))
|
||||
- 【security/crypto】新增支持密码编码器加密 ([38b6428](https://github.com/continew-org/continew-starter/commit/38b6428662b909875df4ae8f36f180b0394accc1))
|
||||
|
||||
### 💎 功能优化
|
||||
|
||||
- 【extension/crud】重构查询树列表功能,增加重载方法,支持构建单个根节点或者多个根节点的树结构 (Gitee#75@lishuyanla) ([55660ba](https://github.com/continew-org/continew-starter/commit/55660ba18bb3b8b8cecc1c979aa71cde5b4b39d9)) ([a213537](https://github.com/continew-org/continew-starter/commit/a2135374b231ee410bafc8573e706443c6097353))
|
||||
- 【core】TreeBuildUtils => TreeUtils ([c76d777](https://github.com/continew-org/continew-starter/commit/c76d777a2e3b20a0542ef606cb3a4c85068a25fe))
|
||||
- 【extension/crud】优化部分代码 ([0a9027d](https://github.com/continew-org/continew-starter/commit/0a9027d91f3a2618f91e7b5417cbed5288e1e46b))
|
||||
- 【web】拆分 default-web.yml 为 default-response.yml 和 default-server.yml 配置文件 ([e64553e](https://github.com/continew-org/continew-starter/commit/e64553e6205ca3473a656f60448304bf4c18ddca))
|
||||
|
||||
### 🐛 问题修复
|
||||
|
||||
- 【security/crypto】修复新版 API 未支持自定义加密器问题 (Gitee#74@lishuyanla) ([36c30a2](https://github.com/continew-org/continew-starter/commit/36c30a20ddff30832a31e7d6751d0140c45de3a7))
|
||||
|
||||
### 📦 依赖升级
|
||||
|
||||
- 【dependencies】spel-validator 0.5.1-beta => 0.5.2-beta ([9d39012](https://github.com/continew-org/continew-starter/commit/9d39012f0b53baa81040a863526048955cab6d11))
|
||||
|
||||
## [v2.13.2](https://github.com/continew-org/continew-starter/compare/v2.13.1...v2.13.2) (2025-07-21)
|
||||
|
||||
### ✨ 新特性
|
||||
|
||||
@@ -36,6 +36,7 @@ import org.springframework.context.annotation.PropertySource;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
import top.continew.starter.auth.satoken.autoconfigure.dao.SaTokenDaoConfiguration;
|
||||
import top.continew.starter.core.constant.OrderedConstants;
|
||||
import top.continew.starter.core.constant.PropertiesConstants;
|
||||
import top.continew.starter.core.constant.StringConstants;
|
||||
import top.continew.starter.core.util.GeneralPropertySourceFactory;
|
||||
@@ -61,7 +62,9 @@ public class SaTokenAutoConfiguration implements WebMvcConfigurer {
|
||||
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
registry.addInterceptor(SpringUtil.getBean(SaInterceptor.class)).addPathPatterns(StringConstants.PATH_PATTERN);
|
||||
registry.addInterceptor(SpringUtil.getBean(SaInterceptor.class))
|
||||
.addPathPatterns(StringConstants.PATH_PATTERN)
|
||||
.order(OrderedConstants.Interceptor.AUTH_INTERCEPTOR);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<description>ContiNew Starter BOM</description>
|
||||
|
||||
<properties>
|
||||
<revision>2.13.2</revision>
|
||||
<revision>2.13.3</revision>
|
||||
</properties>
|
||||
|
||||
<dependencyManagement>
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* 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.constant;
|
||||
|
||||
import org.springframework.core.Ordered;
|
||||
|
||||
/**
|
||||
* 过滤器和拦截器相关顺序常量
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 2.13.3
|
||||
*/
|
||||
public class OrderedConstants {
|
||||
|
||||
/**
|
||||
* 过滤器顺序
|
||||
*/
|
||||
public static final class Filter {
|
||||
|
||||
/**
|
||||
* 链路追踪过滤器顺序
|
||||
*/
|
||||
public static final int TRACE_FILTER = Ordered.HIGHEST_PRECEDENCE + 100;
|
||||
|
||||
/**
|
||||
* XSS 过滤器顺序
|
||||
*/
|
||||
public static final int XSS_FILTER = Ordered.HIGHEST_PRECEDENCE + 200;
|
||||
|
||||
/**
|
||||
* 日志过滤器顺序
|
||||
*/
|
||||
public static final int LOG_FILTER = Ordered.LOWEST_PRECEDENCE - 100;
|
||||
|
||||
private Filter() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 拦截器顺序
|
||||
*/
|
||||
public static final class Interceptor {
|
||||
|
||||
/**
|
||||
* 租户拦截器顺序
|
||||
*/
|
||||
public static final int TENANT_INTERCEPTOR = Ordered.HIGHEST_PRECEDENCE + 100;
|
||||
|
||||
/**
|
||||
* 认证拦截器顺序
|
||||
*/
|
||||
public static final int AUTH_INTERCEPTOR = Ordered.HIGHEST_PRECEDENCE + 200;
|
||||
|
||||
/**
|
||||
* 日志拦截器顺序
|
||||
*/
|
||||
public static final int LOG_INTERCEPTOR = Ordered.LOWEST_PRECEDENCE - 100;
|
||||
|
||||
private Interceptor() {
|
||||
}
|
||||
}
|
||||
|
||||
private OrderedConstants() {
|
||||
}
|
||||
}
|
||||
@@ -17,11 +17,17 @@
|
||||
package top.continew.starter.core.util;
|
||||
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
import top.continew.starter.core.constant.StringConstants;
|
||||
import top.continew.starter.core.exception.BusinessException;
|
||||
|
||||
import java.lang.invoke.MethodHandleProxies;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
@@ -60,4 +66,27 @@ public class ReflectUtils {
|
||||
Field[] fields = ReflectUtil.getFields(beanClass);
|
||||
return Arrays.stream(fields).filter(f -> !Modifier.isStatic(f.getModifiers())).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过反射创建方法引用,支持在父类中查找方法
|
||||
*
|
||||
* @param clazz 实体类类型
|
||||
* @param methodName 方法名
|
||||
* @param <T> 实体类类型
|
||||
* @param <K> 返回值类型
|
||||
* @return Function<T, K> 方法引用
|
||||
* @throws IllegalArgumentException 如果参数不合法
|
||||
* @author lishuyan
|
||||
* @since 2.13.2
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T, K> Function<T, K> createMethodReference(Class<T> clazz, String methodName) {
|
||||
try {
|
||||
Method method = ReflectUtil.getMethodByName(clazz, methodName);
|
||||
method.setAccessible(true);
|
||||
return MethodHandleProxies.asInterfaceInstance(Function.class, MethodHandles.lookup().unreflect(method));
|
||||
} catch (Exception e) {
|
||||
throw new BusinessException("创建方法引用失败:" + clazz.getName() + StringConstants.DOT + methodName, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import cn.hutool.core.lang.tree.parser.NodeParser;
|
||||
import cn.hutool.core.text.CharSequenceUtil;
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
@@ -31,14 +32,18 @@ import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* 扩展 hutool TreeUtil 封装树构建
|
||||
* 树工具类
|
||||
*
|
||||
* @author Lion Li
|
||||
* <p>
|
||||
* 扩展 Hutool TreeUtil 封装树构建
|
||||
* </p>
|
||||
*
|
||||
* @author Lion Li(<a href="https://gitee.com/dromara/RuoYi-Vue-Plus">RuoYi-Vue-Plus</a>)
|
||||
* @author lishuyan
|
||||
*/
|
||||
public class TreeBuildUtils extends TreeUtil {
|
||||
public class TreeUtils extends TreeUtil {
|
||||
|
||||
private TreeBuildUtils() {
|
||||
private TreeUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -52,7 +57,7 @@ public class TreeBuildUtils extends TreeUtil {
|
||||
*/
|
||||
public static <T, K> List<Tree<K>> build(List<T> list, NodeParser<T, K> nodeParser) {
|
||||
if (CollUtil.isEmpty(list)) {
|
||||
return CollUtil.newArrayList();
|
||||
return new ArrayList<>(0);
|
||||
}
|
||||
K k = ReflectUtil.invoke(list.get(0), CharSequenceUtil.genGetter("parentId"));
|
||||
return TreeUtil.build(list, k, TreeNodeConfig.DEFAULT_CONFIG, nodeParser);
|
||||
@@ -70,7 +75,7 @@ public class TreeBuildUtils extends TreeUtil {
|
||||
*/
|
||||
public static <T, K> List<Tree<K>> build(List<T> list, K parentId, NodeParser<T, K> nodeParser) {
|
||||
if (CollUtil.isEmpty(list)) {
|
||||
return CollUtil.newArrayList();
|
||||
return new ArrayList<>(0);
|
||||
}
|
||||
return TreeUtil.build(list, parentId, TreeNodeConfig.DEFAULT_CONFIG, nodeParser);
|
||||
}
|
||||
@@ -91,7 +96,7 @@ public class TreeBuildUtils extends TreeUtil {
|
||||
TreeNodeConfig treeNodeConfig,
|
||||
NodeParser<T, K> nodeParser) {
|
||||
if (CollUtil.isEmpty(list)) {
|
||||
return CollUtil.newArrayList();
|
||||
return new ArrayList<>(0);
|
||||
}
|
||||
return TreeUtil.build(list, parentId, treeNodeConfig, nodeParser);
|
||||
}
|
||||
@@ -112,7 +117,7 @@ public class TreeBuildUtils extends TreeUtil {
|
||||
Function<T, K> getParentId,
|
||||
NodeParser<T, K> parser) {
|
||||
if (CollUtil.isEmpty(list)) {
|
||||
return CollUtil.newArrayList();
|
||||
return new ArrayList<>(0);
|
||||
}
|
||||
Set<K> rootParentIds = CollUtils.mapToSet(list, getParentId);
|
||||
rootParentIds.removeAll(CollUtils.mapToSet(list, getId));
|
||||
@@ -140,7 +145,7 @@ public class TreeBuildUtils extends TreeUtil {
|
||||
TreeNodeConfig treeNodeConfig,
|
||||
NodeParser<T, K> parser) {
|
||||
if (CollUtil.isEmpty(list)) {
|
||||
return CollUtil.newArrayList();
|
||||
return new ArrayList<>(0);
|
||||
}
|
||||
Set<K> rootParentIds = CollUtils.mapToSet(list, getParentId);
|
||||
rootParentIds.removeAll(CollUtils.mapToSet(list, getId));
|
||||
@@ -159,9 +164,9 @@ public class TreeBuildUtils extends TreeUtil {
|
||||
*/
|
||||
public static <K> List<Tree<K>> getLeafNodes(List<Tree<K>> nodes) {
|
||||
if (CollUtil.isEmpty(nodes)) {
|
||||
return CollUtil.newArrayList();
|
||||
return new ArrayList<>(0);
|
||||
}
|
||||
return nodes.stream().flatMap(TreeBuildUtils::extractLeafNodes).collect(Collectors.toList());
|
||||
return nodes.stream().flatMap(TreeUtils::extractLeafNodes).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -176,7 +181,7 @@ public class TreeBuildUtils extends TreeUtil {
|
||||
return Stream.of(node);
|
||||
} else {
|
||||
// 递归调用,获取所有子节点的叶子节点
|
||||
return node.getChildren().stream().flatMap(TreeBuildUtils::extractLeafNodes);
|
||||
return node.getChildren().stream().flatMap(TreeUtils::extractLeafNodes);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package top.continew.starter.data.annotation;
|
||||
|
||||
import top.continew.starter.data.enums.LogicalRelation;
|
||||
import top.continew.starter.data.enums.QueryType;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
@@ -46,4 +47,9 @@ public @interface Query {
|
||||
* 查询类型(等值查询、模糊查询、范围查询等)
|
||||
*/
|
||||
QueryType type() default QueryType.EQ;
|
||||
|
||||
/**
|
||||
* 多列查询时的逻辑关系(仅当 columns 长度大于 1 时生效)
|
||||
*/
|
||||
LogicalRelation logicalRelation() default LogicalRelation.OR;
|
||||
}
|
||||
|
||||
@@ -14,23 +14,23 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.starter.security.password.constant;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
package top.continew.starter.data.enums;
|
||||
|
||||
/**
|
||||
* 密码编码器相关常量
|
||||
* 逻辑关系枚举
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 2.12.0
|
||||
* @since 2.13.3
|
||||
*/
|
||||
public class PasswordEncoderConstants {
|
||||
public enum LogicalRelation {
|
||||
|
||||
/**
|
||||
* BCrypt 正则表达式
|
||||
* 并且关系
|
||||
*/
|
||||
public static final Pattern BCRYPT_PATTERN = Pattern.compile("\\A\\$2(a|y|b)?\\$(\\d\\d)\\$[./0-9A-Za-z]{53}");
|
||||
AND,
|
||||
|
||||
private PasswordEncoderConstants() {
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 或者关系
|
||||
*/
|
||||
OR
|
||||
}
|
||||
@@ -32,6 +32,7 @@ import top.continew.starter.core.util.validation.ValidationUtils;
|
||||
import top.continew.starter.data.annotation.Query;
|
||||
import top.continew.starter.data.annotation.QueryIgnore;
|
||||
import top.continew.starter.data.enums.QueryType;
|
||||
import top.continew.starter.data.enums.LogicalRelation;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
@@ -174,8 +175,21 @@ public class QueryWrapperHelper {
|
||||
return consumers;
|
||||
}
|
||||
// 解析多列查询
|
||||
LogicalRelation logicalRelation = queryAnnotation.logicalRelation();
|
||||
List<Consumer<QueryWrapper<R>>> columnConsumers = new ArrayList<>();
|
||||
for (String column : columns) {
|
||||
parse(queryType, column, fieldValue, consumers);
|
||||
parse(queryType, column, fieldValue, columnConsumers);
|
||||
}
|
||||
|
||||
if (logicalRelation == LogicalRelation.AND) {
|
||||
if (!columnConsumers.isEmpty()) {
|
||||
consumers.add(q -> {
|
||||
columnConsumers.get(0).accept(q);
|
||||
columnConsumers.subList(1, columnConsumers.size()).forEach(q::and);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
consumers.addAll(columnConsumers);
|
||||
}
|
||||
return consumers;
|
||||
} catch (BadRequestException e) {
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
<properties>
|
||||
<!-- Project Version -->
|
||||
<revision>2.13.2</revision>
|
||||
<revision>2.13.3</revision>
|
||||
|
||||
<!-- Core Framework Versions -->
|
||||
<spring-boot.version>3.3.12</spring-boot.version>
|
||||
@@ -58,7 +58,7 @@
|
||||
|
||||
<!-- Validation and Response Processing Versions -->
|
||||
<graceful-response.version>5.0.5-boot3</graceful-response.version>
|
||||
<spel-validator.version>0.5.1-beta</spel-validator.version>
|
||||
<spel-validator.version>0.5.2-beta</spel-validator.version>
|
||||
<crane4j.version>2.9.0</crane4j.version>
|
||||
|
||||
<!-- API Documentation Versions -->
|
||||
|
||||
@@ -21,6 +21,7 @@ import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import top.continew.starter.extension.crud.annotation.TreeField;
|
||||
import top.continew.starter.extension.crud.model.query.PageQuery;
|
||||
import top.continew.starter.extension.crud.model.query.SortQuery;
|
||||
import top.continew.starter.extension.crud.model.resp.BasePageResp;
|
||||
@@ -60,15 +61,30 @@ public interface CrudService<L, D, Q, C> {
|
||||
List<L> list(@Valid Q query, @Valid SortQuery sortQuery);
|
||||
|
||||
/**
|
||||
* 查询树列表
|
||||
* 查询树列表(多个根节点)
|
||||
*
|
||||
* @param query 查询条件
|
||||
* @param sortQuery 排序查询条件
|
||||
* @param isSimple 是否为简单树结构(不包含基本树结构之外的扩展字段,简单树(下拉列表)使用全局配置结构,复杂树(表格)使用 @DictField 局部配置)
|
||||
* @param isSimple 是否为简单树结构(不包含基本树结构之外的扩展字段,简单树(下拉列表)使用全局配置结构,复杂树(表格)使用 @TreeField 局部配置)
|
||||
* @return 树列表信息
|
||||
* @see TreeField
|
||||
*/
|
||||
List<Tree<Long>> tree(@Valid Q query, @Valid SortQuery sortQuery, boolean isSimple);
|
||||
|
||||
/**
|
||||
* 查询树列表
|
||||
*
|
||||
* @param query 查询条件
|
||||
* @param sortQuery 排序查询条件
|
||||
* @param isSimple 是否为简单树结构(不包含基本树结构之外的扩展字段,简单树(下拉列表)使用全局配置结构,复杂树(表格)使用 @TreeField 局部配置)
|
||||
* @param isSingleRoot 是否为单个根节点
|
||||
* @return 树列表信息
|
||||
* @author lishuyan
|
||||
* @since 2.13.3
|
||||
* @see TreeField
|
||||
*/
|
||||
List<Tree<Long>> tree(@Valid Q query, @Valid SortQuery sortQuery, boolean isSimple, boolean isSingleRoot);
|
||||
|
||||
/**
|
||||
* 查询详情
|
||||
*
|
||||
@@ -84,6 +100,7 @@ public interface CrudService<L, D, Q, C> {
|
||||
* @param sortQuery 排序查询条件
|
||||
* @return 字典列表信息
|
||||
* @since 2.1.0
|
||||
* @see top.continew.starter.extension.crud.annotation.DictModel
|
||||
*/
|
||||
List<LabelValueResp> listDict(@Valid Q query, @Valid SortQuery sortQuery);
|
||||
|
||||
|
||||
@@ -32,10 +32,12 @@ import org.springframework.data.domain.Sort;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import top.continew.starter.core.constant.StringConstants;
|
||||
import top.continew.starter.core.util.ReflectUtils;
|
||||
import top.continew.starter.core.util.TreeUtils;
|
||||
import top.continew.starter.core.util.validation.ValidationUtils;
|
||||
import top.continew.starter.data.base.BaseMapper;
|
||||
import top.continew.starter.data.service.impl.ServiceImpl;
|
||||
import top.continew.starter.data.util.QueryWrapperHelper;
|
||||
import top.continew.starter.excel.util.ExcelUtils;
|
||||
import top.continew.starter.extension.crud.annotation.TreeField;
|
||||
import top.continew.starter.extension.crud.autoconfigure.CrudProperties;
|
||||
import top.continew.starter.extension.crud.autoconfigure.CrudTreeProperties;
|
||||
@@ -44,12 +46,11 @@ import top.continew.starter.extension.crud.model.query.PageQuery;
|
||||
import top.continew.starter.extension.crud.model.query.SortQuery;
|
||||
import top.continew.starter.extension.crud.model.resp.LabelValueResp;
|
||||
import top.continew.starter.extension.crud.model.resp.PageResp;
|
||||
import top.continew.starter.excel.util.ExcelUtils;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* CRUD 业务实现基类
|
||||
@@ -89,9 +90,14 @@ public class CrudServiceImpl<M extends BaseMapper<T>, T extends BaseIdDO, L, D,
|
||||
|
||||
@Override
|
||||
public List<Tree<Long>> tree(Q query, SortQuery sortQuery, boolean isSimple) {
|
||||
return this.tree(query, sortQuery, isSimple, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Tree<Long>> tree(Q query, SortQuery sortQuery, boolean isSimple, boolean isSingleRoot) {
|
||||
List<L> list = this.list(query, sortQuery);
|
||||
if (CollUtil.isEmpty(list)) {
|
||||
return new ArrayList<>(0);
|
||||
return CollUtil.newArrayList();
|
||||
}
|
||||
CrudProperties crudProperties = SpringUtil.getBean(CrudProperties.class);
|
||||
CrudTreeProperties treeProperties = crudProperties.getTree();
|
||||
@@ -106,21 +112,19 @@ public class CrudServiceImpl<M extends BaseMapper<T>, T extends BaseIdDO, L, D,
|
||||
treeNodeConfig = treeProperties.genTreeNodeConfig(treeField);
|
||||
rootId = treeField.rootId();
|
||||
}
|
||||
// 构建树
|
||||
return TreeUtil.build(list, rootId, treeNodeConfig, (node, tree) -> {
|
||||
tree.setId(ReflectUtil.invoke(node, CharSequenceUtil.genGetter(treeField.value())));
|
||||
tree.setParentId(ReflectUtil.invoke(node, CharSequenceUtil.genGetter(treeField.parentIdKey())));
|
||||
tree.setName(ReflectUtil.invoke(node, CharSequenceUtil.genGetter(treeField.nameKey())));
|
||||
tree.setWeight(ReflectUtil.invoke(node, CharSequenceUtil.genGetter(treeField.weightKey())));
|
||||
// 如果构建简单树结构,则不包含扩展字段
|
||||
if (!isSimple) {
|
||||
List<Field> fieldList = ReflectUtils.getNonStaticFields(listClass);
|
||||
fieldList.removeIf(f -> CharSequenceUtil.equalsAnyIgnoreCase(f.getName(), treeField.value(), treeField
|
||||
.parentIdKey(), treeField.nameKey(), treeField.weightKey(), treeField.childrenKey()));
|
||||
fieldList.forEach(f -> tree.putExtra(f.getName(), ReflectUtil.invoke(node, CharSequenceUtil.genGetter(f
|
||||
.getName()))));
|
||||
}
|
||||
});
|
||||
if (isSingleRoot) {
|
||||
// 构建单根节点树
|
||||
return TreeUtil.build(list, rootId, treeNodeConfig, (node,
|
||||
tree) -> buildTreeField(isSimple, node, tree, treeField));
|
||||
} else {
|
||||
Function<L, Long> getId = ReflectUtils.createMethodReference(listClass, CharSequenceUtil.genGetter(treeField
|
||||
.value()));
|
||||
Function<L, Long> getParentId = ReflectUtils.createMethodReference(listClass, CharSequenceUtil
|
||||
.genGetter(treeField.parentIdKey()));
|
||||
// 构建多根节点树
|
||||
return TreeUtils.buildMultiRoot(list, getId, getParentId, treeNodeConfig, (node,
|
||||
tree) -> buildTreeField(isSimple, node, tree, treeField));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -323,4 +327,26 @@ public class CrudServiceImpl<M extends BaseMapper<T>, T extends BaseIdDO, L, D,
|
||||
return (Class<Q>)this.typeArguments[4];
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建树字段
|
||||
*
|
||||
* @param isSimple 是否简单树结构
|
||||
* @param node 节点
|
||||
* @param tree 树
|
||||
* @param treeField 树字段
|
||||
*/
|
||||
private void buildTreeField(boolean isSimple, L node, Tree<Long> tree, TreeField treeField) {
|
||||
tree.setId(ReflectUtil.invoke(node, CharSequenceUtil.genGetter(treeField.value())));
|
||||
tree.setParentId(ReflectUtil.invoke(node, CharSequenceUtil.genGetter(treeField.parentIdKey())));
|
||||
tree.setName(ReflectUtil.invoke(node, CharSequenceUtil.genGetter(treeField.nameKey())));
|
||||
tree.setWeight(ReflectUtil.invoke(node, CharSequenceUtil.genGetter(treeField.weightKey())));
|
||||
// 如果构建简单树结构,则不包含扩展字段
|
||||
if (!isSimple) {
|
||||
List<Field> fieldList = ReflectUtils.getNonStaticFields(listClass);
|
||||
fieldList.removeIf(f -> CharSequenceUtil.equalsAnyIgnoreCase(f.getName(), treeField.value(), treeField
|
||||
.parentIdKey(), treeField.nameKey(), treeField.weightKey(), treeField.childrenKey()));
|
||||
fieldList.forEach(f -> tree.putExtra(f.getName(), ReflectUtil.invoke(node, CharSequenceUtil.genGetter(f
|
||||
.getName()))));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.lang.tree.Tree;
|
||||
import cn.hutool.core.lang.tree.TreeNodeConfig;
|
||||
import cn.hutool.core.lang.tree.TreeUtil;
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.core.text.CharSequenceUtil;
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
@@ -35,7 +36,7 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
import top.continew.starter.core.constant.StringConstants;
|
||||
import top.continew.starter.core.util.ClassUtils;
|
||||
import top.continew.starter.core.util.ReflectUtils;
|
||||
import top.continew.starter.core.util.TreeBuildUtils;
|
||||
import top.continew.starter.core.util.TreeUtils;
|
||||
import top.continew.starter.core.util.validation.CheckUtils;
|
||||
import top.continew.starter.core.util.validation.ValidationUtils;
|
||||
import top.continew.starter.data.mapper.BaseMapper;
|
||||
@@ -52,10 +53,7 @@ import top.continew.starter.extension.crud.model.query.SortQuery;
|
||||
import top.continew.starter.extension.crud.model.resp.LabelValueResp;
|
||||
import top.continew.starter.extension.crud.model.resp.PageResp;
|
||||
|
||||
import java.lang.invoke.MethodHandleProxies;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
|
||||
@@ -97,55 +95,40 @@ public class CrudServiceImpl<M extends BaseMapper<T>, T extends BaseIdDO, L, D,
|
||||
|
||||
@Override
|
||||
public List<Tree<Long>> tree(Q query, SortQuery sortQuery, boolean isSimple) {
|
||||
return this.tree(query, sortQuery, isSimple, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Tree<Long>> tree(Q query, SortQuery sortQuery, boolean isSimple, boolean isSingleRoot) {
|
||||
List<L> list = this.list(query, sortQuery);
|
||||
if (CollUtil.isEmpty(list)) {
|
||||
return new ArrayList<>(0);
|
||||
return CollUtil.newArrayList();
|
||||
}
|
||||
CrudProperties crudProperties = SpringUtil.getBean(CrudProperties.class);
|
||||
CrudTreeProperties treeProperties = crudProperties.getTree();
|
||||
TreeField treeField = listClass.getDeclaredAnnotation(TreeField.class);
|
||||
TreeNodeConfig treeNodeConfig;
|
||||
Long rootId;
|
||||
// 简单树(下拉列表)使用全局配置结构,复杂树(表格)使用局部配置
|
||||
TreeNodeConfig treeNodeConfig = isSimple
|
||||
? treeProperties.genTreeNodeConfig()
|
||||
: treeProperties.genTreeNodeConfig(treeField);
|
||||
String valueGetter = CharSequenceUtil.genGetter(treeField.value());
|
||||
String parentIdKeyGetter = CharSequenceUtil.genGetter(treeField.parentIdKey());
|
||||
Function<L, Long> getId = createMethodReference(listClass, valueGetter);
|
||||
Function<L, Long> getParentId = createMethodReference(listClass, parentIdKeyGetter);
|
||||
// 构建树
|
||||
return TreeBuildUtils.buildMultiRoot(list, getId, getParentId, treeNodeConfig, (node, tree) -> {
|
||||
tree.setId(ReflectUtil.invoke(node, valueGetter));
|
||||
tree.setParentId(ReflectUtil.invoke(node, parentIdKeyGetter));
|
||||
tree.setName(ReflectUtil.invoke(node, CharSequenceUtil.genGetter(treeField.nameKey())));
|
||||
tree.setWeight(ReflectUtil.invoke(node, CharSequenceUtil.genGetter(treeField.weightKey())));
|
||||
// 如果构建简单树结构,则不包含扩展字段
|
||||
if (!isSimple) {
|
||||
List<Field> fieldList = ReflectUtils.getNonStaticFields(listClass);
|
||||
fieldList.removeIf(f -> CharSequenceUtil.equalsAnyIgnoreCase(f.getName(), treeField.value(), treeField
|
||||
.parentIdKey(), treeField.nameKey(), treeField.weightKey(), treeField.childrenKey()));
|
||||
fieldList.forEach(f -> tree.putExtra(f.getName(), ReflectUtil.invoke(node, CharSequenceUtil.genGetter(f
|
||||
.getName()))));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过反射创建方法引用
|
||||
*
|
||||
* @param clazz 实体类类型
|
||||
* @param methodName 方法名
|
||||
* @param <T> 实体类类型
|
||||
* @param <K> 返回值类型
|
||||
* @return Function<T, K> 方法引用
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T, K> Function<T, K> createMethodReference(Class<T> clazz, String methodName) {
|
||||
try {
|
||||
Method method = clazz.getDeclaredMethod(methodName);
|
||||
method.setAccessible(true);
|
||||
return MethodHandleProxies.asInterfaceInstance(Function.class, MethodHandles.lookup().unreflect(method));
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to create method reference for " + methodName, e);
|
||||
if (isSimple) {
|
||||
treeNodeConfig = treeProperties.genTreeNodeConfig();
|
||||
rootId = treeProperties.getRootId();
|
||||
} else {
|
||||
treeNodeConfig = treeProperties.genTreeNodeConfig(treeField);
|
||||
rootId = treeField.rootId();
|
||||
}
|
||||
if (isSingleRoot) {
|
||||
// 构建单根节点树
|
||||
return TreeUtil.build(list, rootId, treeNodeConfig, (node,
|
||||
tree) -> buildTreeField(isSimple, node, tree, treeField));
|
||||
} else {
|
||||
Function<L, Long> getId = ReflectUtils.createMethodReference(listClass, CharSequenceUtil.genGetter(treeField
|
||||
.value()));
|
||||
Function<L, Long> getParentId = ReflectUtils.createMethodReference(listClass, CharSequenceUtil
|
||||
.genGetter(treeField.parentIdKey()));
|
||||
// 构建多根节点树
|
||||
return TreeUtils.buildMultiRoot(list, getId, getParentId, treeNodeConfig, (node,
|
||||
tree) -> buildTreeField(isSimple, node, tree, treeField));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -401,4 +384,27 @@ public class CrudServiceImpl<M extends BaseMapper<T>, T extends BaseIdDO, L, D,
|
||||
protected void afterDelete(List<Long> ids) {
|
||||
/* 删除后置处理 */
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建树字段
|
||||
*
|
||||
* @param isSimple 是否简单树结构
|
||||
* @param node 节点
|
||||
* @param tree 树
|
||||
* @param treeField 树字段
|
||||
*/
|
||||
private void buildTreeField(boolean isSimple, L node, Tree<Long> tree, TreeField treeField) {
|
||||
tree.setId(ReflectUtil.invoke(node, CharSequenceUtil.genGetter(treeField.value())));
|
||||
tree.setParentId(ReflectUtil.invoke(node, CharSequenceUtil.genGetter(treeField.parentIdKey())));
|
||||
tree.setName(ReflectUtil.invoke(node, CharSequenceUtil.genGetter(treeField.nameKey())));
|
||||
tree.setWeight(ReflectUtil.invoke(node, CharSequenceUtil.genGetter(treeField.weightKey())));
|
||||
// 如果构建简单树结构,则不包含扩展字段
|
||||
if (!isSimple) {
|
||||
List<Field> fieldList = ReflectUtils.getNonStaticFields(listClass);
|
||||
fieldList.removeIf(f -> CharSequenceUtil.equalsAnyIgnoreCase(f.getName(), treeField.value(), treeField
|
||||
.parentIdKey(), treeField.nameKey(), treeField.weightKey(), treeField.childrenKey()));
|
||||
fieldList.forEach(f -> tree.putExtra(f.getName(), ReflectUtil.invoke(node, CharSequenceUtil.genGetter(f
|
||||
.getName()))));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,9 +19,9 @@ package top.continew.starter.extension.tenant.autoconfigure;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
import top.continew.starter.core.constant.OrderedConstants;
|
||||
import top.continew.starter.extension.tenant.annotation.ConditionalOnEnabledTenant;
|
||||
import top.continew.starter.extension.tenant.config.TenantProvider;
|
||||
import top.continew.starter.extension.tenant.interceptor.TenantInterceptor;
|
||||
@@ -49,6 +49,6 @@ public class TenantWebMvcAutoConfiguration implements WebMvcConfigurer {
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
registry.addInterceptor(new TenantInterceptor(tenantProperties, tenantProvider))
|
||||
.order(Ordered.HIGHEST_PRECEDENCE);
|
||||
.order(OrderedConstants.Interceptor.TENANT_INTERCEPTOR);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,8 +22,10 @@ import org.slf4j.LoggerFactory;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import top.continew.starter.core.constant.OrderedConstants;
|
||||
import top.continew.starter.log.annotation.ConditionalOnEnabledLog;
|
||||
import top.continew.starter.log.aspect.AccessLogAspect;
|
||||
import top.continew.starter.log.aspect.LogAspect;
|
||||
@@ -61,8 +63,11 @@ public class LogAutoConfiguration {
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public LogFilter logFilter() {
|
||||
return new LogFilter(logProperties);
|
||||
public FilterRegistrationBean<LogFilter> logFilter() {
|
||||
FilterRegistrationBean<LogFilter> registrationBean = new FilterRegistrationBean<>();
|
||||
registrationBean.setFilter(new LogFilter(logProperties));
|
||||
registrationBean.setOrder(OrderedConstants.Filter.LOG_FILTER);
|
||||
return registrationBean;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -22,7 +22,6 @@ import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.boot.autoconfigure.web.ServerProperties;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
import top.continew.starter.core.wrapper.RepeatReadRequestWrapper;
|
||||
@@ -40,7 +39,7 @@ import java.net.URISyntaxException;
|
||||
* @author echo
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public class LogFilter extends OncePerRequestFilter implements Ordered {
|
||||
public class LogFilter extends OncePerRequestFilter {
|
||||
|
||||
private final LogProperties logProperties;
|
||||
|
||||
@@ -48,11 +47,6 @@ public class LogFilter extends OncePerRequestFilter implements Ordered {
|
||||
this.logProperties = logProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return Ordered.LOWEST_PRECEDENCE - 10;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(@NonNull HttpServletRequest request,
|
||||
@NonNull HttpServletResponse response,
|
||||
|
||||
@@ -22,10 +22,12 @@ import org.slf4j.LoggerFactory;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
import top.continew.starter.core.constant.OrderedConstants;
|
||||
import top.continew.starter.core.constant.StringConstants;
|
||||
import top.continew.starter.log.annotation.ConditionalOnEnabledLog;
|
||||
import top.continew.starter.log.dao.LogDao;
|
||||
@@ -59,7 +61,8 @@ public class LogAutoConfiguration implements WebMvcConfigurer {
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
registry.addInterceptor(new LogInterceptor(logProperties, logHandler(), logDao()))
|
||||
.addPathPatterns(StringConstants.PATH_PATTERN)
|
||||
.excludePathPatterns(logProperties.getExcludePatterns());
|
||||
.excludePathPatterns(logProperties.getExcludePatterns())
|
||||
.order(OrderedConstants.Interceptor.LOG_INTERCEPTOR);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -67,8 +70,11 @@ public class LogAutoConfiguration implements WebMvcConfigurer {
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public LogFilter logFilter() {
|
||||
return new LogFilter(logProperties);
|
||||
public FilterRegistrationBean<LogFilter> logFilter() {
|
||||
FilterRegistrationBean<LogFilter> registrationBean = new FilterRegistrationBean<>();
|
||||
registrationBean.setFilter(new LogFilter(logProperties));
|
||||
registrationBean.setOrder(OrderedConstants.Filter.LOG_FILTER);
|
||||
return registrationBean;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -16,6 +16,13 @@
|
||||
<description>ContiNew Starter 安全模块 - 加密</description>
|
||||
|
||||
<dependencies>
|
||||
<!-- 安全模块 - 密码编码器 -->
|
||||
<dependency>
|
||||
<groupId>top.continew.starter</groupId>
|
||||
<artifactId>continew-starter-security-password</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- Hutool 加密解密模块(封装 JDK 中加密解密算法) -->
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
|
||||
@@ -24,10 +24,12 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.PropertySource;
|
||||
import top.continew.starter.core.constant.PropertiesConstants;
|
||||
import top.continew.starter.security.crypto.core.MyBatisDecryptInterceptor;
|
||||
import top.continew.starter.security.crypto.core.MyBatisEncryptInterceptor;
|
||||
import top.continew.starter.security.crypto.utils.EncryptHelper;
|
||||
import top.continew.starter.core.util.GeneralPropertySourceFactory;
|
||||
import top.continew.starter.security.crypto.mybatis.MyBatisDecryptInterceptor;
|
||||
import top.continew.starter.security.crypto.mybatis.MyBatisEncryptInterceptor;
|
||||
import top.continew.starter.security.crypto.util.EncryptHelper;
|
||||
|
||||
/**
|
||||
* 加/解密自动配置
|
||||
@@ -39,6 +41,7 @@ import top.continew.starter.security.crypto.utils.EncryptHelper;
|
||||
@AutoConfiguration
|
||||
@EnableConfigurationProperties(CryptoProperties.class)
|
||||
@ConditionalOnProperty(prefix = PropertiesConstants.SECURITY_CRYPTO, name = PropertiesConstants.ENABLED, havingValue = "true", matchIfMissing = true)
|
||||
@PropertySource(value = "classpath:default-crypto.yml", factory = GeneralPropertySourceFactory.class)
|
||||
public class CryptoAutoConfiguration {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(CryptoAutoConfiguration.class);
|
||||
|
||||
@@ -14,8 +14,9 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.starter.security.crypto.encryptor;
|
||||
package top.continew.starter.security.crypto.autoconfigure;
|
||||
|
||||
import top.continew.starter.security.crypto.encryptor.IEncryptor;
|
||||
import top.continew.starter.security.crypto.enums.Algorithm;
|
||||
|
||||
import java.util.Objects;
|
||||
@@ -33,6 +34,14 @@ public class CryptoContext {
|
||||
*/
|
||||
private Algorithm algorithm;
|
||||
|
||||
/**
|
||||
* 加密/解密处理器
|
||||
* <p>
|
||||
* 优先级高于加密/解密算法
|
||||
* </p>
|
||||
*/
|
||||
Class<? extends IEncryptor> encryptor;
|
||||
|
||||
/**
|
||||
* 对称加密算法密钥
|
||||
*/
|
||||
@@ -56,6 +65,14 @@ public class CryptoContext {
|
||||
this.algorithm = algorithm;
|
||||
}
|
||||
|
||||
public Class<? extends IEncryptor> getEncryptor() {
|
||||
return encryptor;
|
||||
}
|
||||
|
||||
public void setEncryptor(Class<? extends IEncryptor> encryptor) {
|
||||
this.encryptor = encryptor;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
@@ -89,12 +106,13 @@ public class CryptoContext {
|
||||
return false;
|
||||
}
|
||||
CryptoContext that = (CryptoContext)o;
|
||||
return algorithm == that.algorithm && Objects.equals(password, that.password) && Objects
|
||||
.equals(publicKey, that.publicKey) && Objects.equals(privateKey, that.privateKey);
|
||||
return algorithm == that.algorithm && Objects.equals(encryptor, that.encryptor) && Objects
|
||||
.equals(password, that.password) && Objects.equals(publicKey, that.publicKey) && Objects
|
||||
.equals(privateKey, that.privateKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(algorithm, password, publicKey, privateKey);
|
||||
return Objects.hash(algorithm, encryptor, password, publicKey, privateKey);
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package top.continew.starter.security.crypto.encryptor;
|
||||
|
||||
import top.continew.starter.security.crypto.autoconfigure.CryptoContext;
|
||||
|
||||
/**
|
||||
* 加密器基类
|
||||
*
|
||||
|
||||
@@ -20,6 +20,7 @@ import cn.hutool.core.text.CharSequenceUtil;
|
||||
import cn.hutool.crypto.symmetric.SymmetricAlgorithm;
|
||||
import cn.hutool.crypto.symmetric.SymmetricCrypto;
|
||||
import top.continew.starter.core.constant.StringConstants;
|
||||
import top.continew.starter.security.crypto.autoconfigure.CryptoContext;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package top.continew.starter.security.crypto.encryptor;
|
||||
|
||||
import cn.hutool.crypto.symmetric.SymmetricAlgorithm;
|
||||
import top.continew.starter.security.crypto.autoconfigure.CryptoContext;
|
||||
|
||||
/**
|
||||
* AES(Advanced Encryption Standard) 加/解密处理器
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package top.continew.starter.security.crypto.encryptor;
|
||||
|
||||
import cn.hutool.crypto.symmetric.SymmetricAlgorithm;
|
||||
import top.continew.starter.security.crypto.autoconfigure.CryptoContext;
|
||||
|
||||
/**
|
||||
* DES(Data Encryption Standard) 加/解密处理器
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* 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.security.crypto.encryptor;
|
||||
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import top.continew.starter.security.crypto.autoconfigure.CryptoContext;
|
||||
import top.continew.starter.security.password.autoconfigure.PasswordEncoderProperties;
|
||||
|
||||
/**
|
||||
* 密码编码器加/解密处理器
|
||||
*
|
||||
* <p>
|
||||
* 使用前必须注入 {@link PasswordEncoder},此加密方式不可逆,适合于密码场景
|
||||
* </p>
|
||||
*
|
||||
* @see PasswordEncoder
|
||||
* @see PasswordEncoderProperties
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 2.13.3
|
||||
*/
|
||||
public class PasswordEncoderEncryptor extends AbstractEncryptor {
|
||||
|
||||
private final PasswordEncoder passwordEncoder = SpringUtil.getBean(PasswordEncoder.class);
|
||||
private final PasswordEncoderProperties properties = SpringUtil.getBean(PasswordEncoderProperties.class);
|
||||
|
||||
public PasswordEncoderEncryptor(CryptoContext context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String encrypt(String plaintext) {
|
||||
// 如果已经是加密格式,直接返回
|
||||
if (properties.getAlgorithm().getPattern().matcher(plaintext).matches()) {
|
||||
return plaintext;
|
||||
}
|
||||
return passwordEncoder.encode(plaintext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String decrypt(String ciphertext) {
|
||||
return ciphertext;
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@
|
||||
package top.continew.starter.security.crypto.encryptor;
|
||||
|
||||
import cn.hutool.crypto.symmetric.SymmetricAlgorithm;
|
||||
import top.continew.starter.security.crypto.autoconfigure.CryptoContext;
|
||||
|
||||
/**
|
||||
* PBEWithMD5AndDES(Password Based Encryption With MD5 And DES) 加/解密处理器
|
||||
|
||||
@@ -19,6 +19,7 @@ package top.continew.starter.security.crypto.encryptor;
|
||||
import cn.hutool.core.codec.Base64;
|
||||
import cn.hutool.crypto.SecureUtil;
|
||||
import cn.hutool.crypto.asymmetric.KeyType;
|
||||
import top.continew.starter.security.crypto.autoconfigure.CryptoContext;
|
||||
|
||||
/**
|
||||
* RSA 加/解密处理器
|
||||
|
||||
@@ -55,7 +55,12 @@ public enum Algorithm {
|
||||
/**
|
||||
* Base64
|
||||
*/
|
||||
BASE64(Base64Encryptor.class),;
|
||||
BASE64(Base64Encryptor.class),
|
||||
|
||||
/**
|
||||
* 密码编码器,支持算法:BCrypt、SCRYPT、PBKDF2、ARGON2
|
||||
*/
|
||||
PASSWORD_ENCODER(PasswordEncoderEncryptor.class);
|
||||
|
||||
/**
|
||||
* 加密/解密处理器
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.starter.security.crypto.core;
|
||||
package top.continew.starter.security.crypto.mybatis;
|
||||
|
||||
import cn.hutool.core.text.CharSequenceUtil;
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.starter.security.crypto.core;
|
||||
package top.continew.starter.security.crypto.mybatis;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.text.CharSequenceUtil;
|
||||
@@ -27,7 +27,7 @@ import org.apache.ibatis.plugin.Invocation;
|
||||
import org.apache.ibatis.plugin.Signature;
|
||||
import org.apache.ibatis.type.SimpleTypeRegistry;
|
||||
import top.continew.starter.security.crypto.annotation.FieldEncrypt;
|
||||
import top.continew.starter.security.crypto.utils.EncryptHelper;
|
||||
import top.continew.starter.security.crypto.util.EncryptHelper;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.sql.Statement;
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.starter.security.crypto.core;
|
||||
package top.continew.starter.security.crypto.mybatis;
|
||||
|
||||
import cn.hutool.core.text.CharSequenceUtil;
|
||||
import cn.hutool.core.util.ClassUtil;
|
||||
@@ -30,7 +30,7 @@ import org.apache.ibatis.session.ResultHandler;
|
||||
import org.apache.ibatis.session.RowBounds;
|
||||
import top.continew.starter.core.constant.StringConstants;
|
||||
import top.continew.starter.security.crypto.annotation.FieldEncrypt;
|
||||
import top.continew.starter.security.crypto.utils.EncryptHelper;
|
||||
import top.continew.starter.security.crypto.util.EncryptHelper;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Arrays;
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.starter.security.crypto.utils;
|
||||
package top.continew.starter.security.crypto.util;
|
||||
|
||||
import cn.hutool.core.text.CharSequenceUtil;
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
@@ -22,7 +22,7 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import top.continew.starter.security.crypto.annotation.FieldEncrypt;
|
||||
import top.continew.starter.security.crypto.autoconfigure.CryptoProperties;
|
||||
import top.continew.starter.security.crypto.encryptor.CryptoContext;
|
||||
import top.continew.starter.security.crypto.autoconfigure.CryptoContext;
|
||||
import top.continew.starter.security.crypto.encryptor.IEncryptor;
|
||||
import top.continew.starter.security.crypto.enums.Algorithm;
|
||||
|
||||
@@ -73,8 +73,9 @@ public class EncryptHelper {
|
||||
*/
|
||||
public static IEncryptor registerAndGetEncryptor(CryptoContext encryptContext) {
|
||||
int key = encryptContext.hashCode();
|
||||
return ENCRYPTOR_CACHE.computeIfAbsent(key, k -> ReflectUtil.newInstance(encryptContext.getAlgorithm()
|
||||
.getEncryptor(), encryptContext));
|
||||
return ENCRYPTOR_CACHE.computeIfAbsent(key, k -> encryptContext.getEncryptor().equals(IEncryptor.class)
|
||||
? ReflectUtil.newInstance(encryptContext.getAlgorithm().getEncryptor(), encryptContext)
|
||||
: ReflectUtil.newInstance(encryptContext.getEncryptor(), encryptContext));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -187,6 +188,9 @@ public class EncryptHelper {
|
||||
encryptContext.setAlgorithm(fieldEncrypt.value() == Algorithm.DEFAULT
|
||||
? defaultProperties.getAlgorithm()
|
||||
: fieldEncrypt.value());
|
||||
encryptContext.setEncryptor(fieldEncrypt.encryptor().equals(IEncryptor.class)
|
||||
? IEncryptor.class
|
||||
: fieldEncrypt.encryptor());
|
||||
encryptContext.setPassword(fieldEncrypt.password().isEmpty()
|
||||
? defaultProperties.getPassword()
|
||||
: fieldEncrypt.password());
|
||||
@@ -0,0 +1,6 @@
|
||||
--- ### 安全配置:字段加/解密配置
|
||||
continew-starter.security:
|
||||
crypto:
|
||||
enabled: true
|
||||
# 默认算法,即 @FieldEncrypt 默认采用的算法(默认:AES 对称加密算法)
|
||||
algorithm: AES
|
||||
@@ -16,8 +16,6 @@
|
||||
|
||||
package top.continew.starter.security.password.autoconfigure;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.text.CharSequenceUtil;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -25,18 +23,17 @@ import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.security.crypto.argon2.Argon2PasswordEncoder;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.context.annotation.PropertySource;
|
||||
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
|
||||
import org.springframework.security.crypto.password.DelegatingPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.security.crypto.password.Pbkdf2PasswordEncoder;
|
||||
import org.springframework.security.crypto.scrypt.SCryptPasswordEncoder;
|
||||
import top.continew.starter.core.constant.PropertiesConstants;
|
||||
import top.continew.starter.core.util.GeneralPropertySourceFactory;
|
||||
import top.continew.starter.core.util.validation.CheckUtils;
|
||||
import top.continew.starter.security.password.enums.PasswordEncoderAlgorithm;
|
||||
import top.continew.starter.security.password.util.PasswordEncoderUtil;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
@@ -55,15 +52,11 @@ import java.util.Map;
|
||||
*/
|
||||
@AutoConfiguration
|
||||
@EnableConfigurationProperties(PasswordEncoderProperties.class)
|
||||
@PropertySource(value = "classpath:default-password.yml", factory = GeneralPropertySourceFactory.class)
|
||||
@ConditionalOnProperty(prefix = PropertiesConstants.SECURITY_PASSWORD, name = PropertiesConstants.ENABLED, havingValue = "true", matchIfMissing = true)
|
||||
public class PasswordEncoderAutoConfiguration {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(PasswordEncoderAutoConfiguration.class);
|
||||
private final PasswordEncoderProperties properties;
|
||||
|
||||
public PasswordEncoderAutoConfiguration(PasswordEncoderProperties properties) {
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* 密码编码器
|
||||
@@ -72,23 +65,19 @@ public class PasswordEncoderAutoConfiguration {
|
||||
* @see PasswordEncoderFactories
|
||||
*/
|
||||
@Bean
|
||||
public PasswordEncoder passwordEncoder(List<PasswordEncoder> passwordEncoderList) {
|
||||
public PasswordEncoder passwordEncoder(PasswordEncoderProperties properties) {
|
||||
Map<String, PasswordEncoder> encoders = new HashMap<>();
|
||||
encoders.put("bcrypt", new BCryptPasswordEncoder());
|
||||
encoders.put("pbkdf2", Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_8());
|
||||
encoders.put("scrypt", SCryptPasswordEncoder.defaultsForSpringSecurity_v5_8());
|
||||
encoders.put("argon2", Argon2PasswordEncoder.defaultsForSpringSecurity_v5_8());
|
||||
// 添加自定义的密码编解码器
|
||||
if (CollUtil.isNotEmpty(passwordEncoderList)) {
|
||||
passwordEncoderList.forEach(passwordEncoder -> {
|
||||
String simpleName = passwordEncoder.getClass().getSimpleName();
|
||||
encoders.put(CharSequenceUtil.removeSuffix(simpleName, "PasswordEncoder")
|
||||
.toLowerCase(), passwordEncoder);
|
||||
});
|
||||
}
|
||||
String encodingId = properties.getEncodingId();
|
||||
CheckUtils.throwIf(!encoders.containsKey(encodingId), "{} is not found in idToPasswordEncoder.", encodingId);
|
||||
return new DelegatingPasswordEncoder(encodingId, encoders);
|
||||
encoders.put(PasswordEncoderAlgorithm.BCRYPT.name().toLowerCase(), PasswordEncoderUtil
|
||||
.getEncoder(PasswordEncoderAlgorithm.BCRYPT));
|
||||
encoders.put(PasswordEncoderAlgorithm.SCRYPT.name().toLowerCase(), PasswordEncoderUtil
|
||||
.getEncoder(PasswordEncoderAlgorithm.SCRYPT));
|
||||
encoders.put(PasswordEncoderAlgorithm.PBKDF2.name().toLowerCase(), PasswordEncoderUtil
|
||||
.getEncoder(PasswordEncoderAlgorithm.PBKDF2));
|
||||
encoders.put(PasswordEncoderAlgorithm.ARGON2.name().toLowerCase(), PasswordEncoderUtil
|
||||
.getEncoder(PasswordEncoderAlgorithm.ARGON2));
|
||||
PasswordEncoderAlgorithm algorithm = properties.getAlgorithm();
|
||||
CheckUtils.throwIf(PasswordEncoderUtil.getEncoder(algorithm) == null, "不支持的加密算法: {}", algorithm);
|
||||
return new DelegatingPasswordEncoder(algorithm.name().toLowerCase(), encoders);
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
|
||||
@@ -18,6 +18,7 @@ package top.continew.starter.security.password.autoconfigure;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import top.continew.starter.core.constant.PropertiesConstants;
|
||||
import top.continew.starter.security.password.enums.PasswordEncoderAlgorithm;
|
||||
|
||||
/**
|
||||
* 密码编解码配置属性
|
||||
@@ -34,9 +35,9 @@ public class PasswordEncoderProperties {
|
||||
private boolean enabled = true;
|
||||
|
||||
/**
|
||||
* 默认启用的编码器 ID(默认:BCryptPasswordEncoder)
|
||||
* 默认启用的编码器算法(默认:BCrypt 加密算法)
|
||||
*/
|
||||
private String encodingId = "bcrypt";
|
||||
private PasswordEncoderAlgorithm algorithm = PasswordEncoderAlgorithm.BCRYPT;
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
@@ -46,11 +47,11 @@ public class PasswordEncoderProperties {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public String getEncodingId() {
|
||||
return encodingId;
|
||||
public PasswordEncoderAlgorithm getAlgorithm() {
|
||||
return algorithm;
|
||||
}
|
||||
|
||||
public void setEncodingId(String encodingId) {
|
||||
this.encodingId = encodingId;
|
||||
public void setAlgorithm(PasswordEncoderAlgorithm algorithm) {
|
||||
this.algorithm = algorithm;
|
||||
}
|
||||
}
|
||||
@@ -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.security.password.enums;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* 密码编码器加密算法枚举
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 2.13.3
|
||||
*/
|
||||
public enum PasswordEncoderAlgorithm {
|
||||
|
||||
/** BCrypt加密算法 */
|
||||
BCRYPT(Pattern.compile("\\A\\$2(a|y|b)?\\$(\\d\\d)\\$[./0-9A-Za-z]{53}")),
|
||||
|
||||
/** SCrypt加密算法 */
|
||||
SCRYPT(Pattern.compile("\\A\\$s0\\$[0-9a-f]+\\$[0-9a-f]+\\$[0-9a-f]+")),
|
||||
|
||||
/** PBKDF2加密算法 */
|
||||
PBKDF2(Pattern.compile("\\A\\$pbkdf2-sha256\\$\\d+\\$[0-9a-f]+\\$[0-9a-f]+")),
|
||||
|
||||
/** Argon2加密算法 */
|
||||
ARGON2(Pattern.compile("\\A\\$argon2(id|i|d)\\$v=\\d+\\$m=\\d+,t=\\d+,p=\\d+\\$[0-9a-zA-Z+/]+\\$[0-9a-zA-Z+/]+"));
|
||||
|
||||
/** 正则匹配 */
|
||||
private final Pattern pattern;
|
||||
|
||||
PasswordEncoderAlgorithm(Pattern pattern) {
|
||||
this.pattern = pattern;
|
||||
}
|
||||
|
||||
public Pattern getPattern() {
|
||||
return pattern;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* 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.security.password.exception;
|
||||
|
||||
import top.continew.starter.core.exception.BaseException;
|
||||
|
||||
import java.io.Serial;
|
||||
|
||||
/**
|
||||
* 密码编码异常
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 2.13.3
|
||||
*/
|
||||
public class PasswordEncodeException extends BaseException {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public PasswordEncodeException() {
|
||||
}
|
||||
|
||||
public PasswordEncodeException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public PasswordEncodeException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public PasswordEncodeException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* 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.security.password.util;
|
||||
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.crypto.argon2.Argon2PasswordEncoder;
|
||||
import org.springframework.security.crypto.password.Pbkdf2PasswordEncoder;
|
||||
import org.springframework.security.crypto.scrypt.SCryptPasswordEncoder;
|
||||
import top.continew.starter.security.password.enums.PasswordEncoderAlgorithm;
|
||||
import top.continew.starter.security.password.exception.PasswordEncodeException;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* 密码加密工具类
|
||||
* <p>
|
||||
* 支持多种加密算法,可通过编码ID动态选择加密方式
|
||||
* </p>
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 2.13.3
|
||||
*/
|
||||
public final class PasswordEncoderUtil {
|
||||
|
||||
private static final Map<PasswordEncoderAlgorithm, PasswordEncoder> ENCODER_CACHE = new ConcurrentHashMap<>();
|
||||
|
||||
static {
|
||||
// 初始化默认的加密算法实例
|
||||
ENCODER_CACHE.put(PasswordEncoderAlgorithm.BCRYPT, new BCryptPasswordEncoder());
|
||||
ENCODER_CACHE.put(PasswordEncoderAlgorithm.SCRYPT, SCryptPasswordEncoder.defaultsForSpringSecurity_v5_8());
|
||||
ENCODER_CACHE.put(PasswordEncoderAlgorithm.PBKDF2, Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_8());
|
||||
ENCODER_CACHE.put(PasswordEncoderAlgorithm.ARGON2, Argon2PasswordEncoder.defaultsForSpringSecurity_v5_8());
|
||||
}
|
||||
|
||||
private PasswordEncoderUtil() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用指定的加密算法加密密码
|
||||
*
|
||||
* @param algorithm 加密算法
|
||||
* @param rawPassword 原始密码
|
||||
* @return 加密后的密码
|
||||
* @throws IllegalArgumentException 如果不支持指定的加密算法
|
||||
*/
|
||||
public static String encode(PasswordEncoderAlgorithm algorithm, String rawPassword) {
|
||||
// 参数校验
|
||||
if (algorithm == null) {
|
||||
throw new IllegalArgumentException("加密算法不能为空");
|
||||
}
|
||||
if (rawPassword == null) {
|
||||
throw new IllegalArgumentException("原始密码不能为空");
|
||||
}
|
||||
|
||||
// 获取对应的密码编码器
|
||||
PasswordEncoder encoder = ENCODER_CACHE.get(algorithm);
|
||||
if (encoder == null) {
|
||||
throw new IllegalArgumentException("不支持的加密算法: " + algorithm);
|
||||
}
|
||||
|
||||
try {
|
||||
return encoder.encode(rawPassword);
|
||||
} catch (Exception e) {
|
||||
throw new PasswordEncodeException("密码加密失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证密码是否匹配
|
||||
*
|
||||
* @param algorithm 加密算法
|
||||
* @param rawPassword 原始密码
|
||||
* @param encodedPassword 加密后的密码
|
||||
* @return 是否匹配
|
||||
* @throws IllegalArgumentException 如果不支持指定的加密算法
|
||||
*/
|
||||
public static boolean matches(PasswordEncoderAlgorithm algorithm, String rawPassword, String encodedPassword) {
|
||||
// 参数校验
|
||||
if (algorithm == null) {
|
||||
throw new IllegalArgumentException("加密算法不能为空");
|
||||
}
|
||||
if (rawPassword == null || encodedPassword == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 获取对应的密码编码器
|
||||
PasswordEncoder encoder = ENCODER_CACHE.get(algorithm);
|
||||
if (encoder == null) {
|
||||
throw new IllegalArgumentException("不支持的加密算法: " + algorithm);
|
||||
}
|
||||
|
||||
try {
|
||||
return encoder.matches(rawPassword, encodedPassword);
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定算法的密码编码器
|
||||
*
|
||||
* @param algorithm 加密算法
|
||||
* @return 密码编码器实例,不存在则返回null
|
||||
*/
|
||||
public static PasswordEncoder getEncoder(PasswordEncoderAlgorithm algorithm) {
|
||||
if (algorithm == null) {
|
||||
return null;
|
||||
}
|
||||
return ENCODER_CACHE.get(algorithm);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
--- ### 安全配置:密码编码器配置
|
||||
continew-starter.security:
|
||||
password:
|
||||
enabled: true
|
||||
# 默认启用的编码器算法(默认:BCrypt 加密算法)
|
||||
algorithm: BCRYPT
|
||||
@@ -25,6 +25,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplicat
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import top.continew.starter.core.constant.OrderedConstants;
|
||||
import top.continew.starter.core.constant.PropertiesConstants;
|
||||
import top.continew.starter.security.xss.filter.XssFilter;
|
||||
|
||||
@@ -43,12 +44,13 @@ public class XssAutoConfiguration {
|
||||
private static final Logger log = LoggerFactory.getLogger(XssAutoConfiguration.class);
|
||||
|
||||
/**
|
||||
* XSS 过滤器配置
|
||||
* XSS 过滤器
|
||||
*/
|
||||
@Bean
|
||||
public FilterRegistrationBean<XssFilter> xssFilter(XssProperties xssProperties) {
|
||||
FilterRegistrationBean<XssFilter> registrationBean = new FilterRegistrationBean<>();
|
||||
registrationBean.setFilter(new XssFilter(xssProperties));
|
||||
registrationBean.setOrder(OrderedConstants.Filter.XSS_FILTER);
|
||||
return registrationBean;
|
||||
}
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties
|
||||
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.core.Ordered;
|
||||
import top.continew.starter.core.constant.OrderedConstants;
|
||||
import top.continew.starter.core.constant.PropertiesConstants;
|
||||
import top.continew.starter.trace.filter.TLogServletFilter;
|
||||
import top.continew.starter.trace.handler.TraceIdGenerator;
|
||||
@@ -70,14 +70,14 @@ public class TraceAutoConfiguration {
|
||||
}
|
||||
|
||||
/**
|
||||
* TLog 过滤器配置
|
||||
* TLog 过滤器
|
||||
*/
|
||||
@Bean
|
||||
public FilterRegistrationBean<TLogServletFilter> tLogServletFilter() {
|
||||
FilterRegistrationBean<TLogServletFilter> registration = new FilterRegistrationBean<>();
|
||||
registration.setFilter(new TLogServletFilter(traceProperties));
|
||||
registration.setOrder(Ordered.HIGHEST_PRECEDENCE);
|
||||
return registration;
|
||||
FilterRegistrationBean<TLogServletFilter> registrationBean = new FilterRegistrationBean<>();
|
||||
registrationBean.setFilter(new TLogServletFilter(traceProperties));
|
||||
registrationBean.setOrder(OrderedConstants.Filter.TRACE_FILTER);
|
||||
return registrationBean;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -54,7 +54,7 @@ import java.util.concurrent.CopyOnWriteArrayList;
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@EnableConfigurationProperties(GlobalResponseProperties.class)
|
||||
@PropertySource(value = "classpath:default-web.yml", factory = GeneralPropertySourceFactory.class)
|
||||
@PropertySource(value = "classpath:default-response.yml", factory = GeneralPropertySourceFactory.class)
|
||||
public class GlobalResponseAutoConfiguration {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(GlobalResponseAutoConfiguration.class);
|
||||
|
||||
@@ -30,8 +30,10 @@ import org.springframework.context.annotation.Bean;
|
||||
import io.undertow.Undertow;
|
||||
import io.undertow.server.handlers.DisallowedMethodsHandler;
|
||||
import io.undertow.util.HttpString;
|
||||
import org.springframework.context.annotation.PropertySource;
|
||||
import top.continew.starter.core.constant.PropertiesConstants;
|
||||
import top.continew.starter.core.util.CollUtils;
|
||||
import top.continew.starter.core.util.GeneralPropertySourceFactory;
|
||||
|
||||
/**
|
||||
* Undertow 自动配置
|
||||
@@ -44,6 +46,7 @@ import top.continew.starter.core.util.CollUtils;
|
||||
@ConditionalOnWebApplication
|
||||
@ConditionalOnClass(Undertow.class)
|
||||
@EnableConfigurationProperties(ServerExtensionProperties.class)
|
||||
@PropertySource(value = "classpath:default-server.yml", factory = GeneralPropertySourceFactory.class)
|
||||
@ConditionalOnProperty(prefix = "server.extension", name = PropertiesConstants.ENABLED, havingValue = "true")
|
||||
public class UndertowAutoConfiguration {
|
||||
|
||||
|
||||
@@ -23,20 +23,3 @@ continew-starter.web.response:
|
||||
- io.swagger.**
|
||||
- org.springdoc.**
|
||||
- org.springframework.boot.actuate.*
|
||||
|
||||
--- ### 服务器配置
|
||||
server:
|
||||
## Undertow 服务器配置
|
||||
undertow:
|
||||
# HTTP POST 请求内容的大小上限(默认 -1,不限制)
|
||||
max-http-post-size: -1
|
||||
# 以下的配置会影响 buffer,这些 buffer 会用于服务器连接的 IO 操作,有点类似 Netty 的池化内存管理
|
||||
# 每块 buffer的空间大小(越小的空间被利用越充分,不要设置太大,以免影响其他应用,合适即可)
|
||||
buffer-size: 512
|
||||
# 是否分配的直接内存(NIO 直接分配的堆外内存)
|
||||
direct-buffers: true
|
||||
threads:
|
||||
# 设置 IO 线程数,它主要执行非阻塞的任务,它们会负责多个连接(默认每个 CPU 核心一个线程)
|
||||
io: 8
|
||||
# 阻塞任务线程池,当执行类似 Servlet 请求阻塞操作,Undertow 会从这个线程池中取得线程(它的值设置取决于系统的负载)
|
||||
worker: 256
|
||||
16
continew-starter-web/src/main/resources/default-server.yml
Normal file
16
continew-starter-web/src/main/resources/default-server.yml
Normal file
@@ -0,0 +1,16 @@
|
||||
--- ### 服务器配置
|
||||
server:
|
||||
## Undertow 服务器配置
|
||||
undertow:
|
||||
# HTTP POST 请求内容的大小上限(默认 -1,不限制)
|
||||
max-http-post-size: -1
|
||||
# 以下的配置会影响 buffer,这些 buffer 会用于服务器连接的 IO 操作,有点类似 Netty 的池化内存管理
|
||||
# 每块 buffer的空间大小(越小的空间被利用越充分,不要设置太大,以免影响其他应用,合适即可)
|
||||
buffer-size: 512
|
||||
# 是否分配的直接内存(NIO 直接分配的堆外内存)
|
||||
direct-buffers: true
|
||||
threads:
|
||||
# 设置 IO 线程数,它主要执行非阻塞的任务,它们会负责多个连接(默认每个 CPU 核心一个线程)
|
||||
io: 8
|
||||
# 阻塞任务线程池,当执行类似 Servlet 请求阻塞操作,Undertow 会从这个线程池中取得线程(它的值设置取决于系统的负载)
|
||||
worker: 256
|
||||
Reference in New Issue
Block a user