mirror of
https://github.com/continew-org/continew-starter.git
synced 2025-11-12 18:57:14 +08:00
Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7571c05f9c | |||
| 4c4f98a86e | |||
|
|
bf51837991 | ||
| 22ebdfeb9f | |||
| b0a2a8c927 | |||
| ea6b316296 | |||
| 4786af5104 | |||
| 6693cd49b9 | |||
| b9f106572a | |||
| be3a121c09 | |||
| 6e76269bb6 | |||
| 0b41f2d10c | |||
| 9ec2e6b981 | |||
| 31c3162563 |
34
CHANGELOG.md
34
CHANGELOG.md
@@ -1,3 +1,37 @@
|
||||
## [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)
|
||||
|
||||
### 🐛 问题修复
|
||||
|
||||
- 【data】移除 SQL 函数接口中的 SQL 拼接 ([6693cd4](https://github.com/continew-org/continew-starter/commit/6693cd49b93244b42dfadd4f4be28e526d764425))
|
||||
|
||||
## [v2.5.0](https://github.com/continew-org/continew-starter/compare/v2.4.0...v2.5.0) (2024-08-07)
|
||||
|
||||
### ✨ 新特性
|
||||
|
||||
- 【web】重构全局响应处理方案 ([0b41f2d](https://github.com/continew-org/continew-starter/commit/0b41f2d10c5dfb309585b36bde7008cdff6c912a))
|
||||
- 【web】FileUploadUtils 新增下载重载方法 ([be3a121](https://github.com/continew-org/continew-starter/commit/be3a121c0901e7c31bd2d30ac3befa74197c8c35))
|
||||
|
||||
### 🐛 问题修复
|
||||
|
||||
- 【log】仅支持获取 JSON 结构响应体 ([6e76269](https://github.com/continew-org/continew-starter/commit/6e76269bb6f335fa655fab5a5bee85824c1ca9e3))
|
||||
|
||||
### 💎 功能优化
|
||||
|
||||
- 【web】BaseEnumConverterAutoConfiguration => WebMvcAutoConfiguration ([9ec2e6b](https://github.com/continew-org/continew-starter/commit/9ec2e6b98137ed54fbba25d4e895d28cc39e7d93))
|
||||
- 【log】log-httptrace-pro => log-interceptor ([31c3162](https://github.com/continew-org/continew-starter/commit/31c3162563589b71f7bc09797d39abb0177bee27))
|
||||
|
||||
## [v2.4.0](https://github.com/continew-org/continew-starter/compare/v2.3.0...v2.4.0) (2024-07-31)
|
||||
|
||||
### ✨ 新特性
|
||||
|
||||
@@ -172,10 +172,10 @@ continew-starter.web:
|
||||
|
||||
### 日志模块
|
||||
|
||||
| 模块名称 | 模块说明 |
|
||||
| ---------------------------------- | ----------------------------------------- |
|
||||
| continew-starter-log-core | 日志核心模块 |
|
||||
| continew-starter-log-httptrace-pro | Spring Boot Actuator HttpTrace 重置增强版 |
|
||||
| 模块名称 | 模块说明 |
|
||||
|----------------------------------| ----------------------------------------- |
|
||||
| continew-starter-log-core | 日志核心模块 |
|
||||
| continew-starter-log-interceptor | 拦截器版(Spring Boot Actuator HttpTrace 增强版) |
|
||||
|
||||
### 存储模块
|
||||
|
||||
|
||||
@@ -17,16 +17,11 @@
|
||||
package top.continew.starter.apidoc.autoconfigure;
|
||||
|
||||
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.OpenAPI;
|
||||
import io.swagger.v3.oas.models.info.Contact;
|
||||
import io.swagger.v3.oas.models.info.Info;
|
||||
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.SecurityScheme;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
@@ -48,15 +43,13 @@ import org.springframework.http.CacheControl;
|
||||
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
|
||||
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
|
||||
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.util.EnumTypeUtils;
|
||||
import top.continew.starter.core.autoconfigure.project.ProjectProperties;
|
||||
import top.continew.starter.core.enums.BaseEnum;
|
||||
import top.continew.starter.core.util.GeneralPropertySourceFactory;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* API 文档自动配置
|
||||
@@ -156,95 +149,14 @@ public class SpringDocAutoConfiguration implements WebMvcConfigurer {
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义参数配置(针对 BaseEnum 展示枚举值和描述)
|
||||
* 自定义 BaseEnum 枚举参数配置(针对实现了 BaseEnum 的枚举,优化其枚举值和描述展示)
|
||||
*
|
||||
* @return {@link BaseEnumParameterHandler }
|
||||
* @since 2.4.0
|
||||
*/
|
||||
@Bean
|
||||
public ParameterCustomizer customParameterCustomizer() {
|
||||
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));
|
||||
public BaseEnumParameterHandler customParameterCustomizer() {
|
||||
return new BaseEnumParameterHandler();
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* 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));
|
||||
}
|
||||
}
|
||||
@@ -1,73 +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 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";
|
||||
}
|
||||
}
|
||||
@@ -44,7 +44,6 @@ import top.continew.starter.core.util.GeneralPropertySourceFactory;
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@AutoConfiguration
|
||||
@ComponentScan("top.continew.starter.auth.satoken.exception")
|
||||
@EnableConfigurationProperties(SaTokenExtensionProperties.class)
|
||||
@ConditionalOnProperty(prefix = "sa-token.extension", name = PropertiesConstants.ENABLED, havingValue = "true")
|
||||
@PropertySource(value = "classpath:default-auth-satoken.yml", factory = GeneralPropertySourceFactory.class)
|
||||
|
||||
@@ -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.auth.satoken.exception;
|
||||
|
||||
import cn.dev33.satoken.exception.NotLoginException;
|
||||
import cn.dev33.satoken.exception.NotPermissionException;
|
||||
import cn.dev33.satoken.exception.NotRoleException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
import top.continew.starter.web.model.R;
|
||||
|
||||
/**
|
||||
* 全局 SaToken 异常处理器
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 1.2.0
|
||||
*/
|
||||
@RestControllerAdvice
|
||||
public class GlobalSaTokenExceptionHandler {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(GlobalSaTokenExceptionHandler.class);
|
||||
|
||||
/**
|
||||
* 认证异常-登录认证
|
||||
*/
|
||||
@ExceptionHandler(NotLoginException.class)
|
||||
public R<Void> handleNotLoginException(NotLoginException e, HttpServletRequest request) {
|
||||
log.error("请求地址 [{}],认证失败,无法访问系统资源。", request.getRequestURI(), e);
|
||||
String errorMsg = switch (e.getType()) {
|
||||
case NotLoginException.KICK_OUT -> "您已被踢下线。";
|
||||
case NotLoginException.BE_REPLACED_MESSAGE -> "您已被顶下线。";
|
||||
default -> "您的登录状态已过期,请重新登录。";
|
||||
};
|
||||
return R.fail(HttpStatus.UNAUTHORIZED.value(), errorMsg);
|
||||
}
|
||||
|
||||
/**
|
||||
* 认证异常-权限认证
|
||||
*/
|
||||
@ExceptionHandler(NotPermissionException.class)
|
||||
public R<Void> handleNotPermissionException(NotPermissionException e, HttpServletRequest request) {
|
||||
log.error("请求地址 [{}],权限码校验失败。", request.getRequestURI(), e);
|
||||
return R.fail(HttpStatus.FORBIDDEN.value(), "没有访问权限,请联系管理员授权");
|
||||
}
|
||||
|
||||
/**
|
||||
* 认证异常-角色认证
|
||||
*/
|
||||
@ExceptionHandler(NotRoleException.class)
|
||||
public R<Void> handleNotRoleException(NotRoleException e, HttpServletRequest request) {
|
||||
log.error("请求地址 [{}],角色权限校验失败。", request.getRequestURI(), e);
|
||||
return R.fail(HttpStatus.FORBIDDEN.value(), "没有访问权限,请联系管理员授权");
|
||||
}
|
||||
}
|
||||
@@ -64,6 +64,11 @@ public class PropertiesConstants {
|
||||
*/
|
||||
public static final String WEB_CORS = WEB + StringConstants.DOT + "cors";
|
||||
|
||||
/**
|
||||
* 响应配置
|
||||
*/
|
||||
public static final String WEB_RESPONSE = WEB + StringConstants.DOT + "response";
|
||||
|
||||
/**
|
||||
* 链路配置
|
||||
*/
|
||||
@@ -74,11 +79,6 @@ public class PropertiesConstants {
|
||||
*/
|
||||
public static final String WEB_XSS = WEB + StringConstants.DOT + "xss";
|
||||
|
||||
/**
|
||||
* 国际化配置
|
||||
*/
|
||||
public static final String WEB_I18N = WEB + StringConstants.DOT + "i18n";
|
||||
|
||||
/**
|
||||
* 日志配置
|
||||
*/
|
||||
|
||||
@@ -1,43 +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.core.exception;
|
||||
|
||||
/**
|
||||
* 统一错误码异常
|
||||
*
|
||||
* @author Jasmine
|
||||
* @since 2.2.0
|
||||
*/
|
||||
public class GlobalException extends Exception {
|
||||
|
||||
private ResultInfoInterface resultInfo;
|
||||
|
||||
public GlobalException() {
|
||||
}
|
||||
|
||||
public GlobalException(ResultInfoInterface resultInfo) {
|
||||
this.resultInfo = resultInfo;
|
||||
}
|
||||
|
||||
public ResultInfoInterface getResultInfo() {
|
||||
return this.resultInfo;
|
||||
}
|
||||
|
||||
public void setResultInfo(ResultInfoInterface resultInfo) {
|
||||
this.resultInfo = resultInfo;
|
||||
}
|
||||
}
|
||||
@@ -1,66 +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.core.exception;
|
||||
|
||||
/**
|
||||
* 接口返回码 所有业务异常都要继承该接口
|
||||
*
|
||||
* @author Jasmine
|
||||
* @since 2.2.0
|
||||
*/
|
||||
public enum GlobalResultInfoEnum implements ResultInfoInterface {
|
||||
|
||||
/**
|
||||
* 操作成功
|
||||
*/
|
||||
SUCCESS(200, "操作成功"),
|
||||
|
||||
/**
|
||||
* 操作失败
|
||||
*/
|
||||
FAILED(500, "操作失败");
|
||||
|
||||
private int code;
|
||||
private String messageKey;
|
||||
private String defaultMessage;
|
||||
|
||||
GlobalResultInfoEnum(int code, String defaultMessage) {
|
||||
this.code = code;
|
||||
this.defaultMessage = defaultMessage;
|
||||
}
|
||||
|
||||
GlobalResultInfoEnum(int code, String messageKey, String defaultMessage) {
|
||||
this.code = code;
|
||||
this.messageKey = messageKey;
|
||||
this.defaultMessage = defaultMessage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCode() {
|
||||
return this.code;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessageKey() {
|
||||
return this.messageKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDefaultMessage() {
|
||||
return this.defaultMessage;
|
||||
}
|
||||
}
|
||||
@@ -1,49 +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.core.exception;
|
||||
|
||||
/**
|
||||
* 接口返回码与消息 所有业务异常都要继承该接口
|
||||
*
|
||||
* @author Jasmine
|
||||
* @since 2.2.0
|
||||
*/
|
||||
public interface ResultInfoInterface {
|
||||
|
||||
/**
|
||||
* 获取编码
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
int getCode();
|
||||
|
||||
/**
|
||||
* 国际化消息key
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
default String getMessageKey() {
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取默认消息 若从国际化文件里没有获取到值,就取默认值
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
String getDefaultMessage();
|
||||
}
|
||||
@@ -18,5 +18,10 @@
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-db</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-commons</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -18,8 +18,6 @@ package top.continew.starter.data.core.enums;
|
||||
|
||||
import top.continew.starter.data.core.function.ISqlFunction;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 数据库类型枚举
|
||||
*
|
||||
@@ -33,8 +31,8 @@ public enum DatabaseType implements ISqlFunction {
|
||||
*/
|
||||
MYSQL("MySQL") {
|
||||
@Override
|
||||
public String findInSet(Serializable value, String set) {
|
||||
return "find_in_set('%s', %s) <> 0".formatted(value, set);
|
||||
public String findInSet() {
|
||||
return "find_in_set({0}, {1}) <> 0";
|
||||
}
|
||||
},
|
||||
|
||||
@@ -43,8 +41,8 @@ public enum DatabaseType implements ISqlFunction {
|
||||
*/
|
||||
POSTGRE_SQL("PostgreSQL") {
|
||||
@Override
|
||||
public String findInSet(Serializable value, String set) {
|
||||
return "(select position(',%s,' in ','||%s||',')) <> 0".formatted(value, set);
|
||||
public String findInSet() {
|
||||
return "(select position(',{0},' in ','||{1}||',')) <> 0";
|
||||
}
|
||||
},;
|
||||
|
||||
|
||||
@@ -16,8 +16,6 @@
|
||||
|
||||
package top.continew.starter.data.core.function;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* SQL 函数接口
|
||||
*
|
||||
@@ -29,9 +27,7 @@ public interface ISqlFunction {
|
||||
/**
|
||||
* find_in_set 函数
|
||||
*
|
||||
* @param value 值
|
||||
* @param set 集合
|
||||
* @return 函数实现
|
||||
*/
|
||||
String findInSet(Serializable value, String set);
|
||||
String findInSet();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* 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.data.core.util;
|
||||
|
||||
import cn.hutool.core.text.CharSequenceUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* SQL 注入验证工具类
|
||||
*
|
||||
* @author hubin(<a href="https://github.com/baomidou/mybatis-plus">MyBatis Plus</a>)
|
||||
* @author zhoujf(<a href="https://github.com/jeecgboot/JeecgBoot">JeecgBoot</a>)
|
||||
* @author Charles7c
|
||||
* @since 2.5.2
|
||||
*/
|
||||
public class SqlInjectionUtils {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(SqlInjectionUtils.class);
|
||||
|
||||
/**
|
||||
* SQL语法检查正则:符合两个关键字(有先后顺序)才算匹配
|
||||
*/
|
||||
private static final Pattern SQL_SYNTAX_PATTERN = Pattern
|
||||
.compile("(insert|delete|update|select|create|drop|truncate|grant|alter|deny|revoke|call|execute|exec|declare|show|rename|set)" + "\\s+.*(into|from|set|where|table|database|view|index|on|cursor|procedure|trigger|for|password|union|and|or)|(select\\s*\\*\\s*from\\s+)|(and|or)\\s+.*", Pattern.CASE_INSENSITIVE);
|
||||
|
||||
/**
|
||||
* 使用'、;或注释截断SQL检查正则
|
||||
*/
|
||||
private static final Pattern SQL_COMMENT_PATTERN = Pattern
|
||||
.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 注入
|
||||
*
|
||||
* @param value 检查参数
|
||||
* @return true:非法;false:合法
|
||||
*/
|
||||
public static boolean check(String value) {
|
||||
return check(value, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查参数是否存在 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;
|
||||
}
|
||||
}
|
||||
@@ -14,21 +14,23 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.starter.data.mybatis.flex.query;
|
||||
package top.continew.starter.data.mybatis.flex.util;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.text.CharSequenceUtil;
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.mybatisflex.core.query.QueryWrapper;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.text.CharSequenceUtil;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import top.continew.starter.core.exception.BadRequestException;
|
||||
import top.continew.starter.core.util.ReflectUtils;
|
||||
import top.continew.starter.core.util.validate.ValidationUtils;
|
||||
import top.continew.starter.data.core.annotation.Query;
|
||||
import top.continew.starter.data.core.annotation.QueryIgnore;
|
||||
import top.continew.starter.data.core.enums.QueryType;
|
||||
import top.continew.starter.data.core.util.SqlInjectionUtils;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
@@ -56,10 +58,9 @@ public class QueryWrapperHelper {
|
||||
*
|
||||
* @param query 查询条件
|
||||
* @param <Q> 查询条件数据类型
|
||||
* @param <R> 查询数据类型
|
||||
* @return QueryWrapper
|
||||
*/
|
||||
public static <Q, R> QueryWrapper build(Q query) {
|
||||
public static <Q> QueryWrapper build(Q query) {
|
||||
QueryWrapper queryWrapper = QueryWrapper.create();
|
||||
// 没有查询条件,直接返回
|
||||
if (null == query) {
|
||||
@@ -70,6 +71,34 @@ public class QueryWrapperHelper {
|
||||
return build(query, fieldList, queryWrapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建 QueryWrapper
|
||||
*
|
||||
* @param query 查询条件
|
||||
* @param sort 排序条件
|
||||
* @param <Q> 查询条件数据类型
|
||||
* @return QueryWrapper
|
||||
* @since 2.5.2
|
||||
*/
|
||||
public static <Q> QueryWrapper build(Q query, Sort sort) {
|
||||
QueryWrapper queryWrapper = QueryWrapper.create();
|
||||
// 没有查询条件,直接返回
|
||||
if (null == query) {
|
||||
return queryWrapper;
|
||||
}
|
||||
// 设置排序条件
|
||||
if (sort != null && sort.isSorted()) {
|
||||
for (Sort.Order order : sort) {
|
||||
String field = CharSequenceUtil.toUnderlineCase(order.getProperty());
|
||||
ValidationUtils.throwIf(SqlInjectionUtils.check(field), "排序字段包含非法字符");
|
||||
queryWrapper.orderBy(field, order.isAscending());
|
||||
}
|
||||
}
|
||||
// 获取查询条件中所有的字段
|
||||
List<Field> fieldList = ReflectUtils.getNonStaticFields(query.getClass());
|
||||
return build(query, fieldList, queryWrapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建 QueryWrapper
|
||||
*
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.starter.data.mybatis.plus.query;
|
||||
package top.continew.starter.data.mybatis.plus.util;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -23,18 +23,17 @@ import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.text.CharSequenceUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import top.continew.starter.core.exception.BadRequestException;
|
||||
import top.continew.starter.core.util.ReflectUtils;
|
||||
import top.continew.starter.core.util.validate.ValidationUtils;
|
||||
import top.continew.starter.data.core.annotation.Query;
|
||||
import top.continew.starter.data.core.annotation.QueryIgnore;
|
||||
import top.continew.starter.data.core.enums.QueryType;
|
||||
import top.continew.starter.data.core.util.SqlInjectionUtils;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
@@ -60,11 +59,33 @@ public class QueryWrapperHelper {
|
||||
* @return QueryWrapper
|
||||
*/
|
||||
public static <Q, R> QueryWrapper<R> build(Q query) {
|
||||
return build(query, Sort.unsorted());
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建 QueryWrapper
|
||||
*
|
||||
* @param query 查询条件
|
||||
* @param sort 排序条件
|
||||
* @param <Q> 查询条件数据类型
|
||||
* @param <R> 查询数据类型
|
||||
* @return QueryWrapper
|
||||
* @since 2.5.2
|
||||
*/
|
||||
public static <Q, R> QueryWrapper<R> build(Q query, Sort sort) {
|
||||
QueryWrapper<R> queryWrapper = new QueryWrapper<>();
|
||||
// 没有查询条件,直接返回
|
||||
if (null == query) {
|
||||
return queryWrapper;
|
||||
}
|
||||
// 设置排序条件
|
||||
if (sort != null && sort.isSorted()) {
|
||||
for (Sort.Order order : sort) {
|
||||
String field = CharSequenceUtil.toUnderlineCase(order.getProperty());
|
||||
ValidationUtils.throwIf(SqlInjectionUtils.check(field), "排序字段包含非法字符");
|
||||
queryWrapper.orderBy(true, order.isAscending(), field);
|
||||
}
|
||||
}
|
||||
// 获取查询条件中所有的字段
|
||||
List<Field> fieldList = ReflectUtils.getNonStaticFields(query.getClass());
|
||||
return build(query, fieldList, queryWrapper);
|
||||
@@ -43,7 +43,7 @@
|
||||
|
||||
<properties>
|
||||
<!-- 项目版本号 -->
|
||||
<revision>2.4.0</revision>
|
||||
<revision>2.5.2</revision>
|
||||
<snail-job.version>1.1.0</snail-job.version>
|
||||
<sa-token.version>1.38.0</sa-token.version>
|
||||
<just-auth.version>1.16.6</just-auth.version>
|
||||
@@ -61,6 +61,7 @@
|
||||
<nashorn.version>15.4</nashorn.version>
|
||||
<x-file-storage.version>2.2.0</x-file-storage.version>
|
||||
<aws-s3.version>1.12.761</aws-s3.version>
|
||||
<graceful-response.version>4.0.1-boot3</graceful-response.version>
|
||||
<crane4j.version>2.9.0</crane4j.version>
|
||||
<knife4j.version>4.5.0</knife4j.version>
|
||||
<tlog.version>1.5.2</tlog.version>
|
||||
@@ -261,6 +262,13 @@
|
||||
<version>${aws-s3.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Graceful Response(一个Spring Boot技术栈下的优雅响应处理组件,可以帮助开发者完成响应数据封装、异常处理、错误码填充等过程,提高开发效率,提高代码质量) -->
|
||||
<dependency>
|
||||
<groupId>com.feiniaojin</groupId>
|
||||
<artifactId>graceful-response</artifactId>
|
||||
<version>${graceful-response.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Crane4j(一个基于注解的,用于完成一切 “根据 A 的 key 值拿到 B,再把 B 的属性映射到 A” 这类需求的字段填充框架) -->
|
||||
<dependency>
|
||||
<groupId>cn.crane4j</groupId>
|
||||
@@ -446,10 +454,10 @@
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 日志模块 - HttpTracePro(Spring Boot Actuator HttpTrace 定制增强版) -->
|
||||
<!-- 日志模块 - 拦截器版(Spring Boot Actuator HttpTrace 增强版) -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-log-httptrace-pro</artifactId>
|
||||
<artifactId>continew-starter-log-interceptor</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
@@ -24,6 +24,12 @@
|
||||
<artifactId>spring-data-commons</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Web 模块 -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 认证模块 - SaToken -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
@@ -36,16 +42,16 @@
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<!-- 数据访问模块 - 核心模块 -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-data-core</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 文件处理模块 - Excel -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-file-excel</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Web 模块 -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-web</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -21,6 +21,8 @@ import cn.hutool.core.util.ArrayUtil;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import top.continew.starter.core.constant.StringConstants;
|
||||
import top.continew.starter.core.util.validate.ValidationUtils;
|
||||
import top.continew.starter.data.core.util.SqlInjectionUtils;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
@@ -54,20 +56,17 @@ public class SortQuery implements Serializable {
|
||||
if (ArrayUtil.isEmpty(sort)) {
|
||||
return Sort.unsorted();
|
||||
}
|
||||
|
||||
ValidationUtils.throwIf(sort.length < 2, "排序条件非法");
|
||||
List<Sort.Order> orders = new ArrayList<>(sort.length);
|
||||
if (CharSequenceUtil.contains(sort[0], StringConstants.COMMA)) {
|
||||
// e.g "sort=createTime,desc&sort=name,asc"
|
||||
for (String s : sort) {
|
||||
List<String> sortList = CharSequenceUtil.splitTrim(s, StringConstants.COMMA);
|
||||
Sort.Order order = new Sort.Order(Sort.Direction.valueOf(sortList.get(1).toUpperCase()), sortList
|
||||
.get(0));
|
||||
orders.add(order);
|
||||
orders.add(this.getOrder(sortList.get(0), sortList.get(1)));
|
||||
}
|
||||
} else {
|
||||
// e.g "sort=createTime,desc"
|
||||
Sort.Order order = new Sort.Order(Sort.Direction.valueOf(sort[1].toUpperCase()), sort[0]);
|
||||
orders.add(order);
|
||||
orders.add(this.getOrder(sort[0], sort[1]));
|
||||
}
|
||||
return Sort.by(orders);
|
||||
}
|
||||
@@ -75,4 +74,16 @@ public class SortQuery implements Serializable {
|
||||
public void setSort(String[] sort) {
|
||||
this.sort = sort;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取排序条件
|
||||
*
|
||||
* @param field 字段
|
||||
* @param direction 排序方向
|
||||
* @return 排序条件
|
||||
*/
|
||||
private Sort.Order getOrder(String field, String direction) {
|
||||
ValidationUtils.throwIf(SqlInjectionUtils.check(field), "排序字段包含非法字符");
|
||||
return new Sort.Order(Sort.Direction.valueOf(direction.toUpperCase()), field);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||
* <p>
|
||||
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.gnu.org/licenses/lgpl.html
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.starter.extension.crud.model.resp;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* ID 响应信息
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 2.5.0
|
||||
*/
|
||||
public class BaseIdResp<T extends Serializable> implements Serializable {
|
||||
|
||||
/**
|
||||
* ID
|
||||
*/
|
||||
@Schema(description = "ID", example = "1")
|
||||
private T id;
|
||||
|
||||
public T getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(T id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
BaseIdResp(final T id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public static <T extends Serializable> BaseIdRespBuilder<T> builder() {
|
||||
return new BaseIdRespBuilder();
|
||||
}
|
||||
|
||||
public static class BaseIdRespBuilder<T extends Serializable> {
|
||||
private T id;
|
||||
|
||||
BaseIdRespBuilder() {
|
||||
}
|
||||
|
||||
public BaseIdRespBuilder<T> id(final T id) {
|
||||
this.id = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
public BaseIdResp<T> build() {
|
||||
return new BaseIdResp(this.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.extension.crud.model.resp;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 分页信息
|
||||
*
|
||||
* @param <T> 列表数据类型
|
||||
* @author Charles7c
|
||||
* @since 2.5.2
|
||||
*/
|
||||
@Schema(description = "分页信息")
|
||||
public class BasePageResp<T> implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 列表数据
|
||||
*/
|
||||
@Schema(description = "列表数据")
|
||||
private List<T> list;
|
||||
|
||||
/**
|
||||
* 总记录数
|
||||
*/
|
||||
@Schema(description = "总记录数", example = "10")
|
||||
private long total;
|
||||
|
||||
public BasePageResp() {
|
||||
}
|
||||
|
||||
public BasePageResp(final List<T> list, final long total) {
|
||||
this.list = list;
|
||||
this.total = total;
|
||||
}
|
||||
|
||||
public List<T> getList() {
|
||||
return list;
|
||||
}
|
||||
|
||||
public void setList(List<T> list) {
|
||||
this.list = list;
|
||||
}
|
||||
|
||||
public long getTotal() {
|
||||
return total;
|
||||
}
|
||||
|
||||
public void setTotal(long total) {
|
||||
this.total = total;
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ package top.continew.starter.extension.crud.controller;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.hutool.core.lang.tree.Tree;
|
||||
import cn.hutool.core.text.CharSequenceUtil;
|
||||
import com.feiniaojin.gracefulresponse.api.ExcludeFromGracefulResponse;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.enums.ParameterIn;
|
||||
@@ -29,13 +30,13 @@ import org.springframework.web.bind.annotation.*;
|
||||
import top.continew.starter.core.constant.StringConstants;
|
||||
import top.continew.starter.extension.crud.annotation.CrudRequestMapping;
|
||||
import top.continew.starter.extension.crud.enums.Api;
|
||||
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.req.BaseReq;
|
||||
import top.continew.starter.extension.crud.model.resp.BaseIdResp;
|
||||
import top.continew.starter.extension.crud.model.resp.PageResp;
|
||||
import top.continew.starter.extension.crud.service.BaseService;
|
||||
import top.continew.starter.extension.crud.util.ValidateGroup;
|
||||
import top.continew.starter.extension.crud.model.query.PageQuery;
|
||||
import top.continew.starter.extension.crud.model.resp.PageResp;
|
||||
import top.continew.starter.web.model.R;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -65,9 +66,9 @@ public abstract class BaseController<S extends BaseService<L, D, Q, C>, L, D, Q,
|
||||
@Operation(summary = "分页查询列表", description = "分页查询列表")
|
||||
@ResponseBody
|
||||
@GetMapping
|
||||
public R<PageResp<L>> page(Q query, @Validated PageQuery pageQuery) {
|
||||
public PageResp<L> page(Q query, @Validated PageQuery pageQuery) {
|
||||
this.checkPermission(Api.LIST);
|
||||
return R.ok(baseService.page(query, pageQuery));
|
||||
return baseService.page(query, pageQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -80,9 +81,9 @@ public abstract class BaseController<S extends BaseService<L, D, Q, C>, L, D, Q,
|
||||
@Operation(summary = "查询树列表", description = "查询树列表")
|
||||
@ResponseBody
|
||||
@GetMapping("/tree")
|
||||
public R<List<Tree<Long>>> tree(Q query, SortQuery sortQuery) {
|
||||
public List<Tree<Long>> tree(Q query, SortQuery sortQuery) {
|
||||
this.checkPermission(Api.LIST);
|
||||
return R.ok(baseService.tree(query, sortQuery, false));
|
||||
return baseService.tree(query, sortQuery, false);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -95,9 +96,9 @@ public abstract class BaseController<S extends BaseService<L, D, Q, C>, L, D, Q,
|
||||
@Operation(summary = "查询列表", description = "查询列表")
|
||||
@ResponseBody
|
||||
@GetMapping("/list")
|
||||
public R<List<L>> list(Q query, SortQuery sortQuery) {
|
||||
public List<L> list(Q query, SortQuery sortQuery) {
|
||||
this.checkPermission(Api.LIST);
|
||||
return R.ok(baseService.list(query, sortQuery));
|
||||
return baseService.list(query, sortQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -110,9 +111,9 @@ public abstract class BaseController<S extends BaseService<L, D, Q, C>, L, D, Q,
|
||||
@Parameter(name = "id", description = "ID", example = "1", in = ParameterIn.PATH)
|
||||
@ResponseBody
|
||||
@GetMapping("/{id}")
|
||||
public R<D> get(@PathVariable("id") Long id) {
|
||||
public D get(@PathVariable("id") Long id) {
|
||||
this.checkPermission(Api.LIST);
|
||||
return R.ok(baseService.get(id));
|
||||
return baseService.get(id);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -124,9 +125,9 @@ public abstract class BaseController<S extends BaseService<L, D, Q, C>, L, D, Q,
|
||||
@Operation(summary = "新增数据", description = "新增数据")
|
||||
@ResponseBody
|
||||
@PostMapping
|
||||
public R<Long> add(@Validated(ValidateGroup.Crud.Add.class) @RequestBody C req) {
|
||||
public BaseIdResp<Long> add(@Validated(ValidateGroup.Crud.Add.class) @RequestBody C req) {
|
||||
this.checkPermission(Api.ADD);
|
||||
return R.ok("新增成功", baseService.add(req));
|
||||
return BaseIdResp.<Long>builder().id(baseService.add(req)).build();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -134,32 +135,28 @@ public abstract class BaseController<S extends BaseService<L, D, Q, C>, L, D, Q,
|
||||
*
|
||||
* @param req 修改信息
|
||||
* @param id ID
|
||||
* @return /
|
||||
*/
|
||||
@Operation(summary = "修改数据", description = "修改数据")
|
||||
@Parameter(name = "id", description = "ID", example = "1", in = ParameterIn.PATH)
|
||||
@ResponseBody
|
||||
@PutMapping("/{id}")
|
||||
public R<Void> update(@Validated(ValidateGroup.Crud.Update.class) @RequestBody C req, @PathVariable("id") Long id) {
|
||||
public void update(@Validated(ValidateGroup.Crud.Update.class) @RequestBody C req, @PathVariable("id") Long id) {
|
||||
this.checkPermission(Api.UPDATE);
|
||||
baseService.update(req, id);
|
||||
return R.ok("修改成功");
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
*
|
||||
* @param ids ID 列表
|
||||
* @return /
|
||||
*/
|
||||
@Operation(summary = "删除数据", description = "删除数据")
|
||||
@Parameter(name = "ids", description = "ID 列表", example = "1,2", in = ParameterIn.PATH)
|
||||
@ResponseBody
|
||||
@DeleteMapping("/{ids}")
|
||||
public R<Void> delete(@PathVariable("ids") List<Long> ids) {
|
||||
public void delete(@PathVariable("ids") List<Long> ids) {
|
||||
this.checkPermission(Api.DELETE);
|
||||
baseService.delete(ids);
|
||||
return R.ok("删除成功");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -169,6 +166,7 @@ public abstract class BaseController<S extends BaseService<L, D, Q, C>, L, D, Q,
|
||||
* @param sortQuery 排序查询条件
|
||||
* @param response 响应对象
|
||||
*/
|
||||
@ExcludeFromGracefulResponse
|
||||
@Operation(summary = "导出数据", description = "导出数据")
|
||||
@GetMapping("/export")
|
||||
public void export(Q query, SortQuery sortQuery, HttpServletResponse response) {
|
||||
|
||||
@@ -22,7 +22,6 @@ import com.mybatisflex.core.paginate.Page;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@@ -35,22 +34,17 @@ import java.util.List;
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@Schema(description = "分页信息")
|
||||
public class PageResp<L> implements Serializable {
|
||||
public class PageResp<L> extends BasePageResp<L> {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 列表数据
|
||||
*/
|
||||
@Schema(description = "列表数据")
|
||||
private List<L> list;
|
||||
public PageResp() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 总记录数
|
||||
*/
|
||||
@Schema(description = "总记录数", example = "10")
|
||||
private long total;
|
||||
public PageResp(final List<L> list, final long total) {
|
||||
super(list, total);
|
||||
}
|
||||
|
||||
/**
|
||||
* 基于 MyBatis Plus 分页数据构建分页信息,并将源数据转换为指定类型数据
|
||||
@@ -65,10 +59,7 @@ public class PageResp<L> implements Serializable {
|
||||
if (null == page) {
|
||||
return empty();
|
||||
}
|
||||
PageResp<L> pageResp = new PageResp<>();
|
||||
pageResp.setList(BeanUtil.copyToList(page.getRecords(), targetClass));
|
||||
pageResp.setTotal(page.getTotalRow());
|
||||
return pageResp;
|
||||
return new PageResp<>(BeanUtil.copyToList(page.getRecords(), targetClass), page.getTotalRow());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -82,10 +73,7 @@ public class PageResp<L> implements Serializable {
|
||||
if (null == page) {
|
||||
return empty();
|
||||
}
|
||||
PageResp<L> pageResp = new PageResp<>();
|
||||
pageResp.setList(page.getRecords());
|
||||
pageResp.setTotal(page.getTotalRow());
|
||||
return pageResp;
|
||||
return new PageResp<>(page.getRecords(), page.getTotalRow());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -123,24 +111,6 @@ public class PageResp<L> implements Serializable {
|
||||
* @return 分页信息
|
||||
*/
|
||||
private static <L> PageResp<L> empty() {
|
||||
PageResp<L> pageResp = new PageResp<>();
|
||||
pageResp.setList(Collections.emptyList());
|
||||
return pageResp;
|
||||
}
|
||||
|
||||
public List<L> getList() {
|
||||
return list;
|
||||
}
|
||||
|
||||
public void setList(List<L> list) {
|
||||
this.list = list;
|
||||
}
|
||||
|
||||
public long getTotal() {
|
||||
return total;
|
||||
}
|
||||
|
||||
public void setTotal(long total) {
|
||||
this.total = total;
|
||||
return new PageResp<>(Collections.emptyList(), 0L);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ import cn.crane4j.core.support.OperateTemplate;
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.bean.copier.CopyOptions;
|
||||
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.TreeNodeConfig;
|
||||
import cn.hutool.core.text.CharSequenceUtil;
|
||||
@@ -35,7 +34,7 @@ import top.continew.starter.core.constant.StringConstants;
|
||||
import top.continew.starter.core.util.ReflectUtils;
|
||||
import top.continew.starter.core.util.validate.ValidationUtils;
|
||||
import top.continew.starter.data.mybatis.flex.base.BaseMapper;
|
||||
import top.continew.starter.data.mybatis.flex.query.QueryWrapperHelper;
|
||||
import top.continew.starter.data.mybatis.flex.util.QueryWrapperHelper;
|
||||
import top.continew.starter.data.mybatis.flex.service.impl.ServiceImpl;
|
||||
import top.continew.starter.extension.crud.annotation.TreeField;
|
||||
import top.continew.starter.extension.crud.model.query.PageQuery;
|
||||
@@ -73,6 +72,7 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T extends BaseIdD
|
||||
@Override
|
||||
public PageResp<L> page(Q query, PageQuery pageQuery) {
|
||||
QueryWrapper queryWrapper = this.buildQueryWrapper(query);
|
||||
this.sort(queryWrapper, pageQuery);
|
||||
Page<T> page = mapper.paginate(pageQuery.getPage(), pageQuery.getSize(), queryWrapper);
|
||||
PageResp<L> pageResp = PageResp.build(page, listClass);
|
||||
pageResp.getList().forEach(this::fill);
|
||||
@@ -185,12 +185,12 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T extends BaseIdD
|
||||
* @param sortQuery 排序查询条件
|
||||
*/
|
||||
protected void sort(QueryWrapper queryWrapper, SortQuery sortQuery) {
|
||||
Sort sort = Opt.ofNullable(sortQuery).orElseGet(SortQuery::new).getSort();
|
||||
if (sortQuery == null || sortQuery.getSort().isUnsorted()) {
|
||||
return;
|
||||
}
|
||||
Sort sort = sortQuery.getSort();
|
||||
List<Field> entityFields = ReflectUtils.getNonStaticFields(this.entityClass);
|
||||
for (Sort.Order order : sort) {
|
||||
if (null == order) {
|
||||
continue;
|
||||
}
|
||||
String property = order.getProperty();
|
||||
String checkProperty;
|
||||
// 携带表别名则获取 . 后面的字段名
|
||||
|
||||
@@ -19,6 +19,7 @@ package top.continew.starter.extension.crud.controller;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.hutool.core.lang.tree.Tree;
|
||||
import cn.hutool.core.text.CharSequenceUtil;
|
||||
import com.feiniaojin.gracefulresponse.api.ExcludeFromGracefulResponse;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.enums.ParameterIn;
|
||||
@@ -31,11 +32,11 @@ import top.continew.starter.extension.crud.annotation.CrudRequestMapping;
|
||||
import top.continew.starter.extension.crud.enums.Api;
|
||||
import top.continew.starter.extension.crud.model.query.SortQuery;
|
||||
import top.continew.starter.extension.crud.model.req.BaseReq;
|
||||
import top.continew.starter.extension.crud.model.resp.BaseIdResp;
|
||||
import top.continew.starter.extension.crud.util.ValidateGroup;
|
||||
import top.continew.starter.extension.crud.model.query.PageQuery;
|
||||
import top.continew.starter.extension.crud.model.resp.PageResp;
|
||||
import top.continew.starter.extension.crud.service.BaseService;
|
||||
import top.continew.starter.web.model.R;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -65,9 +66,9 @@ public abstract class BaseController<S extends BaseService<L, D, Q, C>, L, D, Q,
|
||||
@Operation(summary = "分页查询列表", description = "分页查询列表")
|
||||
@ResponseBody
|
||||
@GetMapping
|
||||
public R<PageResp<L>> page(Q query, @Validated PageQuery pageQuery) {
|
||||
public PageResp<L> page(Q query, @Validated PageQuery pageQuery) {
|
||||
this.checkPermission(Api.LIST);
|
||||
return R.ok(baseService.page(query, pageQuery));
|
||||
return baseService.page(query, pageQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -80,9 +81,9 @@ public abstract class BaseController<S extends BaseService<L, D, Q, C>, L, D, Q,
|
||||
@Operation(summary = "查询树列表", description = "查询树列表")
|
||||
@ResponseBody
|
||||
@GetMapping("/tree")
|
||||
public R<List<Tree<Long>>> tree(Q query, SortQuery sortQuery) {
|
||||
public List<Tree<Long>> tree(Q query, SortQuery sortQuery) {
|
||||
this.checkPermission(Api.LIST);
|
||||
return R.ok(baseService.tree(query, sortQuery, false));
|
||||
return baseService.tree(query, sortQuery, false);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -95,9 +96,9 @@ public abstract class BaseController<S extends BaseService<L, D, Q, C>, L, D, Q,
|
||||
@Operation(summary = "查询列表", description = "查询列表")
|
||||
@ResponseBody
|
||||
@GetMapping("/list")
|
||||
public R<List<L>> list(Q query, SortQuery sortQuery) {
|
||||
public List<L> list(Q query, SortQuery sortQuery) {
|
||||
this.checkPermission(Api.LIST);
|
||||
return R.ok(baseService.list(query, sortQuery));
|
||||
return baseService.list(query, sortQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -110,9 +111,9 @@ public abstract class BaseController<S extends BaseService<L, D, Q, C>, L, D, Q,
|
||||
@Parameter(name = "id", description = "ID", example = "1", in = ParameterIn.PATH)
|
||||
@ResponseBody
|
||||
@GetMapping("/{id}")
|
||||
public R<D> get(@PathVariable("id") Long id) {
|
||||
public D get(@PathVariable("id") Long id) {
|
||||
this.checkPermission(Api.LIST);
|
||||
return R.ok(baseService.get(id));
|
||||
return baseService.get(id);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -124,9 +125,9 @@ public abstract class BaseController<S extends BaseService<L, D, Q, C>, L, D, Q,
|
||||
@Operation(summary = "新增数据", description = "新增数据")
|
||||
@ResponseBody
|
||||
@PostMapping
|
||||
public R<Long> add(@Validated(ValidateGroup.Crud.Add.class) @RequestBody C req) {
|
||||
public BaseIdResp<Long> add(@Validated(ValidateGroup.Crud.Add.class) @RequestBody C req) {
|
||||
this.checkPermission(Api.ADD);
|
||||
return R.ok("新增成功", baseService.add(req));
|
||||
return BaseIdResp.<Long>builder().id(baseService.add(req)).build();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -134,32 +135,28 @@ public abstract class BaseController<S extends BaseService<L, D, Q, C>, L, D, Q,
|
||||
*
|
||||
* @param req 修改信息
|
||||
* @param id ID
|
||||
* @return /
|
||||
*/
|
||||
@Operation(summary = "修改数据", description = "修改数据")
|
||||
@Parameter(name = "id", description = "ID", example = "1", in = ParameterIn.PATH)
|
||||
@ResponseBody
|
||||
@PutMapping("/{id}")
|
||||
public R<Void> update(@Validated(ValidateGroup.Crud.Update.class) @RequestBody C req, @PathVariable("id") Long id) {
|
||||
public void update(@Validated(ValidateGroup.Crud.Update.class) @RequestBody C req, @PathVariable("id") Long id) {
|
||||
this.checkPermission(Api.UPDATE);
|
||||
baseService.update(req, id);
|
||||
return R.ok("修改成功");
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
*
|
||||
* @param ids ID 列表
|
||||
* @return /
|
||||
*/
|
||||
@Operation(summary = "删除数据", description = "删除数据")
|
||||
@Parameter(name = "ids", description = "ID 列表", example = "1,2", in = ParameterIn.PATH)
|
||||
@ResponseBody
|
||||
@DeleteMapping("/{ids}")
|
||||
public R<Void> delete(@PathVariable("ids") List<Long> ids) {
|
||||
public void delete(@PathVariable("ids") List<Long> ids) {
|
||||
this.checkPermission(Api.DELETE);
|
||||
baseService.delete(ids);
|
||||
return R.ok("删除成功");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -169,6 +166,7 @@ public abstract class BaseController<S extends BaseService<L, D, Q, C>, L, D, Q,
|
||||
* @param sortQuery 排序查询条件
|
||||
* @param response 响应对象
|
||||
*/
|
||||
@ExcludeFromGracefulResponse
|
||||
@Operation(summary = "导出数据", description = "导出数据")
|
||||
@GetMapping("/export")
|
||||
public void export(Q query, SortQuery sortQuery, HttpServletResponse response) {
|
||||
|
||||
@@ -1,102 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||
* <p>
|
||||
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.gnu.org/licenses/lgpl.html
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.starter.extension.crud.model.query;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.text.CharSequenceUtil;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.core.metadata.OrderItem;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.Min;
|
||||
import org.hibernate.validator.constraints.Range;
|
||||
import org.springdoc.core.annotations.ParameterObject;
|
||||
import org.springframework.data.domain.Sort;
|
||||
|
||||
import java.io.Serial;
|
||||
|
||||
/**
|
||||
* 分页查询条件
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@ParameterObject
|
||||
@Schema(description = "分页查询条件")
|
||||
public class PageQuery extends SortQuery {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
/**
|
||||
* 默认页码:1
|
||||
*/
|
||||
private static final int DEFAULT_PAGE = 1;
|
||||
/**
|
||||
* 默认每页条数:10
|
||||
*/
|
||||
private static final int DEFAULT_SIZE = 10;
|
||||
|
||||
/**
|
||||
* 页码
|
||||
*/
|
||||
@Schema(description = "页码", example = "1")
|
||||
@Min(value = 1, message = "页码最小值为 {value}")
|
||||
private Integer page = DEFAULT_PAGE;
|
||||
|
||||
/**
|
||||
* 每页条数
|
||||
*/
|
||||
@Schema(description = "每页条数", example = "10")
|
||||
@Range(min = 1, max = 1000, message = "每页条数(取值范围 {min}-{max})")
|
||||
private Integer size = DEFAULT_SIZE;
|
||||
|
||||
/**
|
||||
* 基于分页查询条件转换为 MyBatis Plus 分页条件
|
||||
*
|
||||
* @param <T> 列表数据类型
|
||||
* @return MyBatis Plus 分页条件
|
||||
*/
|
||||
public <T> IPage<T> toPage() {
|
||||
Page<T> mybatisPage = new Page<>(this.getPage(), this.getSize());
|
||||
Sort pageSort = this.getSort();
|
||||
if (CollUtil.isNotEmpty(pageSort)) {
|
||||
for (Sort.Order order : pageSort) {
|
||||
OrderItem orderItem = new OrderItem();
|
||||
orderItem.setAsc(order.isAscending());
|
||||
orderItem.setColumn(CharSequenceUtil.toUnderlineCase(order.getProperty()));
|
||||
mybatisPage.addOrder(orderItem);
|
||||
}
|
||||
}
|
||||
return mybatisPage;
|
||||
}
|
||||
|
||||
public Integer getPage() {
|
||||
return page;
|
||||
}
|
||||
|
||||
public void setPage(Integer page) {
|
||||
this.page = page;
|
||||
}
|
||||
|
||||
public Integer getSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
public void setSize(Integer size) {
|
||||
this.size = size;
|
||||
}
|
||||
}
|
||||
@@ -22,8 +22,8 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -34,22 +34,17 @@ import java.util.List;
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@Schema(description = "分页信息")
|
||||
public class PageResp<L> implements Serializable {
|
||||
public class PageResp<L> extends BasePageResp<L> {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 列表数据
|
||||
*/
|
||||
@Schema(description = "列表数据")
|
||||
private List<L> list;
|
||||
public PageResp() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 总记录数
|
||||
*/
|
||||
@Schema(description = "总记录数", example = "10")
|
||||
private long total;
|
||||
public PageResp(final List<L> list, final long total) {
|
||||
super(list, total);
|
||||
}
|
||||
|
||||
/**
|
||||
* 基于 MyBatis Plus 分页数据构建分页信息,并将源数据转换为指定类型数据
|
||||
@@ -64,10 +59,7 @@ public class PageResp<L> implements Serializable {
|
||||
if (null == page) {
|
||||
return empty();
|
||||
}
|
||||
PageResp<L> pageResp = new PageResp<>();
|
||||
pageResp.setList(BeanUtil.copyToList(page.getRecords(), targetClass));
|
||||
pageResp.setTotal(page.getTotal());
|
||||
return pageResp;
|
||||
return new PageResp<>(BeanUtil.copyToList(page.getRecords(), targetClass), page.getTotal());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -81,10 +73,7 @@ public class PageResp<L> implements Serializable {
|
||||
if (null == page) {
|
||||
return empty();
|
||||
}
|
||||
PageResp<L> pageResp = new PageResp<>();
|
||||
pageResp.setList(page.getRecords());
|
||||
pageResp.setTotal(page.getTotal());
|
||||
return pageResp;
|
||||
return new PageResp<>(page.getRecords(), page.getTotal());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -122,24 +111,6 @@ public class PageResp<L> implements Serializable {
|
||||
* @return 分页信息
|
||||
*/
|
||||
private static <L> PageResp<L> empty() {
|
||||
PageResp<L> pageResp = new PageResp<>();
|
||||
pageResp.setList(new ArrayList<>(0));
|
||||
return pageResp;
|
||||
}
|
||||
|
||||
public List<L> getList() {
|
||||
return list;
|
||||
}
|
||||
|
||||
public void setList(List<L> list) {
|
||||
this.list = list;
|
||||
}
|
||||
|
||||
public long getTotal() {
|
||||
return total;
|
||||
}
|
||||
|
||||
public void setTotal(long total) {
|
||||
this.total = total;
|
||||
return new PageResp<>(Collections.emptyList(), 0L);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,15 +20,15 @@ import cn.crane4j.core.support.OperateTemplate;
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.bean.copier.CopyOptions;
|
||||
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.TreeNodeConfig;
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
import cn.hutool.core.text.CharSequenceUtil;
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
@@ -38,21 +38,24 @@ import top.continew.starter.core.util.ReflectUtils;
|
||||
import top.continew.starter.core.util.validate.CheckUtils;
|
||||
import top.continew.starter.core.util.validate.ValidationUtils;
|
||||
import top.continew.starter.data.mybatis.plus.base.BaseMapper;
|
||||
import top.continew.starter.data.mybatis.plus.query.QueryWrapperHelper;
|
||||
import top.continew.starter.data.mybatis.plus.service.impl.ServiceImpl;
|
||||
import top.continew.starter.data.mybatis.plus.util.QueryWrapperHelper;
|
||||
import top.continew.starter.extension.crud.annotation.DictField;
|
||||
import top.continew.starter.extension.crud.annotation.TreeField;
|
||||
import top.continew.starter.extension.crud.model.entity.BaseIdDO;
|
||||
import top.continew.starter.extension.crud.model.query.PageQuery;
|
||||
import top.continew.starter.extension.crud.model.query.SortQuery;
|
||||
import top.continew.starter.extension.crud.model.resp.LabelValueResp;
|
||||
import top.continew.starter.extension.crud.model.resp.PageResp;
|
||||
import top.continew.starter.extension.crud.service.BaseService;
|
||||
import top.continew.starter.extension.crud.util.TreeUtils;
|
||||
import top.continew.starter.extension.crud.model.query.PageQuery;
|
||||
import top.continew.starter.file.excel.util.ExcelUtils;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 业务实现基类
|
||||
@@ -76,7 +79,8 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T extends BaseIdD
|
||||
@Override
|
||||
public PageResp<L> page(Q query, PageQuery pageQuery) {
|
||||
QueryWrapper<T> queryWrapper = this.buildQueryWrapper(query);
|
||||
IPage<T> page = baseMapper.selectPage(pageQuery.toPage(), queryWrapper);
|
||||
this.sort(queryWrapper, pageQuery);
|
||||
IPage<T> page = baseMapper.selectPage(new Page<>(pageQuery.getPage(), pageQuery.getSize()), queryWrapper);
|
||||
PageResp<L> pageResp = PageResp.build(page, this.getListClass());
|
||||
pageResp.getList().forEach(this::fill);
|
||||
return pageResp;
|
||||
@@ -253,23 +257,24 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T extends BaseIdD
|
||||
* @param sortQuery 排序查询条件
|
||||
*/
|
||||
protected void sort(QueryWrapper<T> queryWrapper, SortQuery sortQuery) {
|
||||
Sort sort = Opt.ofNullable(sortQuery).orElseGet(SortQuery::new).getSort();
|
||||
if (sortQuery == null || sortQuery.getSort().isUnsorted()) {
|
||||
return;
|
||||
}
|
||||
Sort sort = sortQuery.getSort();
|
||||
for (Sort.Order order : sort) {
|
||||
if (null != order) {
|
||||
String property = order.getProperty();
|
||||
String checkProperty;
|
||||
// 携带表别名则获取 . 后面的字段名
|
||||
if (property.contains(StringConstants.DOT)) {
|
||||
checkProperty = CollUtil.getLast(CharSequenceUtil.split(property, StringConstants.DOT));
|
||||
} else {
|
||||
checkProperty = property;
|
||||
}
|
||||
Optional<Field> optional = super.getEntityFields().stream()
|
||||
.filter(field -> checkProperty.equals(field.getName()))
|
||||
.findFirst();
|
||||
ValidationUtils.throwIf(optional.isEmpty(), "无效的排序字段 [{}]", property);
|
||||
queryWrapper.orderBy(true, order.isAscending(), CharSequenceUtil.toUnderlineCase(property));
|
||||
String property = order.getProperty();
|
||||
String checkProperty;
|
||||
// 携带表别名则获取 . 后面的字段名
|
||||
if (property.contains(StringConstants.DOT)) {
|
||||
checkProperty = CollUtil.getLast(CharSequenceUtil.split(property, StringConstants.DOT));
|
||||
} else {
|
||||
checkProperty = property;
|
||||
}
|
||||
Optional<Field> optional = super.getEntityFields().stream()
|
||||
.filter(field -> checkProperty.equals(field.getName()))
|
||||
.findFirst();
|
||||
ValidationUtils.throwIf(optional.isEmpty(), "无效的排序字段 [{}]", property);
|
||||
queryWrapper.orderBy(true, order.isAscending(), CharSequenceUtil.toUnderlineCase(property));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
top.continew.starter.log.httptracepro.autoconfigure.LogAutoConfiguration
|
||||
@@ -9,8 +9,8 @@
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>continew-starter-log-httptrace-pro</artifactId>
|
||||
<description>ContiNew Starter 日志模块 - HttpTracePro(Spring Boot Actuator HttpTrace 重置增强版)</description>
|
||||
<artifactId>continew-starter-log-interceptor</artifactId>
|
||||
<description>ContiNew Starter 日志模块 - 拦截器版(Spring Boot Actuator HttpTrace 增强版)</description>
|
||||
|
||||
<dependencies>
|
||||
<!-- Swagger 注解 -->
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.starter.log.httptracepro.autoconfigure;
|
||||
package top.continew.starter.log.interceptor.autoconfigure;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import top.continew.starter.core.constant.PropertiesConstants;
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.starter.log.httptracepro.autoconfigure;
|
||||
package top.continew.starter.log.interceptor.autoconfigure;
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import org.slf4j.Logger;
|
||||
@@ -28,8 +28,8 @@ import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
import top.continew.starter.log.core.dao.LogDao;
|
||||
import top.continew.starter.log.core.dao.impl.LogDaoDefaultImpl;
|
||||
import top.continew.starter.log.httptracepro.handler.LogFilter;
|
||||
import top.continew.starter.log.httptracepro.handler.LogInterceptor;
|
||||
import top.continew.starter.log.interceptor.handler.LogFilter;
|
||||
import top.continew.starter.log.interceptor.handler.LogInterceptor;
|
||||
|
||||
/**
|
||||
* 日志自动配置
|
||||
@@ -75,6 +75,6 @@ public class LogAutoConfiguration implements WebMvcConfigurer {
|
||||
|
||||
@PostConstruct
|
||||
public void postConstruct() {
|
||||
log.debug("[ContiNew Starter] - Auto Configuration 'Log-HttpTracePro' completed initialization.");
|
||||
log.debug("[ContiNew Starter] - Auto Configuration 'Log' completed initialization.");
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.starter.log.httptracepro.autoconfigure;
|
||||
package top.continew.starter.log.interceptor.autoconfigure;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import top.continew.starter.core.constant.PropertiesConstants;
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.starter.log.httptracepro.handler;
|
||||
package top.continew.starter.log.interceptor.handler;
|
||||
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import jakarta.servlet.FilterChain;
|
||||
@@ -29,7 +29,7 @@ import org.springframework.web.util.ContentCachingRequestWrapper;
|
||||
import org.springframework.web.util.ContentCachingResponseWrapper;
|
||||
import org.springframework.web.util.WebUtils;
|
||||
import top.continew.starter.log.core.enums.Include;
|
||||
import top.continew.starter.log.httptracepro.autoconfigure.LogProperties;
|
||||
import top.continew.starter.log.interceptor.autoconfigure.LogProperties;
|
||||
import top.continew.starter.web.util.SpringWebUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.starter.log.httptracepro.handler;
|
||||
package top.continew.starter.log.interceptor.handler;
|
||||
|
||||
import cn.hutool.core.text.CharSequenceUtil;
|
||||
import com.alibaba.ttl.TransmittableThreadLocal;
|
||||
@@ -33,7 +33,7 @@ import top.continew.starter.log.core.dao.LogDao;
|
||||
import top.continew.starter.log.core.enums.Include;
|
||||
import top.continew.starter.log.core.model.LogRecord;
|
||||
import top.continew.starter.log.core.model.LogResponse;
|
||||
import top.continew.starter.log.httptracepro.autoconfigure.LogProperties;
|
||||
import top.continew.starter.log.interceptor.autoconfigure.LogProperties;
|
||||
|
||||
import java.time.Clock;
|
||||
import java.util.Set;
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.starter.log.httptracepro.handler;
|
||||
package top.continew.starter.log.interceptor.handler;
|
||||
|
||||
import cn.hutool.core.text.CharSequenceUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.starter.log.httptracepro.handler;
|
||||
package top.continew.starter.log.interceptor.handler;
|
||||
|
||||
import cn.hutool.core.text.CharSequenceUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
@@ -22,7 +22,6 @@ import cn.hutool.json.JSONUtil;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.web.util.ContentCachingResponseWrapper;
|
||||
import org.springframework.web.util.WebUtils;
|
||||
import top.continew.starter.core.constant.StringConstants;
|
||||
import top.continew.starter.log.core.model.RecordableHttpResponse;
|
||||
import top.continew.starter.web.util.ServletUtils;
|
||||
|
||||
@@ -60,9 +59,10 @@ public final class RecordableServletHttpResponse implements RecordableHttpRespon
|
||||
ContentCachingResponseWrapper wrapper = WebUtils
|
||||
.getNativeResponse(response, ContentCachingResponseWrapper.class);
|
||||
if (null != wrapper) {
|
||||
return StrUtil.utf8Str(wrapper.getContentAsByteArray());
|
||||
String body = StrUtil.utf8Str(wrapper.getContentAsByteArray());
|
||||
return JSONUtil.isTypeJSON(body) ? body : null;
|
||||
}
|
||||
return StringConstants.EMPTY;
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -0,0 +1 @@
|
||||
top.continew.starter.log.interceptor.autoconfigure.LogAutoConfiguration
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
<modules>
|
||||
<module>continew-starter-log-core</module>
|
||||
<module>continew-starter-log-httptrace-pro</module>
|
||||
<module>continew-starter-log-interceptor</module>
|
||||
</modules>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@@ -22,7 +22,9 @@ import cn.hutool.core.util.ReflectUtil;
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Constants;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.plugin.*;
|
||||
import org.apache.ibatis.mapping.MappedStatement;
|
||||
import org.apache.ibatis.mapping.SqlCommandType;
|
||||
import org.apache.ibatis.plugin.Interceptor;
|
||||
import top.continew.starter.core.constant.StringConstants;
|
||||
import top.continew.starter.core.exception.BusinessException;
|
||||
import top.continew.starter.security.crypto.annotation.FieldEncrypt;
|
||||
@@ -46,39 +48,6 @@ public abstract class AbstractMyBatisInterceptor implements Interceptor {
|
||||
|
||||
private static final Map<String, Map<String, FieldEncrypt>> ENCRYPT_PARAM_CACHE = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* 获取加密参数
|
||||
*
|
||||
* @param mappedStatementId 映射语句 ID
|
||||
* @return 加密参数
|
||||
*/
|
||||
public Map<String, FieldEncrypt> getEncryptParams(String mappedStatementId) {
|
||||
return getEncryptParams(mappedStatementId, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取加密参数
|
||||
*
|
||||
* @param mappedStatementId 映射语句 ID
|
||||
* @param parameterIndex 参数索引
|
||||
* @return 加密参数
|
||||
*/
|
||||
public Map<String, FieldEncrypt> getEncryptParams(String mappedStatementId, Integer parameterIndex) {
|
||||
return ENCRYPT_PARAM_CACHE
|
||||
.computeIfAbsent(mappedStatementId, key -> getEncryptParamsNoCached(mappedStatementId, parameterIndex));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取参数名称
|
||||
*
|
||||
* @param parameter 参数
|
||||
* @return 参数名称
|
||||
*/
|
||||
public String getParameterName(Parameter parameter) {
|
||||
Param param = parameter.getAnnotation(Param.class);
|
||||
return null != param ? param.value() : parameter.getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有字符串类型、需要加/解密的、有值字段
|
||||
*
|
||||
@@ -114,13 +83,67 @@ public abstract class AbstractMyBatisInterceptor implements Interceptor {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取参数列表(无缓存)
|
||||
* 获取加密参数
|
||||
*
|
||||
* @param mappedStatement 映射语句
|
||||
* @return 加密参数
|
||||
*/
|
||||
public Map<String, FieldEncrypt> getEncryptParams(MappedStatement mappedStatement) {
|
||||
return getEncryptParams(mappedStatement, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取加密参数
|
||||
*
|
||||
* @param mappedStatement 映射语句
|
||||
* @param parameterCount 参数数量
|
||||
* @return 加密参数
|
||||
*/
|
||||
public Map<String, FieldEncrypt> getEncryptParams(MappedStatement mappedStatement, Integer parameterCount) {
|
||||
String mappedStatementId = mappedStatement.getId();
|
||||
SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();
|
||||
if (SqlCommandType.UPDATE != sqlCommandType) {
|
||||
return ENCRYPT_PARAM_CACHE.computeIfAbsent(mappedStatementId, key -> this
|
||||
.getEncryptParams(mappedStatementId, parameterCount));
|
||||
} else {
|
||||
return this.getEncryptParams(mappedStatementId, parameterCount);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取参数名称
|
||||
*
|
||||
* @param parameter 参数
|
||||
* @return 参数名称
|
||||
*/
|
||||
public String getParameterName(Parameter parameter) {
|
||||
Param param = parameter.getAnnotation(Param.class);
|
||||
return null != param ? param.value() : parameter.getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取加密参数列表
|
||||
*
|
||||
* @param mappedStatementId 映射语句 ID
|
||||
* @param parameterIndex 参数数量
|
||||
* @return 参数列表
|
||||
* @param parameterCount 参数数量
|
||||
* @return 加密参数列表
|
||||
*/
|
||||
private Map<String, FieldEncrypt> getEncryptParamsNoCached(String mappedStatementId, Integer parameterIndex) {
|
||||
private Map<String, FieldEncrypt> getEncryptParams(String mappedStatementId, Integer parameterCount) {
|
||||
Method method = this.getMethod(mappedStatementId, parameterCount);
|
||||
if (method == null) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
return this.getEncryptParams(method);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取映射方法
|
||||
*
|
||||
* @param mappedStatementId 映射语句 ID
|
||||
* @param parameterCount 参数数量
|
||||
* @return 映射方法
|
||||
*/
|
||||
private Method getMethod(String mappedStatementId, Integer parameterCount) {
|
||||
try {
|
||||
String className = CharSequenceUtil.subBefore(mappedStatementId, StringConstants.DOT, true);
|
||||
String wrapperMethodName = CharSequenceUtil.subAfter(mappedStatementId, StringConstants.DOT, true);
|
||||
@@ -131,35 +154,42 @@ public abstract class AbstractMyBatisInterceptor implements Interceptor {
|
||||
.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;
|
||||
if (parameterCount != null) {
|
||||
return Objects.equals(m.getName(), methodName) && m.getParameterCount() == parameterCount;
|
||||
}
|
||||
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;
|
||||
return methodOptional.orElse(null);
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new BusinessException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取加密参数列表
|
||||
*
|
||||
* @param method 方法
|
||||
* @return 加密参数列表
|
||||
*/
|
||||
private Map<String, FieldEncrypt> getEncryptParams(Method method) {
|
||||
// 获取方法中的加密参数
|
||||
Map<String, FieldEncrypt> map = MapUtil.newHashMap();
|
||||
Parameter[] parameterArr = method.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;
|
||||
}
|
||||
}
|
||||
@@ -109,8 +109,10 @@ public class MyBatisEncryptInterceptor extends AbstractMyBatisInterceptor {
|
||||
* @throws Exception /
|
||||
*/
|
||||
private void encryptMap(HashMap<String, Object> parameterMap, MappedStatement mappedStatement) throws Exception {
|
||||
Map<String, FieldEncrypt> encryptParamMap = super.getEncryptParams(mappedStatement.getId(), parameterMap
|
||||
.isEmpty() ? null : parameterMap.size() / 2);
|
||||
Map<String, FieldEncrypt> encryptParamMap = super.getEncryptParams(mappedStatement);
|
||||
if (encryptParamMap.isEmpty() && !parameterMap.isEmpty()) {
|
||||
encryptParamMap = super.getEncryptParams(mappedStatement, parameterMap.size() / 2);
|
||||
}
|
||||
for (Map.Entry<String, FieldEncrypt> encryptParamEntry : encryptParamMap.entrySet()) {
|
||||
String parameterName = encryptParamEntry.getKey();
|
||||
if (parameterName.startsWith(Constants.ENTITY)) {
|
||||
|
||||
@@ -44,6 +44,12 @@
|
||||
<artifactId>tlog-web-spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Graceful Response(一个Spring Boot技术栈下的优雅响应处理组件,可以帮助开发者完成响应数据封装、异常处理、错误码填充等过程,提高开发效率,提高代码质量) -->
|
||||
<dependency>
|
||||
<groupId>com.feiniaojin</groupId>
|
||||
<artifactId>graceful-response</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- API 文档模块 -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
|
||||
@@ -17,12 +17,12 @@
|
||||
package top.continew.starter.web.annotation;
|
||||
|
||||
import org.springframework.context.annotation.Import;
|
||||
import top.continew.starter.web.autoconfigure.exception.GlobalExceptionHandlerAutoConfiguration;
|
||||
import top.continew.starter.web.autoconfigure.response.GlobalResponseAutoConfiguration;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 全局异常、错误处理器启用注解
|
||||
* 全局响应启用注解
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 1.2.0
|
||||
@@ -30,6 +30,7 @@ import java.lang.annotation.*;
|
||||
@Target({ElementType.TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@Import({GlobalExceptionHandlerAutoConfiguration.class})
|
||||
public @interface EnableGlobalExceptionHandler {
|
||||
@Inherited
|
||||
@Import({GlobalResponseAutoConfiguration.class})
|
||||
public @interface EnableGlobalResponse {
|
||||
}
|
||||
@@ -1,48 +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.converter;
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.format.FormatterRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
/**
|
||||
* BaseEnum 参数转换自动配置
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 2.4.0
|
||||
*/
|
||||
@EnableWebMvc
|
||||
@AutoConfiguration
|
||||
public class BaseEnumConverterAutoConfiguration implements WebMvcConfigurer {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(BaseEnumConverterAutoConfiguration.class);
|
||||
|
||||
@Override
|
||||
public void addFormatters(FormatterRegistry registry) {
|
||||
registry.addConverterFactory(new BaseEnumConverterFactory());
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void postConstruct() {
|
||||
log.debug("[ContiNew Starter] - Auto Configuration 'Web-BaseEnum Converter' completed initialization.");
|
||||
}
|
||||
}
|
||||
@@ -1,94 +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.exception;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.boot.autoconfigure.web.ServerProperties;
|
||||
import org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController;
|
||||
import org.springframework.boot.autoconfigure.web.servlet.error.ErrorViewResolver;
|
||||
import org.springframework.boot.web.servlet.error.ErrorAttributes;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
import top.continew.starter.web.model.R;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 全局错误处理器
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@RestController
|
||||
public class GlobalErrorHandler extends BasicErrorController {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(GlobalErrorHandler.class);
|
||||
|
||||
@Resource
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
public GlobalErrorHandler(ErrorAttributes errorAttributes,
|
||||
ServerProperties serverProperties,
|
||||
List<ErrorViewResolver> errorViewResolvers) {
|
||||
super(errorAttributes, serverProperties.getError(), errorViewResolvers);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
|
||||
Map<String, Object> errorAttributeMap = super.getErrorAttributes(request, super.getErrorAttributeOptions(request, MediaType.TEXT_HTML));
|
||||
String path = (String)errorAttributeMap.get("path");
|
||||
HttpStatus status = super.getStatus(request);
|
||||
R<Object> result = R.fail(status.value(), (String)errorAttributeMap.get("error"));
|
||||
result.setData(path);
|
||||
try {
|
||||
response.setStatus(HttpStatus.OK.value());
|
||||
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
|
||||
objectMapper.writeValue(response.getWriter(), result);
|
||||
} catch (IOException e) {
|
||||
log.error("请求地址 [{}],默认错误处理时发生 IO 异常。", path, e);
|
||||
}
|
||||
if (log.isErrorEnabled()) {
|
||||
log.error("请求地址 [{}],发生错误,错误信息:{}。", path, JSONUtil.toJsonStr(errorAttributeMap));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
|
||||
Map<String, Object> errorAttributeMap = super.getErrorAttributes(request, super.getErrorAttributeOptions(request, MediaType.ALL));
|
||||
String path = (String)errorAttributeMap.get("path");
|
||||
HttpStatus status = super.getStatus(request);
|
||||
R<Object> result = R.fail(status.value(), (String)errorAttributeMap.get("error"));
|
||||
result.setData(path);
|
||||
if (log.isErrorEnabled()) {
|
||||
log.error("请求地址 [{}],发生错误,错误信息:{}。", path, JSONUtil.toJsonStr(errorAttributeMap));
|
||||
}
|
||||
return new ResponseEntity<>(BeanUtil.beanToMap(result), HttpStatus.OK);
|
||||
}
|
||||
}
|
||||
@@ -1,199 +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.exception;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.NumberUtil;
|
||||
import cn.hutool.core.text.CharSequenceUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.validation.ConstraintViolation;
|
||||
import jakarta.validation.ConstraintViolationException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.context.support.DefaultMessageSourceResolvable;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.validation.BindException;
|
||||
import org.springframework.web.HttpRequestMethodNotSupportedException;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
|
||||
import org.springframework.web.multipart.MultipartException;
|
||||
import top.continew.starter.core.constant.StringConstants;
|
||||
import top.continew.starter.core.exception.BadRequestException;
|
||||
import top.continew.starter.core.exception.BusinessException;
|
||||
import top.continew.starter.core.exception.GlobalException;
|
||||
import top.continew.starter.core.exception.ResultInfoInterface;
|
||||
import top.continew.starter.web.autoconfigure.i18n.I18nProperties;
|
||||
import top.continew.starter.web.model.R;
|
||||
import top.continew.starter.core.util.MessageSourceUtils;
|
||||
|
||||
/**
|
||||
* 全局异常处理器
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 1.1.0
|
||||
*/
|
||||
@RestControllerAdvice
|
||||
public class GlobalExceptionHandler {
|
||||
private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
|
||||
|
||||
private static final String PARAM_FAILED = "请求地址 [{}],参数验证失败。";
|
||||
@Resource
|
||||
private I18nProperties i18nProperties;
|
||||
|
||||
/**
|
||||
* 拦截自定义验证异常-错误请求
|
||||
*/
|
||||
@ExceptionHandler(BadRequestException.class)
|
||||
public R<Void> handleBadRequestException(BadRequestException e, HttpServletRequest request) {
|
||||
log.warn("请求地址 [{}],自定义验证失败。", request.getRequestURI(), e);
|
||||
return R.fail(HttpStatus.BAD_REQUEST.value(), e.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* 拦截校验异常-违反约束异常
|
||||
*/
|
||||
@ExceptionHandler(ConstraintViolationException.class)
|
||||
public R<Void> constraintViolationException(ConstraintViolationException e, HttpServletRequest request) {
|
||||
log.warn(PARAM_FAILED, request.getRequestURI(), e);
|
||||
String errorMsg = CollUtil.join(e
|
||||
.getConstraintViolations(), StringConstants.CHINESE_COMMA, ConstraintViolation::getMessage);
|
||||
return R.fail(HttpStatus.BAD_REQUEST.value(), errorMsg);
|
||||
}
|
||||
|
||||
/**
|
||||
* 拦截校验异常-绑定异常
|
||||
*/
|
||||
@ExceptionHandler(BindException.class)
|
||||
public R<Void> handleBindException(BindException e, HttpServletRequest request) {
|
||||
log.warn(PARAM_FAILED, request.getRequestURI(), e);
|
||||
String errorMsg = CollUtil.join(e
|
||||
.getAllErrors(), StringConstants.CHINESE_COMMA, DefaultMessageSourceResolvable::getDefaultMessage);
|
||||
return R.fail(HttpStatus.BAD_REQUEST.value(), errorMsg);
|
||||
}
|
||||
|
||||
/**
|
||||
* 拦截校验异常-方法参数无效异常
|
||||
*/
|
||||
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||||
public R<Void> handleMethodArgumentNotValidException(MethodArgumentNotValidException e,
|
||||
HttpServletRequest request) {
|
||||
log.warn(PARAM_FAILED, request.getRequestURI(), e);
|
||||
String errorMsg = CollUtil.join(e
|
||||
.getAllErrors(), StringConstants.CHINESE_COMMA, DefaultMessageSourceResolvable::getDefaultMessage);
|
||||
return R.fail(HttpStatus.BAD_REQUEST.value(), errorMsg);
|
||||
}
|
||||
|
||||
/**
|
||||
* 拦截校验异常-方法参数类型不匹配异常
|
||||
*/
|
||||
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
|
||||
public R<Void> handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e,
|
||||
HttpServletRequest request) {
|
||||
String errorMsg = CharSequenceUtil.format("参数名:[{}],期望参数类型:[{}]", e.getName(), e.getParameter()
|
||||
.getParameterType());
|
||||
log.warn("请求地址 [{}],参数转换失败,{}。", request.getRequestURI(), errorMsg, e);
|
||||
return R.fail(HttpStatus.BAD_REQUEST.value(), errorMsg);
|
||||
}
|
||||
|
||||
/**
|
||||
* 拦截文件上传异常-超过上传大小限制
|
||||
*/
|
||||
@ExceptionHandler(MultipartException.class)
|
||||
public R<Void> handleRequestTooBigException(MultipartException e, HttpServletRequest request) {
|
||||
String msg = e.getMessage();
|
||||
R<Void> defaultFail = R.fail(HttpStatus.BAD_REQUEST.value(), msg);
|
||||
if (CharSequenceUtil.isBlank(msg)) {
|
||||
return defaultFail;
|
||||
}
|
||||
|
||||
String sizeLimit;
|
||||
Throwable cause = e.getCause();
|
||||
if (null != cause) {
|
||||
msg = msg.concat(cause.getMessage().toLowerCase());
|
||||
}
|
||||
if (msg.contains("size") && msg.contains("exceed")) {
|
||||
sizeLimit = CharSequenceUtil.subBetween(msg, "the maximum size ", " for");
|
||||
} else if (msg.contains("larger than")) {
|
||||
sizeLimit = CharSequenceUtil.subAfter(msg, "larger than ", true);
|
||||
} else {
|
||||
return defaultFail;
|
||||
}
|
||||
|
||||
String errorMsg = "请上传小于 %sKB 的文件".formatted(NumberUtil.parseLong(sizeLimit) / 1024);
|
||||
log.warn("请求地址 [{}],上传文件失败,文件大小超过限制。", request.getRequestURI(), e);
|
||||
return R.fail(HttpStatus.BAD_REQUEST.value(), errorMsg);
|
||||
}
|
||||
|
||||
/**
|
||||
* 拦截校验异常-请求方式不支持异常
|
||||
*/
|
||||
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
|
||||
public R<Void> handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException e,
|
||||
HttpServletRequest request) {
|
||||
log.error("请求地址 [{}],不支持 [{}] 请求。", request.getRequestURI(), e.getMethod());
|
||||
return R.fail(HttpStatus.METHOD_NOT_ALLOWED.value(), e.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* 拦截业务异常
|
||||
*/
|
||||
@ExceptionHandler(BusinessException.class)
|
||||
public R<Void> handleServiceException(BusinessException e, HttpServletRequest request) {
|
||||
log.error("请求地址 [{}],发生业务异常。", request.getRequestURI(), e);
|
||||
return R.fail(HttpStatus.INTERNAL_SERVER_ERROR.value(), e.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* 拦截全局应用异常
|
||||
*/
|
||||
@ExceptionHandler(GlobalException.class)
|
||||
public R<Void> handleGlobalException(GlobalException e, HttpServletRequest request) {
|
||||
log.error("请求地址 [{}],发生业务异常。", request.getRequestURI(), e);
|
||||
ResultInfoInterface resultInfo = e.getResultInfo();
|
||||
// 未开启,直接返回
|
||||
if (!i18nProperties.getEnabled()) {
|
||||
return R.fail(resultInfo.getCode(), resultInfo.getDefaultMessage());
|
||||
}
|
||||
// 以用户自定的messageKey优先,否则枚举当messageKey
|
||||
String messageKey = StrUtil.blankToDefault(resultInfo.getMessageKey(), resultInfo.toString());
|
||||
String message = MessageSourceUtils.getMessage(messageKey, resultInfo.getDefaultMessage());
|
||||
return R.fail(resultInfo.getCode(), message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 拦截未知的运行时异常
|
||||
*/
|
||||
@ExceptionHandler(RuntimeException.class)
|
||||
public R<Void> handleRuntimeException(RuntimeException e, HttpServletRequest request) {
|
||||
log.error("请求地址 [{}],发生系统异常。", request.getRequestURI(), e);
|
||||
return R.fail(e.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* 拦截未知的系统异常
|
||||
*/
|
||||
@ExceptionHandler(Throwable.class)
|
||||
public R<Void> handleException(Throwable e, HttpServletRequest request) {
|
||||
log.error("请求地址 [{}],发生未知异常。", request.getRequestURI(), e);
|
||||
return R.fail(e.getMessage());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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.exception;
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import top.continew.starter.web.autoconfigure.i18n.I18nProperties;
|
||||
|
||||
/**
|
||||
* 全局异常处理器自动配置
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnMissingBean(BasicErrorController.class)
|
||||
@Import({GlobalExceptionHandler.class, GlobalErrorHandler.class})
|
||||
@EnableConfigurationProperties(I18nProperties.class)
|
||||
public class GlobalExceptionHandlerAutoConfiguration {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandlerAutoConfiguration.class);
|
||||
|
||||
@PostConstruct
|
||||
public void postConstruct() {
|
||||
log.debug("[ContiNew Starter] - Auto Configuration 'Web-Global Exception Handler' completed initialization.");
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.starter.web.autoconfigure.converter;
|
||||
package top.continew.starter.web.autoconfigure.mvc;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import top.continew.starter.core.enums.BaseEnum;
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.starter.web.autoconfigure.converter;
|
||||
package top.continew.starter.web.autoconfigure.mvc;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.core.convert.converter.ConverterFactory;
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||
* <p>
|
||||
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.gnu.org/licenses/lgpl.html
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.starter.web.autoconfigure.mvc;
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.format.FormatterRegistry;
|
||||
import org.springframework.http.converter.ByteArrayHttpMessageConverter;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
|
||||
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Web MVC 自动配置
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 2.4.0
|
||||
*/
|
||||
@EnableWebMvc
|
||||
@AutoConfiguration
|
||||
public class WebMvcAutoConfiguration implements WebMvcConfigurer {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(WebMvcAutoConfiguration.class);
|
||||
private final MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter;
|
||||
|
||||
public WebMvcAutoConfiguration(MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter) {
|
||||
this.mappingJackson2HttpMessageConverter = mappingJackson2HttpMessageConverter;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解决 Jackson2ObjectMapperBuilderCustomizer 配置不生效的问题
|
||||
* <p>
|
||||
* MappingJackson2HttpMessageConverter 对象在程序启动时创建了多个,移除多余的,保证只有一个
|
||||
* </p>
|
||||
*/
|
||||
@Override
|
||||
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
|
||||
converters.removeIf(MappingJackson2HttpMessageConverter.class::isInstance);
|
||||
if (Objects.isNull(mappingJackson2HttpMessageConverter)) {
|
||||
converters.add(0, new MappingJackson2HttpMessageConverter());
|
||||
} else {
|
||||
converters.add(0, mappingJackson2HttpMessageConverter);
|
||||
}
|
||||
// 自定义 converters 时,需要手动在最前面添加 ByteArrayHttpMessageConverter
|
||||
// 否则 Spring Doc OpenAPI 的 /*/api-docs/**(例如:/v3/api-docs/default)接口响应内容会变为 Base64 编码后的内容,最终导致接口文档解析失败
|
||||
// 详情请参阅:https://github.com/springdoc/springdoc-openapi/issues/2143
|
||||
converters.add(0, new ByteArrayHttpMessageConverter());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addFormatters(FormatterRegistry registry) {
|
||||
registry.addConverterFactory(new BaseEnumConverterFactory());
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void postConstruct() {
|
||||
log.debug("[ContiNew Starter] - Auto Configuration 'Web MVC' completed initialization.");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* 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 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 static final Class<R> R_TYPE = R.class;
|
||||
|
||||
/**
|
||||
* 获取返回类型
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
// 如果为 R<T> 则直接返回
|
||||
if (returnType.getTypeName().contains("top.continew.starter.web.model.R")) {
|
||||
return returnType;
|
||||
}
|
||||
// 如果是 void类型,则返回 R<Void>
|
||||
if (returnType == void.class || returnType == Void.class) {
|
||||
return TypeUtils.parameterize(R_TYPE, Void.class);
|
||||
}
|
||||
// 返回 R<T>
|
||||
return TypeUtils.parameterize(R_TYPE, returnType);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
* 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.ExceptionAliasRegister;
|
||||
import com.feiniaojin.gracefulresponse.advice.*;
|
||||
import com.feiniaojin.gracefulresponse.api.ResponseFactory;
|
||||
import com.feiniaojin.gracefulresponse.api.ResponseStatusFactory;
|
||||
import com.feiniaojin.gracefulresponse.defaults.DefaultResponseFactory;
|
||||
import com.feiniaojin.gracefulresponse.defaults.DefaultResponseStatusFactoryImpl;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.MessageSource;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.PropertySource;
|
||||
import org.springframework.context.support.ResourceBundleMessageSource;
|
||||
import top.continew.starter.core.constant.PropertiesConstants;
|
||||
import top.continew.starter.core.util.GeneralPropertySourceFactory;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* 全局响应自动配置
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@EnableConfigurationProperties(GlobalResponseProperties.class)
|
||||
@PropertySource(value = "classpath:default-web.yml", factory = GeneralPropertySourceFactory.class)
|
||||
public class GlobalResponseAutoConfiguration {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(GlobalResponseAutoConfiguration.class);
|
||||
|
||||
/**
|
||||
* 全局异常处理
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public GrGlobalExceptionAdvice globalExceptionAdvice() {
|
||||
return new GrGlobalExceptionAdvice();
|
||||
}
|
||||
|
||||
/**
|
||||
* 全局校验异常处理
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public GrValidationExceptionAdvice validationExceptionAdvice() {
|
||||
return new GrValidationExceptionAdvice();
|
||||
}
|
||||
|
||||
/**
|
||||
* 全局响应体处理(非 void)
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public GrNotVoidResponseBodyAdvice notVoidResponseBodyAdvice() {
|
||||
return new GrNotVoidResponseBodyAdvice();
|
||||
}
|
||||
|
||||
/**
|
||||
* 全局响应体处理(void)
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public GrVoidResponseBodyAdvice voidResponseBodyAdvice() {
|
||||
return new GrVoidResponseBodyAdvice();
|
||||
}
|
||||
|
||||
/**
|
||||
* 响应工厂
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public ResponseFactory responseBeanFactory() {
|
||||
return new DefaultResponseFactory();
|
||||
}
|
||||
|
||||
/**
|
||||
* 响应状态工厂
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public ResponseStatusFactory responseStatusFactory() {
|
||||
return new DefaultResponseStatusFactoryImpl();
|
||||
}
|
||||
|
||||
/**
|
||||
* 异常别名注册
|
||||
*/
|
||||
@Bean
|
||||
public ExceptionAliasRegister exceptionAliasRegister() {
|
||||
return new ExceptionAliasRegister();
|
||||
}
|
||||
|
||||
/**
|
||||
* 响应支持
|
||||
*/
|
||||
@Bean
|
||||
public AdviceSupport adviceSupport() {
|
||||
return new AdviceSupport();
|
||||
}
|
||||
|
||||
/**
|
||||
* 国际化支持
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnProperty(prefix = PropertiesConstants.WEB_RESPONSE, name = "i18n", havingValue = "true")
|
||||
public GrI18nAdvice i18nAdvice() {
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* SpringDoc 全局响应处理器
|
||||
*
|
||||
* @return {@link ApiDocGlobalResponseHandler }
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public ApiDocGlobalResponseHandler apiDocGlobalResponseHandler() {
|
||||
return new ApiDocGlobalResponseHandler();
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void postConstruct() {
|
||||
log.debug("[ContiNew Starter] - Auto Configuration 'Web-Global Response' completed initialization.");
|
||||
}
|
||||
}
|
||||
@@ -14,30 +14,18 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.starter.web.autoconfigure.i18n;
|
||||
package top.continew.starter.web.autoconfigure.response;
|
||||
|
||||
import com.feiniaojin.gracefulresponse.GracefulResponseProperties;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import top.continew.starter.core.constant.PropertiesConstants;
|
||||
|
||||
/**
|
||||
* 国际化配置属性
|
||||
* 全局响应配置属性
|
||||
*
|
||||
* @author Jasmine
|
||||
* @since 2.2.0
|
||||
* @author Charles7c
|
||||
* @since 2.5.0
|
||||
*/
|
||||
@ConfigurationProperties(PropertiesConstants.WEB_I18N)
|
||||
public class I18nProperties {
|
||||
|
||||
/**
|
||||
* 国际化开启 true-开启, false-关闭
|
||||
*/
|
||||
private Boolean enabled;
|
||||
|
||||
public Boolean getEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(Boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
@ConfigurationProperties(PropertiesConstants.WEB_RESPONSE)
|
||||
public class GlobalResponseProperties extends GracefulResponseProperties {
|
||||
}
|
||||
@@ -16,12 +16,12 @@
|
||||
|
||||
package top.continew.starter.web.model;
|
||||
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.feiniaojin.gracefulresponse.data.Response;
|
||||
import com.feiniaojin.gracefulresponse.data.ResponseStatus;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import top.continew.starter.web.autoconfigure.response.GlobalResponseProperties;
|
||||
|
||||
/**
|
||||
* 响应信息
|
||||
@@ -30,13 +30,25 @@ import java.io.Serializable;
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@Schema(description = "响应信息")
|
||||
public class R<T> implements Serializable {
|
||||
public class R<T> implements Response {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final GlobalResponseProperties PROPERTIES = SpringUtil.getBean(GlobalResponseProperties.class);
|
||||
private static final String DEFAULT_SUCCESS_CODE = PROPERTIES.getDefaultSuccessCode();
|
||||
private static final String DEFAULT_SUCCESS_MSG = PROPERTIES.getDefaultSuccessMsg();
|
||||
private static final String DEFAULT_ERROR_CODE = PROPERTIES.getDefaultErrorCode();
|
||||
private static final String DEFAULT_ERROR_MSG = PROPERTIES.getDefaultErrorMsg();
|
||||
|
||||
private static final int SUCCESS_CODE = HttpStatus.OK.value();
|
||||
private static final int FAIL_CODE = HttpStatus.INTERNAL_SERVER_ERROR.value();
|
||||
/**
|
||||
* 状态码
|
||||
*/
|
||||
@Schema(description = "状态码", example = "1")
|
||||
private String code;
|
||||
|
||||
/**
|
||||
* 状态信息
|
||||
*/
|
||||
@Schema(description = "状态信息", example = "ok")
|
||||
private String msg;
|
||||
|
||||
/**
|
||||
* 是否成功
|
||||
@@ -45,16 +57,10 @@ public class R<T> implements Serializable {
|
||||
private boolean success;
|
||||
|
||||
/**
|
||||
* 业务状态码
|
||||
* 时间戳
|
||||
*/
|
||||
@Schema(description = "业务状态码", example = "200")
|
||||
private int code;
|
||||
|
||||
/**
|
||||
* 业务状态信息
|
||||
*/
|
||||
@Schema(description = "业务状态信息", example = "操作成功")
|
||||
private String msg;
|
||||
@Schema(description = "时间戳", example = "1691453288000")
|
||||
private final Long timestamp = System.currentTimeMillis();
|
||||
|
||||
/**
|
||||
* 响应数据
|
||||
@@ -62,136 +68,49 @@ public class R<T> implements Serializable {
|
||||
@Schema(description = "响应数据")
|
||||
private T data;
|
||||
|
||||
/**
|
||||
* 时间戳
|
||||
*/
|
||||
@Schema(description = "时间戳", example = "1691453288")
|
||||
private long timestamp = DateUtil.currentSeconds();
|
||||
|
||||
private R() {
|
||||
public R() {
|
||||
}
|
||||
|
||||
private R(boolean success, int code, String msg, T data) {
|
||||
this.success = success;
|
||||
this.code = code;
|
||||
this.msg = msg;
|
||||
public R(String code, String msg) {
|
||||
this.setCode(code);
|
||||
this.setMsg(msg);
|
||||
}
|
||||
|
||||
public R(String code, String msg, T data) {
|
||||
this(code, msg);
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 操作成功
|
||||
*
|
||||
* @param <T> 响应数据类型
|
||||
* @return R /
|
||||
*/
|
||||
public static <T> R<T> ok() {
|
||||
return new R<>(true, SUCCESS_CODE, "操作成功", null);
|
||||
@Override
|
||||
public void setStatus(ResponseStatus status) {
|
||||
this.setCode(status.getCode());
|
||||
this.setMsg(status.getMsg());
|
||||
}
|
||||
|
||||
/**
|
||||
* 操作成功
|
||||
*
|
||||
* @param data 响应数据
|
||||
* @param <T> 响应数据类型
|
||||
* @return R /
|
||||
*/
|
||||
public static <T> R<T> ok(T data) {
|
||||
return new R<>(true, SUCCESS_CODE, "操作成功", data);
|
||||
@Override
|
||||
@JsonIgnore
|
||||
public ResponseStatus getStatus() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 操作成功
|
||||
*
|
||||
* @param msg 业务状态信息
|
||||
* @param <T> 响应数据类型
|
||||
* @return R /
|
||||
*/
|
||||
public static <T> R<T> ok(String msg) {
|
||||
return new R<>(true, SUCCESS_CODE, msg, null);
|
||||
@Override
|
||||
public void setPayload(Object payload) {
|
||||
this.data = (T)payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* 操作成功
|
||||
*
|
||||
* @param msg 业务状态信息
|
||||
* @param data 响应数据
|
||||
* @param <T> 响应数据类型
|
||||
* @return R /
|
||||
*/
|
||||
public static <T> R<T> ok(String msg, T data) {
|
||||
return new R<>(true, SUCCESS_CODE, msg, data);
|
||||
@Override
|
||||
@JsonIgnore
|
||||
public Object getPayload() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 操作失败
|
||||
*
|
||||
* @param <T> 响应数据类型
|
||||
* @return R /
|
||||
*/
|
||||
public static <T> R<T> fail() {
|
||||
return new R<>(false, FAIL_CODE, "操作失败", null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 操作失败
|
||||
*
|
||||
* @param msg 业务状态信息
|
||||
* @param <T> 响应数据类型
|
||||
* @return R /
|
||||
*/
|
||||
public static <T> R<T> fail(String msg) {
|
||||
return new R<>(false, FAIL_CODE, msg, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 操作失败
|
||||
*
|
||||
* @param data 响应数据
|
||||
* @param <T> 响应数据类型
|
||||
* @return R /
|
||||
*/
|
||||
public static <T> R<T> fail(T data) {
|
||||
return new R<>(false, FAIL_CODE, "操作失败", data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 操作失败
|
||||
*
|
||||
* @param msg 业务状态信息
|
||||
* @param data 响应数据
|
||||
* @param <T> 响应数据类型
|
||||
* @return R /
|
||||
*/
|
||||
public static <T> R<T> fail(String msg, T data) {
|
||||
return new R<>(false, FAIL_CODE, msg, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 操作失败
|
||||
*
|
||||
* @param code 业务状态码
|
||||
* @param msg 业务状态信息
|
||||
* @param <T> 响应数据类型
|
||||
* @return R /
|
||||
*/
|
||||
public static <T> R<T> fail(int code, String msg) {
|
||||
return new R<>(false, code, msg, null);
|
||||
}
|
||||
|
||||
public boolean isSuccess() {
|
||||
return success;
|
||||
}
|
||||
|
||||
public void setSuccess(boolean success) {
|
||||
this.success = success;
|
||||
}
|
||||
|
||||
public int getCode() {
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public void setCode(int code) {
|
||||
public void setCode(String code) {
|
||||
this.code = code;
|
||||
this.success = DEFAULT_SUCCESS_CODE.equals(code);
|
||||
}
|
||||
|
||||
public String getMsg() {
|
||||
@@ -210,11 +129,65 @@ public class R<T> implements Serializable {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public long getTimestamp() {
|
||||
public boolean isSuccess() {
|
||||
return success;
|
||||
}
|
||||
|
||||
public void setSuccess(boolean success) {
|
||||
this.success = success;
|
||||
}
|
||||
|
||||
public Long getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
public void setTimestamp(long timestamp) {
|
||||
this.timestamp = timestamp;
|
||||
/**
|
||||
* 操作成功
|
||||
*
|
||||
* @return R /
|
||||
*/
|
||||
public static R ok() {
|
||||
return new R(DEFAULT_SUCCESS_CODE, DEFAULT_SUCCESS_MSG);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 操作成功
|
||||
*
|
||||
* @param data 响应数据
|
||||
* @return R /
|
||||
*/
|
||||
public static R ok(Object data) {
|
||||
return new R(DEFAULT_SUCCESS_CODE, DEFAULT_SUCCESS_MSG, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 操作成功
|
||||
*
|
||||
* @param msg 业务状态信息
|
||||
* @param data 响应数据
|
||||
* @return R /
|
||||
*/
|
||||
public static R ok(String msg, Object data) {
|
||||
return new R(DEFAULT_SUCCESS_CODE, msg, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 操作失败
|
||||
*
|
||||
* @return R /
|
||||
*/
|
||||
public static R fail() {
|
||||
return new R(DEFAULT_ERROR_CODE, DEFAULT_ERROR_MSG);
|
||||
}
|
||||
|
||||
/**
|
||||
* 操作失败
|
||||
*
|
||||
* @param code 业务状态码
|
||||
* @param msg 业务状态信息
|
||||
* @return R /
|
||||
*/
|
||||
public static R fail(String code, String msg) {
|
||||
return new R(code, msg);
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,7 @@ import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.io.file.FileNameUtil;
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import cn.hutool.core.util.URLUtil;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -29,8 +29,8 @@ import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
@@ -83,27 +83,29 @@ public class FileUploadUtils {
|
||||
/**
|
||||
* 下载
|
||||
*
|
||||
* @param request 请求对象
|
||||
* @param response 响应对象
|
||||
* @param file 文件
|
||||
* @param autoDelete 下载后自动删除
|
||||
* @param response 响应对象
|
||||
* @param file 文件
|
||||
*/
|
||||
public static void download(HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
File file,
|
||||
boolean autoDelete) {
|
||||
response.setCharacterEncoding(request.getCharacterEncoding());
|
||||
public static void download(HttpServletResponse response, File file) throws IOException {
|
||||
download(response, new FileInputStream(file), file.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载
|
||||
*
|
||||
* @param response 响应对象
|
||||
* @param inputStream 文件流
|
||||
* @param fileName 文件名
|
||||
* @since 2.5.0
|
||||
*/
|
||||
public static void download(HttpServletResponse response,
|
||||
InputStream inputStream,
|
||||
String fileName) throws IOException {
|
||||
byte[] bytes = IoUtil.readBytes(inputStream);
|
||||
response.setCharacterEncoding(StandardCharsets.UTF_8.toString());
|
||||
response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
|
||||
response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + file.getName());
|
||||
try (FileInputStream fis = new FileInputStream(file)) {
|
||||
IoUtil.copy(fis, response.getOutputStream());
|
||||
response.flushBuffer();
|
||||
} catch (Exception e) {
|
||||
log.error(e.getMessage(), e);
|
||||
} finally {
|
||||
if (autoDelete) {
|
||||
file.deleteOnExit();
|
||||
}
|
||||
}
|
||||
response.setContentLength(bytes.length);
|
||||
response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + URLUtil.encode(fileName));
|
||||
IoUtil.write(response.getOutputStream(), true, bytes);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
top.continew.starter.web.autoconfigure.converter.BaseEnumConverterAutoConfiguration
|
||||
top.continew.starter.web.autoconfigure.mvc.WebMvcAutoConfiguration
|
||||
top.continew.starter.web.autoconfigure.cors.CorsAutoConfiguration
|
||||
top.continew.starter.web.autoconfigure.trace.TraceAutoConfiguration
|
||||
top.continew.starter.web.autoconfigure.xss.XssAutoConfiguration
|
||||
|
||||
23
continew-starter-web/src/main/resources/default-web.yml
Normal file
23
continew-starter-web/src/main/resources/default-web.yml
Normal file
@@ -0,0 +1,23 @@
|
||||
--- ### 响应配置
|
||||
continew-starter.web.response:
|
||||
# 是否开启国际化(默认:false)
|
||||
i18n: false
|
||||
# 响应类全名(配置后 response-style 将不再生效)
|
||||
response-class-full-name: top.continew.starter.web.model.R
|
||||
# 自定义成功响应码(默认:0)
|
||||
default-success-code: 0
|
||||
# 自定义成功提示(默认:ok)
|
||||
default-success-msg: ok
|
||||
# 自定义失败响应码(默认:1)
|
||||
default-error-code: 1
|
||||
# 自定义失败提示(默认:error)
|
||||
default-error-msg: error
|
||||
# 是否打印异常日志(默认:false)
|
||||
print-exception-in-global-advice: true
|
||||
# 是否将原生异常错误信息填充到状态信息中(默认:false)
|
||||
origin-exception-using-detail-message: true
|
||||
# 例外包路径(支持数字, * 和 ** 通配符匹配),该包路径下的 Controller 将被忽略处理
|
||||
exclude-packages:
|
||||
- io.swagger.**
|
||||
- org.springdoc.**
|
||||
- org.springframework.boot.actuate.*
|
||||
Reference in New Issue
Block a user