refactor(web): 请求响应可重复读流处理由 core 调整到 web 模块

This commit is contained in:
2025-03-26 20:43:16 +08:00
parent 85285e56a8
commit 21262701dc
14 changed files with 70 additions and 70 deletions

View File

@@ -23,6 +23,12 @@
<artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
<!-- AOPAspectJ -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
<!-- Hibernate Validator -->
<dependency>
<groupId>org.hibernate.validator</groupId>
@@ -59,12 +65,5 @@
<groupId>cn.hutool</groupId>
<artifactId>hutool-http</artifactId>
</dependency>
<!-- Jakarta原 Javax Servlet -->
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>

View File

@@ -40,45 +40,40 @@ public class PropertiesConstants {
public static final String SECURITY = CONTINEW_STARTER + StringConstants.DOT + "security";
/**
* 密码编解码配置
* 安全-密码编解码配置
*/
public static final String SECURITY_PASSWORD = SECURITY + StringConstants.DOT + "password";
/**
* 加/解密配置
* 安全-加/解密配置
*/
public static final String SECURITY_CRYPTO = SECURITY + StringConstants.DOT + "crypto";
/**
* 敏感词配置
* 安全-敏感词配置
*/
public static final String SECURITY_SENSITIVE_WORDS = SECURITY + StringConstants.DOT + "sensitive-words";
/**
* 安全-XSS 配置
*/
public static final String SECURITY_XSS = SECURITY + StringConstants.DOT + "xss";
/**
* Web 配置
*/
public static final String WEB = CONTINEW_STARTER + StringConstants.DOT + "web";
/**
* 跨域配置
* Web-跨域配置
*/
public static final String WEB_CORS = WEB + StringConstants.DOT + "cors";
/**
* 响应配置
* Web-响应配置
*/
public static final String WEB_RESPONSE = WEB + StringConstants.DOT + "response";
/**
* 链路配置
*/
public static final String WEB_TRACE = WEB + StringConstants.DOT + "trace";
/**
* XSS 配置
*/
public static final String WEB_XSS = WEB + StringConstants.DOT + "xss";
/**
* 日志配置
*/
@@ -89,11 +84,6 @@ public class PropertiesConstants {
*/
public static final String STORAGE = CONTINEW_STARTER + StringConstants.DOT + "storage";
/**
* 本地存储配置
*/
public static final String STORAGE_LOCAL = STORAGE + StringConstants.DOT + "local";
/**
* 验证码配置
*/
@@ -144,6 +134,11 @@ public class PropertiesConstants {
*/
public static final String IDEMPOTENT = CONTINEW_STARTER + StringConstants.DOT + "idempotent";
/**
* 链路追踪配置
*/
public static final String TRACE = CONTINEW_STARTER + StringConstants.DOT + "trace";
private PropertiesConstants() {
}
}

View File

@@ -1,104 +0,0 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.core.wrapper;
import cn.hutool.core.io.IoUtil;
import jakarta.servlet.ReadListener;
import jakarta.servlet.ServletInputStream;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
/**
* 可重复读取请求体的包装器
* 支持文件流直接透传,非文件流可重复读取
*
* @author echo
* @since 2.10.0
*/
public class RepeatReadRequestWrapper extends HttpServletRequestWrapper {
private byte[] cachedBody;
private final HttpServletRequest originalRequest;
public RepeatReadRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
this.originalRequest = request;
// 判断是否为文件上传请求
if (!isMultipartContent(request)) {
this.cachedBody = IoUtil.readBytes(request.getInputStream(), false);
}
}
@Override
public ServletInputStream getInputStream() throws IOException {
// 如果是文件上传,直接返回原始输入流
if (isMultipartContent(originalRequest)) {
return originalRequest.getInputStream();
}
// 非文件上传,返回可重复读取的输入流
final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(cachedBody);
return new ServletInputStream() {
@Override
public boolean isFinished() {
return byteArrayInputStream.available() == 0;
}
@Override
public boolean isReady() {
return true;
}
@Override
public void setReadListener(ReadListener readListener) {
// 非阻塞I/O这里可以根据需要实现
}
@Override
public int read() {
return byteArrayInputStream.read();
}
};
}
@Override
public BufferedReader getReader() throws IOException {
// 如果是文件上传直接返回原始Reader
if (isMultipartContent(originalRequest)) {
return originalRequest.getReader();
}
return new BufferedReader(new InputStreamReader(new ByteArrayInputStream(cachedBody), StandardCharsets.UTF_8));
}
/**
* 检查是否为文件上传请求
*
* @param request 请求对象
* @return 是否为文件上传请求
*/
private boolean isMultipartContent(HttpServletRequest request) {
return request.getContentType() != null && request.getContentType().toLowerCase().startsWith("multipart/");
}
}

View File

@@ -1,145 +0,0 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.core.wrapper;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.WriteListener;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletResponseWrapper;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
/**
* 可重复读取响应内容的包装器
* 支持缓存响应内容,便于日志记录和后续处理 (不缓存SSE)
*
* @author echo
* @author Charles7c
* @since 2.10.0
*/
public class RepeatReadResponseWrapper extends HttpServletResponseWrapper {
private final ByteArrayOutputStream cachedOutputStream = new ByteArrayOutputStream();
private final PrintWriter writer = new PrintWriter(cachedOutputStream, true);
/**
* 是否为流式响应
*/
private boolean isStreamingResponse = false;
public RepeatReadResponseWrapper(HttpServletResponse response) {
super(response);
checkStreamingResponse();
}
@Override
public void setContentType(String type) {
super.setContentType(type);
// 根据 Content-Type 判断是否为流式响应
if (type != null) {
String lowerType = type.toLowerCase();
isStreamingResponse = lowerType.contains("text/event-stream");
}
}
private void checkStreamingResponse() {
String contentType = getContentType();
if (contentType != null) {
String lowerType = contentType.toLowerCase();
isStreamingResponse = lowerType.contains("text/event-stream");
}
}
@Override
public ServletOutputStream getOutputStream() throws IOException {
checkStreamingResponse();
// 对于 SSE 流式响应,直接返回原始响应流,不做额外处理
if (isStreamingResponse) {
return super.getOutputStream();
}
return new ServletOutputStream() {
@Override
public boolean isReady() {
return true;
}
@Override
public void setWriteListener(WriteListener writeListener) {
}
@Override
public void write(int b) throws IOException {
cachedOutputStream.write(b);
}
@Override
public void write(byte[] b) throws IOException {
cachedOutputStream.write(b);
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
cachedOutputStream.write(b, off, len);
}
};
}
@Override
public PrintWriter getWriter() throws IOException {
checkStreamingResponse();
if (isStreamingResponse) {
// 对于 SSE 流式响应,直接返回原始响应写入器,不做额外处理
return super.getWriter();
}
return writer;
}
/**
* 获取缓存的响应内容
*
* @return 缓存的响应内容
*/
public String getResponseContent() {
if (!isStreamingResponse) {
writer.flush();
return cachedOutputStream.toString(StandardCharsets.UTF_8);
}
return null;
}
/**
* 将缓存的响应内容复制到原始响应中
*
* @throws IOException IO 异常
*/
public void copyBodyToResponse() throws IOException {
if (!isStreamingResponse && cachedOutputStream.size() > 0) {
getResponse().getOutputStream().write(cachedOutputStream.toByteArray());
}
}
/**
* 是否为流式响应
*
* @return 是否为流式响应
*/
public boolean isStreamingResponse() {
return isStreamingResponse;
}
}