zhaowenyu
2026-02-05 08d5077ac4139455e6573f3a61bbae99ca62573e
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
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();
        }
    }
}