Compare commits

..

4 Commits

Author SHA1 Message Date
疯狂的狮子Li
bbc684b335 update 删除已经过期的配置类 2026-01-06 17:26:45 +08:00
疯狂的狮子Li
2b8f4e1d2c update 下架过期的赞助商 2026-01-06 17:22:44 +08:00
AprilWind
d634c2a292 update 优化oss日志侦听器打印级别 2026-01-05 14:39:40 +08:00
ColorDreams
8b97e7bc53 update ip2region version to 3.3.2 2025-12-24 19:09:36 +08:00
35 changed files with 322 additions and 262 deletions

18
pom.xml
View File

@@ -14,23 +14,23 @@
<properties>
<revision>5.5.2</revision>
<spring-boot.version>4.0.1</spring-boot.version>
<spring-boot.version>3.5.9</spring-boot.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>17</java.version>
<mybatis.version>3.5.16</mybatis.version>
<springdoc.version>3.0.1</springdoc.version>
<springdoc.version>2.8.14</springdoc.version>
<therapi-javadoc.version>0.15.0</therapi-javadoc.version>
<fastexcel.version>1.3.0</fastexcel.version>
<velocity.version>2.3</velocity.version>
<satoken.version>1.44.0</satoken.version>
<mybatis-plus.version>3.5.15</mybatis-plus.version>
<mybatis-plus.version>3.5.14</mybatis-plus.version>
<p6spy.version>3.9.1</p6spy.version>
<hutool.version>5.8.40</hutool.version>
<spring-boot-admin.version>3.5.6</spring-boot-admin.version>
<redisson.version>4.1.0</redisson.version>
<spring-boot-admin.version>3.5.5</spring-boot-admin.version>
<redisson.version>3.52.0</redisson.version>
<lock4j.version>2.2.7</lock4j.version>
<dynamic-ds.version>4.5.0</dynamic-ds.version>
<dynamic-ds.version>4.3.1</dynamic-ds.version>
<snailjob.version>1.9.0</snailjob.version>
<mapstruct-plus.version>1.5.0</mapstruct-plus.version>
<mapstruct-plus.lombok.version>0.2.0</mapstruct-plus.lombok.version>
@@ -38,7 +38,7 @@
<bouncycastle.version>1.80</bouncycastle.version>
<justauth.version>1.16.7</justauth.version>
<!-- 离线IP地址定位库 -->
<ip2region.version>3.3.1</ip2region.version>
<ip2region.version>3.3.2</ip2region.version>
<!-- OSS 配置 -->
<aws.sdk.version>2.28.22</aws.sdk.version>
<!-- SMS 配置 -->
@@ -185,7 +185,7 @@
<!-- dynamic-datasource 多数据源-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot4-starter</artifactId>
<artifactId>dynamic-datasource-spring-boot3-starter</artifactId>
<version>${dynamic-ds.version}</version>
</dependency>
@@ -197,7 +197,7 @@
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot4-starter</artifactId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>

View File

@@ -5,15 +5,20 @@ server:
servlet:
# 应用的访问路径
context-path: /
# jetty 配置
jetty:
# undertow 配置
undertow:
# HTTP post内容的最大大小。当值为-1时默认值为大小是无限的
max-http-form-post-size: -1
max-http-post-size: -1
# 以下的配置会影响buffer,这些buffer会用于服务器连接的IO操作,有点类似netty的池化内存管理
# 每块buffer的空间大小,越小的空间被利用越充分
buffer-size: 512
# 是否分配的直接内存
direct-buffers: true
threads:
# 最小线程数
min: 8
# 最大线程数
max: 256
# 设置IO线程数, 它主要执行非阻塞的任务,它们会负责多个连接, 默认设置每个CPU核心一个线程
io: 8
# 阻塞任务线程池, 当执行类似servlet请求阻塞操作, undertow会从这个线程池中取得线程,它的值设置取决于系统的负载
worker: 256
captcha:
# 是否启用验证码校验

View File

@@ -36,7 +36,7 @@
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aspectj</artifactId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!--常用工具类 -->

View File

@@ -3,7 +3,7 @@ package org.dromara.common.core.config;
import jakarta.validation.Validator;
import org.hibernate.validator.HibernateValidator;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.validation.autoconfigure.ValidationAutoConfiguration;
import org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;

View File

@@ -2,7 +2,7 @@ package org.dromara.common.core.utils;
import cn.hutool.extra.spring.SpringUtil;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.boot.thread.Threading;
import org.springframework.boot.autoconfigure.thread.Threading;
import org.springframework.context.ApplicationContext;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;

View File

@@ -1,6 +1,5 @@
package org.dromara.common.core.utils.ip;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.resource.ResourceUtil;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.exception.ServiceException;
@@ -9,7 +8,6 @@ import org.lionsoul.ip2region.service.Config;
import org.lionsoul.ip2region.service.Ip2Region;
import org.lionsoul.ip2region.xdb.Util;
import java.io.File;
import java.io.InputStream;
import java.time.Duration;
@@ -31,6 +29,11 @@ public class RegionUtils {
// 下载地址https://gitee.com/lionsoul/ip2region/blob/master/data/ip2region_v6.xdb
public static final String DEFAULT_IPV6_XDB_PATH = "ip2region_v6.xdb";
// 默认缓存切片大小为15MB仅针对BufferCache全量读取有效如果你的xdb数据库很大合理设置该值可以有效提升BufferCache模式下的查询效率具体可以查看Ip2Region的README
// 注意设置过大的值可能会申请内存时因内存不足而导致OOM请合理设置该值。
// READMEhttps://gitee.com/lionsoul/ip2region/tree/master/binding/java
public static final int DEFAULT_CACHE_SLICE_BYTES = 1024 * 1024 * 15;
// 未知地址
public static final String UNKNOWN_ADDRESS = "未知";
@@ -43,20 +46,18 @@ public class RegionUtils {
// 注意Ip2Region 的xdb文件加载策略 CachePolicy 有三种分别是BufferCache全量读取xdb到内存中、VIndexCache默认策略按需读取并缓存、NoCache实时读取
// 本项目工具使用的 CachePolicy 为 BufferCacheBufferCache会加载整个xdb文件到内存中setXdbInputStream 仅支持 BufferCache 策略。
// 因为加载整个xdb文件会耗费非常大的内存如果你不希望加载整个xdb到内存中更推荐使用 VIndexCache 或 NoCache即实时读取文件策略和 setXdbPath/setXdbFile 加载方法需要注意的一点setXdbPath 和 setXdbFile 不支持读取ClassPath即源码和resource目录中的文件
// 一般而言更建议把xdb数据库放到一个指定的文件目录中即不打包进jar包中然后使用 NoCache + 配合SearcherPool的并发池读取数据更方便随时更新xdb数据库
// 一般而言更建议把xdb数据库放到一个指定的文件目录中即不打包进jar包中然后使用 VIndexCache + 配合SearcherPool的并发池读取数据更方便随时更新xdb数据库
// TODO 2025年12月23日 Ip2Region封装的 InputStream 读取函数 Searcher.loadContentFromInputStream 在Linux环境下会申请过大的byte[]空间而导致OOM这里先用临时文件的方案解决等后续 Ip2Region 更新解决方案
// 创建临时文件
File v4TempXdb = FileUtil.writeFromStream(ResourceUtil.getStream(DEFAULT_IPV4_XDB_PATH), FileUtil.createTempFile());
InputStream v4InputStream = ResourceUtil.getStream(DEFAULT_IPV4_XDB_PATH);
// IPv4配置
Config v4Config = Config.custom()
.setCachePolicy(Config.BufferCache)
.setXdbFile(v4TempXdb)
// .setXdbInputStream(ResourceUtil.getStream(DEFAULT_IPV4_XDB_PATH))
//.setXdbFile(v4TempXdb)
.setXdbInputStream(v4InputStream)
//
.setCacheSliceBytes(DEFAULT_CACHE_SLICE_BYTES)
.asV4();
// 删除临时文件
v4TempXdb.delete();
// IPv6配置
Config v6Config = null;
@@ -64,17 +65,12 @@ public class RegionUtils {
if (v6XdbInputStream == null) {
log.warn("未加载 IPv6 地址库:未在类路径下找到文件 {}。当前仅启用 IPv4 查询。如需启用 IPv6请将 ip2region_v6.xdb 放置到 resources 目录", DEFAULT_IPV6_XDB_PATH);
} else {
// 创建临时文件
File v6TempXdb = FileUtil.writeFromStream(ResourceUtil.getStream(DEFAULT_IPV4_XDB_PATH), FileUtil.createTempFile());
v6Config = Config.custom()
.setCachePolicy(Config.BufferCache)
.setXdbFile(v6TempXdb)
// .setXdbInputStream(v6XdbInputStream)
//.setXdbFile(v6TempXdb)
.setXdbInputStream(v6XdbInputStream)
.setCacheSliceBytes(DEFAULT_CACHE_SLICE_BYTES)
.asV6();
// 删除临时文件
v6TempXdb.delete();
}
// 初始化Ip2Region实例

View File

@@ -21,11 +21,6 @@
<artifactId>ruoyi-common-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-web-server</artifactId>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-api</artifactId>

View File

@@ -20,8 +20,8 @@ import org.springdoc.core.utils.PropertyResolverUtils;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.server.autoconfigure.ServerProperties;
import org.springframework.context.annotation.Bean;
import java.util.ArrayList;

View File

@@ -39,7 +39,7 @@
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot4-starter</artifactId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<optional>true</optional>
<exclusions>
<exclusion>

View File

@@ -21,9 +21,15 @@
<artifactId>ruoyi-common-core</artifactId>
</dependency>
<!-- JSON工具类 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jackson</artifactId>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
</dependencies>

View File

@@ -1,16 +1,17 @@
package org.dromara.common.json.config;
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.json.handler.BigNumberSerializer;
import org.dromara.common.json.handler.CustomDateDeserializer;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.jackson.autoconfigure.JacksonAutoConfiguration;
import org.springframework.boot.jackson.autoconfigure.JsonMapperBuilderCustomizer;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
import org.springframework.context.annotation.Bean;
import tools.jackson.databind.ext.javatime.deser.LocalDateTimeDeserializer;
import tools.jackson.databind.ext.javatime.ser.LocalDateTimeSerializer;
import tools.jackson.databind.module.SimpleModule;
import tools.jackson.databind.ser.std.ToStringSerializer;
import java.math.BigDecimal;
import java.math.BigInteger;
@@ -29,24 +30,24 @@ import java.util.TimeZone;
public class JacksonConfig {
@Bean
public SimpleModule registerJavaTimeModule() {
public Module registerJavaTimeModule() {
// 全局配置序列化返回 JSON 处理
SimpleModule module = new SimpleModule();
module.addSerializer(Long.class, BigNumberSerializer.INSTANCE);
module.addSerializer(Long.TYPE, BigNumberSerializer.INSTANCE);
module.addSerializer(BigInteger.class, BigNumberSerializer.INSTANCE);
module.addSerializer(BigDecimal.class, ToStringSerializer.instance);
JavaTimeModule javaTimeModule = new JavaTimeModule();
javaTimeModule.addSerializer(Long.class, BigNumberSerializer.INSTANCE);
javaTimeModule.addSerializer(Long.TYPE, BigNumberSerializer.INSTANCE);
javaTimeModule.addSerializer(BigInteger.class, BigNumberSerializer.INSTANCE);
javaTimeModule.addSerializer(BigDecimal.class, ToStringSerializer.instance);
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
module.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(formatter));
module.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(formatter));
module.addDeserializer(Date.class, new CustomDateDeserializer());
return module;
javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(formatter));
javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(formatter));
javaTimeModule.addDeserializer(Date.class, new CustomDateDeserializer());
return javaTimeModule;
}
@Bean
public JsonMapperBuilderCustomizer jsonInitCustomizer() {
public Jackson2ObjectMapperBuilderCustomizer customizer() {
return builder -> {
builder.defaultTimeZone(TimeZone.getDefault());
builder.timeZone(TimeZone.getDefault());
log.info("初始化 jackson 配置");
};
}

View File

@@ -1,9 +1,11 @@
package org.dromara.common.json.handler;
import tools.jackson.core.JsonGenerator;
import tools.jackson.databind.SerializationContext;
import tools.jackson.databind.annotation.JacksonStdImpl;
import tools.jackson.databind.ser.jdk.NumberSerializer;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.annotation.JacksonStdImpl;
import com.fasterxml.jackson.databind.ser.std.NumberSerializer;
import java.io.IOException;
/**
* 超出 JS 最大最小值 处理
@@ -29,7 +31,7 @@ public class BigNumberSerializer extends NumberSerializer {
}
@Override
public void serialize(Number value, JsonGenerator gen, SerializationContext provider) {
public void serialize(Number value, JsonGenerator gen, SerializerProvider provider) throws IOException {
// 超出范围 序列化为字符串
if (value.longValue() > MIN_SAFE_INTEGER && value.longValue() < MAX_SAFE_INTEGER) {
super.serialize(value, gen, provider);

View File

@@ -2,11 +2,12 @@ package org.dromara.common.json.handler;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import org.dromara.common.core.utils.ObjectUtils;
import tools.jackson.core.JsonParser;
import tools.jackson.databind.DeserializationContext;
import tools.jackson.databind.ValueDeserializer;
import java.io.IOException;
import java.util.Date;
/**
@@ -14,7 +15,7 @@ import java.util.Date;
*
* @author AprilWind
*/
public class CustomDateDeserializer extends ValueDeserializer<Date> {
public class CustomDateDeserializer extends JsonDeserializer<Date> {
/**
* 反序列化逻辑:将字符串转换为 Date 对象
@@ -22,9 +23,10 @@ public class CustomDateDeserializer extends ValueDeserializer<Date> {
* @param p JSON 解析器,用于获取字符串值
* @param ctxt 上下文环境(可用于获取更多配置)
* @return 转换后的 Date 对象,若为空字符串返回 null
* @throws IOException 当字符串格式非法或转换失败时抛出
*/
@Override
public Date deserialize(JsonParser p, DeserializationContext ctxt) {
public Date deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
DateTime parse = DateUtil.parse(p.getText());
if (ObjectUtils.isNull(parse)) {
return null;

View File

@@ -3,29 +3,32 @@ package org.dromara.common.json.utils;
import cn.hutool.core.lang.Dict;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.exc.MismatchedInputException;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.core.utils.StringUtils;
import tools.jackson.core.type.TypeReference;
import tools.jackson.databind.JsonNode;
import tools.jackson.databind.json.JsonMapper;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* JSON 工具类
*
* @author Lion Li
* @author 芋道源码
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class JsonUtils {
private static final JsonMapper JSON_MAPPER = SpringUtils.getBean(JsonMapper.class);
private static final ObjectMapper OBJECT_MAPPER = SpringUtils.getBean(ObjectMapper.class);
public static JsonMapper getJsonMapper() {
return JSON_MAPPER;
public static ObjectMapper getObjectMapper() {
return OBJECT_MAPPER;
}
/**
@@ -39,7 +42,11 @@ public class JsonUtils {
if (ObjectUtil.isNull(object)) {
return null;
}
return JSON_MAPPER.writeValueAsString(object);
try {
return OBJECT_MAPPER.writeValueAsString(object);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
/**
@@ -55,7 +62,11 @@ public class JsonUtils {
if (StringUtils.isEmpty(text)) {
return null;
}
return JSON_MAPPER.readValue(text, clazz);
try {
return OBJECT_MAPPER.readValue(text, clazz);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
@@ -71,7 +82,11 @@ public class JsonUtils {
if (ArrayUtil.isEmpty(bytes)) {
return null;
}
return JSON_MAPPER.readValue(bytes, clazz);
try {
return OBJECT_MAPPER.readValue(bytes, clazz);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
@@ -87,7 +102,11 @@ public class JsonUtils {
if (StringUtils.isBlank(text)) {
return null;
}
return JSON_MAPPER.readValue(text, typeReference);
try {
return OBJECT_MAPPER.readValue(text, typeReference);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
@@ -101,7 +120,14 @@ public class JsonUtils {
if (StringUtils.isBlank(text)) {
return null;
}
return JSON_MAPPER.readValue(text, JSON_MAPPER.getTypeFactory().constructType(Dict.class));
try {
return OBJECT_MAPPER.readValue(text, OBJECT_MAPPER.getTypeFactory().constructType(Dict.class));
} catch (MismatchedInputException e) {
// 类型不匹配说明不是json
return null;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
@@ -115,7 +141,11 @@ public class JsonUtils {
if (StringUtils.isBlank(text)) {
return null;
}
return JSON_MAPPER.readValue(text, JSON_MAPPER.getTypeFactory().constructCollectionType(List.class, Dict.class));
try {
return OBJECT_MAPPER.readValue(text, OBJECT_MAPPER.getTypeFactory().constructCollectionType(List.class, Dict.class));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
@@ -131,7 +161,11 @@ public class JsonUtils {
if (StringUtils.isEmpty(text)) {
return new ArrayList<>();
}
return JSON_MAPPER.readValue(text, JSON_MAPPER.getTypeFactory().constructCollectionType(List.class, clazz));
try {
return OBJECT_MAPPER.readValue(text, OBJECT_MAPPER.getTypeFactory().constructCollectionType(List.class, clazz));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
@@ -145,7 +179,7 @@ public class JsonUtils {
return false;
}
try {
JSON_MAPPER.readTree(str);
OBJECT_MAPPER.readTree(str);
return true;
} catch (Exception e) {
return false;
@@ -163,7 +197,7 @@ public class JsonUtils {
return false;
}
try {
JsonNode node = JSON_MAPPER.readTree(str);
JsonNode node = OBJECT_MAPPER.readTree(str);
return node.isObject();
} catch (Exception e) {
return false;
@@ -181,7 +215,7 @@ public class JsonUtils {
return false;
}
try {
JsonNode node = JSON_MAPPER.readTree(str);
JsonNode node = OBJECT_MAPPER.readTree(str);
return node.isArray();
} catch (Exception e) {
return false;

View File

@@ -29,12 +29,12 @@
<!-- dynamic-datasource 多数据源-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot4-starter</artifactId>
<artifactId>dynamic-datasource-spring-boot3-starter</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot4-starter</artifactId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
</dependency>
<dependency>

View File

@@ -141,7 +141,8 @@ public class OssClient {
try {
// 构建上传请求对象
FileUpload fileUpload = transferManager.uploadFile(
x -> x.putObjectRequest(
x -> {
x.source(filePath).putObjectRequest(
y -> y.bucket(properties.getBucketName())
.key(key)
.contentMD5(StringUtils.isNotEmpty(md5Digest) ? md5Digest : null)
@@ -149,10 +150,13 @@ public class OssClient {
// 用于设置对象的访问控制列表ACL。不同云厂商对ACL的支持和实现方式有所不同
// 因此根据具体的云服务提供商你可能需要进行不同的配置自行开启阿里云有acl权限配置腾讯云没有acl权限配置
//.acl(getAccessPolicy().getObjectCannedACL())
.build())
.addTransferListener(LoggingTransferListener.create())
.source(filePath).build());
.build()
);
if (log.isDebugEnabled()) {
x.addTransferListener(LoggingTransferListener.create());
}
}
);
// 等待上传完成并获取上传结果
CompletedFileUpload uploadResult = fileUpload.completionFuture().join();
String eTag = uploadResult.response().eTag();
@@ -192,16 +196,21 @@ public class OssClient {
// 使用 transferManager 进行上传
Upload upload = transferManager.upload(
x -> x.requestBody(body).addTransferListener(LoggingTransferListener.create())
.putObjectRequest(
x -> {
x.requestBody(body).putObjectRequest(
y -> y.bucket(properties.getBucketName())
.key(key)
.contentType(contentType)
// 用于设置对象的访问控制列表ACL。不同云厂商对ACL的支持和实现方式有所不同
// 因此根据具体的云服务提供商你可能需要进行不同的配置自行开启阿里云有acl权限配置腾讯云没有acl权限配置
//.acl(getAccessPolicy().getObjectCannedACL())
.build())
.build());
.build()
);
if (log.isDebugEnabled()) {
x.addTransferListener(LoggingTransferListener.create());
}
}
);
// 将输入流写入请求体
body.writeInputStream(inputStream);
@@ -229,13 +238,17 @@ public class OssClient {
Path tempFilePath = FileUtils.createTempFile().toPath();
// 使用 S3TransferManager 下载文件
FileDownload downloadFile = transferManager.downloadFile(
x -> x.getObjectRequest(
x -> {
x.destination(tempFilePath).getObjectRequest(
y -> y.bucket(properties.getBucketName())
.key(removeBaseUrl(path))
.build())
.addTransferListener(LoggingTransferListener.create())
.destination(tempFilePath)
.build());
.build()
);
if (log.isDebugEnabled()) {
x.addTransferListener(LoggingTransferListener.create());
}
}
);
// 等待文件下载操作完成
downloadFile.completionFuture().join();
return tempFilePath;
@@ -244,8 +257,8 @@ public class OssClient {
/**
* 下载文件从 Amazon S3 到 输出流
*
* @param key 文件在 Amazon S3 中的对象键
* @param out 输出流
* @param key 文件在 Amazon S3 中的对象键
* @param out 输出流
* @param consumer 自定义处理逻辑
* @throws OssException 如果下载失败,抛出自定义异常
*/
@@ -260,26 +273,24 @@ public class OssClient {
/**
* 下载文件从 Amazon S3 到 输出流
*
* @param key 文件在 Amazon S3 中的对象键
* @param key 文件在 Amazon S3 中的对象键
* @param contentLengthConsumer 文件大小消费者函数
* @return 写出订阅器
* @throws OssException 如果下载失败,抛出自定义异常
*/
public WriteOutSubscriber<OutputStream> download(String key, Consumer<Long> contentLengthConsumer) {
try {
// 构建下载请求
DownloadRequest<ResponsePublisher<GetObjectResponse>> publisherDownloadRequest = DownloadRequest.builder()
// 文件对象
.getObjectRequest(y -> y.bucket(properties.getBucketName())
.key(key)
.build())
.addTransferListener(LoggingTransferListener.create())
DownloadRequest.TypedBuilder<ResponsePublisher<GetObjectResponse>> typedBuilder = DownloadRequest.builder()
// 使用发布订阅转换器
.responseTransformer(AsyncResponseTransformer.toPublisher())
.build();
// 文件对象
.getObjectRequest(y -> y.bucket(properties.getBucketName()).key(key).build());
if (log.isDebugEnabled()) {
typedBuilder.addTransferListener(LoggingTransferListener.create());
}
// 使用 S3TransferManager 下载文件
Download<ResponsePublisher<GetObjectResponse>> publisherDownload = transferManager.download(publisherDownloadRequest);
Download<ResponsePublisher<GetObjectResponse>> publisherDownload = transferManager.download(typedBuilder.build());
// 获取下载发布订阅转换器
ResponsePublisher<GetObjectResponse> publisher = publisherDownload.completionFuture().join().result();
// 执行文件大小消费者函数
@@ -289,7 +300,7 @@ public class OssClient {
// 构建写出订阅器对象
return out -> {
// 创建可写入的字节通道
try(WritableByteChannel channel = Channels.newChannel(out)){
try (WritableByteChannel channel = Channels.newChannel(out)) {
// 订阅数据
publisher.subscribe(byteBuffer -> {
while (byteBuffer.hasRemaining()) {
@@ -347,7 +358,7 @@ public class OssClient {
*
* @param objectKey 对象KEY
* @param expiredTime 链接授权到期时间
* @param metadata 元数据
* @param metadata 元数据
*/
public String createPresignedPutUrl(String objectKey, Duration expiredTime, Map<String, String> metadata) {
// 使用 AWS S3 预签名 URL 的生成器 获取上传文件对象的预签名 URL

View File

@@ -28,12 +28,6 @@
<artifactId>redisson-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-cache</artifactId>
<version>${redisson.version}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>lock4j-redisson-spring-boot-starter</artifactId>

View File

@@ -4,7 +4,6 @@ import cn.hutool.core.util.ObjectUtil;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
@@ -48,7 +47,7 @@ public class RedisConfig {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(formatter));
javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(formatter));
JsonMapper om = new JsonMapper();
ObjectMapper om = new ObjectMapper();
om.registerModule(javaTimeModule);
om.setTimeZone(TimeZone.getDefault());
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
@@ -65,8 +64,6 @@ public class RedisConfig {
.setNettyThreads(redissonProperties.getNettyThreads())
// 缓存 Lua 脚本 减少网络传输(redisson 大部分的功能都是基于 Lua 脚本实现)
.setUseScriptCache(true)
//设置redis key前缀
.setNameMapper(new KeyPrefixHandler(redissonProperties.getKeyPrefix()))
.setCodec(codec);
if (SpringUtils.isVirtual()) {
config.setNettyExecutor(new VirtualThreadTaskExecutor("redisson-"));
@@ -75,6 +72,8 @@ public class RedisConfig {
if (ObjectUtil.isNotNull(singleServerConfig)) {
// 使用单机模式
config.useSingleServer()
//设置redis key前缀
.setNameMapper(new KeyPrefixHandler(redissonProperties.getKeyPrefix()))
.setTimeout(singleServerConfig.getTimeout())
.setClientName(singleServerConfig.getClientName())
.setIdleConnectionTimeout(singleServerConfig.getIdleConnectionTimeout())
@@ -86,6 +85,8 @@ public class RedisConfig {
RedissonProperties.ClusterServersConfig clusterServersConfig = redissonProperties.getClusterServersConfig();
if (ObjectUtil.isNotNull(clusterServersConfig)) {
config.useClusterServers()
//设置redis key前缀
.setNameMapper(new KeyPrefixHandler(redissonProperties.getKeyPrefix()))
.setTimeout(clusterServersConfig.getTimeout())
.setClientName(clusterServersConfig.getClientName())
.setIdleConnectionTimeout(clusterServersConfig.getIdleConnectionTimeout())

View File

@@ -1,7 +1,7 @@
package org.dromara.common.redis.handler;
import org.dromara.common.core.utils.StringUtils;
import org.redisson.config.NameMapper;
import org.redisson.api.NameMapper;
/**
* redis缓存key前缀处理

View File

@@ -18,7 +18,6 @@ package org.dromara.common.redis.manager;
import org.dromara.common.redis.utils.RedisUtils;
import org.redisson.api.RMap;
import org.redisson.api.RMapCache;
import org.redisson.api.map.event.MapEntryListener;
import org.redisson.spring.cache.CacheConfig;
import org.redisson.spring.cache.RedissonCache;
import org.springframework.boot.convert.DurationStyle;
@@ -190,9 +189,6 @@ public class PlusSpringCacheManager implements CacheManager {
cache = oldCache;
} else {
map.setMaxSize(config.getMaxSize());
for (MapEntryListener listener : config.getListeners()) {
map.addListener(listener);
}
}
return cache;
}

View File

@@ -1,9 +1,9 @@
package org.dromara.common.sensitive.annotation;
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import org.dromara.common.sensitive.core.SensitiveStrategy;
import org.dromara.common.sensitive.handler.SensitiveHandler;
import tools.jackson.databind.annotation.JsonSerialize;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
@@ -20,7 +20,6 @@ import java.lang.annotation.Target;
@JacksonAnnotationsInside
@JsonSerialize(using = SensitiveHandler.class)
public @interface Sensitive {
SensitiveStrategy strategy();
/**

View File

@@ -1,18 +1,20 @@
package org.dromara.common.sensitive.handler;
import cn.hutool.core.util.ObjectUtil;
import lombok.extern.slf4j.Slf4j;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.ContextualSerializer;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.sensitive.annotation.Sensitive;
import org.dromara.common.sensitive.core.SensitiveService;
import org.dromara.common.sensitive.core.SensitiveStrategy;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import tools.jackson.core.JacksonException;
import tools.jackson.core.JsonGenerator;
import tools.jackson.databind.BeanProperty;
import tools.jackson.databind.SerializationContext;
import tools.jackson.databind.ValueSerializer;
import java.io.IOException;
import java.util.Objects;
/**
@@ -21,14 +23,14 @@ import java.util.Objects;
* @author Yjoioooo
*/
@Slf4j
public class SensitiveHandler extends ValueSerializer<String> {
public class SensitiveHandler extends JsonSerializer<String> implements ContextualSerializer {
private SensitiveStrategy strategy;
private String[] roleKey;
private String[] perms;
@Override
public void serialize(String value, JsonGenerator gen, SerializationContext ctxt) throws JacksonException {
public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
try {
SensitiveService sensitiveService = SpringUtils.getBean(SensitiveService.class);
if (ObjectUtil.isNotNull(sensitiveService) && sensitiveService.isSensitive(roleKey, perms)) {
@@ -43,7 +45,7 @@ public class SensitiveHandler extends ValueSerializer<String> {
}
@Override
public ValueSerializer<?> createContextual(SerializationContext ctxt, BeanProperty property) {
public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException {
Sensitive annotation = property.getAnnotation(Sensitive.class);
if (Objects.nonNull(annotation) && Objects.equals(String.class, property.getType().getRawClass())) {
this.strategy = annotation.strategy();
@@ -51,6 +53,6 @@ public class SensitiveHandler extends ValueSerializer<String> {
this.perms = annotation.perms();
return this;
}
return super.createContextual(ctxt, property);
return prov.findValueSerializer(property.getType(), property);
}
}

View File

@@ -4,7 +4,7 @@ import org.dromara.common.sms.core.dao.PlusSmsDao;
import org.dromara.common.sms.handler.SmsExceptionHandler;
import org.dromara.sms4j.api.dao.SmsDao;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.data.redis.autoconfigure.DataRedisAutoConfiguration;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
@@ -13,7 +13,7 @@ import org.springframework.context.annotation.Primary;
*
* @author Feng
*/
@AutoConfiguration(after = {DataRedisAutoConfiguration.class})
@AutoConfiguration(after = {RedisAutoConfiguration.class})
public class SmsAutoConfiguration {
@Primary

View File

@@ -1,7 +1,9 @@
package org.dromara.common.tenant.config;
import cn.dev33.satoken.dao.SaTokenDao;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
import org.dromara.common.core.utils.reflect.ReflectUtils;
import org.dromara.common.redis.config.RedisConfig;
import org.dromara.common.redis.config.properties.RedissonProperties;
import org.dromara.common.tenant.core.TenantSaTokenDao;
@@ -9,6 +11,8 @@ import org.dromara.common.tenant.handle.PlusTenantLineHandler;
import org.dromara.common.tenant.handle.TenantKeyPrefixHandler;
import org.dromara.common.tenant.manager.TenantSpringCacheManager;
import org.dromara.common.tenant.properties.TenantProperties;
import org.redisson.config.ClusterServersConfig;
import org.redisson.config.SingleServerConfig;
import org.redisson.spring.starter.RedissonAutoConfigurationCustomizer;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
@@ -45,8 +49,19 @@ public class TenantConfig {
@Bean
public RedissonAutoConfigurationCustomizer tenantRedissonCustomizer(RedissonProperties redissonProperties) {
return config -> {
// 设置多租户 redis key前缀
config.setNameMapper(new TenantKeyPrefixHandler(redissonProperties.getKeyPrefix()));
TenantKeyPrefixHandler nameMapper = new TenantKeyPrefixHandler(redissonProperties.getKeyPrefix());
SingleServerConfig singleServerConfig = ReflectUtils.invokeGetter(config, "singleServerConfig");
if (ObjectUtil.isNotNull(singleServerConfig)) {
// 使用单机模式
// 设置多租户 redis key前缀
singleServerConfig.setNameMapper(nameMapper);
}
ClusterServersConfig clusterServersConfig = ReflectUtils.invokeGetter(config, "clusterServersConfig");
// 集群配置方式 参考下方注释
if (ObjectUtil.isNotNull(clusterServersConfig)) {
// 设置多租户 redis key前缀
clusterServersConfig.setNameMapper(nameMapper);
}
};
}

View File

@@ -1,8 +1,8 @@
package org.dromara.common.translation.annotation;
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import org.dromara.common.translation.core.handler.TranslationHandler;
import tools.jackson.databind.annotation.JsonSerialize;
import java.lang.annotation.*;
@@ -11,8 +11,10 @@ import java.lang.annotation.*;
*
* @author Lion Li
*/
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.METHOD})
@Documented
@JacksonAnnotationsInside
@JsonSerialize(using = TranslationHandler.class)
public @interface Translation {

View File

@@ -1,16 +1,14 @@
package org.dromara.common.translation.config;
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.dromara.common.translation.annotation.TranslationType;
import org.dromara.common.translation.core.TranslationInterface;
import org.dromara.common.translation.core.handler.TranslationBeanSerializerModifier;
import org.dromara.common.translation.core.handler.TranslationHandler;
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.jackson.autoconfigure.JsonMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import tools.jackson.databind.ser.SerializerFactory;
import java.util.HashMap;
import java.util.List;
@@ -28,6 +26,9 @@ public class TranslationConfig {
@Autowired
private List<TranslationInterface<?>> list;
@Autowired
private ObjectMapper objectMapper;
@PostConstruct
public void init() {
Map<String, TranslationInterface<?>> map = new HashMap<>(list.size());
@@ -40,15 +41,10 @@ public class TranslationConfig {
}
}
TranslationHandler.TRANSLATION_MAPPER.putAll(map);
}
@Bean
public JsonMapperBuilderCustomizer translationInitCustomizer() {
return builder -> {
SerializerFactory serializerFactory = builder.serializerFactory();
serializerFactory = serializerFactory.withSerializerModifier(new TranslationBeanSerializerModifier());
builder.serializerFactory(serializerFactory);
};
// 设置 Bean 序列化修改器
objectMapper.setSerializerFactory(
objectMapper.getSerializerFactory()
.withSerializerModifier(new TranslationBeanSerializerModifier()));
}
}

View File

@@ -1,9 +1,9 @@
package org.dromara.common.translation.core.handler;
import tools.jackson.databind.BeanDescription;
import tools.jackson.databind.SerializationConfig;
import tools.jackson.databind.ser.BeanPropertyWriter;
import tools.jackson.databind.ser.ValueSerializerModifier;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.SerializationConfig;
import com.fasterxml.jackson.databind.ser.BeanPropertyWriter;
import com.fasterxml.jackson.databind.ser.BeanSerializerModifier;
import java.util.List;
@@ -12,10 +12,10 @@ import java.util.List;
*
* @author Lion Li
*/
public class TranslationBeanSerializerModifier extends ValueSerializerModifier {
public class TranslationBeanSerializerModifier extends BeanSerializerModifier {
@Override
public List<BeanPropertyWriter> changeProperties(SerializationConfig config, BeanDescription.Supplier beanDesc,
public List<BeanPropertyWriter> changeProperties(SerializationConfig config, BeanDescription beanDesc,
List<BeanPropertyWriter> beanProperties) {
for (BeanPropertyWriter writer : beanProperties) {
// 如果序列化器为 TranslationHandler 的话 将 Null 值也交给他处理
@@ -23,7 +23,7 @@ public class TranslationBeanSerializerModifier extends ValueSerializerModifier {
writer.assignNullSerializer(serializer);
}
}
return super.changeProperties(config, beanDesc, beanProperties);
return beanProperties;
}
}

View File

@@ -1,17 +1,19 @@
package org.dromara.common.translation.core.handler;
import cn.hutool.core.util.ObjectUtil;
import lombok.extern.slf4j.Slf4j;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.ContextualSerializer;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.core.utils.reflect.ReflectUtils;
import org.dromara.common.translation.annotation.Translation;
import org.dromara.common.translation.core.TranslationInterface;
import tools.jackson.core.JacksonException;
import tools.jackson.core.JsonGenerator;
import tools.jackson.databind.BeanProperty;
import tools.jackson.databind.SerializationContext;
import tools.jackson.databind.ValueSerializer;
import lombok.extern.slf4j.Slf4j;
import java.io.IOException;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
@@ -22,7 +24,7 @@ import java.util.concurrent.ConcurrentHashMap;
* @author Lion Li
*/
@Slf4j
public class TranslationHandler extends ValueSerializer<Object> {
public class TranslationHandler extends JsonSerializer<Object> implements ContextualSerializer {
/**
* 全局翻译实现类映射器
@@ -32,7 +34,7 @@ public class TranslationHandler extends ValueSerializer<Object> {
private Translation translation;
@Override
public void serialize(Object value, JsonGenerator gen, SerializationContext ctxt) throws JacksonException {
public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
TranslationInterface<?> trans = TRANSLATION_MAPPER.get(translation.type());
if (ObjectUtil.isNotNull(trans)) {
// 如果映射字段不为空 则取映射字段的值
@@ -46,24 +48,24 @@ public class TranslationHandler extends ValueSerializer<Object> {
}
try {
Object result = trans.translation(value, translation.other());
gen.writePOJO(result);
gen.writeObject(result);
} catch (Exception e) {
log.error("翻译处理异常type: {}, value: {}", translation.type(), value, e);
// 出现异常时输出原始值而不是中断序列化
gen.writePOJO(value);
gen.writeObject(value);
}
} else {
gen.writePOJO(value);
gen.writeObject(value);
}
}
@Override
public ValueSerializer<?> createContextual(SerializationContext ctxt, BeanProperty property) {
public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException {
Translation translation = property.getAnnotation(Translation.class);
if (Objects.nonNull(translation)) {
this.translation = translation;
return this;
}
return super.createContextual(ctxt, property);
return prov.findValueSerializer(property.getType(), property);
}
}

View File

@@ -35,7 +35,7 @@
<!-- web 容器使用 undertow 性能更强 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
<dependency>

View File

@@ -2,7 +2,7 @@ package org.dromara.common.web.config;
import org.dromara.common.web.core.I18nLocaleResolver;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.webmvc.autoconfigure.WebMvcAutoConfiguration;
import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.web.servlet.LocaleResolver;

View File

@@ -1,63 +1,63 @@
//package org.dromara.common.web.config;
//
//import io.undertow.server.DefaultByteBufferPool;
//import io.undertow.server.handlers.DisallowedMethodsHandler;
//import io.undertow.util.HttpString;
//import io.undertow.websockets.jsr.WebSocketDeploymentInfo;
//import org.dromara.common.core.utils.SpringUtils;
//import org.springframework.boot.autoconfigure.AutoConfiguration;
//import org.springframework.boot.web.embedded.undertow.UndertowServletWebServerFactory;
//import org.springframework.boot.web.server.WebServerFactoryCustomizer;
//import org.springframework.core.task.VirtualThreadTaskExecutor;
//
///**
// * Undertow 自定义配置
// *
// * @author Lion Li
// */
//@AutoConfiguration
//public class UndertowConfig implements WebServerFactoryCustomizer<UndertowServletWebServerFactory> {
//
// /**
// * 自定义 Undertow 配置
// * <p>
// * 主要配置内容包括:
// * 1. 配置 WebSocket 部署信息
// * 2. 在虚拟线程模式下使用虚拟线程池
// * 3. 禁用不安全的 HTTP 方法,如 CONNECT、TRACE、TRACK
// * </p>
// *
// * @param factory Undertow 的 Web 服务器工厂
// */
// @Override
// public void customize(UndertowServletWebServerFactory factory) {
// factory.addDeploymentInfoCustomizers(deploymentInfo -> {
// // 配置 WebSocket 部署信息,设置 WebSocket 使用的缓冲区池
// WebSocketDeploymentInfo webSocketDeploymentInfo = new WebSocketDeploymentInfo();
// webSocketDeploymentInfo.setBuffers(new DefaultByteBufferPool(true, 1024));
// deploymentInfo.addServletContextAttribute("io.undertow.websockets.jsr.WebSocketDeploymentInfo", webSocketDeploymentInfo);
//
// // 如果启用了虚拟线程,配置 Undertow 使用虚拟线程池
// if (SpringUtils.isVirtual()) {
// // 创建虚拟线程池,线程池前缀为 "undertow-"
// VirtualThreadTaskExecutor executor = new VirtualThreadTaskExecutor("undertow-");
// // 设置虚拟线程池为执行器和异步执行器
// deploymentInfo.setExecutor(executor);
// deploymentInfo.setAsyncExecutor(executor);
// }
//
// // 配置禁止某些不安全的 HTTP 方法(如 CONNECT、TRACE、TRACK
// deploymentInfo.addInitialHandlerChainWrapper(handler -> {
// // 禁止三个方法 CONNECT/TRACE/TRACK 也是不安全的 避免爬虫骚扰
// HttpString[] disallowedHttpMethods = {
// HttpString.tryFromString("CONNECT"),
// HttpString.tryFromString("TRACE"),
// HttpString.tryFromString("TRACK")
// };
// // 使用 DisallowedMethodsHandler 拦截并拒绝这些方法的请求
// return new DisallowedMethodsHandler(handler, disallowedHttpMethods);
// });
// });
// }
//
//}
package org.dromara.common.web.config;
import io.undertow.server.DefaultByteBufferPool;
import io.undertow.server.handlers.DisallowedMethodsHandler;
import io.undertow.util.HttpString;
import io.undertow.websockets.jsr.WebSocketDeploymentInfo;
import org.dromara.common.core.utils.SpringUtils;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.web.embedded.undertow.UndertowServletWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.core.task.VirtualThreadTaskExecutor;
/**
* Undertow 自定义配置
*
* @author Lion Li
*/
@AutoConfiguration
public class UndertowConfig implements WebServerFactoryCustomizer<UndertowServletWebServerFactory> {
/**
* 自定义 Undertow 配置
* <p>
* 主要配置内容包括:
* 1. 配置 WebSocket 部署信息
* 2. 在虚拟线程模式下使用虚拟线程池
* 3. 禁用不安全的 HTTP 方法,如 CONNECT、TRACE、TRACK
* </p>
*
* @param factory Undertow 的 Web 服务器工厂
*/
@Override
public void customize(UndertowServletWebServerFactory factory) {
factory.addDeploymentInfoCustomizers(deploymentInfo -> {
// 配置 WebSocket 部署信息,设置 WebSocket 使用的缓冲区池
WebSocketDeploymentInfo webSocketDeploymentInfo = new WebSocketDeploymentInfo();
webSocketDeploymentInfo.setBuffers(new DefaultByteBufferPool(true, 1024));
deploymentInfo.addServletContextAttribute("io.undertow.websockets.jsr.WebSocketDeploymentInfo", webSocketDeploymentInfo);
// 如果启用了虚拟线程,配置 Undertow 使用虚拟线程池
if (SpringUtils.isVirtual()) {
// 创建虚拟线程池,线程池前缀为 "undertow-"
VirtualThreadTaskExecutor executor = new VirtualThreadTaskExecutor("undertow-");
// 设置虚拟线程池为执行器和异步执行器
deploymentInfo.setExecutor(executor);
deploymentInfo.setAsyncExecutor(executor);
}
// 配置禁止某些不安全的 HTTP 方法(如 CONNECT、TRACE、TRACK
deploymentInfo.addInitialHandlerChainWrapper(handler -> {
// 禁止三个方法 CONNECT/TRACE/TRACK 也是不安全的 避免爬虫骚扰
HttpString[] disallowedHttpMethods = {
HttpString.tryFromString("CONNECT"),
HttpString.tryFromString("TRACE"),
HttpString.tryFromString("TRACK")
};
// 使用 DisallowedMethodsHandler 拦截并拒绝这些方法的请求
return new DisallowedMethodsHandler(handler, disallowedHttpMethods);
});
});
}
}

View File

@@ -2,6 +2,7 @@ package org.dromara.common.web.handler;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.http.HttpStatus;
import com.fasterxml.jackson.core.JsonParseException;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.ConstraintViolation;
@@ -13,7 +14,6 @@ import org.dromara.common.core.exception.SseException;
import org.dromara.common.core.exception.base.BaseException;
import org.dromara.common.core.utils.StreamUtils;
import org.dromara.common.json.utils.JsonUtils;
import org.springframework.boot.json.JsonParseException;
import org.springframework.context.MessageSourceResolvable;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.expression.ExpressionException;

View File

@@ -4,6 +4,10 @@ import cn.hutool.core.io.IoUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
@@ -15,10 +19,6 @@ import org.dromara.common.web.filter.RepeatedlyRequestWrapper;
import org.springframework.http.MediaType;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import tools.jackson.databind.JsonNode;
import tools.jackson.databind.json.JsonMapper;
import tools.jackson.databind.node.ArrayNode;
import tools.jackson.databind.node.ObjectNode;
import java.util.HashSet;
import java.util.LinkedHashMap;
@@ -45,8 +45,8 @@ public class PlusWebInvokeTimeInterceptor implements HandlerInterceptor {
if (request instanceof RepeatedlyRequestWrapper) {
jsonParam = IoUtil.read(request.getReader());
if (StringUtils.isNotBlank(jsonParam)) {
JsonMapper jsonMapper = JsonUtils.getJsonMapper();
JsonNode rootNode = jsonMapper.readTree(jsonParam);
ObjectMapper objectMapper = JsonUtils.getObjectMapper();
JsonNode rootNode = objectMapper.readTree(jsonParam);
removeSensitiveFields(rootNode, SystemConstants.EXCLUDE_PROPERTIES);
jsonParam = rootNode.toString();
}
@@ -79,14 +79,14 @@ public class PlusWebInvokeTimeInterceptor implements HandlerInterceptor {
ObjectNode objectNode = (ObjectNode) node;
// 收集要删除的字段名(避免 ConcurrentModification
Set<String> fieldsToRemove = new HashSet<>();
objectNode.propertyNames().forEach(fieldName -> {
objectNode.fieldNames().forEachRemaining(fieldName -> {
if (ArrayUtil.contains(excludeProperties, fieldName)) {
fieldsToRemove.add(fieldName);
}
});
fieldsToRemove.forEach(objectNode::remove);
// 递归处理子节点
objectNode.values().forEach(child -> removeSensitiveFields(child, excludeProperties));
objectNode.elements().forEachRemaining(child -> removeSensitiveFields(child, excludeProperties));
} else if (node.isArray()) {
ArrayNode arrayNode = (ArrayNode) node;
for (JsonNode child : arrayNode) {

View File

@@ -2,3 +2,4 @@ org.dromara.common.web.config.CaptchaConfig
org.dromara.common.web.config.FilterConfig
org.dromara.common.web.config.I18nConfig
org.dromara.common.web.config.ResourcesConfig
org.dromara.common.web.config.UndertowConfig

View File

@@ -26,7 +26,7 @@
<!-- web 容器使用 undertow 性能更强 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
<!-- spring security 安全认证 -->