package com.ard.web.controller.sample.media;
|
|
import io.swagger.annotations.Api;
|
import io.swagger.annotations.ApiOperation;
|
import lombok.extern.slf4j.Slf4j;
|
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RestController;
|
|
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletResponse;
|
import java.io.IOException;
|
import java.io.InputStream;
|
import java.io.OutputStream;
|
import java.net.*;
|
import java.util.Enumeration;
|
|
/**
|
* MinIO S3协议 原生流式透传代理
|
* 零修改转发请求头、请求体、响应,保证S3签名校验通过
|
*/
|
@Api(tags = "机场上传minio")
|
@Slf4j
|
@RestController
|
@RequestMapping("/minioTransfer")
|
public class MinioStreamProxyController {
|
|
// 配置MinIO服务地址,例如:http://127.0.0.1:9000
|
@Value("${oss.endpoint}")
|
private String minioEndpoint;
|
|
private static final String PROXY_PREFIX = "/minioTransfer";
|
// 流拷贝缓冲区大小(适配大文件)
|
private static final int BUFFER_SIZE = 8192;
|
|
@ApiOperation("MinIO原生接口代理(无感中转)")
|
@RequestMapping("/**")
|
public void streamProxy(HttpServletRequest request, HttpServletResponse response) throws IOException {
|
// 1. 解析原始请求信息
|
String method = request.getMethod();
|
String requestUri = request.getRequestURI();
|
String queryString = request.getQueryString();
|
log.info("S3代理透传 | 方法:{} | 路径:{} | 参数:{}", method, requestUri, queryString);
|
|
// 2. 拼接MinIO目标地址
|
String targetPath = requestUri.substring(PROXY_PREFIX.length());
|
StringBuilder targetUrl = new StringBuilder(minioEndpoint).append(targetPath);
|
if (queryString != null && !queryString.isBlank()) {
|
targetUrl.append("?").append(queryString);
|
}
|
|
// 3. 创建目标连接
|
URL url = new URL(targetUrl.toString());
|
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
// 配置连接参数
|
conn.setRequestMethod(method);
|
conn.setDoInput(true);
|
conn.setConnectTimeout(10000);
|
conn.setReadTimeout(300000);
|
// 关键:允许写入请求体(POST/PUT/PATCH)
|
conn.setDoOutput(!method.equalsIgnoreCase("GET") && !method.equalsIgnoreCase("HEAD"));
|
// 禁止自动重定向,保持S3协议一致性
|
conn.setInstanceFollowRedirects(false);
|
|
try {
|
// 4. 透传所有请求头(S3签名头完整保留,不修改、不删除)
|
Enumeration<String> headerNames = request.getHeaderNames();
|
if (headerNames != null) {
|
while (headerNames.hasMoreElements()) {
|
String name = headerNames.nextElement();
|
String value = request.getHeader(name);
|
conn.setRequestProperty(name, value);
|
}
|
}
|
|
// 5. 透传请求体(流式拷贝,不加载到内存,保证签名完整性)
|
if (conn.getDoOutput()) {
|
try (InputStream requestIn = request.getInputStream();
|
OutputStream connOut = conn.getOutputStream()) {
|
byte[] buffer = new byte[BUFFER_SIZE];
|
int len;
|
while ((len = requestIn.read(buffer)) != -1) {
|
connOut.write(buffer, 0, len);
|
}
|
}
|
}
|
|
// 6. 获取MinIO响应,透传状态码、响应头、响应体
|
int statusCode = conn.getResponseCode();
|
response.setStatus(statusCode);
|
|
// 透传响应头
|
conn.getHeaderFields().forEach((key, values) -> {
|
if (key != null && values != null && !values.isEmpty()) {
|
response.setHeader(key, values.get(0));
|
}
|
});
|
|
// 透传响应流(区分正常流/错误流)
|
InputStream responseIn;
|
try {
|
responseIn = conn.getInputStream();
|
} catch (IOException e) {
|
responseIn = conn.getErrorStream();
|
}
|
|
try (OutputStream responseOut = response.getOutputStream()) {
|
byte[] buffer = new byte[BUFFER_SIZE];
|
int len;
|
while ((len = responseIn.read(buffer)) != -1) {
|
responseOut.write(buffer, 0, len);
|
}
|
} finally {
|
if (responseIn != null) {
|
responseIn.close();
|
}
|
}
|
|
log.info("S3代理转发成功 | 目标地址:{} | 状态码:{}", targetUrl, statusCode);
|
|
} catch (Exception e) {
|
log.error("S3代理转发异常", e);
|
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "代理服务异常");
|
} finally {
|
// 关闭连接
|
conn.disconnect();
|
}
|
}
|
}
|