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