初始化仓库

This commit is contained in:
2026-04-21 15:08:07 +08:00
parent 444d984122
commit b5119afb9f
195 changed files with 11034 additions and 19 deletions

View File

@@ -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.0.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>

View File

@@ -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);
}
}

View File

@@ -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);
}
}
}

View File

@@ -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<>();
}

View File

@@ -0,0 +1,34 @@
package xtools.boot.web.base.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
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 {
/**
* 字符串转时间戳
*
* @return 转换器
*/
@Bean
public Converter<String, Instant> stringToInstantConverter() {
return TimeUtils::toInstant;
}
}

View File

@@ -0,0 +1,238 @@
package xtools.boot.web.base.exception;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
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.base.config.BaseParams;
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.core.StringUtils;
import xtools.core.enums.LogLevel;
import xtools.core.extend.TemplateUtils;
import java.util.Objects;
import java.util.StringJoiner;
/**
* <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 implements BaseParams {
/**
* 参数类型不匹配异常模板
*/
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());
logException("参数类型不匹配", 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();
logException("参数校验异常", 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();
logException("参数校验异常", 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 = "请求参数格式错误";
logException(errMsg, e.getMessage(), request, e);
return Result.badRequest(errMsg);
}
/**
* 请求方法不支持
*
* @param e 请求方法不支持异常
* @param request 请求
* @return 错误结果
*/
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public Result<Object> handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e, HttpServletRequest request) {
logException("请求方法不支持", null, request, e);
return new Result<>(ResultType.METHOD_NOT_ALLOWED, null);
}
/**
* 资源未找到
*
* @param e 资源未找到异常
* @param request 请求
* @return 错误结果
*/
@ExceptionHandler(NoResourceFoundException.class)
public Result<Object> handleNoResourceFoundException(NoResourceFoundException e, HttpServletRequest request) {
logException("资源未找到", null, request, e);
return new Result<>(ResultType.NOT_FOUND, null);
}
/**
* 认证异常
*
* @param e 认证异常
* @param request 请求
* @return 错误结果
*/
@ExceptionHandler(UnauthorizedError.class)
public Result<Object> handleUnauthorizedErrorException(UnauthorizedError e, HttpServletRequest request) {
logException("认证异常", e.getMessage(), request, e);
return new Result<>(ResultType.UNAUTHORIZED, null);
}
/**
* 公钥错误
*
* @param e 公钥错误
* @param request 请求
* @return 错误结果
*/
@ExceptionHandler(BizPublicKeyError.class)
public Result<Object> handleBizPublicKeyErrorException(BizPublicKeyError e, HttpServletRequest request) {
logException("公钥错误", 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) {
logException("业务异常", 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) {
logException("业务警告", e.getMessage(), request, e);
return new Result<>(true, e.getCode(), e.getMessage(), null);
}
/**
* 默认全局异常处理
*
* @param e 异常
* @param request 请求
* @return 错误结果
*/
@ExceptionHandler(Exception.class)
public Result<Object> handleException(Exception e, HttpServletRequest request) {
logException("默认全局异常处理", null, request, e);
LogTrack logTrack = LogTrackHolder.getDefNull();
if (Objects.nonNull(logTrack)) {
LogBus.init(LogLevel.ERROR, LogBusBaseType.CONTROLLER, logTrack).title("默认全局异常处理").error(e).save();
} else {
log.error("默认全局异常处理", e);
}
return new Result<>(ResultType.INTERNAL_SERVER_ERROR, null);
}
/**
* 日志异常
*
* @param type 异常类型
* @param msg 错误消息
* @param request 请求
* @param e 异常
*/
private void logException(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);
}
}
}

View File

@@ -0,0 +1,57 @@
package xtools.boot.web.base.filter;
import com.alibaba.fastjson2.JSONObject;
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.base.config.BaseParams;
import xtools.boot.api.enums.ResultType;
import xtools.boot.api.model.dto.Result;
import xtools.boot.core.holder.CommonHolder;
import xtools.boot.web.filter.base.BaseFilter;
import xtools.web.HttpServletUtils;
import java.io.IOException;
/**
* <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, BaseParams {
@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) {
log.error("添加通用ScopedValue异常", e);
HttpServletUtils.respWriter(response, JSONObject.from(new Result<>(ResultType.INTERNAL_SERVER_ERROR, null)));
}
});
}
}

View File

@@ -0,0 +1,79 @@
package xtools.boot.web.base.filter;
import com.alibaba.fastjson2.JSONObject;
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.base.config.BaseParams;
import xtools.boot.api.constant.BootCommonConstant;
import xtools.boot.api.enums.ResultType;
import xtools.boot.api.model.dto.Result;
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 xtools.web.HttpServletUtils;
import java.io.IOException;
/**
* <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, BaseParams {
@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) {
log.error("添加日志追踪ScopedValue异常", e);
HttpServletUtils.respWriter(response, JSONObject.from(new Result<>(ResultType.INTERNAL_SERVER_ERROR, null)));
}
});
}
}

View File

@@ -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");
}
}