初始化项目
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.xujun</groupId>
|
||||
<artifactId>xtools-boot</artifactId>
|
||||
<version>5.1.0</version>
|
||||
</parent>
|
||||
<packaging>pom</packaging>
|
||||
<artifactId>xtools-boot-web</artifactId>
|
||||
|
||||
<!-- 子模块 -->
|
||||
<modules>
|
||||
<module>xtools-boot-web-base</module>
|
||||
<module>xtools-boot-web-filter</module>
|
||||
</modules>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,61 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.xujun</groupId>
|
||||
<artifactId>xtools-boot-web</artifactId>
|
||||
<version>5.1.0</version>
|
||||
</parent>
|
||||
<artifactId>xtools-boot-web-base</artifactId>
|
||||
|
||||
<!-- 依赖 -->
|
||||
<dependencies>
|
||||
<!-- xtools begin -->
|
||||
<!-- xtools-web 模块 -->
|
||||
<dependency>
|
||||
<groupId>org.xujun</groupId>
|
||||
<artifactId>xtools-web</artifactId>
|
||||
</dependency>
|
||||
<!-- xtools end -->
|
||||
|
||||
<!-- xtools-boot begin -->
|
||||
<!-- xtools-boot-core -->
|
||||
<dependency>
|
||||
<groupId>org.xujun</groupId>
|
||||
<artifactId>xtools-boot-core</artifactId>
|
||||
</dependency>
|
||||
<!-- xtools-boot-log -->
|
||||
<dependency>
|
||||
<groupId>org.xujun</groupId>
|
||||
<artifactId>xtools-boot-log</artifactId>
|
||||
</dependency>
|
||||
<!-- xtools-boot-web-filter -->
|
||||
<dependency>
|
||||
<groupId>org.xujun</groupId>
|
||||
<artifactId>xtools-boot-web-filter</artifactId>
|
||||
</dependency>
|
||||
<!-- xtools-boot end -->
|
||||
|
||||
<!-- SpringBoot begin -->
|
||||
<!-- SpringBoot-Web -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<!-- SpringBoot-Validation -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
</dependency>
|
||||
<!-- SpringBoot end -->
|
||||
|
||||
<!-- fastjson2 -->
|
||||
<dependency>
|
||||
<groupId>com.alibaba.fastjson2</groupId>
|
||||
<artifactId>fastjson2</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package xtools.boot.web.base;
|
||||
|
||||
import org.springframework.context.annotation.Import;
|
||||
import xtools.boot.core.utils.ModuleLoadUtils;
|
||||
import xtools.boot.web.base.selector.BootWebBaseImportSelector;
|
||||
|
||||
/**
|
||||
* <p>Title : BootWebBaseConfiguration</p>
|
||||
* <p>Description : BootWebBaseConfiguration</p>
|
||||
* <p>DevelopTools : Idea_x64_v2026.1</p>
|
||||
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
|
||||
* <p>Company : org.xujun</p>
|
||||
*
|
||||
* @author : XuJun
|
||||
* @version : 5.0.0
|
||||
* @date : 2026/01/01 09:30
|
||||
*/
|
||||
@Import(BootWebBaseImportSelector.class)
|
||||
public class BootWebBaseConfiguration {
|
||||
|
||||
/**
|
||||
* 构造方法
|
||||
*/
|
||||
public BootWebBaseConfiguration() {
|
||||
ModuleLoadUtils.loadSuccess(BootWebBaseConfiguration.class);
|
||||
}
|
||||
|
||||
}
|
||||
+135
@@ -0,0 +1,135 @@
|
||||
package xtools.boot.web.base.config;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import tools.jackson.core.JacksonException;
|
||||
import tools.jackson.core.JsonGenerator;
|
||||
import tools.jackson.core.JsonParser;
|
||||
import tools.jackson.databind.BeanProperty;
|
||||
import tools.jackson.databind.DeserializationContext;
|
||||
import tools.jackson.databind.SerializationContext;
|
||||
import tools.jackson.databind.ValueDeserializer;
|
||||
import tools.jackson.databind.ValueSerializer;
|
||||
import tools.jackson.databind.json.JsonMapper;
|
||||
import tools.jackson.databind.module.SimpleModule;
|
||||
import xtools.boot.api.anntation.IgnoreXss;
|
||||
import xtools.boot.core.utils.TimeUtils;
|
||||
import xtools.core.time.InstantUtils;
|
||||
import xtools.web.xss.XssUtils;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* <p>Title : JacksonConfig</p>
|
||||
* <p>Description : JacksonConfig</p>
|
||||
* <p>DevelopTools : Idea_x64_v2026.1</p>
|
||||
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
|
||||
* <p>Company : org.xujun</p>
|
||||
*
|
||||
* @author : XuJun
|
||||
* @version : 5.0.0
|
||||
* @date : 2026/1/6 12:15
|
||||
*/
|
||||
@Configuration
|
||||
public class JacksonConfig {
|
||||
|
||||
/**
|
||||
* 忽略 null 值
|
||||
*/
|
||||
public static final JsonMapper NON_NULL;
|
||||
|
||||
// 初始化
|
||||
static {
|
||||
NON_NULL = JsonMapper.builder().changeDefaultPropertyInclusion(handler -> handler.withValueInclusion(JsonInclude.Include.NON_NULL)).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建 SimpleModule
|
||||
*
|
||||
* @return SimpleModule
|
||||
*/
|
||||
@Bean
|
||||
public SimpleModule simpleModule() {
|
||||
SimpleModule module = new SimpleModule();
|
||||
|
||||
// 序列化
|
||||
module.addSerializer(Long.class, new ValueSerializer<>() {
|
||||
@Override
|
||||
public void serialize(Long value, JsonGenerator gen, SerializationContext context) throws JacksonException {
|
||||
if (value == null) {
|
||||
gen.writeNull();
|
||||
} else {
|
||||
gen.writeString(value.toString());
|
||||
}
|
||||
}
|
||||
});
|
||||
module.addSerializer(Instant.class, new ValueSerializer<>() {
|
||||
@Override
|
||||
public void serialize(Instant value, JsonGenerator gen, SerializationContext context) throws JacksonException {
|
||||
if (value == null) {
|
||||
gen.writeNull();
|
||||
} else {
|
||||
gen.writeString(InstantUtils.format(value));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 反序列化
|
||||
module.addDeserializer(Instant.class, new ValueDeserializer<>() {
|
||||
@Override
|
||||
public Instant deserialize(JsonParser p, DeserializationContext context) throws JacksonException {
|
||||
return TimeUtils.toInstant(p.getString());
|
||||
}
|
||||
});
|
||||
// xss处理
|
||||
module.addDeserializer(String.class, new XssStringDeserializer());
|
||||
|
||||
return module;
|
||||
}
|
||||
|
||||
/**
|
||||
* XSS 字符串反序列化器
|
||||
*/
|
||||
static class XssStringDeserializer extends ValueDeserializer<String> {
|
||||
|
||||
/**
|
||||
* 是否忽略 XSS 过滤
|
||||
*/
|
||||
private final boolean ignore;
|
||||
|
||||
/**
|
||||
* 创建 XSS 忽略字符串反序列化器
|
||||
*/
|
||||
XssStringDeserializer() {
|
||||
this(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建 XSS 忽略字符串反序列化器
|
||||
*
|
||||
* @param ignore 是否忽略 XSS 过滤
|
||||
*/
|
||||
XssStringDeserializer(boolean ignore) {
|
||||
this.ignore = ignore;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String deserialize(JsonParser parser, DeserializationContext context) throws JacksonException {
|
||||
String value = parser.getString();
|
||||
if (ignore) {
|
||||
return value;
|
||||
}
|
||||
return XssUtils.filterParam(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ValueDeserializer<String> createContextual(DeserializationContext context, BeanProperty property) throws JacksonException {
|
||||
if (Objects.nonNull(property) && Objects.nonNull(property.getAnnotation(IgnoreXss.class))) {
|
||||
return new XssStringDeserializer(true);
|
||||
}
|
||||
return new XssStringDeserializer(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
package xtools.boot.web.base.config;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* <p>Title : LogTrackConfig</p>
|
||||
* <p>Description : LogTrackConfig</p>
|
||||
* <p>DevelopTools : Idea_x64_v2026.1</p>
|
||||
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
|
||||
* <p>Company : org.xujun</p>
|
||||
*
|
||||
* @author : XuJun
|
||||
* @version : 5.0.0
|
||||
* @date : 2026/2/3 15:07
|
||||
*/
|
||||
@Data
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "sys.log.track")
|
||||
public class LogTrackConfig {
|
||||
|
||||
/**
|
||||
* 日志追踪忽略路径
|
||||
*/
|
||||
private List<String> ignorePath = new ArrayList<>();
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
package xtools.boot.web.base.config;
|
||||
|
||||
import org.jspecify.annotations.NonNull;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.format.FormatterRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
import xtools.boot.core.utils.TimeUtils;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
/**
|
||||
* <p>Title : MvcConverterConfig</p>
|
||||
* <p>Description : MvcConverterConfig</p>
|
||||
* <p>DevelopTools : Idea_x64_v2026.1</p>
|
||||
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
|
||||
* <p>Company : org.xujun</p>
|
||||
*
|
||||
* @author : XuJun
|
||||
* @version : 5.0.0
|
||||
* @date : 2026/1/6 14:44
|
||||
*/
|
||||
@Configuration
|
||||
public class MvcConverterConfig implements WebMvcConfigurer {
|
||||
|
||||
/**
|
||||
* 添加转换器
|
||||
*
|
||||
* @param registry registry
|
||||
*/
|
||||
@Override
|
||||
public void addFormatters(FormatterRegistry registry) {
|
||||
|
||||
// 添加时间转换器
|
||||
registry.addConverter(new Converter<String, Instant>() {
|
||||
@Override
|
||||
public Instant convert(@NonNull String source) {
|
||||
return TimeUtils.toInstant(source);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
+332
@@ -0,0 +1,332 @@
|
||||
package xtools.boot.web.base.exception;
|
||||
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.catalina.connector.ClientAbortException;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.converter.HttpMessageNotReadableException;
|
||||
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.HandlerMethodValidationException;
|
||||
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
|
||||
import org.springframework.web.servlet.resource.NoResourceFoundException;
|
||||
import xtools.boot.api.enums.ResultType;
|
||||
import xtools.boot.api.exection.BizError;
|
||||
import xtools.boot.api.exection.BizPublicKeyError;
|
||||
import xtools.boot.api.exection.BizWarning;
|
||||
import xtools.boot.api.exection.UnauthorizedError;
|
||||
import xtools.boot.api.model.dto.Result;
|
||||
import xtools.boot.api.model.dto.log.LogTrack;
|
||||
import xtools.boot.log.LogBus;
|
||||
import xtools.boot.log.enums.LogBusBaseType;
|
||||
import xtools.boot.log.holder.LogTrackHolder;
|
||||
import xtools.boot.log.interfaces.LogBusType;
|
||||
import xtools.core.StringUtils;
|
||||
import xtools.core.enums.LogLevel;
|
||||
import xtools.core.extend.TemplateUtils;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.StringJoiner;
|
||||
|
||||
import static xtools.base.config.Constants.CP_COLON;
|
||||
import static xtools.base.config.Constants.CP_COMMA;
|
||||
|
||||
/**
|
||||
* <p>Title : GlobalExceptionHandler</p>
|
||||
* <p>Description : GlobalExceptionHandler</p>
|
||||
* <p>DevelopTools : Idea_x64_v2026.1</p>
|
||||
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
|
||||
* <p>Company : org.xujun</p>
|
||||
*
|
||||
* @author : XuJun
|
||||
* @version : 5.0.0
|
||||
* @date : 2026/1/5 15:21
|
||||
*/
|
||||
@Slf4j
|
||||
@RestControllerAdvice
|
||||
public class GlobalControllerExceptionHandler {
|
||||
|
||||
/**
|
||||
* 参数类型不匹配异常模板
|
||||
*/
|
||||
private static final String ARGUMENT_TYPE_MISMATCH_TEMPLATE = "参数[{}]类型不匹配";
|
||||
|
||||
/**
|
||||
* 参数类型不匹配
|
||||
*
|
||||
* @param e 参数类型不匹配异常
|
||||
* @param request 请求
|
||||
* @return 错误结果
|
||||
*/
|
||||
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
|
||||
public Result<Object> handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e, HttpServletRequest request) {
|
||||
String errMsg = TemplateUtils.format(ARGUMENT_TYPE_MISMATCH_TEMPLATE, e.getPropertyName());
|
||||
debugLog("参数类型不匹配", errMsg, request, e);
|
||||
return Result.badRequest(errMsg);
|
||||
}
|
||||
|
||||
/**
|
||||
* 参数校验异常
|
||||
*
|
||||
* @param e 参数校验异常
|
||||
* @param request 请求
|
||||
* @return 错误结果
|
||||
*/
|
||||
@ExceptionHandler(HandlerMethodValidationException.class)
|
||||
public Result<Object> handleHandlerMethodValidationException(HandlerMethodValidationException e, HttpServletRequest request) {
|
||||
// 错误结果
|
||||
StringJoiner msg = new StringJoiner(CP_COMMA);
|
||||
// 处理参数验证结果
|
||||
e.getParameterValidationResults().forEach(validation -> {
|
||||
// 获取参数名称
|
||||
String paramName = validation.getMethodParameter().getParameterName();
|
||||
// 获取字段约束违规
|
||||
validation.getResolvableErrors().forEach(item -> msg.add(paramName + CP_COLON + item.getDefaultMessage()));
|
||||
});
|
||||
String errMsg = msg.toString();
|
||||
debugLog("参数校验异常", errMsg, request, e);
|
||||
return Result.badRequest(errMsg);
|
||||
}
|
||||
|
||||
/**
|
||||
* 参数校验异常
|
||||
*
|
||||
* @param e 参数校验异常
|
||||
* @param request 请求
|
||||
* @return 错误结果
|
||||
*/
|
||||
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||||
public Result<Object> handleMethodArgumentNotValidException(MethodArgumentNotValidException e, HttpServletRequest request) {
|
||||
// 错误结果
|
||||
StringJoiner msg = new StringJoiner(CP_COMMA);
|
||||
// 处理参数验证结果
|
||||
e.getBindingResult().getFieldErrors().forEach(fieldError -> {
|
||||
String fieldName = fieldError.getField();
|
||||
String errorMessage = fieldError.getDefaultMessage();
|
||||
msg.add(fieldName + CP_COLON + errorMessage);
|
||||
});
|
||||
String errMsg = msg.toString();
|
||||
debugLog("参数校验异常", errMsg, request, e);
|
||||
return Result.badRequest(errMsg);
|
||||
}
|
||||
|
||||
/**
|
||||
* 消息不可读异常
|
||||
*
|
||||
* @param e 消息不可读异常
|
||||
* @param request 请求
|
||||
* @return 错误结果
|
||||
*/
|
||||
@ExceptionHandler(HttpMessageNotReadableException.class)
|
||||
public Result<Object> handleHttpMessageNotReadableException(HttpMessageNotReadableException e, HttpServletRequest request) {
|
||||
String errMsg = "请求参数格式错误";
|
||||
debugLog(errMsg, e.getMessage(), request, e);
|
||||
return Result.badRequest(errMsg);
|
||||
}
|
||||
|
||||
/**
|
||||
* 公钥错误
|
||||
*
|
||||
* @param e 公钥错误
|
||||
* @param request 请求
|
||||
* @return 错误结果
|
||||
*/
|
||||
@ExceptionHandler(BizPublicKeyError.class)
|
||||
public Result<Object> handleBizPublicKeyErrorException(BizPublicKeyError e, HttpServletRequest request) {
|
||||
debugLog("公钥错误", e.getMessage(), request, e);
|
||||
return new Result<>(true, e.getCode(), e.getMessage(), null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 业务异常
|
||||
*
|
||||
* @param e 业务异常
|
||||
* @param request 请求
|
||||
* @return 错误结果
|
||||
*/
|
||||
@ExceptionHandler(BizError.class)
|
||||
public Result<Object> handleBizErrorException(BizError e, HttpServletRequest request) {
|
||||
debugLog("业务异常", e.getMessage(), request, e);
|
||||
return new Result<>(true, e.getCode(), e.getMessage(), null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 业务警告
|
||||
*
|
||||
* @param e 业务异常
|
||||
* @param request 请求
|
||||
* @return 警告结果
|
||||
*/
|
||||
@ExceptionHandler(BizWarning.class)
|
||||
public Result<Object> handleBizWarningException(BizWarning e, HttpServletRequest request) {
|
||||
debugLog("业务警告", e.getMessage(), request, e);
|
||||
return new Result<>(true, e.getCode(), e.getMessage(), null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 认证异常
|
||||
*
|
||||
* @param e 认证异常
|
||||
* @return 错误结果
|
||||
*/
|
||||
@ExceptionHandler(UnauthorizedError.class)
|
||||
public Result<Object> handleUnauthorizedErrorException(UnauthorizedError e) {
|
||||
errorLog(LogLevel.ERROR, LogBusBaseType.CONTROLLER, "认证异常", e);
|
||||
return new Result<>(ResultType.UNAUTHORIZED, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 请求方法不支持
|
||||
*
|
||||
* @param e 请求方法不支持异常
|
||||
* @return 错误结果
|
||||
*/
|
||||
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
|
||||
public Result<Object> handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) {
|
||||
errorLog(LogLevel.WARN, LogBusBaseType.CONTROLLER, "请求方法不支持", e, false);
|
||||
return new Result<>(ResultType.METHOD_NOT_ALLOWED, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 资源未找到
|
||||
*
|
||||
* @param e 资源未找到异常
|
||||
* @return 错误结果
|
||||
*/
|
||||
@ExceptionHandler(NoResourceFoundException.class)
|
||||
public ResponseEntity<?> handleNoResourceFoundException(NoResourceFoundException e, HttpServletRequest request) {
|
||||
errorLog(LogLevel.WARN, LogBusBaseType.CONTROLLER, "资源未找到", e, false);
|
||||
if (checkTextResponse(request)) {
|
||||
String body = JSONObject.toJSONString(new Result<>(ResultType.NOT_FOUND, null));
|
||||
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(body);
|
||||
} else {
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否为文本响应
|
||||
*
|
||||
* @param request 请求
|
||||
* @return 是否为文本响应
|
||||
*/
|
||||
private boolean checkTextResponse(HttpServletRequest request) {
|
||||
String acceptHeader = request.getHeader(HttpHeaders.ACCEPT);
|
||||
if (StringUtils.isBlank(acceptHeader)) {
|
||||
return true;
|
||||
}
|
||||
List<MediaType> acceptedTypes = MediaType.parseMediaTypes(acceptHeader);
|
||||
for (MediaType item : acceptedTypes) {
|
||||
// 通配符 */* 表示可接受任意类型,包括文本
|
||||
if (item.equals(MediaType.ALL)) {
|
||||
return true;
|
||||
}
|
||||
String type = item.getType();
|
||||
switch (type) {
|
||||
case "text" -> {
|
||||
return true;
|
||||
}
|
||||
case "image", "multipart" -> {
|
||||
return false;
|
||||
}
|
||||
default -> {
|
||||
}
|
||||
}
|
||||
if (
|
||||
item.isCompatibleWith(MediaType.APPLICATION_ATOM_XML) ||
|
||||
item.isCompatibleWith(MediaType.APPLICATION_FORM_URLENCODED) ||
|
||||
item.isCompatibleWith(MediaType.APPLICATION_GRAPHQL_RESPONSE) ||
|
||||
item.isCompatibleWith(MediaType.APPLICATION_JSON) ||
|
||||
item.isCompatibleWith(MediaType.APPLICATION_NDJSON) ||
|
||||
item.isCompatibleWith(MediaType.APPLICATION_PROBLEM_JSON) ||
|
||||
item.isCompatibleWith(MediaType.APPLICATION_PROBLEM_XML) ||
|
||||
item.isCompatibleWith(MediaType.APPLICATION_RSS_XML) ||
|
||||
item.isCompatibleWith(MediaType.APPLICATION_XHTML_XML) ||
|
||||
item.isCompatibleWith(MediaType.APPLICATION_XML) ||
|
||||
item.isCompatibleWith(MediaType.APPLICATION_YAML)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 客户端提前断开连接
|
||||
*
|
||||
* @param e 客户端提前断开连接异常
|
||||
*/
|
||||
@ExceptionHandler(ClientAbortException.class)
|
||||
public void handleClientAbortException(ClientAbortException e) {
|
||||
errorLog(LogLevel.WARN, LogBusBaseType.CONTROLLER, "客户端提前断开连接", e, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 默认全局异常处理
|
||||
*
|
||||
* @param e 异常
|
||||
* @return 错误结果
|
||||
*/
|
||||
@ExceptionHandler(Exception.class)
|
||||
public Result<Object> handleException(Exception e) {
|
||||
errorLog(LogLevel.ERROR, LogBusBaseType.CONTROLLER, "默认全局异常处理", e);
|
||||
return new Result<>(ResultType.INTERNAL_SERVER_ERROR, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 错误日志
|
||||
*
|
||||
* @param level 日志级别
|
||||
* @param type 异常类型
|
||||
* @param title 标题
|
||||
* @param e 异常
|
||||
*/
|
||||
private void errorLog(LogLevel level, LogBusType type, String title, Exception e) {
|
||||
errorLog(level, type, title, e, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 错误日志
|
||||
*
|
||||
* @param level 日志级别
|
||||
* @param type 异常类型
|
||||
* @param title 标题
|
||||
* @param e 异常
|
||||
* @param print 是否打印
|
||||
*/
|
||||
private void errorLog(LogLevel level, LogBusType type, String title, Exception e, Boolean print) {
|
||||
LogTrack logTrack = LogTrackHolder.getDefNull();
|
||||
if (Objects.isNull(logTrack)) {
|
||||
logTrack = LogTrackHolder.getNewMain();
|
||||
}
|
||||
LogBus logBus = LogBus.init(level, type, logTrack).title(title).error(e);
|
||||
if (Objects.nonNull(print)) {
|
||||
logBus.print(print);
|
||||
}
|
||||
logBus.save();
|
||||
}
|
||||
|
||||
/**
|
||||
* 调试日志
|
||||
*
|
||||
* @param type 异常类型
|
||||
* @param msg 错误消息
|
||||
* @param request 请求
|
||||
* @param e 异常
|
||||
*/
|
||||
private void debugLog(String type, String msg, HttpServletRequest request, Exception e) {
|
||||
if (StringUtils.isBlank(msg)) {
|
||||
log.debug("请求[{}]异常,类型[{}]", request.getRequestURI(), type, e);
|
||||
} else {
|
||||
log.debug("请求[{}]异常,类型[{}],错误消息[{}]", request.getRequestURI(), type, msg);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
package xtools.boot.web.base.filter;
|
||||
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jspecify.annotations.NonNull;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.stereotype.Component;
|
||||
import xtools.boot.core.holder.CommonHolder;
|
||||
import xtools.boot.web.filter.base.BaseFilter;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static xtools.base.config.Constants.CP_NUM1;
|
||||
import static xtools.base.config.Constants.CP_NUM100;
|
||||
|
||||
/**
|
||||
* <p>Title : CommonFilter</p>
|
||||
* <p>Description : CommonFilter</p>
|
||||
* <p>DevelopTools : Idea_x64_v2026.1</p>
|
||||
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
|
||||
* <p>Company : org.xujun</p>
|
||||
*
|
||||
* @author : XuJun
|
||||
* @version : 5.0.0
|
||||
* @date : 2026/1/6 16:37
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class CommonFilter extends BaseFilter implements Ordered {
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return CP_NUM100 + CP_NUM1;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(
|
||||
@NonNull HttpServletRequest request,
|
||||
@NonNull HttpServletResponse response,
|
||||
@NonNull FilterChain filterChain
|
||||
) {
|
||||
ScopedValue.where(CommonHolder.getScoped(), CommonHolder.create()).run(() -> {
|
||||
try {
|
||||
filterChain.doFilter(request, response);
|
||||
} catch (IOException | ServletException e) {
|
||||
printLog(e, "添加通用ScopedValue异常");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
package xtools.boot.web.base.filter;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jspecify.annotations.NonNull;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.stereotype.Component;
|
||||
import xtools.boot.api.constant.BootCommonConstant;
|
||||
import xtools.boot.api.model.dto.log.HolderLogTrack;
|
||||
import xtools.boot.core.utils.PathPatternUtils;
|
||||
import xtools.boot.log.holder.LogTrackHolder;
|
||||
import xtools.boot.web.base.config.LogTrackConfig;
|
||||
import xtools.boot.web.filter.base.BaseFilter;
|
||||
import xtools.core.StringUtils;
|
||||
import xtools.web.HeaderUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static xtools.base.config.Constants.CP_NUM100;
|
||||
|
||||
/**
|
||||
* <p>Title : LogTrackFilter</p>
|
||||
* <p>Description : LogTrackFilter</p>
|
||||
* <p>DevelopTools : Idea_x64_v2026.1</p>
|
||||
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
|
||||
* <p>Company : org.xujun</p>
|
||||
*
|
||||
* @author : XuJun
|
||||
* @version : 5.0.0
|
||||
* @date : 2026/1/6 16:37
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class LogTrackFilter extends BaseFilter implements Ordered {
|
||||
|
||||
@Resource
|
||||
private LogTrackConfig logTrackConfig;
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return CP_NUM100;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(
|
||||
@NonNull HttpServletRequest request,
|
||||
@NonNull HttpServletResponse response,
|
||||
@NonNull FilterChain filterChain
|
||||
) {
|
||||
String uri = HeaderUtils.getUri(request);
|
||||
String headerLogTrack = HeaderUtils.getHeader(request, BootCommonConstant.LOG_TRACK);
|
||||
HolderLogTrack logTrack;
|
||||
if (StringUtils.isBlank(headerLogTrack)) {
|
||||
logTrack = LogTrackHolder.newMain();
|
||||
} else {
|
||||
logTrack = LogTrackHolder.newByBase64(headerLogTrack);
|
||||
}
|
||||
ScopedValue.where(LogTrackHolder.getScoped(), logTrack).run(() -> {
|
||||
// 判断该uri是否忽略记录日志
|
||||
if (PathPatternUtils.match(logTrackConfig.getIgnorePath(), uri)) {
|
||||
LogTrackHolder.saveLog(false);
|
||||
}
|
||||
try {
|
||||
filterChain.doFilter(request, response);
|
||||
} catch (IOException | ServletException e) {
|
||||
printLog(e, "添加日志追踪ScopedValue异常");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
package xtools.boot.web.base.selector;
|
||||
|
||||
import org.jspecify.annotations.NonNull;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
|
||||
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
|
||||
/**
|
||||
* <p>Title : BootWebBaseImportSelector</p>
|
||||
* <p>Description : BootWebBaseImportSelector</p>
|
||||
* <p>DevelopTools : Idea_x64_v2026.1</p>
|
||||
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
|
||||
* <p>Company : org.xujun</p>
|
||||
*
|
||||
* @author : XuJun
|
||||
* @version : 5.0.0
|
||||
* @date : 2026/01/01 09:30
|
||||
*/
|
||||
public class BootWebBaseImportSelector implements ImportBeanDefinitionRegistrar {
|
||||
|
||||
/**
|
||||
* 根据给定的注释元数据,根据需要注册bean
|
||||
*
|
||||
* @param importingClassMetadata AnnotationMetadata
|
||||
* @param registry BeanDefinitionRegistry
|
||||
*/
|
||||
@Override
|
||||
public void registerBeanDefinitions(@NonNull AnnotationMetadata importingClassMetadata, @NonNull BeanDefinitionRegistry registry) {
|
||||
// 构建扫描对象
|
||||
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(registry, true);
|
||||
// 扫描包下路径
|
||||
scanner.scan("xtools.boot.web.base");
|
||||
}
|
||||
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
xtools.boot.web.base.BootWebBaseConfiguration
|
||||
@@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.xujun</groupId>
|
||||
<artifactId>xtools-boot-web</artifactId>
|
||||
<version>5.1.0</version>
|
||||
</parent>
|
||||
<artifactId>xtools-boot-web-filter</artifactId>
|
||||
|
||||
|
||||
<!-- 依赖 -->
|
||||
<dependencies>
|
||||
<!-- xtools-boot begin -->
|
||||
<!-- xtools-boot-core -->
|
||||
<dependency>
|
||||
<groupId>org.xujun</groupId>
|
||||
<artifactId>xtools-boot-core</artifactId>
|
||||
</dependency>
|
||||
<!-- xtools-boot end -->
|
||||
|
||||
<!-- SpringBoot begin -->
|
||||
<!-- SpringBoot-Web -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<!-- SpringBoot end -->
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package xtools.boot.web.filter;
|
||||
|
||||
import org.springframework.context.annotation.Import;
|
||||
import xtools.boot.core.utils.ModuleLoadUtils;
|
||||
import xtools.boot.web.filter.selector.BootWebFilterImportSelector;
|
||||
|
||||
/**
|
||||
* <p>Title : BootWebFilterConfiguration</p>
|
||||
* <p>Description : BootWebFilterConfiguration</p>
|
||||
* <p>DevelopTools : Idea_x64_v2026.1</p>
|
||||
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
|
||||
* <p>Company : org.xujun</p>
|
||||
*
|
||||
* @author : XuJun
|
||||
* @version : 5.0.0
|
||||
* @date : 2026/01/01 09:30
|
||||
*/
|
||||
@Import(BootWebFilterImportSelector.class)
|
||||
public class BootWebFilterConfiguration {
|
||||
|
||||
/**
|
||||
* 构造方法
|
||||
*/
|
||||
public BootWebFilterConfiguration() {
|
||||
ModuleLoadUtils.loadSuccess(BootWebFilterConfiguration.class);
|
||||
}
|
||||
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
package xtools.boot.web.filter.base;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.catalina.connector.ClientAbortException;
|
||||
import org.jspecify.annotations.NonNull;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
/**
|
||||
* <p>Title : BaseFilter</p>
|
||||
* <p>Description : BaseFilter</p>
|
||||
* <p>DevelopTools : Idea_x64_v2026.1</p>
|
||||
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
|
||||
* <p>Company : org.xujun</p>
|
||||
*
|
||||
* @author : XuJun
|
||||
* @version : 5.0.0
|
||||
* @date : 2026/3/8 11:06
|
||||
*/
|
||||
@Slf4j
|
||||
public abstract class BaseFilter extends OncePerRequestFilter {
|
||||
|
||||
/**
|
||||
* 跳过过滤器标识
|
||||
*/
|
||||
private static final String SKIP_FILTER = "skip.filters";
|
||||
|
||||
/**
|
||||
* 添加跳过过滤器标识
|
||||
*
|
||||
* @param request 请求
|
||||
*/
|
||||
public void addSkipFilter(HttpServletRequest request) {
|
||||
request.setAttribute(SKIP_FILTER, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 日志
|
||||
*
|
||||
* @param e 异常
|
||||
* @param msg 消息
|
||||
*/
|
||||
public void printLog(Exception e, String msg) {
|
||||
if (e instanceof ClientAbortException) {
|
||||
return;
|
||||
}
|
||||
log.error(msg, e);
|
||||
}
|
||||
|
||||
/**
|
||||
* 跳过过滤器
|
||||
*
|
||||
* @param request 请求
|
||||
* @return 是否跳过过滤器
|
||||
*/
|
||||
@Override
|
||||
protected boolean shouldNotFilter(@NonNull HttpServletRequest request) {
|
||||
Object attribute = request.getAttribute(SKIP_FILTER);
|
||||
if (attribute instanceof Boolean skip) {
|
||||
return skip;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
package xtools.boot.web.filter.common;
|
||||
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.jspecify.annotations.NonNull;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.stereotype.Component;
|
||||
import xtools.boot.core.interfaces.FilterWhitelist;
|
||||
import xtools.boot.core.utils.PathPatternUtils;
|
||||
import xtools.boot.core.utils.SpringContextUtils;
|
||||
import xtools.boot.web.filter.base.BaseFilter;
|
||||
import xtools.core.CollectionUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import static xtools.base.config.Constants.CP_NUM0;
|
||||
|
||||
/**
|
||||
* <p>Title : SkipFilter</p>
|
||||
* <p>Description : SkipFilter</p>
|
||||
* <p>DevelopTools : Idea_x64_v2026.1</p>
|
||||
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
|
||||
* <p>Company : org.xujun</p>
|
||||
*
|
||||
* @author : XuJun
|
||||
* @version : 5.0.0
|
||||
* @date : 2026/3/8 11:00
|
||||
*/
|
||||
@Component
|
||||
public class SkipFilter extends BaseFilter implements Ordered {
|
||||
|
||||
/**
|
||||
* 白名单
|
||||
*/
|
||||
private static final Set<String> WHITE_LIST = new HashSet<>();
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return CP_NUM0;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initFilterBean() throws ServletException {
|
||||
super.initFilterBean();
|
||||
Collection<FilterWhitelist> beanList = SpringContextUtils.getBeanList(FilterWhitelist.class);
|
||||
if (CollectionUtils.isEmpty(beanList)) {
|
||||
return;
|
||||
}
|
||||
beanList.forEach(item -> WHITE_LIST.addAll(item.add()));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(
|
||||
@NonNull HttpServletRequest request,
|
||||
@NonNull HttpServletResponse response,
|
||||
@NonNull FilterChain filterChain
|
||||
) throws ServletException, IOException {
|
||||
// 校验白名单
|
||||
if (PathPatternUtils.match(WHITE_LIST, request.getRequestURI())) {
|
||||
addSkipFilter(request);
|
||||
}
|
||||
filterChain.doFilter(request, response);
|
||||
}
|
||||
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
package xtools.boot.web.filter.selector;
|
||||
|
||||
import org.jspecify.annotations.NonNull;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
|
||||
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
|
||||
/**
|
||||
* <p>Title : BootWebFilterImportSelector</p>
|
||||
* <p>Description : BootWebFilterImportSelector</p>
|
||||
* <p>DevelopTools : Idea_x64_v2026.1</p>
|
||||
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
|
||||
* <p>Company : org.xujun</p>
|
||||
*
|
||||
* @author : XuJun
|
||||
* @version : 5.0.0
|
||||
* @date : 2026/01/01 09:30
|
||||
*/
|
||||
public class BootWebFilterImportSelector implements ImportBeanDefinitionRegistrar {
|
||||
|
||||
/**
|
||||
* 根据给定的注释元数据,根据需要注册bean
|
||||
*
|
||||
* @param importingClassMetadata AnnotationMetadata
|
||||
* @param registry BeanDefinitionRegistry
|
||||
*/
|
||||
@Override
|
||||
public void registerBeanDefinitions(@NonNull AnnotationMetadata importingClassMetadata, @NonNull BeanDefinitionRegistry registry) {
|
||||
// 构建扫描对象
|
||||
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(registry, true);
|
||||
// 扫描包下路径
|
||||
scanner.scan("xtools.boot.web.filter");
|
||||
}
|
||||
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
xtools.boot.web.filter.BootWebFilterConfiguration
|
||||
Reference in New Issue
Block a user