Compare commits

..

2 Commits

Author SHA1 Message Date
79891a1fdb release: v2.5.2 2024-08-13 13:34:28 +08:00
c31fa753b6 refactor(curd): 重构排序字段处理,预防 SQL 注入问题 2024-08-13 13:33:56 +08:00
75 changed files with 660 additions and 1026 deletions

View File

@@ -1,49 +1,8 @@
## [v2.6.0](https://github.com/continew-org/continew-starter/compare/v2.5.2...v2.6.0) (2024-09-06) ## [v2.5.2](https://github.com/continew-org/continew-starter/compare/v2.5.1...v2.5.2) (2024-08-13)
### ✨ 新特性
- 【web】新增 isMatch 路径是否匹配方法 ([e55eb17](https://github.com/continew-org/continew-starter/commit/e55eb17d64d7b42c6e64b18a645cda9558f08d58))
- 【log】不记录日志也支持开启打印访问日志 ([16b6e9b](https://github.com/continew-org/continew-starter/commit/16b6e9b466578d935ab9a8a85a5eac4d09025dee))
### 💎 功能优化
- 【data】移除 DataPermission 注解的 value 属性 ([d3fa00d](https://github.com/continew-org/continew-starter/commit/d3fa00d14ce95ab1bedaebbce8304e29df5da8fd))
- 【data】mybatis-plus => mpmybatis-flex => mf ([5e0eea2](https://github.com/continew-org/continew-starter/commit/5e0eea2ed801b526da9f65b8cb2942b3b7b050ef))
- 【web】提升接口文档响应类型优化扩展性 ([784a56f](https://github.com/continew-org/continew-starter/commit/784a56fd4f85ae170b8a56dd0a64a33a7bff98bc))
- 【web】链路追踪配置属性响应头名称 => 链路 ID 名称 ([260f484](https://github.com/continew-org/continew-starter/commit/260f484af98d112927edec4316c0375e628dd3ba))
- 【log】优化接口耗时相关时间类型使用 ([4caf0a0](https://github.com/continew-org/continew-starter/commit/4caf0a0db69779a5ea409ec7e01c4044817afc94))
### 🐛 问题修复 ### 🐛 问题修复
-log】修复日志全局 includes 配置会被局部修改的问题 ([eac5c1f](https://github.com/continew-org/continew-starter/commit/eac5c1fa79f7f91217b0525a07e0f1314f8efe24)) -extension/crud】重构排序字段处理预防 SQL 注入问题 ([c31fa75](https://github.com/continew-org/continew-starter/commit/c31fa753b6d9753d72a2e28bf3184981ed848ac2))
- 【data】还原 SQL 函数接口 ([9e5f33b](https://github.com/continew-org/continew-starter/commit/9e5f33b2c9b804741f9b77eab5b67ab3c4a8b220))
- 【crypto】修复由于升级 mybatis plus 引发的更新场景加密失效问题 ([e9b81f9](https://github.com/continew-org/continew-starter/commit/e9b81f946603e2f9e44b50471102b2551b9d50ac)) ([9fdbfdf](https://github.com/continew-org/continew-starter/commit/9fdbfdf8bb6719d3d044c71a2a8ceab85ccef8f1))
### 📦 依赖升级
- Graceful Response 4.0.1-boot3 => 5.0.0-boot3 ([2208dbd](https://github.com/continew-org/continew-starter/commit/2208dbd0282964aab76b02e811d6689ba69d75fd))
- Snail Job 1.1.0 => 1.1.2
- Sa Token 1.38.0 => 1.39.0
- MyBatis Flex 1.9.3 => 1.9.7
- Redisson 3.32.0 => 3.35.0
- Cos ID 2.9.1 => 2.9.6
- SMS4J 3.2.1 => 3.3.2
- X File Storage 2.2.0 => 2.2.1
- Hutool 5.8.32 => 5.8.29
- aws-java-sdk-s3 1.12.761 => 1.12.771
- snakeyaml 2.2 => 2.3
## [v2.5.2](https://github.com/continew-org/continew-starter/compare/v2.5.1...v2.5.2) (2024-08-14)
### 💎 功能优化
- 【api-doc】重构接口文档枚举展示处理 ([bf51837](https://github.com/continew-org/continew-starter/commit/bf51837991746c5576d7baffcc2296fe8ca91586)) ([4c4f98a](https://github.com/continew-org/continew-starter/commit/4c4f98a86e6d10b2b52814406bd0dc3d248c8486))
- 【web】针对最新响应风格增加全局响应格式 ([bf51837](https://github.com/continew-org/continew-starter/commit/bf51837991746c5576d7baffcc2296fe8ca91586)) ([4c4f98a](https://github.com/continew-org/continew-starter/commit/4c4f98a86e6d10b2b52814406bd0dc3d248c8486))
### 🐛 问题修复
- 【extension/crud】重构排序字段处理预防 SQL 注入问题 ([c31fa75](https://github.com/continew-org/continew-starter/commit/c31fa753b6d9753d72a2e28bf3184981ed848ac2)) ([22ebdfe](https://github.com/continew-org/continew-starter/commit/22ebdfeb9f30a8832825424a05343acfaf31643b))
- 【security/crypto】修复 updateById 修改未正确加密的问题 ([b0a2a8c](https://github.com/continew-org/continew-starter/commit/b0a2a8c927171795e3ed89a820caf829f76c80ee))
## [v2.5.1](https://github.com/continew-org/continew-starter/compare/v2.5.0...v2.5.1) (2024-08-12) ## [v2.5.1](https://github.com/continew-org/continew-starter/compare/v2.5.0...v2.5.1) (2024-08-12)

View File

@@ -207,10 +207,10 @@ continew-starter.web:
### 数据访问模块 ### 数据访问模块
| 模块名称 | 模块说明 | | 模块名称 | 模块说明 |
|----------------------------| --------------------- | | ---------------------------------- | --------------------- |
| continew-starter-data-core | 数据访问核心模块 | | continew-starter-data-core | 数据访问核心模块 |
| continew-starter-data-mp | MyBatis Plus 自动配置 | | continew-starter-data-mybatis-plus | MyBatis Plus 自动配置 |
| continew-starter-data-mf | MyBatis Flex 自动配置 | | continew-starter-data-mybatis-flex | MyBatis Flex 自动配置 |
### 认证模块 ### 认证模块

View File

@@ -17,11 +17,16 @@
package top.continew.starter.apidoc.autoconfigure; package top.continew.starter.apidoc.autoconfigure;
import cn.hutool.core.map.MapUtil; import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.databind.type.CollectionType;
import com.fasterxml.jackson.databind.type.SimpleType;
import io.swagger.v3.oas.models.Components; import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Contact; import io.swagger.v3.oas.models.info.Contact;
import io.swagger.v3.oas.models.info.Info; import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.info.License; import io.swagger.v3.oas.models.info.License;
import io.swagger.v3.oas.models.media.Schema;
import io.swagger.v3.oas.models.security.SecurityRequirement; import io.swagger.v3.oas.models.security.SecurityRequirement;
import io.swagger.v3.oas.models.security.SecurityScheme; import io.swagger.v3.oas.models.security.SecurityScheme;
import jakarta.annotation.PostConstruct; import jakarta.annotation.PostConstruct;
@@ -43,13 +48,15 @@ import org.springframework.http.CacheControl;
import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
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.apidoc.util.EnumTypeUtils;
import top.continew.starter.core.autoconfigure.project.ProjectProperties; import top.continew.starter.core.autoconfigure.project.ProjectProperties;
import top.continew.starter.core.enums.BaseEnum;
import top.continew.starter.core.util.GeneralPropertySourceFactory; import top.continew.starter.core.util.GeneralPropertySourceFactory;
import java.util.*; import java.util.*;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/** /**
* API 文档自动配置 * API 文档自动配置
@@ -149,14 +156,95 @@ public class SpringDocAutoConfiguration implements WebMvcConfigurer {
} }
/** /**
* 自定义 BaseEnum 枚举参数配置(针对实现了 BaseEnum 的枚举,优化其枚举值和描述展示 * 自定义参数配置(针对 BaseEnum 展示枚举值和描述)
* *
* @return {@link BaseEnumParameterHandler }
* @since 2.4.0 * @since 2.4.0
*/ */
@Bean @Bean
public BaseEnumParameterHandler customParameterCustomizer() { public ParameterCustomizer customParameterCustomizer() {
return new BaseEnumParameterHandler(); return (parameterModel, methodParameter) -> {
Class<?> parameterType = methodParameter.getParameterType();
// 判断是否为 BaseEnum 的子类型
if (!ClassUtil.isAssignable(BaseEnum.class, parameterType)) {
return parameterModel;
}
String description = parameterModel.getDescription();
if (StrUtil.contains(description, "color:red")) {
return parameterModel;
}
// 封装参数配置
this.configureSchema(parameterModel.getSchema(), parameterType);
// 自定义枚举描述
parameterModel.setDescription(description + "<span style='color:red'>" + this
.getDescMap(parameterType) + "</span>");
return parameterModel;
};
}
/**
* 自定义参数配置(针对 BaseEnum 展示枚举值和描述)
*
* @since 2.4.0
*/
@Bean
public PropertyCustomizer customPropertyCustomizer() {
return (schema, type) -> {
Class<?> rawClass;
// 获取原始类的类型
if (type.getType() instanceof SimpleType) {
rawClass = ((SimpleType)type.getType()).getRawClass();
} else if (type.getType() instanceof CollectionType) {
rawClass = ((CollectionType)type.getType()).getContentType().getRawClass();
} else {
rawClass = Object.class;
}
// 判断是否为 BaseEnum 的子类型
if (!ClassUtil.isAssignable(BaseEnum.class, rawClass)) {
return schema;
}
// 封装参数配置
this.configureSchema(schema, rawClass);
// 自定义参数描述
schema.setDescription(schema.getDescription() + "<span style='color:red'>" + this
.getDescMap(rawClass) + "</span>");
return schema;
};
}
/**
* 封装 Schema 配置
*
* @param schema Schema
* @param enumClass 枚举类型
* @since 2.4.0
*/
private void configureSchema(Schema schema, Class<?> enumClass) {
BaseEnum[] enums = (BaseEnum[])enumClass.getEnumConstants();
// 设置枚举可用值
List<String> valueList = Arrays.stream(enums).map(e -> e.getValue().toString()).toList();
schema.setEnum(valueList);
// 设置枚举值类型和格式
String enumValueType = EnumTypeUtils.getEnumValueTypeAsString(enumClass);
schema.setType(enumValueType);
switch (enumValueType) {
case "integer" -> schema.setFormat("int32");
case "long" -> schema.setFormat("int64");
case "number" -> schema.setFormat("double");
default -> schema.setFormat(enumValueType);
}
}
/**
* 获取枚举描述 Map
*
* @param enumClass 枚举类型
* @return 枚举描述 Map
* @since 2.4.0
*/
private Map<Object, String> getDescMap(Class<?> enumClass) {
BaseEnum[] enums = (BaseEnum[])enumClass.getEnumConstants();
return Arrays.stream(enums)
.collect(Collectors.toMap(BaseEnum::getValue, BaseEnum::getDescription, (a, b) -> a, LinkedHashMap::new));
} }
@PostConstruct @PostConstruct

View File

@@ -1,118 +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.apidoc.handler;
import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.databind.type.CollectionType;
import com.fasterxml.jackson.databind.type.SimpleType;
import io.swagger.v3.core.converter.AnnotatedType;
import io.swagger.v3.oas.models.media.Schema;
import io.swagger.v3.oas.models.parameters.Parameter;
import org.springdoc.core.customizers.ParameterCustomizer;
import org.springdoc.core.customizers.PropertyCustomizer;
import org.springframework.core.MethodParameter;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.List;
import top.continew.starter.apidoc.util.DocUtils;
import top.continew.starter.core.enums.BaseEnum;
/**
* 自定义 BaseEnum 枚举参数处理器
* <p>
* 针对实现了 BaseEnum 的枚举,优化其枚举值和描述展示
* </p>
*
* @author echo
* @since 2.5.2
*/
public class BaseEnumParameterHandler implements ParameterCustomizer, PropertyCustomizer {
@Override
public Parameter customize(Parameter parameterModel, MethodParameter methodParameter) {
Class<?> parameterType = methodParameter.getParameterType();
// 判断是否为 BaseEnum 的子类型
if (!ClassUtil.isAssignable(BaseEnum.class, parameterType)) {
return parameterModel;
}
String description = parameterModel.getDescription();
if (StrUtil.contains(description, "color:red")) {
return parameterModel;
}
// 自定义枚举描述并封装参数配置
configureSchema(parameterModel.getSchema(), parameterType);
parameterModel.setDescription(appendEnumDescription(description, parameterType));
return parameterModel;
}
@Override
public Schema customize(Schema schema, AnnotatedType type) {
Class<?> rawClass = resolveRawClass(type.getType());
// 判断是否为 BaseEnum 的子类型
if (!ClassUtil.isAssignable(BaseEnum.class, rawClass)) {
return schema;
}
// 自定义参数描述并封装参数配置
configureSchema(schema, rawClass);
schema.setDescription(appendEnumDescription(schema.getDescription(), rawClass));
return schema;
}
/**
* 封装 Schema 配置
*
* @param schema Schema
* @param enumClass 枚举类型
*/
private void configureSchema(Schema schema, Class<?> enumClass) {
BaseEnum[] enums = (BaseEnum[])enumClass.getEnumConstants();
List<String> valueList = Arrays.stream(enums).map(e -> e.getValue().toString()).toList();
schema.setEnum(valueList);
String enumValueType = DocUtils.getEnumValueTypeAsString(enumClass);
schema.setType(enumValueType);
schema.setFormat(DocUtils.resolveFormat(enumValueType));
}
/**
* 追加枚举描述
*
* @param originalDescription 原始描述
* @param enumClass 枚举类型
* @return 追加后的描述字符串
*/
private String appendEnumDescription(String originalDescription, Class<?> enumClass) {
return originalDescription + "<span style='color:red'>" + DocUtils.getDescMap(enumClass) + "</span>";
}
/**
* 解析原始类
*
* @param type 类型
* @return 原始类的 Class 对象
*/
private Class<?> resolveRawClass(Type type) {
if (type instanceof SimpleType simpleType) {
return simpleType.getRawClass();
} else if (type instanceof CollectionType collectionType) {
return collectionType.getContentType().getRawClass();
} else {
return Object.class;
}
}
}

View File

@@ -1,118 +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.apidoc.util;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.stream.Collectors;
import org.springframework.web.bind.annotation.RestController;
import top.continew.starter.core.enums.BaseEnum;
/**
* 接口文档工具类
*
* @author echo
* @since 2.5.2
*/
public class DocUtils {
private DocUtils() {
}
/**
* 获取枚举值类型
*
* @param enumClass 枚举类型
* @return 枚举值类型
*/
public static String getEnumValueTypeAsString(Class<?> enumClass) {
// 获取枚举类实现的所有接口
Type[] interfaces = enumClass.getGenericInterfaces();
// 定义枚举值类型的映射
Map<Class<?>, String> typeMap = Map
.of(Integer.class, "integer", Long.class, "long", Double.class, "number", String.class, "string");
// 遍历所有接口
for (Type type : interfaces) {
// 检查接口是否为参数化类型并且原始类型为 BaseEnum
if (type instanceof ParameterizedType parameterizedType && parameterizedType
.getRawType() == BaseEnum.class) {
Type actualType = parameterizedType.getActualTypeArguments()[0];
// 检查实际类型参数是否为类类型,并返回对应的字符串类型
if (actualType instanceof Class<?> actualClass) {
return typeMap.getOrDefault(actualClass, "string");
}
}
}
// 默认返回 "string" 类型
return "string";
}
/**
* 解析枚举值的格式
*
* @param enumValueType 枚举值类型
* @return String 格式化类型
*/
public static String resolveFormat(String enumValueType) {
return switch (enumValueType) {
case "integer" -> "int32";
case "long" -> "int64";
case "number" -> "double";
default -> enumValueType;
};
}
/**
* 具有 RestController 注释既检查是否继承了BaseController
*
* @param clazz clazz
* @return boolean
*/
public static boolean hasRestControllerAnnotation(Class<?> clazz) {
// 如果注释包含 RestController 注解,则返回 true
if (clazz.isAnnotationPresent(RestController.class)) {
return true;
}
// 递归检查父类
Class<?> superClass = clazz.getSuperclass();
// 循环检查父类
while (superClass != null && !superClass.equals(Object.class)) {
// 如果父类包含 RestController 注解,则返回 true
if (hasRestControllerAnnotation(superClass)) {
return true;
}
// 递归检查接口
superClass = superClass.getSuperclass();
}
return false;
}
/**
* 获取枚举描述 Map
*
* @param enumClass 枚举类型
* @return 枚举描述 Map
*/
public static Map<Object, String> getDescMap(Class<?> enumClass) {
BaseEnum[] enums = (BaseEnum[])enumClass.getEnumConstants();
return Arrays.stream(enums)
.collect(Collectors.toMap(BaseEnum::getValue, BaseEnum::getDescription, (a, b) -> a, LinkedHashMap::new));
}
}

View File

@@ -0,0 +1,73 @@
/*
* 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.apidoc.util;
import top.continew.starter.core.enums.BaseEnum;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
/**
* 枚举类型工具
*
* @author echo
* @since 2.4.0
*/
public class EnumTypeUtils {
private EnumTypeUtils() {
}
/**
* 获取枚举值类型
*
* @param enumClass 枚举类型
* @return 枚举值类型
*/
public static String getEnumValueTypeAsString(Class<?> enumClass) {
try {
// 获取枚举类实现的所有接口
Type[] interfaces = enumClass.getGenericInterfaces();
// 遍历所有接口
for (Type type : interfaces) {
// 检查接口是否为参数化类型
if (type instanceof ParameterizedType parameterizedType) {
// 检查接口的原始类型是否为 BaseEnum
if (parameterizedType.getRawType() != BaseEnum.class) {
continue;
}
Type actualType = parameterizedType.getActualTypeArguments()[0];
// 检查实际类型参数是否为类类型
if (actualType instanceof Class<?> actualClass) {
if (actualClass == Integer.class) {
return "integer";
} else if (actualClass == Long.class) {
return "long";
} else if (actualClass == Double.class) {
return "number";
} else if (actualClass == String.class) {
return "string";
}
}
}
}
} catch (Exception ignored) {
// ignored
}
return "string";
}
}

View File

@@ -272,11 +272,6 @@ public class StringConstants {
*/ */
public static final String ROUND_BRACKET_END = ")"; public static final String ROUND_BRACKET_END = ")";
/**
* 等号(=
*/
public static final String EQUALS = "=";
/** /**
* 路径模式 * 路径模式
*/ */

View File

@@ -18,8 +18,6 @@ package top.continew.starter.data.core.enums;
import top.continew.starter.data.core.function.ISqlFunction; import top.continew.starter.data.core.function.ISqlFunction;
import java.io.Serializable;
/** /**
* 数据库类型枚举 * 数据库类型枚举
* *
@@ -33,8 +31,8 @@ public enum DatabaseType implements ISqlFunction {
*/ */
MYSQL("MySQL") { MYSQL("MySQL") {
@Override @Override
public String findInSet(Serializable value, String set) { public String findInSet() {
return "find_in_set('%s', %s) <> 0".formatted(value, set); return "find_in_set({0}, {1}) <> 0";
} }
}, },
@@ -43,8 +41,8 @@ public enum DatabaseType implements ISqlFunction {
*/ */
POSTGRE_SQL("PostgreSQL") { POSTGRE_SQL("PostgreSQL") {
@Override @Override
public String findInSet(Serializable value, String set) { public String findInSet() {
return "(select position(',%s,' in ','||%s||',')) <> 0".formatted(value, set); return "(select position(',{0},' in ','||{1}||',')) <> 0";
} }
},; },;

View File

@@ -16,8 +16,6 @@
package top.continew.starter.data.core.function; package top.continew.starter.data.core.function;
import java.io.Serializable;
/** /**
* SQL 函数接口 * SQL 函数接口
* *
@@ -29,9 +27,7 @@ public interface ISqlFunction {
/** /**
* find_in_set 函数 * find_in_set 函数
* *
* @param value 值
* @param set 集合
* @return 函数实现 * @return 函数实现
*/ */
String findInSet(Serializable value, String set); String findInSet();
} }

View File

@@ -16,24 +16,17 @@
package top.continew.starter.data.core.util; package top.continew.starter.data.core.util;
import cn.hutool.core.text.CharSequenceUtil; import java.util.Objects;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.regex.Pattern; import java.util.regex.Pattern;
/** /**
* SQL 注入验证工具类 * SQL 注入验证工具类
* *
* @author hubin<a href="https://github.com/baomidou/mybatis-plus">MyBatis Plus</a> * @author hubin
* @author zhoujf<a href="https://github.com/jeecgboot/JeecgBoot">JeecgBoot</a>
* @author Charles7c
* @since 2.5.2 * @since 2.5.2
*/ */
public class SqlInjectionUtils { public class SqlInjectionUtils {
private static final Logger log = LoggerFactory.getLogger(SqlInjectionUtils.class);
/** /**
* SQL语法检查正则符合两个关键字有先后顺序才算匹配 * SQL语法检查正则符合两个关键字有先后顺序才算匹配
*/ */
@@ -46,23 +39,6 @@ public class SqlInjectionUtils {
private static final Pattern SQL_COMMENT_PATTERN = Pattern private static final Pattern SQL_COMMENT_PATTERN = Pattern
.compile("'.*(or|union|--|#|/\\*|;)", Pattern.CASE_INSENSITIVE); .compile("'.*(or|union|--|#|/\\*|;)", Pattern.CASE_INSENSITIVE);
/**
* SQL 语法关键字
*/
private static final String SQL_SYNTAX_KEYWORD = "and |exec |peformance_schema|information_schema|extractvalue|updatexml|geohash|gtid_subset|gtid_subtract|insert |select |delete |update |drop |count |chr |mid |master |truncate |char |declare |;|or |+|--";
/**
* SQL 函数检查正则
*/
private static final String[] SQL_FUNCTION_PATTERN = new String[] {"chr\\s*\\(", "mid\\s*\\(", " char\\s*\\(",
"sleep\\s*\\(", "user\\s*\\(", "show\\s+tables", "user[\\s]*\\([\\s]*\\)", "show\\s+databases",
"sleep\\(\\d*\\)", "sleep\\(.*\\)",};
private static final String MESSAGE_TEMPLATE = "SQL 注入检查: 检查值=>{}<=存在 SQL 注入关键字, 关键字=>{}<=";
private SqlInjectionUtils() {
}
/** /**
* 检查参数是否存在 SQL 注入 * 检查参数是否存在 SQL 注入
* *
@@ -70,59 +46,8 @@ public class SqlInjectionUtils {
* @return true非法false合法 * @return true非法false合法
*/ */
public static boolean check(String value) { public static boolean check(String value) {
return check(value, null); Objects.requireNonNull(value);
} // 处理是否包含 SQL 注释字符 || 检查是否包含 SQ L注入敏感字符
return SQL_COMMENT_PATTERN.matcher(value).find() || SQL_SYNTAX_PATTERN.matcher(value).find();
/**
* 检查参数是否存在 SQL 注入
*
* @param value 检查参数
* @param customKeyword 自定义关键字
* @return true非法false合法
*/
public static boolean check(String value, String customKeyword) {
if (CharSequenceUtil.isBlank(value)) {
return false;
}
// 处理是否包含 SQL 注释字符 || 检查是否包含 SQL 注入敏感字符
if (SQL_COMMENT_PATTERN.matcher(value).find() || SQL_SYNTAX_PATTERN.matcher(value).find()) {
log.warn("SQL 注入检查: 检查值=>{}<=存在 SQL 注释字符或 SQL 注入敏感字符", value);
return true;
}
// 转换成小写再进行比较
value = value.toLowerCase().trim();
// 检查是否包含 SQL 语法关键字
if (checkKeyword(value, SQL_SYNTAX_KEYWORD.split("\\|"))) {
return true;
}
// 检查是否包含自定义关键字
if (CharSequenceUtil.isNotBlank(customKeyword) && checkKeyword(value, customKeyword.split("\\|"))) {
return true;
}
// 检查是否包含 SQL 注入敏感字符
for (String pattern : SQL_FUNCTION_PATTERN) {
if (Pattern.matches(".*" + pattern + ".*", value)) {
log.warn(MESSAGE_TEMPLATE, value, pattern);
return true;
}
}
return false;
}
/**
* 检查参数是否存在关键字
*
* @param value 检查参数
* @param keywords 关键字列表
* @return true非法false合法
*/
private static boolean checkKeyword(String value, String[] keywords) {
for (String keyword : keywords) {
if (value.contains(keyword)) {
log.warn(MESSAGE_TEMPLATE, value, keyword);
return true;
}
}
return false;
} }
} }

View File

@@ -1 +0,0 @@
top.continew.starter.data.mf.autoconfigure.MybatisFlexAutoConfiguration

View File

@@ -1 +0,0 @@
top.continew.starter.data.mp.autoconfigure.MybatisPlusAutoConfiguration

View File

@@ -9,7 +9,7 @@
<version>${revision}</version> <version>${revision}</version>
</parent> </parent>
<artifactId>continew-starter-data-mf</artifactId> <artifactId>continew-starter-data-mybatis-flex</artifactId>
<description>ContiNew Starter 数据访问模块 - MyBatis Flex</description> <description>ContiNew Starter 数据访问模块 - MyBatis Flex</description>
<dependencies> <dependencies>

View File

@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package top.continew.starter.data.mf.autoconfigure; package top.continew.starter.data.mybatis.flex.autoconfigure;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import top.continew.starter.core.constant.PropertiesConstants; import top.continew.starter.core.constant.PropertiesConstants;

View File

@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package top.continew.starter.data.mf.autoconfigure; package top.continew.starter.data.mybatis.flex.autoconfigure;
import com.mybatisflex.core.dialect.DbType; import com.mybatisflex.core.dialect.DbType;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;

View File

@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package top.continew.starter.data.mf.autoconfigure; package top.continew.starter.data.mybatis.flex.autoconfigure;
import com.mybatisflex.core.dialect.DbType; import com.mybatisflex.core.dialect.DbType;
import com.mybatisflex.core.dialect.DialectFactory; import com.mybatisflex.core.dialect.DialectFactory;
@@ -30,8 +30,8 @@ import org.springframework.context.annotation.PropertySource;
import org.springframework.transaction.annotation.EnableTransactionManagement; import org.springframework.transaction.annotation.EnableTransactionManagement;
import top.continew.starter.core.constant.PropertiesConstants; import top.continew.starter.core.constant.PropertiesConstants;
import top.continew.starter.core.util.GeneralPropertySourceFactory; import top.continew.starter.core.util.GeneralPropertySourceFactory;
import top.continew.starter.data.mf.datapermission.DataPermissionDialect; import top.continew.starter.data.mybatis.flex.datapermission.DataPermissionDialect;
import top.continew.starter.data.mf.datapermission.DataPermissionFilter; import top.continew.starter.data.mybatis.flex.datapermission.DataPermissionFilter;
/** /**
* MyBatis Flex 自动配置 * MyBatis Flex 自动配置

View File

@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package top.continew.starter.data.mf.base; package top.continew.starter.data.mybatis.flex.base;
import cn.hutool.core.util.ClassUtil; import cn.hutool.core.util.ClassUtil;
import com.mybatisflex.core.query.QueryWrapper; import com.mybatisflex.core.query.QueryWrapper;

View File

@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package top.continew.starter.data.mf.base; package top.continew.starter.data.mybatis.flex.base;
import java.io.Serializable; import java.io.Serializable;

View File

@@ -14,7 +14,9 @@
* limitations under the License. * limitations under the License.
*/ */
package top.continew.starter.data.mf.datapermission; package top.continew.starter.data.mybatis.flex.datapermission;
import org.springframework.core.annotation.AliasFor;
import java.lang.annotation.*; import java.lang.annotation.*;
@@ -29,9 +31,16 @@ import java.lang.annotation.*;
@Documented @Documented
public @interface DataPermission { public @interface DataPermission {
/**
* Alias for the {@link #tableAlias()} attribute.
*/
@AliasFor("tableAlias")
String value() default "";
/** /**
* 表别名 * 表别名
*/ */
@AliasFor("value")
String tableAlias() default ""; String tableAlias() default "";
/** /**

View File

@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package top.continew.starter.data.mf.datapermission; package top.continew.starter.data.mybatis.flex.datapermission;
import org.aspectj.lang.annotation.*; import org.aspectj.lang.annotation.*;

View File

@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package top.continew.starter.data.mf.datapermission; package top.continew.starter.data.mybatis.flex.datapermission;
import java.util.Set; import java.util.Set;

View File

@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package top.continew.starter.data.mf.datapermission; package top.continew.starter.data.mybatis.flex.datapermission;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import com.mybatisflex.core.dialect.impl.CommonsDialectImpl; import com.mybatisflex.core.dialect.impl.CommonsDialectImpl;

View File

@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package top.continew.starter.data.mf.datapermission; package top.continew.starter.data.mybatis.flex.datapermission;
/** /**
* 数据权限过滤器接口 * 数据权限过滤器接口

View File

@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package top.continew.starter.data.mf.datapermission; package top.continew.starter.data.mybatis.flex.datapermission;
/** /**
* 数据权限枚举 * 数据权限枚举

View File

@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package top.continew.starter.data.mf.service; package top.continew.starter.data.mybatis.flex.service;
/** /**
* 通用业务接口 * 通用业务接口

View File

@@ -14,11 +14,11 @@
* limitations under the License. * limitations under the License.
*/ */
package top.continew.starter.data.mf.service.impl; package top.continew.starter.data.mybatis.flex.service.impl;
import top.continew.starter.core.util.ClassUtils; import top.continew.starter.core.util.ClassUtils;
import top.continew.starter.data.mf.base.BaseMapper; import top.continew.starter.data.mybatis.flex.base.BaseMapper;
import top.continew.starter.data.mf.service.IService; import top.continew.starter.data.mybatis.flex.service.IService;
/** /**
* 通用业务实现类 * 通用业务实现类

View File

@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package top.continew.starter.data.mf.util; package top.continew.starter.data.mybatis.flex.util;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.text.CharSequenceUtil; import cn.hutool.core.text.CharSequenceUtil;

View File

@@ -0,0 +1 @@
top.continew.starter.data.mybatis.flex.autoconfigure.MybatisFlexAutoConfiguration

View File

@@ -9,7 +9,7 @@
<version>${revision}</version> <version>${revision}</version>
</parent> </parent>
<artifactId>continew-starter-data-mp</artifactId> <artifactId>continew-starter-data-mybatis-plus</artifactId>
<description>ContiNew Starter 数据访问模块 - MyBatis Plus</description> <description>ContiNew Starter 数据访问模块 - MyBatis Plus</description>
<dependencies> <dependencies>

View File

@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package top.continew.starter.data.mp.autoconfigure; package top.continew.starter.data.mybatis.plus.autoconfigure;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import top.continew.starter.core.constant.PropertiesConstants; import top.continew.starter.core.constant.PropertiesConstants;

View File

@@ -14,12 +14,12 @@
* limitations under the License. * limitations under the License.
*/ */
package top.continew.starter.data.mp.autoconfigure; package top.continew.starter.data.mybatis.plus.autoconfigure;
import com.baomidou.mybatisplus.annotation.DbType; import com.baomidou.mybatisplus.annotation.DbType;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.NestedConfigurationProperty; import org.springframework.boot.context.properties.NestedConfigurationProperty;
import top.continew.starter.data.mp.autoconfigure.idgenerator.MyBatisPlusIdGeneratorProperties; import top.continew.starter.data.mybatis.plus.autoconfigure.idgenerator.MyBatisPlusIdGeneratorProperties;
/** /**
* MyBatis Plus 扩展配置属性 * MyBatis Plus 扩展配置属性

View File

@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package top.continew.starter.data.mp.autoconfigure; package top.continew.starter.data.mybatis.plus.autoconfigure;
import cn.hutool.extra.spring.SpringUtil; import cn.hutool.extra.spring.SpringUtil;
import com.baomidou.mybatisplus.autoconfigure.MybatisPlusPropertiesCustomizer; import com.baomidou.mybatisplus.autoconfigure.MybatisPlusPropertiesCustomizer;
@@ -22,7 +22,6 @@ import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.handler.DataPermissionHandler; import com.baomidou.mybatisplus.extension.plugins.handler.DataPermissionHandler;
import com.baomidou.mybatisplus.extension.plugins.inner.BlockAttackInnerInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.BlockAttackInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.DataPermissionInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.DataPermissionInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import jakarta.annotation.PostConstruct; import jakarta.annotation.PostConstruct;
import org.mybatis.spring.annotation.MapperScan; import org.mybatis.spring.annotation.MapperScan;
@@ -39,12 +38,10 @@ import org.springframework.context.annotation.PropertySource;
import org.springframework.transaction.annotation.EnableTransactionManagement; import org.springframework.transaction.annotation.EnableTransactionManagement;
import top.continew.starter.core.constant.PropertiesConstants; import top.continew.starter.core.constant.PropertiesConstants;
import top.continew.starter.core.util.GeneralPropertySourceFactory; import top.continew.starter.core.util.GeneralPropertySourceFactory;
import top.continew.starter.data.mp.autoconfigure.idgenerator.MyBatisPlusIdGeneratorConfiguration; import top.continew.starter.data.mybatis.plus.autoconfigure.idgenerator.MyBatisPlusIdGeneratorConfiguration;
import top.continew.starter.data.mp.datapermission.DataPermissionFilter; import top.continew.starter.data.mybatis.plus.datapermission.DataPermissionFilter;
import top.continew.starter.data.mp.datapermission.DataPermissionHandlerImpl; import top.continew.starter.data.mybatis.plus.datapermission.DataPermissionHandlerImpl;
import top.continew.starter.data.mp.handler.MybatisBaseEnumTypeHandler; import top.continew.starter.data.mybatis.plus.handler.MybatisBaseEnumTypeHandler;
import java.util.Map;
/** /**
* MyBatis Plus 自动配置 * MyBatis Plus 自动配置
@@ -79,11 +76,6 @@ public class MybatisPlusAutoConfiguration {
@ConditionalOnMissingBean @ConditionalOnMissingBean
public MybatisPlusInterceptor mybatisPlusInterceptor(MyBatisPlusExtensionProperties properties) { public MybatisPlusInterceptor mybatisPlusInterceptor(MyBatisPlusExtensionProperties properties) {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 其他拦截器
Map<String, InnerInterceptor> innerInterceptors = SpringUtil.getBeansOfType(InnerInterceptor.class);
if (!innerInterceptors.isEmpty()) {
innerInterceptors.values().forEach(interceptor::addInnerInterceptor);
}
// 数据权限插件 // 数据权限插件
MyBatisPlusExtensionProperties.DataPermissionProperties dataPermissionProperties = properties MyBatisPlusExtensionProperties.DataPermissionProperties dataPermissionProperties = properties
.getDataPermission(); .getDataPermission();

View File

@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package top.continew.starter.data.mp.autoconfigure.idgenerator; package top.continew.starter.data.mybatis.plus.autoconfigure.idgenerator;
import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator; import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator;
import me.ahoo.cosid.snowflake.SnowflakeId; import me.ahoo.cosid.snowflake.SnowflakeId;

View File

@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package top.continew.starter.data.mp.autoconfigure.idgenerator; package top.continew.starter.data.mybatis.plus.autoconfigure.idgenerator;
import cn.hutool.core.net.NetUtil; import cn.hutool.core.net.NetUtil;
import com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator; import com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator;

View File

@@ -14,9 +14,9 @@
* limitations under the License. * limitations under the License.
*/ */
package top.continew.starter.data.mp.autoconfigure.idgenerator; package top.continew.starter.data.mybatis.plus.autoconfigure.idgenerator;
import top.continew.starter.data.mp.enums.MyBatisPlusIdGeneratorType; import top.continew.starter.data.mybatis.plus.enums.MyBatisPlusIdGeneratorType;
/** /**
* MyBatis ID 生成器配置属性 * MyBatis ID 生成器配置属性

View File

@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package top.continew.starter.data.mp.base; package top.continew.starter.data.mybatis.plus.base;
import cn.hutool.core.util.ClassUtil; import cn.hutool.core.util.ClassUtil;
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper; import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;

View File

@@ -14,7 +14,9 @@
* limitations under the License. * limitations under the License.
*/ */
package top.continew.starter.data.mp.datapermission; package top.continew.starter.data.mybatis.plus.datapermission;
import org.springframework.core.annotation.AliasFor;
import java.lang.annotation.*; import java.lang.annotation.*;
@@ -29,9 +31,16 @@ import java.lang.annotation.*;
@Documented @Documented
public @interface DataPermission { public @interface DataPermission {
/**
* Alias for the {@link #tableAlias()} attribute.
*/
@AliasFor("tableAlias")
String value() default "";
/** /**
* 表别名 * 表别名
*/ */
@AliasFor("value")
String tableAlias() default ""; String tableAlias() default "";
/** /**

View File

@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package top.continew.starter.data.mp.datapermission; package top.continew.starter.data.mybatis.plus.datapermission;
import java.util.Set; import java.util.Set;

View File

@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package top.continew.starter.data.mp.datapermission; package top.continew.starter.data.mybatis.plus.datapermission;
/** /**
* 数据权限过滤器接口 * 数据权限过滤器接口

View File

@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package top.continew.starter.data.mp.datapermission; package top.continew.starter.data.mybatis.plus.datapermission;
import cn.hutool.core.text.CharSequenceUtil; import cn.hutool.core.text.CharSequenceUtil;
import com.baomidou.mybatisplus.core.toolkit.StringUtils; import com.baomidou.mybatisplus.core.toolkit.StringUtils;

View File

@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package top.continew.starter.data.mp.datapermission; package top.continew.starter.data.mybatis.plus.datapermission;
/** /**
* 数据权限枚举 * 数据权限枚举

View File

@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package top.continew.starter.data.mp.enums; package top.continew.starter.data.mybatis.plus.enums;
/** /**
* MyBatis ID 生成器类型枚举 * MyBatis ID 生成器类型枚举

View File

@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package top.continew.starter.data.mp.handler; package top.continew.starter.data.mybatis.plus.handler;
import com.baomidou.mybatisplus.annotation.EnumValue; import com.baomidou.mybatisplus.annotation.EnumValue;
import com.baomidou.mybatisplus.annotation.IEnum; import com.baomidou.mybatisplus.annotation.IEnum;

View File

@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package top.continew.starter.data.mp.service; package top.continew.starter.data.mybatis.plus.service;
/** /**
* 通用业务接口 * 通用业务接口

View File

@@ -14,13 +14,13 @@
* limitations under the License. * limitations under the License.
*/ */
package top.continew.starter.data.mp.service.impl; package top.continew.starter.data.mybatis.plus.service.impl;
import cn.hutool.core.util.ClassUtil; import cn.hutool.core.util.ClassUtil;
import top.continew.starter.core.util.ReflectUtils; import top.continew.starter.core.util.ReflectUtils;
import top.continew.starter.core.util.validate.CheckUtils; import top.continew.starter.core.util.validate.CheckUtils;
import top.continew.starter.data.mp.base.BaseMapper; import top.continew.starter.data.mybatis.plus.base.BaseMapper;
import top.continew.starter.data.mp.service.IService; import top.continew.starter.data.mybatis.plus.service.IService;
import java.io.Serializable; import java.io.Serializable;
import java.lang.reflect.Field; import java.lang.reflect.Field;

View File

@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package top.continew.starter.data.mp.util; package top.continew.starter.data.mybatis.plus.util;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;

View File

@@ -0,0 +1 @@
top.continew.starter.data.mybatis.plus.autoconfigure.MybatisPlusAutoConfiguration

View File

@@ -15,8 +15,8 @@
<modules> <modules>
<module>continew-starter-data-core</module> <module>continew-starter-data-core</module>
<module>continew-starter-data-mp</module> <module>continew-starter-data-mybatis-plus</module>
<module>continew-starter-data-mf</module> <module>continew-starter-data-mybatis-flex</module>
</modules> </modules>
<dependencies> <dependencies>

View File

@@ -43,33 +43,33 @@
<properties> <properties>
<!-- 项目版本号 --> <!-- 项目版本号 -->
<revision>2.6.0</revision> <revision>2.5.2</revision>
<snail-job.version>1.1.2</snail-job.version> <snail-job.version>1.1.0</snail-job.version>
<sa-token.version>1.39.0</sa-token.version> <sa-token.version>1.38.0</sa-token.version>
<just-auth.version>1.16.6</just-auth.version> <just-auth.version>1.16.6</just-auth.version>
<mybatis-plus.version>3.5.7</mybatis-plus.version> <mybatis-plus.version>3.5.7</mybatis-plus.version>
<mybatis-flex.version>1.9.7</mybatis-flex.version> <mybatis-flex.version>1.9.3</mybatis-flex.version>
<dynamic-datasource.version>4.3.1</dynamic-datasource.version> <dynamic-datasource.version>4.3.1</dynamic-datasource.version>
<p6spy.version>3.9.1</p6spy.version> <p6spy.version>3.9.1</p6spy.version>
<jetcache.version>2.7.6</jetcache.version> <jetcache.version>2.7.6</jetcache.version>
<redisson.version>3.35.0</redisson.version> <redisson.version>3.32.0</redisson.version>
<cosid.version>2.9.6</cosid.version> <cosid.version>2.9.1</cosid.version>
<sms4j.version>3.2.1</sms4j.version> <sms4j.version>3.2.1</sms4j.version>
<aj-captcha.version>1.3.0</aj-captcha.version> <aj-captcha.version>1.3.0</aj-captcha.version>
<easy-captcha.version>1.6.2</easy-captcha.version> <easy-captcha.version>1.6.2</easy-captcha.version>
<easy-excel.version>3.3.4</easy-excel.version> <easy-excel.version>3.3.4</easy-excel.version>
<nashorn.version>15.4</nashorn.version> <nashorn.version>15.4</nashorn.version>
<x-file-storage.version>2.2.0</x-file-storage.version> <x-file-storage.version>2.2.0</x-file-storage.version>
<aws-s3.version>1.12.771</aws-s3.version> <aws-s3.version>1.12.761</aws-s3.version>
<graceful-response.version>5.0.0-boot3</graceful-response.version> <graceful-response.version>4.0.1-boot3</graceful-response.version>
<crane4j.version>2.9.0</crane4j.version> <crane4j.version>2.9.0</crane4j.version>
<knife4j.version>4.5.0</knife4j.version> <knife4j.version>4.5.0</knife4j.version>
<tlog.version>1.5.2</tlog.version> <tlog.version>1.5.2</tlog.version>
<snakeyaml.version>2.3</snakeyaml.version> <snakeyaml.version>2.2</snakeyaml.version>
<okhttp.version>4.12.0</okhttp.version> <okhttp.version>4.12.0</okhttp.version>
<ttl.version>2.14.5</ttl.version> <ttl.version>2.14.5</ttl.version>
<ip2region.version>3.2.6</ip2region.version> <ip2region.version>3.2.6</ip2region.version>
<hutool.version>5.8.32</hutool.version> <hutool.version>5.8.29</hutool.version>
<!-- Maven Plugin Versions --> <!-- Maven Plugin Versions -->
<flatten.version>1.6.0</flatten.version> <flatten.version>1.6.0</flatten.version>
<spotless.version>2.43.0</spotless.version> <spotless.version>2.43.0</spotless.version>
@@ -137,11 +137,6 @@
<artifactId>mybatis-plus-core</artifactId> <artifactId>mybatis-plus-core</artifactId>
<version>${mybatis-plus.version}</version> <version>${mybatis-plus.version}</version>
</dependency> </dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-extension</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<!-- MyBatis FlexMyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,简化开发、提高效率) --> <!-- MyBatis FlexMyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,简化开发、提高效率) -->
<dependency> <dependency>
@@ -378,14 +373,14 @@
<!-- 数据访问模块 - MyBatis Plus --> <!-- 数据访问模块 - MyBatis Plus -->
<dependency> <dependency>
<groupId>top.continew</groupId> <groupId>top.continew</groupId>
<artifactId>continew-starter-data-mp</artifactId> <artifactId>continew-starter-data-mybatis-plus</artifactId>
<version>${revision}</version> <version>${revision}</version>
</dependency> </dependency>
<!-- 数据访问模块 - MyBatis Flex --> <!-- 数据访问模块 - MyBatis Flex -->
<dependency> <dependency>
<groupId>top.continew</groupId> <groupId>top.continew</groupId>
<artifactId>continew-starter-data-mf</artifactId> <artifactId>continew-starter-data-mybatis-flex</artifactId>
<version>${revision}</version> <version>${revision}</version>
</dependency> </dependency>

View File

@@ -16,6 +16,7 @@
package top.continew.starter.extension.crud.model.query; package top.continew.starter.extension.crud.model.query;
import cn.hutool.core.net.URLDecoder;
import cn.hutool.core.text.CharSequenceUtil; import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.ArrayUtil;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
@@ -26,6 +27,7 @@ import top.continew.starter.data.core.util.SqlInjectionUtils;
import java.io.Serial; import java.io.Serial;
import java.io.Serializable; import java.io.Serializable;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@@ -58,7 +60,7 @@ public class SortQuery implements Serializable {
} }
ValidationUtils.throwIf(sort.length < 2, "排序条件非法"); ValidationUtils.throwIf(sort.length < 2, "排序条件非法");
List<Sort.Order> orders = new ArrayList<>(sort.length); List<Sort.Order> orders = new ArrayList<>(sort.length);
if (CharSequenceUtil.contains(sort[0], StringConstants.COMMA)) { if (CharSequenceUtil.contains(sort[0], URLDecoder.decode(StringConstants.COMMA, StandardCharsets.UTF_8))) {
// e.g "sort=createTime,desc&sort=name,asc" // e.g "sort=createTime,desc&sort=name,asc"
for (String s : sort) { for (String s : sort) {
List<String> sortList = CharSequenceUtil.splitTrim(s, StringConstants.COMMA); List<String> sortList = CharSequenceUtil.splitTrim(s, StringConstants.COMMA);

View File

@@ -22,7 +22,7 @@
<!-- 数据访问模块 - MyBatis Flex --> <!-- 数据访问模块 - MyBatis Flex -->
<dependency> <dependency>
<groupId>top.continew</groupId> <groupId>top.continew</groupId>
<artifactId>continew-starter-data-mf</artifactId> <artifactId>continew-starter-data-mybatis-flex</artifactId>
</dependency> </dependency>
</dependencies> </dependencies>
</project> </project>

View File

@@ -20,6 +20,7 @@ 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;
import cn.hutool.core.lang.Opt;
import cn.hutool.core.lang.tree.Tree; import cn.hutool.core.lang.tree.Tree;
import cn.hutool.core.lang.tree.TreeNodeConfig; import cn.hutool.core.lang.tree.TreeNodeConfig;
import cn.hutool.core.text.CharSequenceUtil; import cn.hutool.core.text.CharSequenceUtil;
@@ -33,9 +34,9 @@ import org.springframework.transaction.annotation.Transactional;
import top.continew.starter.core.constant.StringConstants; import top.continew.starter.core.constant.StringConstants;
import top.continew.starter.core.util.ReflectUtils; import top.continew.starter.core.util.ReflectUtils;
import top.continew.starter.core.util.validate.ValidationUtils; import top.continew.starter.core.util.validate.ValidationUtils;
import top.continew.starter.data.mf.base.BaseMapper; import top.continew.starter.data.mybatis.flex.base.BaseMapper;
import top.continew.starter.data.mf.util.QueryWrapperHelper; import top.continew.starter.data.mybatis.flex.util.QueryWrapperHelper;
import top.continew.starter.data.mf.service.impl.ServiceImpl; import top.continew.starter.data.mybatis.flex.service.impl.ServiceImpl;
import top.continew.starter.extension.crud.annotation.TreeField; 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.PageQuery;
import top.continew.starter.extension.crud.model.query.SortQuery; import top.continew.starter.extension.crud.model.query.SortQuery;

View File

@@ -22,7 +22,7 @@
<!-- 数据访问模块 - MyBatis Plus --> <!-- 数据访问模块 - MyBatis Plus -->
<dependency> <dependency>
<groupId>top.continew</groupId> <groupId>top.continew</groupId>
<artifactId>continew-starter-data-mp</artifactId> <artifactId>continew-starter-data-mybatis-plus</artifactId>
</dependency> </dependency>
</dependencies> </dependencies>
</project> </project>

View File

@@ -37,9 +37,9 @@ import top.continew.starter.core.util.ClassUtils;
import top.continew.starter.core.util.ReflectUtils; import top.continew.starter.core.util.ReflectUtils;
import top.continew.starter.core.util.validate.CheckUtils; import top.continew.starter.core.util.validate.CheckUtils;
import top.continew.starter.core.util.validate.ValidationUtils; import top.continew.starter.core.util.validate.ValidationUtils;
import top.continew.starter.data.mp.base.BaseMapper; import top.continew.starter.data.mybatis.plus.base.BaseMapper;
import top.continew.starter.data.mp.service.impl.ServiceImpl; import top.continew.starter.data.mybatis.plus.service.impl.ServiceImpl;
import top.continew.starter.data.mp.util.QueryWrapperHelper; import top.continew.starter.data.mybatis.plus.util.QueryWrapperHelper;
import top.continew.starter.extension.crud.annotation.DictField; import top.continew.starter.extension.crud.annotation.DictField;
import top.continew.starter.extension.crud.annotation.TreeField; import top.continew.starter.extension.crud.annotation.TreeField;
import top.continew.starter.extension.crud.model.entity.BaseIdDO; import top.continew.starter.extension.crud.model.entity.BaseIdDO;

View File

@@ -18,6 +18,7 @@ package top.continew.starter.log.core.model;
import top.continew.starter.log.core.enums.Include; import top.continew.starter.log.core.enums.Include;
import java.time.Clock;
import java.time.Duration; import java.time.Duration;
import java.time.Instant; import java.time.Instant;
import java.util.Set; import java.util.Set;
@@ -77,7 +78,7 @@ public class LogRecord {
* @return 日志记录器 * @return 日志记录器
*/ */
public static Started start(RecordableHttpRequest request) { public static Started start(RecordableHttpRequest request) {
return start(Instant.now(), request); return start(Clock.systemUTC(), request);
} }
/** /**
@@ -87,7 +88,7 @@ public class LogRecord {
* @param request 请求信息 * @param request 请求信息
* @return 日志记录器 * @return 日志记录器
*/ */
public static Started start(Instant timestamp, RecordableHttpRequest request) { public static Started start(Clock timestamp, RecordableHttpRequest request) {
return new Started(timestamp, request); return new Started(timestamp, request);
} }
@@ -100,11 +101,22 @@ public class LogRecord {
private final RecordableHttpRequest request; private final RecordableHttpRequest request;
private Started(Instant timestamp, RecordableHttpRequest request) { private Started(Clock clock, RecordableHttpRequest request) {
this.timestamp = timestamp; this.timestamp = Instant.now(clock);
this.request = request; this.request = request;
} }
/**
* 结束日志记录
*
* @param response 响应信息
* @param includes 包含信息
* @return 日志记录
*/
public LogRecord finish(RecordableHttpResponse response, Set<Include> includes) {
return finish(Clock.systemUTC(), response, includes);
}
/** /**
* 结束日志记录 * 结束日志记录
* *
@@ -113,10 +125,10 @@ public class LogRecord {
* @param includes 包含信息 * @param includes 包含信息
* @return 日志记录 * @return 日志记录
*/ */
public LogRecord finish(Instant timestamp, RecordableHttpResponse response, Set<Include> includes) { public LogRecord finish(Clock clock, RecordableHttpResponse response, Set<Include> includes) {
LogRequest logRequest = new LogRequest(this.request, includes); LogRequest logRequest = new LogRequest(this.request, includes);
LogResponse logResponse = new LogResponse(response, includes); LogResponse logResponse = new LogResponse(response, includes);
Duration duration = Duration.between(this.timestamp, timestamp); Duration duration = Duration.between(this.timestamp, Instant.now(clock));
return new LogRecord(this.timestamp, logRequest, logResponse, duration); return new LogRecord(this.timestamp, logRequest, logResponse, duration);
} }
} }

View File

@@ -19,9 +19,9 @@ package top.continew.starter.log.interceptor.autoconfigure;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;
import top.continew.starter.core.constant.PropertiesConstants; import top.continew.starter.core.constant.PropertiesConstants;
import top.continew.starter.log.core.enums.Include; import top.continew.starter.log.core.enums.Include;
import top.continew.starter.web.util.SpringWebUtils;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
@@ -41,16 +41,13 @@ public class LogProperties {
/** /**
* 是否打印日志,开启后可打印访问日志(类似于 Nginx access log * 是否打印日志,开启后可打印访问日志(类似于 Nginx access log
* <p>
* 不记录日志也支持开启打印访问日志
* </p>
*/ */
private Boolean isPrint = false; private Boolean isPrint = false;
/** /**
* 包含信息 * 包含信息
*/ */
private Set<Include> includes = Include.defaultIncludes(); private Set<Include> includes = new HashSet<>(Include.defaultIncludes());
/** /**
* 放行路由 * 放行路由
@@ -88,14 +85,4 @@ public class LogProperties {
public void setExcludePatterns(List<String> excludePatterns) { public void setExcludePatterns(List<String> excludePatterns) {
this.excludePatterns = excludePatterns; this.excludePatterns = excludePatterns;
} }
/**
* 是否匹配放行路由
*
* @param uri 请求 URI
* @return 是否匹配
*/
public boolean isMatch(String uri) {
return this.getExcludePatterns().stream().anyMatch(pattern -> SpringWebUtils.isMatch(pattern, uri));
}
} }

View File

@@ -30,6 +30,7 @@ import org.springframework.web.util.ContentCachingResponseWrapper;
import org.springframework.web.util.WebUtils; import org.springframework.web.util.WebUtils;
import top.continew.starter.log.core.enums.Include; import top.continew.starter.log.core.enums.Include;
import top.continew.starter.log.interceptor.autoconfigure.LogProperties; import top.continew.starter.log.interceptor.autoconfigure.LogProperties;
import top.continew.starter.web.util.SpringWebUtils;
import java.io.IOException; import java.io.IOException;
import java.net.URI; import java.net.URI;
@@ -69,13 +70,12 @@ public class LogFilter extends OncePerRequestFilter implements Ordered {
filterChain.doFilter(request, response); filterChain.doFilter(request, response);
return; return;
} }
boolean isMatch = logProperties.isMatch(request.getRequestURI());
// 包装输入流,可重复读取 // 包装输入流,可重复读取
if (!isMatch && this.isRequestWrapper(request)) { if (this.isRequestWrapper(request)) {
request = new ContentCachingRequestWrapper(request); request = new ContentCachingRequestWrapper(request);
} }
// 包装输出流,可重复读取 // 包装输出流,可重复读取
boolean isResponseWrapper = !isMatch && this.isResponseWrapper(response); boolean isResponseWrapper = this.isResponseWrapper(response);
if (isResponseWrapper) { if (isResponseWrapper) {
response = new ContentCachingResponseWrapper(response); response = new ContentCachingResponseWrapper(response);
} }
@@ -98,7 +98,14 @@ public class LogFilter extends OncePerRequestFilter implements Ordered {
} }
// 不拦截 /error // 不拦截 /error
ServerProperties serverProperties = SpringUtil.getBean(ServerProperties.class); ServerProperties serverProperties = SpringUtil.getBean(ServerProperties.class);
return !request.getRequestURI().equals(serverProperties.getError().getPath()); if (request.getRequestURI().equals(serverProperties.getError().getPath())) {
return false;
}
// 放行
boolean isMatch = logProperties.getExcludePatterns()
.stream()
.anyMatch(pattern -> SpringWebUtils.match(pattern, request.getRequestURI()));
return !isMatch;
} }
/** /**

View File

@@ -32,11 +32,10 @@ import top.continew.starter.log.core.annotation.Log;
import top.continew.starter.log.core.dao.LogDao; import top.continew.starter.log.core.dao.LogDao;
import top.continew.starter.log.core.enums.Include; import top.continew.starter.log.core.enums.Include;
import top.continew.starter.log.core.model.LogRecord; import top.continew.starter.log.core.model.LogRecord;
import top.continew.starter.log.core.model.LogResponse;
import top.continew.starter.log.interceptor.autoconfigure.LogProperties; import top.continew.starter.log.interceptor.autoconfigure.LogProperties;
import java.time.Duration; import java.time.Clock;
import java.time.Instant;
import java.util.HashSet;
import java.util.Set; import java.util.Set;
/** /**
@@ -50,8 +49,7 @@ public class LogInterceptor implements HandlerInterceptor {
private static final Logger log = LoggerFactory.getLogger(LogInterceptor.class); private static final Logger log = LoggerFactory.getLogger(LogInterceptor.class);
private final LogDao logDao; private final LogDao logDao;
private final LogProperties logProperties; private final LogProperties logProperties;
private final TransmittableThreadLocal<Instant> timeTtl = new TransmittableThreadLocal<>(); private final TransmittableThreadLocal<LogRecord.Started> timestampTtl = new TransmittableThreadLocal<>();
private final TransmittableThreadLocal<LogRecord.Started> logTtl = new TransmittableThreadLocal<>();
public LogInterceptor(LogDao logDao, LogProperties logProperties) { public LogInterceptor(LogDao logDao, LogProperties logProperties) {
this.logDao = logDao; this.logDao = logDao;
@@ -62,14 +60,13 @@ public class LogInterceptor implements HandlerInterceptor {
public boolean preHandle(@NonNull HttpServletRequest request, public boolean preHandle(@NonNull HttpServletRequest request,
@NonNull HttpServletResponse response, @NonNull HttpServletResponse response,
@NonNull Object handler) { @NonNull Object handler) {
Instant startTime = Instant.now(); Clock timestamp = Clock.systemUTC();
if (this.isRequestRecord(handler)) {
if (Boolean.TRUE.equals(logProperties.getIsPrint())) { if (Boolean.TRUE.equals(logProperties.getIsPrint())) {
log.info("[{}] {}", request.getMethod(), request.getRequestURI()); log.info("[{}] {}", request.getMethod(), request.getRequestURI());
timeTtl.set(startTime);
} }
if (this.isRequestRecord(handler, request)) { LogRecord.Started startedLogRecord = LogRecord.start(timestamp, new RecordableServletHttpRequest(request));
LogRecord.Started startedLogRecord = LogRecord.start(startTime, new RecordableServletHttpRequest(request)); timestampTtl.set(startedLogRecord);
logTtl.set(startedLogRecord);
} }
return true; return true;
} }
@@ -79,23 +76,18 @@ public class LogInterceptor implements HandlerInterceptor {
@NonNull HttpServletResponse response, @NonNull HttpServletResponse response,
@NonNull Object handler, @NonNull Object handler,
Exception e) { Exception e) {
try { LogRecord.Started startedLogRecord = timestampTtl.get();
Instant endTime = Instant.now();
if (Boolean.TRUE.equals(logProperties.getIsPrint())) {
Duration timeTaken = Duration.between(timeTtl.get(), endTime);
log.info("[{}] {} {} {}ms", request.getMethod(), request.getRequestURI(), response
.getStatus(), timeTaken.toMillis());
}
LogRecord.Started startedLogRecord = logTtl.get();
if (null == startedLogRecord) { if (null == startedLogRecord) {
return; return;
} }
timestampTtl.remove();
try {
HandlerMethod handlerMethod = (HandlerMethod)handler; HandlerMethod handlerMethod = (HandlerMethod)handler;
Log methodLog = handlerMethod.getMethodAnnotation(Log.class); Log methodLog = handlerMethod.getMethodAnnotation(Log.class);
Log classLog = handlerMethod.getBeanType().getDeclaredAnnotation(Log.class); Log classLog = handlerMethod.getBeanType().getDeclaredAnnotation(Log.class);
Set<Include> includeSet = this.getIncludes(methodLog, classLog); Set<Include> includeSet = this.getIncludes(methodLog, classLog);
LogRecord finishedLogRecord = startedLogRecord LogRecord finishedLogRecord = startedLogRecord.finish(new RecordableServletHttpResponse(response, response
.finish(endTime, new RecordableServletHttpResponse(response, response.getStatus()), includeSet); .getStatus()), includeSet);
// 记录日志描述 // 记录日志描述
if (includeSet.contains(Include.DESCRIPTION)) { if (includeSet.contains(Include.DESCRIPTION)) {
this.logDescription(finishedLogRecord, methodLog, handlerMethod); this.logDescription(finishedLogRecord, methodLog, handlerMethod);
@@ -104,12 +96,14 @@ public class LogInterceptor implements HandlerInterceptor {
if (includeSet.contains(Include.MODULE)) { if (includeSet.contains(Include.MODULE)) {
this.logModule(finishedLogRecord, methodLog, classLog, handlerMethod); this.logModule(finishedLogRecord, methodLog, classLog, handlerMethod);
} }
if (Boolean.TRUE.equals(logProperties.getIsPrint())) {
LogResponse logResponse = finishedLogRecord.getResponse();
log.info("[{}] {} {} {}ms", request.getMethod(), request.getRequestURI(), logResponse
.getStatus(), finishedLogRecord.getTimeTaken().toMillis());
}
logDao.add(finishedLogRecord); logDao.add(finishedLogRecord);
} catch (Exception ex) { } catch (Exception ex) {
log.error("Logging http log occurred an error: {}.", ex.getMessage(), ex); log.error("Logging http log occurred an error: {}.", ex.getMessage(), ex);
} finally {
timeTtl.remove();
logTtl.remove();
} }
} }
@@ -121,7 +115,7 @@ public class LogInterceptor implements HandlerInterceptor {
* @return 日志包含信息 * @return 日志包含信息
*/ */
private Set<Include> getIncludes(Log methodLog, Log classLog) { private Set<Include> getIncludes(Log methodLog, Log classLog) {
Set<Include> includeSet = new HashSet<>(logProperties.getIncludes()); Set<Include> includeSet = logProperties.getIncludes();
if (null != classLog) { if (null != classLog) {
this.processInclude(includeSet, classLog); this.processInclude(includeSet, classLog);
} }
@@ -200,14 +194,10 @@ public class LogInterceptor implements HandlerInterceptor {
* @param handler 处理器 * @param handler 处理器
* @return true需要记录false不需要记录 * @return true需要记录false不需要记录
*/ */
private boolean isRequestRecord(Object handler, HttpServletRequest request) { private boolean isRequestRecord(Object handler) {
if (!(handler instanceof HandlerMethod handlerMethod)) { if (!(handler instanceof HandlerMethod handlerMethod)) {
return false; return false;
} }
// 如果接口匹配排除列表,不记录日志
if (logProperties.isMatch(request.getRequestURI())) {
return false;
}
// 如果接口被隐藏,不记录日志 // 如果接口被隐藏,不记录日志
Operation methodOperation = handlerMethod.getMethodAnnotation(Operation.class); Operation methodOperation = handlerMethod.getMethodAnnotation(Operation.class);
if (null != methodOperation && methodOperation.hidden()) { if (null != methodOperation && methodOperation.hidden()) {

View File

@@ -22,7 +22,7 @@
<!-- MyBatis PlusMyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,简化开发、提高效率) --> <!-- MyBatis PlusMyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,简化开发、提高效率) -->
<dependency> <dependency>
<groupId>com.baomidou</groupId> <groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-extension</artifactId> <artifactId>mybatis-plus-core</artifactId>
</dependency> </dependency>
</dependencies> </dependencies>
</project> </project>

View File

@@ -50,7 +50,7 @@ public class CryptoAutoConfiguration {
* MyBatis 加密拦截器配置 * MyBatis 加密拦截器配置
*/ */
@Bean @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean(MyBatisEncryptInterceptor.class)
public MyBatisEncryptInterceptor myBatisEncryptInterceptor() { public MyBatisEncryptInterceptor myBatisEncryptInterceptor() {
return new MyBatisEncryptInterceptor(properties); return new MyBatisEncryptInterceptor(properties);
} }

View File

@@ -16,13 +16,15 @@
package top.continew.starter.security.crypto.core; package top.continew.starter.security.crypto.core;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.text.CharSequenceUtil; import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ReflectUtil; import cn.hutool.core.util.ReflectUtil;
import cn.hutool.extra.spring.SpringUtil; import cn.hutool.extra.spring.SpringUtil;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.plugin.*;
import top.continew.starter.core.constant.StringConstants; import top.continew.starter.core.constant.StringConstants;
import top.continew.starter.core.exception.BaseException; import top.continew.starter.core.exception.BusinessException;
import top.continew.starter.security.crypto.annotation.FieldEncrypt; import top.continew.starter.security.crypto.annotation.FieldEncrypt;
import top.continew.starter.security.crypto.encryptor.IEncryptor; import top.continew.starter.security.crypto.encryptor.IEncryptor;
import top.continew.starter.security.crypto.enums.Algorithm; import top.continew.starter.security.crypto.enums.Algorithm;
@@ -40,100 +42,30 @@ import java.util.stream.Stream;
* @author Charles7c * @author Charles7c
* @since 1.4.0 * @since 1.4.0
*/ */
public abstract class AbstractMyBatisInterceptor { public abstract class AbstractMyBatisInterceptor implements Interceptor {
private static final Map<Class<?>, List<Field>> CLASS_FIELD_CACHE = new ConcurrentHashMap<>();
private static final Map<String, Map<String, FieldEncrypt>> ENCRYPT_PARAM_CACHE = new ConcurrentHashMap<>(); private static final Map<String, Map<String, FieldEncrypt>> ENCRYPT_PARAM_CACHE = new ConcurrentHashMap<>();
/** /**
* 获取所有字符串类型、需要加/解密的、有值字段 * 获取加密参数
* *
* @param obj 对象 * @param mappedStatementId 映射语句 ID
* @return 字段列表 * @return 加密参数
*/ */
protected List<Field> getEncryptFields(Object obj) { public Map<String, FieldEncrypt> getEncryptParams(String mappedStatementId) {
if (null == obj) { return getEncryptParams(mappedStatementId, null);
return Collections.emptyList();
}
return this.getEncryptFields(obj.getClass());
}
/**
* 获取所有字符串类型、需要加/解密的、有值字段
*
* @param clazz 类型对象
* @return 字段列表
*/
protected List<Field> getEncryptFields(Class<?> clazz) {
return CLASS_FIELD_CACHE.computeIfAbsent(clazz, key -> Arrays.stream(ReflectUtil.getFields(clazz))
.filter(field -> String.class.equals(field.getType()))
.filter(field -> null != field.getAnnotation(FieldEncrypt.class))
.toList());
}
/**
* 获取字段加/解密处理器
*
* @param fieldEncrypt 字段加密注解
* @return 加/解密处理器
*/
protected IEncryptor getEncryptor(FieldEncrypt fieldEncrypt) {
Class<? extends IEncryptor> encryptorClass = fieldEncrypt.encryptor();
// 使用预定义加/解密处理器
if (encryptorClass == IEncryptor.class) {
Algorithm algorithm = fieldEncrypt.value();
return ReflectUtil.newInstance(algorithm.getEncryptor());
}
// 使用自定义加/解密处理器
return SpringUtil.getBean(encryptorClass);
} }
/** /**
* 获取加密参数 * 获取加密参数
* *
* @param mappedStatement 映射语句
* @return 获取加密参数
*/
protected Map<String, FieldEncrypt> getEncryptParameters(MappedStatement mappedStatement) {
String mappedStatementId = mappedStatement.getId();
return ENCRYPT_PARAM_CACHE.computeIfAbsent(mappedStatementId, key -> {
Method method = this.getMethod(mappedStatementId);
if (null == method) {
return Collections.emptyMap();
}
Map<String, FieldEncrypt> encryptMap = new HashMap<>();
Parameter[] parameters = method.getParameters();
for (int i = 0; i < parameters.length; i++) {
Parameter parameter = parameters[i];
FieldEncrypt fieldEncrypt = parameter.getAnnotation(FieldEncrypt.class);
if (null == fieldEncrypt) {
continue;
}
String parameterName = this.getParameterName(parameter);
encryptMap.put(parameterName, fieldEncrypt);
if (String.class.equals(parameter.getType())) {
encryptMap.put("param" + (i + 1), fieldEncrypt);
}
}
return encryptMap;
});
}
/**
* 获取映射方法
*
* @param mappedStatementId 映射语句 ID * @param mappedStatementId 映射语句 ID
* @return 映射方法 * @param parameterIndex 参数索引
* @return 加密参数
*/ */
private Method getMethod(String mappedStatementId) { public Map<String, FieldEncrypt> getEncryptParams(String mappedStatementId, Integer parameterIndex) {
String className = CharSequenceUtil.subBefore(mappedStatementId, StringConstants.DOT, true); return ENCRYPT_PARAM_CACHE
String methodName = CharSequenceUtil.subAfter(mappedStatementId, StringConstants.DOT, true); .computeIfAbsent(mappedStatementId, key -> getEncryptParamsNoCached(mappedStatementId, parameterIndex));
try {
Method[] methods = ReflectUtil.getMethods(Class.forName(className));
return Stream.of(methods).filter(method -> method.getName().equals(methodName)).findFirst().orElse(null);
} catch (ClassNotFoundException e) {
throw new BaseException(e);
}
} }
/** /**
@@ -146,4 +78,88 @@ public abstract class AbstractMyBatisInterceptor {
Param param = parameter.getAnnotation(Param.class); Param param = parameter.getAnnotation(Param.class);
return null != param ? param.value() : parameter.getName(); return null != param ? param.value() : parameter.getName();
} }
/**
* 获取所有字符串类型、需要加/解密的、有值字段
*
* @param obj 对象
* @return 字段列表
*/
public List<Field> getEncryptFields(Object obj) {
if (null == obj) {
return Collections.emptyList();
}
return Arrays.stream(ReflectUtil.getFields(obj.getClass()))
.filter(field -> String.class.equals(field.getType()))
.filter(field -> null != field.getAnnotation(FieldEncrypt.class))
.filter(field -> null != ReflectUtil.getFieldValue(obj, field))
.toList();
}
/**
* 获取字段加/解密处理器
*
* @param fieldEncrypt 字段加密注解
* @return 加/解密处理器
*/
public IEncryptor getEncryptor(FieldEncrypt fieldEncrypt) {
Class<? extends IEncryptor> encryptorClass = fieldEncrypt.encryptor();
// 使用预定义加/解密处理器
if (encryptorClass == IEncryptor.class) {
Algorithm algorithm = fieldEncrypt.value();
return ReflectUtil.newInstance(algorithm.getEncryptor());
}
// 使用自定义加/解密处理器
return SpringUtil.getBean(encryptorClass);
}
/**
* 获取参数列表(无缓存)
*
* @param mappedStatementId 映射语句 ID
* @param parameterIndex 参数数量
* @return 参数列表
*/
private Map<String, FieldEncrypt> getEncryptParamsNoCached(String mappedStatementId, Integer parameterIndex) {
try {
String className = CharSequenceUtil.subBefore(mappedStatementId, StringConstants.DOT, true);
String wrapperMethodName = CharSequenceUtil.subAfter(mappedStatementId, StringConstants.DOT, true);
String methodName = Stream.of("_mpCount", "_COUNT")
.filter(wrapperMethodName::endsWith)
.findFirst()
.map(suffix -> wrapperMethodName.substring(0, wrapperMethodName.length() - suffix.length()))
.orElse(wrapperMethodName);
// 获取真实方法
Optional<Method> methodOptional = Arrays.stream(ReflectUtil.getMethods(Class.forName(className), m -> {
if (Objects.nonNull(parameterIndex)) {
return Objects.equals(m.getName(), methodName) && m.getParameterCount() == parameterIndex;
}
return Objects.equals(m.getName(), methodName);
})).findFirst();
if (methodOptional.isEmpty()) {
return Collections.emptyMap();
}
// 获取方法中的加密参数
Map<String, FieldEncrypt> map = MapUtil.newHashMap();
Parameter[] parameterArr = methodOptional.get().getParameters();
for (int i = 0; i < parameterArr.length; i++) {
Parameter parameter = parameterArr[i];
String parameterName = this.getParameterName(parameter);
FieldEncrypt fieldEncrypt = parameter.getAnnotation(FieldEncrypt.class);
if (null != fieldEncrypt) {
map.put(parameterName, fieldEncrypt);
if (String.class.equals(parameter.getType())) {
map.put("param" + (i + 1), fieldEncrypt);
}
} else if (parameterName.startsWith(Constants.ENTITY)) {
map.put(parameterName, null);
} else if (parameterName.startsWith(Constants.WRAPPER)) {
map.put(parameterName, null);
}
}
return map;
} catch (ClassNotFoundException e) {
throw new BusinessException(e.getMessage());
}
}
} }

View File

@@ -36,7 +36,7 @@ import java.util.List;
* @since 1.4.0 * @since 1.4.0
*/ */
@Intercepts({@Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})}) @Intercepts({@Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})})
public class MyBatisDecryptInterceptor extends AbstractMyBatisInterceptor implements Interceptor { public class MyBatisDecryptInterceptor extends AbstractMyBatisInterceptor {
private CryptoProperties properties; private CryptoProperties properties;
@@ -65,9 +65,6 @@ public class MyBatisDecryptInterceptor extends AbstractMyBatisInterceptor implem
for (Field field : fieldList) { for (Field field : fieldList) {
IEncryptor encryptor = super.getEncryptor(field.getAnnotation(FieldEncrypt.class)); IEncryptor encryptor = super.getEncryptor(field.getAnnotation(FieldEncrypt.class));
Object fieldValue = ReflectUtil.getFieldValue(result, field); Object fieldValue = ReflectUtil.getFieldValue(result, field);
if (null == fieldValue) {
continue;
}
// 优先获取自定义对称加密算法密钥,获取不到时再获取全局配置 // 优先获取自定义对称加密算法密钥,获取不到时再获取全局配置
String password = ObjectUtil.defaultIfBlank(field.getAnnotation(FieldEncrypt.class) String password = ObjectUtil.defaultIfBlank(field.getAnnotation(FieldEncrypt.class)
.password(), properties.getPassword()); .password(), properties.getPassword());

View File

@@ -17,27 +17,33 @@
package top.continew.starter.security.crypto.core; package top.continew.starter.security.crypto.core;
import cn.hutool.core.text.CharSequenceUtil; import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.ReflectUtil; import cn.hutool.core.util.ReflectUtil;
import com.baomidou.mybatisplus.core.conditions.AbstractWrapper; import com.baomidou.mybatisplus.core.conditions.AbstractWrapper;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.TableFieldInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.baomidou.mybatisplus.core.toolkit.Constants; import com.baomidou.mybatisplus.core.toolkit.Constants;
import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor; import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor; import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql; import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler; import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds; import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.type.SimpleTypeRegistry;
import top.continew.starter.core.constant.StringConstants; import top.continew.starter.core.constant.StringConstants;
import top.continew.starter.core.exception.BaseException;
import top.continew.starter.security.crypto.annotation.FieldEncrypt; import top.continew.starter.security.crypto.annotation.FieldEncrypt;
import top.continew.starter.security.crypto.autoconfigure.CryptoProperties; import top.continew.starter.security.crypto.autoconfigure.CryptoProperties;
import top.continew.starter.security.crypto.encryptor.IEncryptor; import top.continew.starter.security.crypto.encryptor.IEncryptor;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*; import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/** /**
* 字段加密拦截器 * 字段加密拦截器
@@ -45,43 +51,54 @@ import java.util.regex.Pattern;
* @author Charles7c * @author Charles7c
* @since 1.4.0 * @since 1.4.0
*/ */
public class MyBatisEncryptInterceptor extends AbstractMyBatisInterceptor implements InnerInterceptor { @Intercepts({@Signature(method = "update", type = Executor.class, args = {MappedStatement.class, Object.class}),
@Signature(method = "query", type = Executor.class, args = {MappedStatement.class, Object.class, RowBounds.class,
ResultHandler.class, CacheKey.class, BoundSql.class}),
@Signature(method = "query", type = Executor.class, args = {MappedStatement.class, Object.class, RowBounds.class,
ResultHandler.class})})
public class MyBatisEncryptInterceptor extends AbstractMyBatisInterceptor {
private static final Pattern PARAM_PAIRS_PATTERN = Pattern private CryptoProperties properties;
.compile("#\\{ew\\.paramNameValuePairs\\.(" + Constants.WRAPPER_PARAM + "\\d+)\\}");
private final CryptoProperties properties;
public MyBatisEncryptInterceptor(CryptoProperties properties) { public MyBatisEncryptInterceptor(CryptoProperties properties) {
this.properties = properties; this.properties = properties;
} }
@Override public MyBatisEncryptInterceptor() {
public void beforeQuery(Executor executor,
MappedStatement mappedStatement,
Object parameterObject,
RowBounds rowBounds,
ResultHandler resultHandler,
BoundSql boundSql) {
if (null == parameterObject) {
return;
}
if (parameterObject instanceof Map parameterMap) {
this.encryptQueryParameter(parameterMap, mappedStatement);
}
} }
@Override @Override
public void beforeUpdate(Executor executor, MappedStatement mappedStatement, Object parameterObject) { public Object intercept(Invocation invocation) throws Throwable {
if (null == parameterObject) { Object[] args = invocation.getArgs();
return; MappedStatement mappedStatement = (MappedStatement)args[0];
Object parameter = args[1];
if (!this.isEncryptRequired(parameter, mappedStatement.getSqlCommandType())) {
return invocation.proceed();
} }
if (parameterObject instanceof Map parameterMap) { // 使用 @Param 注解的场景
// 带别名方法(使用 @Param 注解的场景) if (parameter instanceof HashMap parameterMap) {
this.encryptMap(parameterMap, mappedStatement); this.encryptMap(parameterMap, mappedStatement);
} else { } else {
// 无别名方法例如MP insert 等方法) this.doEncrypt(this.getEncryptFields(parameter), parameter);
this.encryptEntity(super.getEncryptFields(parameterObject), parameterObject);
} }
return invocation.proceed();
}
/**
* 是否需要加密处理
*
* @param parameter 参数
* @param sqlCommandType SQL 类型
* @return truefalse
*/
private boolean isEncryptRequired(Object parameter, SqlCommandType sqlCommandType) {
if (ObjectUtil.isEmpty(parameter)) {
return false;
}
if (!(SqlCommandType.UPDATE == sqlCommandType || SqlCommandType.INSERT == sqlCommandType || SqlCommandType.SELECT == sqlCommandType)) {
return false;
}
return !SimpleTypeRegistry.isSimpleType(parameter.getClass());
} }
/** /**
@@ -89,132 +106,141 @@ public class MyBatisEncryptInterceptor extends AbstractMyBatisInterceptor implem
* *
* @param parameterMap 参数 * @param parameterMap 参数
* @param mappedStatement 映射语句 * @param mappedStatement 映射语句
* @throws Exception /
*/ */
private void encryptMap(Map<String, Object> parameterMap, MappedStatement mappedStatement) { private void encryptMap(HashMap<String, Object> parameterMap, MappedStatement mappedStatement) throws Exception {
Object parameter; Map<String, FieldEncrypt> encryptParamMap = super.getEncryptParams(mappedStatement.getId(), parameterMap
// 别名带有 et针对 MP 的 updateById、update 等方法) .isEmpty() ? null : parameterMap.size() / 2);
if (parameterMap.containsKey(Constants.ENTITY) && null != (parameter = parameterMap.get(Constants.ENTITY))) { for (Map.Entry<String, FieldEncrypt> encryptParamEntry : encryptParamMap.entrySet()) {
this.encryptEntity(super.getEncryptFields(parameter), parameter); String parameterName = encryptParamEntry.getKey();
} if (parameterName.startsWith(Constants.ENTITY)) {
// 别名带有 ew针对 MP UpdateWrapper、LambdaUpdateWrapper 等参数) // 兼容 MyBatis Plus 封装update 相关方法updateById、update
if (parameterMap.containsKey(Constants.WRAPPER) && null != (parameter = parameterMap.get(Constants.WRAPPER))) { Object entity = parameterMap.getOrDefault(parameterName, null);
this.encryptUpdateWrapper(parameter, mappedStatement); this.doEncrypt(this.getEncryptFields(entity), entity);
} } else if (parameterName.startsWith(Constants.WRAPPER)) {
} // 处理参数为 Wrapper 的情况
Wrapper wrapper = (Wrapper)parameterMap.getOrDefault(parameterName, null);
/** this.doEncrypt(wrapper, mappedStatement);
* 加密查询参数(针对 Map 类型参数)
*
* @param parameterMap 参数
* @param mappedStatement 映射语句
*/
private void encryptQueryParameter(Map<String, Object> parameterMap, MappedStatement mappedStatement) {
Map<String, FieldEncrypt> encryptParameterMap = super.getEncryptParameters(mappedStatement);
for (Map.Entry<String, Object> parameterEntrySet : parameterMap.entrySet()) {
String parameterName = parameterEntrySet.getKey();
Object parameterValue = parameterEntrySet.getValue();
if (null == parameterValue || ClassUtil.isBasicType(parameterValue
.getClass()) || parameterValue instanceof AbstractWrapper) {
continue;
}
if (parameterValue instanceof String str) {
FieldEncrypt fieldEncrypt = encryptParameterMap.get(parameterName);
if (null != fieldEncrypt) {
parameterMap.put(parameterName, this.doEncrypt(str, fieldEncrypt));
}
} else { } else {
// 实体参数 FieldEncrypt fieldEncrypt = encryptParamEntry.getValue();
this.encryptEntity(super.getEncryptFields(parameterValue), parameterValue); parameterMap.put(parameterName, this.doEncrypt(parameterMap.get(parameterName), fieldEncrypt));
} }
} }
} }
/** /**
* 处理 UpdateWrapper 类型参数加密(针对 MP 的 UpdateWrapper、LambdaUpdateWrapper 等参数) * 处理加密
*
* @param parameter Wrapper 参数
* @param mappedStatement 映射语句
* @since 2.1.1
* @author cary
* @author wangshaopeng@talkweb.com.cn<a
* href="https://blog.csdn.net/tianmaxingkonger/article/details/130986784">基于Mybatis-Plus拦截器实现MySQL数据加解密</a>
*/
private void encryptUpdateWrapper(Object parameter, MappedStatement mappedStatement) {
if (parameter instanceof AbstractWrapper updateWrapper) {
String sqlSet = updateWrapper.getSqlSet();
if (CharSequenceUtil.isBlank(sqlSet)) {
return;
}
// 将 name=#{ew.paramNameValuePairs.xxx},age=#{ew.paramNameValuePairs.xxx} 切出来
String[] elArr = sqlSet.split(StringConstants.COMMA);
Map<String, String> propMap = new HashMap<>(elArr.length);
Arrays.stream(elArr).forEach(el -> {
String[] elPart = el.split(StringConstants.EQUALS);
propMap.put(elPart[0], elPart[1]);
});
// 获取加密字段
Class<?> entityClass = mappedStatement.getParameterMap().getType();
List<Field> encryptFieldList = super.getEncryptFields(entityClass);
for (Field field : encryptFieldList) {
FieldEncrypt fieldEncrypt = field.getAnnotation(FieldEncrypt.class);
String el = propMap.get(field.getName());
if (CharSequenceUtil.isBlank(el)) {
continue;
}
Matcher matcher = PARAM_PAIRS_PATTERN.matcher(el);
if (matcher.matches()) {
String valueKey = matcher.group(1);
Object value = updateWrapper.getParamNameValuePairs().get(valueKey);
Object ciphertext = this.doEncrypt(value, fieldEncrypt);
updateWrapper.getParamNameValuePairs().put(valueKey, ciphertext);
}
}
}
}
/**
* 处理实体加密
* *
* @param fieldList 加密字段列表 * @param fieldList 加密字段列表
* @param entity 实体 * @param entity 实体
* @throws Exception /
*/ */
private void encryptEntity(List<Field> fieldList, Object entity) { private void doEncrypt(List<Field> fieldList, Object entity) throws Exception {
for (Field field : fieldList) { for (Field field : fieldList) {
IEncryptor encryptor = super.getEncryptor(field.getAnnotation(FieldEncrypt.class)); IEncryptor encryptor = super.getEncryptor(field.getAnnotation(FieldEncrypt.class));
Object fieldValue = ReflectUtil.getFieldValue(entity, field); Object fieldValue = ReflectUtil.getFieldValue(entity, field);
if (null == fieldValue) {
continue;
}
// 优先获取自定义对称加密算法密钥,获取不到时再获取全局配置 // 优先获取自定义对称加密算法密钥,获取不到时再获取全局配置
String password = ObjectUtil.defaultIfBlank(field.getAnnotation(FieldEncrypt.class).password(), properties String password = ObjectUtil.defaultIfBlank(field.getAnnotation(FieldEncrypt.class).password(), properties
.getPassword()); .getPassword());
String ciphertext; String ciphertext = encryptor.encrypt(fieldValue.toString(), password, properties.getPublicKey());
try {
ciphertext = encryptor.encrypt(fieldValue.toString(), password, properties.getPublicKey());
} catch (Exception e) {
throw new BaseException(e);
}
ReflectUtil.setFieldValue(entity, field, ciphertext); ReflectUtil.setFieldValue(entity, field, ciphertext);
} }
} }
/**
* 处理 Wrapper 加密
*
* @param wrapper Wrapper 对象
* @param mappedStatement 映射语句
* @throws Exception /
*/
private void doEncrypt(Wrapper wrapper, MappedStatement mappedStatement) throws Exception {
if (wrapper instanceof AbstractWrapper abstractWrapper) {
String sqlSet = abstractWrapper.getSqlSet();
if (CharSequenceUtil.isEmpty(sqlSet)) {
return;
}
String className = CharSequenceUtil.subBefore(mappedStatement.getId(), StringConstants.DOT, true);
Class<?> mapperClass = Class.forName(className);
Optional<Class> baseMapperGenerics = getEntityTypeByMapperClass(mapperClass, Optional.empty());
// 获取不到泛型对象 则不进行下面的逻辑
if (baseMapperGenerics.isEmpty()) {
return;
}
TableInfo tableInfo = TableInfoHelper.getTableInfo(baseMapperGenerics.get());
List<TableFieldInfo> fieldList = tableInfo.getFieldList();
// 将 name=#{ew.paramNameValuePairs.xxx},age=#{ew.paramNameValuePairs.xxx} 切出来
for (String sqlFragment : sqlSet.split(Constants.COMMA)) {
String columnName = sqlFragment.split(Constants.EQUALS)[0];
// 截取其中的 xxx 字符,例如:#{ew.paramNameValuePairs.xxx}
String paramNameVal = sqlFragment.split(Constants.EQUALS)[1].substring(25, sqlFragment
.split(Constants.EQUALS)[1].length() - 1);
Optional<TableFieldInfo> fieldInfo = fieldList.stream()
.filter(f -> f.getColumn().equals(columnName))
.findAny();
if (fieldInfo.isPresent()) {
TableFieldInfo tableFieldInfo = fieldInfo.get();
FieldEncrypt fieldEncrypt = tableFieldInfo.getField().getAnnotation(FieldEncrypt.class);
if (fieldEncrypt != null) {
Map<String, Object> paramNameValuePairs = abstractWrapper.getParamNameValuePairs();
paramNameValuePairs.put(paramNameVal, this.doEncrypt(paramNameValuePairs
.get(paramNameVal), fieldEncrypt));
}
}
}
}
}
/** /**
* 处理加密 * 处理加密
* *
* @param parameterValue 参数值 * @param parameterValue 参数值
* @param fieldEncrypt 字段加密注解 * @param fieldEncrypt 字段加密注解
* @throws Exception /
*/ */
private Object doEncrypt(Object parameterValue, FieldEncrypt fieldEncrypt) { private Object doEncrypt(Object parameterValue, FieldEncrypt fieldEncrypt) throws Exception {
if (null == parameterValue) { if (null == parameterValue) {
return null; return null;
} }
IEncryptor encryptor = super.getEncryptor(fieldEncrypt); IEncryptor encryptor = super.getEncryptor(fieldEncrypt);
// 优先获取自定义对称加密算法密钥,获取不到时再获取全局配置 // 优先获取自定义对称加密算法密钥,获取不到时再获取全局配置
String password = ObjectUtil.defaultIfBlank(fieldEncrypt.password(), properties.getPassword()); String password = ObjectUtil.defaultIfBlank(fieldEncrypt.password(), properties.getPassword());
try {
return encryptor.encrypt(parameterValue.toString(), password, properties.getPublicKey()); return encryptor.encrypt(parameterValue.toString(), password, properties.getPublicKey());
} catch (Exception e) {
throw new BaseException(e);
} }
/**
* 从 Mapper 获取泛型
*
* @param mapperClass Mapper class
* @param tempResult 临时存储的泛型对象
* @return 泛型
*/
private static Optional<Class> getEntityTypeByMapperClass(Class<?> mapperClass, Optional<Class> tempResult) {
Type[] genericInterfaces = mapperClass.getGenericInterfaces();
Optional<Class> result = tempResult;
for (Type genericInterface : genericInterfaces) {
if (genericInterface instanceof ParameterizedType parameterizedType) {
Type rawType = parameterizedType.getRawType();
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
// 如果匹配上 BaseMapper 且泛型参数是 Class 类型,则直接返回
if (rawType.equals(BaseMapper.class)) {
return actualTypeArguments[0] instanceof Class
? Optional.of((Class)actualTypeArguments[0])
: result;
} else if (rawType instanceof Class interfaceClass) {
// 如果泛型参数是 Class 类型,则传递给递归调用
if (actualTypeArguments[0] instanceof Class tempResultClass) {
result = Optional.of(tempResultClass);
}
// 递归调用,继续查找
Optional<Class> innerResult = getEntityTypeByMapperClass(interfaceClass, result);
if (innerResult.isPresent()) {
return innerResult;
}
}
}
}
// 如果没有找到,返回传递进来的 tempResult
return Optional.empty();
} }
} }

View File

@@ -1,72 +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.web.autoconfigure.response;
import cn.hutool.core.util.ClassUtil;
import org.apache.commons.lang3.reflect.TypeUtils;
import org.springdoc.core.parsers.ReturnTypeParser;
import org.springframework.core.MethodParameter;
import top.continew.starter.apidoc.util.DocUtils;
import top.continew.starter.web.model.R;
import java.lang.reflect.Type;
/**
* SpringDoc 全局响应处理器
* <p>
* 接口文档全局添加响应格式 {@link R}
* </p>
*
* @author echo
* @since 2.5.2
*/
public class ApiDocGlobalResponseHandler implements ReturnTypeParser {
private final GlobalResponseProperties globalResponseProperties;
private final Class<Object> responseClass;
public ApiDocGlobalResponseHandler(GlobalResponseProperties globalResponseProperties) {
this.globalResponseProperties = globalResponseProperties;
this.responseClass = ClassUtil.loadClass(globalResponseProperties.getResponseClassFullName());
}
/**
* 获取返回类型
*
* @param methodParameter 方法参数
* @return {@link Type }
*/
@Override
public Type getReturnType(MethodParameter methodParameter) {
// 获取返回类型
Type returnType = ReturnTypeParser.super.getReturnType(methodParameter);
// 判断是否具有 RestController 注解
if (!DocUtils.hasRestControllerAnnotation(methodParameter.getContainingClass())) {
return returnType;
}
// 如果为响应类型,则直接返回
if (returnType.getTypeName().contains(globalResponseProperties.getResponseClassFullName())) {
return returnType;
}
// 如果是 void类型则返回 R<Void>
if (returnType == void.class || returnType == Void.class) {
return TypeUtils.parameterize(responseClass, Void.class);
}
// 返回 R<T>
return TypeUtils.parameterize(responseClass, returnType);
}
}

View File

@@ -1,47 +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.web.autoconfigure.response;
import com.feiniaojin.gracefulresponse.advice.lifecycle.exception.BeforeControllerAdviceProcess;
import jakarta.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import top.continew.starter.web.util.SpringWebUtils;
/**
* 默认回调处理器实现
*
* @author Charles7c
* @since 2.6.0
*/
public class DefaultBeforeControllerAdviceProcessImpl implements BeforeControllerAdviceProcess {
private final Logger log = LoggerFactory.getLogger(DefaultBeforeControllerAdviceProcessImpl.class);
private final GlobalResponseProperties globalResponseProperties;
public DefaultBeforeControllerAdviceProcessImpl(GlobalResponseProperties globalResponseProperties) {
this.globalResponseProperties = globalResponseProperties;
}
@Override
public void call(Throwable throwable) {
if (globalResponseProperties.isPrintExceptionInGlobalAdvice()) {
HttpServletRequest request = SpringWebUtils.getRequest();
log.error("[{}] {}", request.getMethod(), request.getRequestURI(), throwable);
}
}
}

View File

@@ -18,9 +18,6 @@ package top.continew.starter.web.autoconfigure.response;
import com.feiniaojin.gracefulresponse.ExceptionAliasRegister; import com.feiniaojin.gracefulresponse.ExceptionAliasRegister;
import com.feiniaojin.gracefulresponse.advice.*; import com.feiniaojin.gracefulresponse.advice.*;
import com.feiniaojin.gracefulresponse.advice.lifecycle.exception.BeforeControllerAdviceProcess;
import com.feiniaojin.gracefulresponse.advice.lifecycle.exception.ControllerAdvicePredicate;
import com.feiniaojin.gracefulresponse.advice.lifecycle.response.ResponseBodyAdvicePredicate;
import com.feiniaojin.gracefulresponse.api.ResponseFactory; import com.feiniaojin.gracefulresponse.api.ResponseFactory;
import com.feiniaojin.gracefulresponse.api.ResponseStatusFactory; import com.feiniaojin.gracefulresponse.api.ResponseStatusFactory;
import com.feiniaojin.gracefulresponse.defaults.DefaultResponseFactory; import com.feiniaojin.gracefulresponse.defaults.DefaultResponseFactory;
@@ -40,7 +37,6 @@ import top.continew.starter.core.constant.PropertiesConstants;
import top.continew.starter.core.util.GeneralPropertySourceFactory; import top.continew.starter.core.util.GeneralPropertySourceFactory;
import java.util.Locale; import java.util.Locale;
import java.util.concurrent.CopyOnWriteArrayList;
/** /**
* 全局响应自动配置 * 全局响应自动配置
@@ -54,10 +50,23 @@ import java.util.concurrent.CopyOnWriteArrayList;
public class GlobalResponseAutoConfiguration { public class GlobalResponseAutoConfiguration {
private static final Logger log = LoggerFactory.getLogger(GlobalResponseAutoConfiguration.class); private static final Logger log = LoggerFactory.getLogger(GlobalResponseAutoConfiguration.class);
private final GlobalResponseProperties globalResponseProperties;
public GlobalResponseAutoConfiguration(GlobalResponseProperties globalResponseProperties) { /**
this.globalResponseProperties = globalResponseProperties; * 全局异常处理
*/
@Bean
@ConditionalOnMissingBean
public GrGlobalExceptionAdvice globalExceptionAdvice() {
return new GrGlobalExceptionAdvice();
}
/**
* 全局校验异常处理
*/
@Bean
@ConditionalOnMissingBean
public GrValidationExceptionAdvice validationExceptionAdvice() {
return new GrValidationExceptionAdvice();
} }
/** /**
@@ -65,13 +74,8 @@ public class GlobalResponseAutoConfiguration {
*/ */
@Bean @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean
public GrNotVoidResponseBodyAdvice grNotVoidResponseBodyAdvice() { public GrNotVoidResponseBodyAdvice notVoidResponseBodyAdvice() {
GrNotVoidResponseBodyAdvice notVoidResponseBodyAdvice = new GrNotVoidResponseBodyAdvice(); return new GrNotVoidResponseBodyAdvice();
CopyOnWriteArrayList<ResponseBodyAdvicePredicate> copyOnWriteArrayList = new CopyOnWriteArrayList<>();
copyOnWriteArrayList.add(notVoidResponseBodyAdvice);
notVoidResponseBodyAdvice.setPredicates(copyOnWriteArrayList);
notVoidResponseBodyAdvice.setResponseBodyAdviceProcessor(notVoidResponseBodyAdvice);
return notVoidResponseBodyAdvice;
} }
/** /**
@@ -79,104 +83,8 @@ public class GlobalResponseAutoConfiguration {
*/ */
@Bean @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean
public GrVoidResponseBodyAdvice grVoidResponseBodyAdvice() { public GrVoidResponseBodyAdvice voidResponseBodyAdvice() {
GrVoidResponseBodyAdvice voidResponseBodyAdvice = new GrVoidResponseBodyAdvice(); return new GrVoidResponseBodyAdvice();
CopyOnWriteArrayList<ResponseBodyAdvicePredicate> copyOnWriteArrayList = new CopyOnWriteArrayList<>();
copyOnWriteArrayList.add(voidResponseBodyAdvice);
voidResponseBodyAdvice.setPredicates(copyOnWriteArrayList);
voidResponseBodyAdvice.setResponseBodyAdviceProcessor(voidResponseBodyAdvice);
return voidResponseBodyAdvice;
}
/**
* 处理前回调(目前仅打印异常日志)
*/
@Bean
@ConditionalOnMissingBean
public BeforeControllerAdviceProcess beforeControllerAdviceProcess() {
return new DefaultBeforeControllerAdviceProcessImpl(globalResponseProperties);
}
/**
* 框架异常处理器
*/
@Bean
public FrameworkExceptionAdvice frameworkExceptionAdvice(BeforeControllerAdviceProcess beforeControllerAdviceProcess) {
FrameworkExceptionAdvice frameworkExceptionAdvice = new FrameworkExceptionAdvice();
frameworkExceptionAdvice.setRejectStrategy(new DefaultRejectStrategyImpl());
frameworkExceptionAdvice.setControllerAdviceProcessor(frameworkExceptionAdvice);
frameworkExceptionAdvice.setBeforeControllerAdviceProcess(beforeControllerAdviceProcess);
frameworkExceptionAdvice.setControllerAdviceHttpProcessor(frameworkExceptionAdvice);
return frameworkExceptionAdvice;
}
/**
* 数据校验异常处理器
*/
@Bean
public DataExceptionAdvice dataExceptionAdvice(BeforeControllerAdviceProcess beforeControllerAdviceProcess) {
DataExceptionAdvice dataExceptionAdvice = new DataExceptionAdvice();
dataExceptionAdvice.setRejectStrategy(new DefaultRejectStrategyImpl());
dataExceptionAdvice.setControllerAdviceProcessor(dataExceptionAdvice);
dataExceptionAdvice.setBeforeControllerAdviceProcess(beforeControllerAdviceProcess);
dataExceptionAdvice.setControllerAdviceHttpProcessor(dataExceptionAdvice);
return dataExceptionAdvice;
}
/**
* 默认全局异常处理器
*/
@Bean
public DefaultGlobalExceptionAdvice defaultGlobalExceptionAdvice(BeforeControllerAdviceProcess beforeControllerAdviceProcess) {
DefaultGlobalExceptionAdvice advice = new DefaultGlobalExceptionAdvice();
advice.setRejectStrategy(new DefaultRejectStrategyImpl());
CopyOnWriteArrayList<ControllerAdvicePredicate> copyOnWriteArrayList = new CopyOnWriteArrayList<>();
copyOnWriteArrayList.add(advice);
advice.setPredicates(copyOnWriteArrayList);
advice.setControllerAdviceProcessor(advice);
advice.setBeforeControllerAdviceProcess(beforeControllerAdviceProcess);
advice.setControllerAdviceHttpProcessor(advice);
return advice;
}
/**
* 默认参数校验异常处理器
*/
@Bean
public DefaultValidationExceptionAdvice defaultValidationExceptionAdvice(BeforeControllerAdviceProcess beforeControllerAdviceProcess) {
DefaultValidationExceptionAdvice advice = new DefaultValidationExceptionAdvice();
advice.setRejectStrategy(new DefaultRejectStrategyImpl());
advice.setControllerAdviceProcessor(advice);
advice.setBeforeControllerAdviceProcess(beforeControllerAdviceProcess);
advice.setControllerAdviceHttpProcessor(advice);
return advice;
}
/**
* 国际化支持
*/
@Bean
@ConditionalOnProperty(prefix = PropertiesConstants.WEB_RESPONSE, name = "i18n", havingValue = "true")
public GrI18nResponseBodyAdvice grI18nResponseBodyAdvice() {
GrI18nResponseBodyAdvice i18nResponseBodyAdvice = new GrI18nResponseBodyAdvice();
CopyOnWriteArrayList<ResponseBodyAdvicePredicate> copyOnWriteArrayList = new CopyOnWriteArrayList<>();
copyOnWriteArrayList.add(i18nResponseBodyAdvice);
i18nResponseBodyAdvice.setPredicates(copyOnWriteArrayList);
i18nResponseBodyAdvice.setResponseBodyAdviceProcessor(i18nResponseBodyAdvice);
return i18nResponseBodyAdvice;
}
/**
* 国际化配置
*/
@Bean
@ConditionalOnProperty(prefix = PropertiesConstants.WEB_RESPONSE, name = "i18n", havingValue = "true")
public MessageSource messageSource() {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
messageSource.setBasenames("i18n", "i18n/empty-messages");
messageSource.setDefaultEncoding("UTF-8");
messageSource.setDefaultLocale(Locale.CHINA);
return messageSource;
} }
/** /**
@@ -214,14 +122,25 @@ public class GlobalResponseAutoConfiguration {
} }
/** /**
* SpringDoc 全局响应处理器 * 国际化支持
*
* @return {@link ApiDocGlobalResponseHandler }
*/ */
@Bean @Bean
@ConditionalOnMissingBean @ConditionalOnProperty(prefix = PropertiesConstants.WEB_RESPONSE, name = "i18n", havingValue = "true")
public ApiDocGlobalResponseHandler apiDocGlobalResponseHandler() { public GrI18nAdvice i18nAdvice() {
return new ApiDocGlobalResponseHandler(globalResponseProperties); return new GrI18nAdvice();
}
/**
* 国际化配置
*/
@Bean
@ConditionalOnProperty(prefix = PropertiesConstants.WEB_RESPONSE, name = "i18n", havingValue = "true")
public MessageSource messageSource() {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
messageSource.setBasenames("i18n", "i18n/empty-messages");
messageSource.setDefaultEncoding("UTF-8");
messageSource.setDefaultLocale(Locale.CHINA);
return messageSource;
} }
@PostConstruct @PostConstruct

View File

@@ -52,9 +52,9 @@ public class TLogServletFilter implements Filter {
try { try {
TLogWebCommon.loadInstance().preHandle(httpServletRequest); TLogWebCommon.loadInstance().preHandle(httpServletRequest);
// 把 traceId 放入 response 的 header为了方便有些人有这样的需求从前端拿整条链路的 traceId // 把 traceId 放入 response 的 header为了方便有些人有这样的需求从前端拿整条链路的 traceId
String traceIdName = traceProperties.getTraceIdName(); String headerName = traceProperties.getHeaderName();
if (CharSequenceUtil.isNotBlank(traceIdName)) { if (CharSequenceUtil.isNotBlank(headerName)) {
httpServletResponse.addHeader(traceIdName, TLogContext.getTraceId()); httpServletResponse.addHeader(headerName, TLogContext.getTraceId());
} }
chain.doFilter(request, response); chain.doFilter(request, response);
} finally { } finally {

View File

@@ -35,9 +35,9 @@ public class TraceProperties {
private boolean enabled = false; private boolean enabled = false;
/** /**
* 链路 ID 名称 * 响应头名称
*/ */
private String traceIdName = "traceId"; private String headerName = "traceId";
/** /**
* TLog 配置 * TLog 配置
@@ -53,12 +53,12 @@ public class TraceProperties {
this.enabled = enabled; this.enabled = enabled;
} }
public String getTraceIdName() { public String getHeaderName() {
return traceIdName; return headerName;
} }
public void setTraceIdName(String traceIdName) { public void setHeaderName(String headerName) {
this.traceIdName = traceIdName; this.headerName = headerName;
} }
public TLogProperties getTlog() { public TLogProperties getTlog() {

View File

@@ -21,7 +21,9 @@ import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import top.continew.starter.web.util.SpringWebUtils; import org.springframework.http.server.PathContainer;
import org.springframework.web.util.pattern.PathPattern;
import org.springframework.web.util.pattern.PathPatternParser;
import java.io.IOException; import java.io.IOException;
import java.util.List; import java.util.List;
@@ -55,15 +57,14 @@ public class XssFilter implements Filter {
if (servletRequest instanceof HttpServletRequest request && xssProperties.isEnabled()) { if (servletRequest instanceof HttpServletRequest request && xssProperties.isEnabled()) {
// 放行路由:忽略 XSS 过滤 // 放行路由:忽略 XSS 过滤
List<String> excludePatterns = xssProperties.getExcludePatterns(); List<String> excludePatterns = xssProperties.getExcludePatterns();
if (CollectionUtil.isNotEmpty(excludePatterns) && SpringWebUtils.isMatch(request if (CollectionUtil.isNotEmpty(excludePatterns) && isMatchPath(request.getServletPath(), excludePatterns)) {
.getServletPath(), excludePatterns)) {
filterChain.doFilter(request, servletResponse); filterChain.doFilter(request, servletResponse);
return; return;
} }
// 拦截路由:执行 XSS 过滤 // 拦截路由:执行 XSS 过滤
List<String> includePatterns = xssProperties.getIncludePatterns(); List<String> includePatterns = xssProperties.getIncludePatterns();
if (CollectionUtil.isNotEmpty(includePatterns)) { if (CollectionUtil.isNotEmpty(includePatterns)) {
if (SpringWebUtils.isMatch(request.getServletPath(), includePatterns)) { if (isMatchPath(request.getServletPath(), includePatterns)) {
filterChain.doFilter(new XssServletRequestWrapper(request, xssProperties), servletResponse); filterChain.doFilter(new XssServletRequestWrapper(request, xssProperties), servletResponse);
} else { } else {
filterChain.doFilter(request, servletResponse); filterChain.doFilter(request, servletResponse);
@@ -76,4 +77,22 @@ public class XssFilter implements Filter {
} }
filterChain.doFilter(servletRequest, servletResponse); filterChain.doFilter(servletRequest, servletResponse);
} }
/**
* 判断数组中是否存在匹配的路径
*
* @param requestUrl 请求地址
* @param pathPatterns 指定匹配路径
* @return true匹配false不匹配
*/
private static boolean isMatchPath(String requestUrl, List<String> pathPatterns) {
for (String pattern : pathPatterns) {
PathPattern pathPattern = PathPatternParser.defaultInstance.parse(pattern);
PathContainer pathContainer = PathContainer.parsePath(requestUrl);
if (pathPattern.matches(pathContainer)) {
return true;
}
}
return false;
}
} }

View File

@@ -23,6 +23,8 @@ import com.feiniaojin.gracefulresponse.data.ResponseStatus;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import top.continew.starter.web.autoconfigure.response.GlobalResponseProperties; import top.continew.starter.web.autoconfigure.response.GlobalResponseProperties;
import java.util.Collections;
/** /**
* 响应信息 * 响应信息
* *
@@ -30,7 +32,7 @@ import top.continew.starter.web.autoconfigure.response.GlobalResponseProperties;
* @since 1.0.0 * @since 1.0.0
*/ */
@Schema(description = "响应信息") @Schema(description = "响应信息")
public class R<T> implements Response { public class R implements Response {
private static final GlobalResponseProperties PROPERTIES = SpringUtil.getBean(GlobalResponseProperties.class); private static final GlobalResponseProperties PROPERTIES = SpringUtil.getBean(GlobalResponseProperties.class);
private static final String DEFAULT_SUCCESS_CODE = PROPERTIES.getDefaultSuccessCode(); private static final String DEFAULT_SUCCESS_CODE = PROPERTIES.getDefaultSuccessCode();
@@ -47,7 +49,7 @@ public class R<T> implements Response {
/** /**
* 状态信息 * 状态信息
*/ */
@Schema(description = "状态信息", example = "ok") @Schema(description = "状态信息", example = "操作成功")
private String msg; private String msg;
/** /**
@@ -66,7 +68,7 @@ public class R<T> implements Response {
* 响应数据 * 响应数据
*/ */
@Schema(description = "响应数据") @Schema(description = "响应数据")
private T data; private Object data = Collections.emptyMap();
public R() { public R() {
} }
@@ -76,7 +78,7 @@ public class R<T> implements Response {
this.setMsg(msg); this.setMsg(msg);
} }
public R(String code, String msg, T data) { public R(String code, String msg, Object data) {
this(code, msg); this(code, msg);
this.data = data; this.data = data;
} }
@@ -95,7 +97,7 @@ public class R<T> implements Response {
@Override @Override
public void setPayload(Object payload) { public void setPayload(Object payload) {
this.data = (T)payload; this.data = payload;
} }
@Override @Override
@@ -121,11 +123,11 @@ public class R<T> implements Response {
this.msg = msg; this.msg = msg;
} }
public T getData() { public Object getData() {
return data; return data;
} }
public void setData(T data) { public void setData(Object data) {
this.data = data; this.data = data;
} }

View File

@@ -16,8 +16,8 @@
package top.continew.starter.web.util; package top.continew.starter.web.util;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ReflectUtil; import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.extra.spring.SpringUtil; import cn.hutool.extra.spring.SpringUtil;
import jakarta.servlet.ServletContext; import jakarta.servlet.ServletContext;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
@@ -35,8 +35,6 @@ import org.springframework.web.util.pattern.PathPattern;
import org.springframework.web.util.pattern.PathPatternParser; import org.springframework.web.util.pattern.PathPatternParser;
import top.continew.starter.core.constant.StringConstants; import top.continew.starter.core.constant.StringConstants;
import java.util.Arrays;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
@@ -72,36 +70,12 @@ public class SpringWebUtils {
/** /**
* 路径是否匹配 * 路径是否匹配
* *
* @param path 路径
* @param patterns 匹配模式列表
* @return 是否匹配
* @since 2.6.0
*/
public static boolean isMatch(String path, List<String> patterns) {
return patterns.stream().anyMatch(pattern -> isMatch(path, pattern));
}
/**
* 路径是否匹配
*
* @param path 路径
* @param patterns 匹配模式列表
* @return 是否匹配
* @since 2.6.0
*/
public static boolean isMatch(String path, String... patterns) {
return Arrays.stream(patterns).anyMatch(pattern -> isMatch(path, pattern));
}
/**
* 路径是否匹配
*
* @param path 路径
* @param pattern 匹配模式 * @param pattern 匹配模式
* @param path 路径
* @return 是否匹配 * @return 是否匹配
* @since 2.4.0 * @since 2.4.0
*/ */
public static boolean isMatch(String path, String pattern) { public static boolean match(String pattern, String path) {
PathPattern pathPattern = PathPatternParser.defaultInstance.parse(pattern); PathPattern pathPattern = PathPatternParser.defaultInstance.parse(pattern);
PathContainer pathContainer = PathContainer.parsePath(path); PathContainer pathContainer = PathContainer.parsePath(path);
return pathPattern.matches(pathContainer); return pathPattern.matches(pathContainer);

View File

@@ -4,8 +4,6 @@ continew-starter.web.response:
i18n: false i18n: false
# 响应类全名(配置后 response-style 将不再生效) # 响应类全名(配置后 response-style 将不再生效)
response-class-full-name: top.continew.starter.web.model.R response-class-full-name: top.continew.starter.web.model.R
# 自定义失败 HTTP 状态码默认200建议业务和通信状态码区分
default-http-status-code-on-error: 200
# 自定义成功响应码默认0 # 自定义成功响应码默认0
default-success-code: 0 default-success-code: 0
# 自定义成功提示默认ok # 自定义成功提示默认ok