From 8e6f450afaab046743945a637d9b53fa8d97c5bc Mon Sep 17 00:00:00 2001
From: zhaowenyu <35589726@qq.com>
Date: Sat, 24 Jan 2026 15:01:03 +0800
Subject: [PATCH] 2026-01-24 zwy nginx将大疆机场媒体文件上传minio的请求转发到接口proxyUpload中,由代码做向minio上传,然后模拟minio响应给机场。
---
ard-sample/pom.xml | 6 +
/dev/null | 65 -------------
ard-sample/src/main/java/com/ard/sample/storage/service/impl/StorageServiceImpl.java | 14 --
ard-admin/src/main/java/com/ard/web/controller/sample/media/MinioProxyUploadController.java | 124 ++++++++++++++++++++++++
ard-sample/src/main/java/com/ard/sample/utils/S3PutObjectResponse.java | 18 +++
pom.xml | 16 +++
6 files changed, 164 insertions(+), 79 deletions(-)
diff --git a/ard-admin/src/main/java/com/ard/web/controller/sample/media/MinioProxyUploadController.java b/ard-admin/src/main/java/com/ard/web/controller/sample/media/MinioProxyUploadController.java
new file mode 100644
index 0000000..cad733a
--- /dev/null
+++ b/ard-admin/src/main/java/com/ard/web/controller/sample/media/MinioProxyUploadController.java
@@ -0,0 +1,124 @@
+package com.ard.web.controller.sample.media;
+
+import com.ard.common.config.OssConfiguration;
+import com.ard.oss.model.CredentialsDTO;
+import com.ard.oss.service.impl.OssServiceContext;
+import com.ard.sample.utils.S3PutObjectResponse;
+import io.minio.MinioClient;
+import io.minio.PutObjectArgs;
+import io.minio.credentials.StaticProvider;
+import io.minio.errors.MinioException;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestHeader;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.io.ByteArrayInputStream;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.time.OffsetDateTime;
+import java.time.format.DateTimeFormatter;
+
+/**
+ * 适配 MinIO SDK 8.3.7 + 服务器 RELEASE.2024-08-03
+ * 承接大疆 PUT 上传请求,中转上传到 MinIO HTTPS 接口
+ */
+@Slf4j
+@RestController
+@RequestMapping("/minioTransfer") // 根路径,匹配 Nginx 转发的 /dock/、/kmz/ 路径
+public class MinioProxyUploadController {
+
+// @Autowired
+// private MinioStsUtils minioStsUtils; // 复用你已有的 HTTPS 临时凭证工具类
+
+ @Autowired
+ private OssServiceContext ossService;
+
+ // 你的 MinIO HTTPS 原生地址(固定,与服务器版本匹配)
+// private static final String MINIO_HTTPS_ENDPOINT = "https://192.168.1.3:9001";
+
+ /**
+ * 核心接口:中转大疆的 PUT 上传请求到 MinIO
+ * 匹配路径:/{bucketName}/{objectName}(例:/dock/xxx.png)
+ * @param bucketName 桶名(dock/kmz,与大疆上传一致)
+ * @param objectName 文件名(例:dji-test-20240124.png)
+ * @param fileContent 文件二进制内容(大疆上传的核心数据)
+ * @param contentType 文件 MIME 类型(默认二进制流,适配多种文件格式)
+ * @return S3 规范 XML 响应(返回给大疆,衔接后续流程)
+ */
+ @PutMapping("/{bucketName}/{objectName}")
+ public ResponseEntity<S3PutObjectResponse> proxyUpload(
+ @PathVariable String bucketName,
+ @PathVariable String objectName,
+ @RequestBody byte[] fileContent,
+ @RequestHeader(value = "Content-Type", defaultValue = "application/octet-stream") String contentType
+ ) {
+ try {
+ // 步骤 1:获取 MinIO HTTPS 临时凭证(复用你的已有工具类,已验证可行)
+ CredentialsDTO credentials = ossService.getCredentials();
+ if (credentials == null || credentials.getAccessKeyId() == null || credentials.getSecurityToken() == null) {
+ return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(null);
+ }
+
+ // 步骤 2:构建 MinioClient(适配 SDK 8.3.7,忽略私有 SSL 证书,使用临时凭证)
+ MinioClient minioClient = MinioClient.builder()
+ .endpoint(OssConfiguration.endpoint) // 指定 MinIO HTTPS 原生地址
+ .credentialsProvider(new StaticProvider(
+ credentials.getAccessKeyId(),
+ credentials.getAccessKeySecret(),
+ credentials.getSecurityToken() // SDK 8.3.7 支持临时凭证的 sessionToken 参数
+ ))
+ .build();
+
+ // 步骤 3:中转上传文件到 MinIO(核心:SDK 8.3.7 兼容 PutObjectArgs 构建)
+ minioClient.putObject(
+ PutObjectArgs.builder()
+ .bucket(bucketName) // 桶名与大疆上传一致(dock/kmz)
+ .object(objectName) // 文件名与大疆上传一致
+ .stream(
+ new ByteArrayInputStream(fileContent), // 文件二进制流
+ fileContent.length, // 文件字节长度
+ -1 // 自动分块(SDK 8.3.7 支持 -1 自动处理,适配小文件上传)
+ )
+ .contentType(contentType) // 文件 MIME 类型
+ .build()
+ );
+
+ // 步骤 4:构造符合 S3 规范的成功响应(返回给大疆,确保流程衔接)
+ S3PutObjectResponse successResponse = new S3PutObjectResponse(
+ // ETag:生成文件 MD5 值(带双引号,符合 S3 规范,依赖 commons-codec)
+ "\"" + org.apache.commons.codec.digest.DigestUtils.md5Hex(fileContent) + "\"",
+ // LastModified:当前时间(ISO 格式,MinIO 服务器兼容该格式)
+ OffsetDateTime.now().format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)
+ );
+
+ // 步骤 5:返回 200 OK + XML 格式响应(Spring Boot 自动转换实体类为 XML)
+ return ResponseEntity.status(HttpStatus.OK)
+ .contentType(MediaType.APPLICATION_XML)
+ .body(successResponse);
+
+ } catch (MinioException e) {
+ // MinIO 上传异常(SDK 8.3.7 抛出的异常类型,与高版本一致)
+ e.printStackTrace();
+ log.info("MinIO 上传异常:{}",e.getMessage());
+ return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(null);
+ } catch (NoSuchAlgorithmException | InvalidKeyException e) {
+ // 凭证/算法异常(适配 SDK 8.3.7 的加密校验逻辑)
+ e.printStackTrace();
+ log.info("MinIO 凭证或算法异常:{}",e.getMessage());
+ return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(null);
+ } catch (Exception e) {
+ // 其他通用异常
+ e.printStackTrace();
+ log.info("MinIO 异常:{}",e.getMessage());
+ return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(null);
+ }
+ }
+}
diff --git a/ard-sample/pom.xml b/ard-sample/pom.xml
index dec4c54..eae7ce1 100644
--- a/ard-sample/pom.xml
+++ b/ard-sample/pom.xml
@@ -32,6 +32,12 @@
<javax-jaxb.version>2.3.0</javax-jaxb.version>
</properties>
<dependencies>
+ <!-- 子模块主动声明,继承主pom中jackson-dataformat-xml的版本,真正引入依赖 -->
+ <dependency>
+ <groupId>com.fasterxml.jackson.dataformat</groupId>
+ <artifactId>jackson-dataformat-xml</artifactId>
+ </dependency>
+
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
diff --git a/ard-sample/src/main/java/com/ard/sample/storage/service/impl/StorageServiceImpl.java b/ard-sample/src/main/java/com/ard/sample/storage/service/impl/StorageServiceImpl.java
index 9d811d3..7516805 100644
--- a/ard-sample/src/main/java/com/ard/sample/storage/service/impl/StorageServiceImpl.java
+++ b/ard-sample/src/main/java/com/ard/sample/storage/service/impl/StorageServiceImpl.java
@@ -8,7 +8,6 @@
import com.ard.sample.config.DockConfiguration;
import com.ard.sample.storage.service.IStorageService;
import com.ard.oss.service.impl.OssServiceContext;
-import com.ard.sample.utils.MinioStsUtils;
import io.minio.MinioClient;
import io.minio.PutObjectArgs;
import io.minio.credentials.StaticProvider;
@@ -38,23 +37,10 @@
@Autowired
private OssServiceContext ossService;
- @Autowired
- private MinioStsUtils minioStsUtils;
@Override
public StsCredentialsDTO getSTSCredentials() {
CredentialsDTO credentials = ossService.getCredentials();
-// CredentialsDTO credentials = minioStsUtils.getCredentials();
-
- //验证一下minio的临时凭证是否可用
- log.info("验证http临时凭证是否有效:{}", credentials);
- execMinio(DockConfiguration.endpoint,
- credentials.getAccessKeyId(),
- credentials.getAccessKeySecret(),
- credentials.getSecurityToken(),
- OssConfiguration.region,
- DockConfiguration.bucket,
- "test_object_http");
log.info("验证https临时凭证是否有效:{}", credentials);
execMinio(OssConfiguration.endpoint,
diff --git a/ard-sample/src/main/java/com/ard/sample/utils/MinioStsUtils.java b/ard-sample/src/main/java/com/ard/sample/utils/MinioStsUtils.java
deleted file mode 100644
index 4208225..0000000
--- a/ard-sample/src/main/java/com/ard/sample/utils/MinioStsUtils.java
+++ /dev/null
@@ -1,65 +0,0 @@
-package com.ard.sample.utils;
-
-import com.ard.oss.model.CredentialsDTO;
-import io.minio.credentials.AssumeRoleProvider;
-import lombok.extern.slf4j.Slf4j;
-import okhttp3.ConnectionPool;
-import okhttp3.OkHttpClient;
-import org.springframework.stereotype.Component;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-import java.security.NoSuchAlgorithmException;
-import java.security.ProviderException;
-import java.util.concurrent.TimeUnit;
-
-import static com.ard.sample.config.DockConfiguration.*;
-
-/**
- * 获取http的STS凭证的工具类,用于简化STS临时凭证的获取过程。
- */
-@Slf4j
-@Component
-public class MinioStsUtils {
-
- public CredentialsDTO getCredentials() {
- try {
- // 构建适配HTTP协议的OkHttpClient(移除所有SSL相关配置)
- OkHttpClient httpClient = new OkHttpClient.Builder()
- // 保留原代码的连接池配置(禁用连接复用,如需复用可删除此行)
- .connectionPool(new ConnectionPool(5, 30, TimeUnit.SECONDS))
- .connectTimeout(10, TimeUnit.SECONDS) // 连接超时
- .readTimeout(30, TimeUnit.SECONDS) // 读取超时
- .writeTimeout(30, TimeUnit.SECONDS) // 写入超时
- .build();
-
- // 构建 AssumeRoleProvider,使用简化后的HTTP客户端
- String roleSessionName = "dji-sts-session-" + System.currentTimeMillis(); // 唯一会话名称(必需,避免重复)
- AssumeRoleProvider provider = new AssumeRoleProvider(
- endpoint, // 注意:此处endpoint需改为HTTP地址(如 http://sts.example.com)
- accessKey, // 主账号AccessKey
- secretKey, // 主账号SecretKey
- Math.toIntExact(expire), // 凭证过期时间(转int)
- null, // 可选参数(如RoleArn,根据实际SDK调整)
- region, // 地域(如cn-hangzhou)
- null,
- roleSessionName,
- null, // 其他可选参数
- httpClient // 传入HTTP版本的OkHttpClient
- );
-
- log.info("生成临时凭证条件:{}",provider);
-
- // 获取 STS 临时凭证
- var stsCredentials = provider.fetch();
- return new CredentialsDTO(stsCredentials, expire);
-
- } catch (ProviderException e) {
- log.error("Failed to fetch/parse STS response (HTTP access)", e);
- throw new RuntimeException("STS response parse failed", e);
- } catch (NoSuchAlgorithmException e) {
- log.error("Failed to fetch/parse STS response (HTTP access)", e);
- throw new RuntimeException(e);
- }
- }
-}
\ No newline at end of file
diff --git a/ard-sample/src/main/java/com/ard/sample/utils/S3PutObjectResponse.java b/ard-sample/src/main/java/com/ard/sample/utils/S3PutObjectResponse.java
new file mode 100644
index 0000000..3c124b5
--- /dev/null
+++ b/ard-sample/src/main/java/com/ard/sample/utils/S3PutObjectResponse.java
@@ -0,0 +1,18 @@
+package com.ard.sample.utils;
+
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 符合 S3 规范的 PutObject 成功响应(XML 格式,大疆可识别)
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@JacksonXmlRootElement(localName = "PutObjectResult") // XML 根节点名称严格遵循 S3 规范
+public class S3PutObjectResponse {
+ private String ETag; // 文件 MD5 值(带双引号,符合 S3 规范要求)
+ private String LastModified; // 上传成功时间(ISO 格式,例:2026-01-23T18:00:00Z)
+}
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 3ae5075..b90c01a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -34,6 +34,8 @@
<okhttp.version>4.8.1</okhttp.version>
<mqtt.version>5.5.5</mqtt.version>
<minio.version>8.3.7</minio.version>
+ <commons.version>1.15</commons.version>
+ <fasterxml.version>2.13.5</fasterxml.version>
<forest.version>1.8.0</forest.version>
<spring-boot-admin.version>2.5.1</spring-boot-admin.version>
</properties>
@@ -245,6 +247,20 @@
<version>${minio.version}</version>
</dependency>
+ <!-- 用于构造返回给大疆的 S3 格式 XML 响应 -->
+ <dependency>
+ <groupId>com.fasterxml.jackson.dataformat</groupId>
+ <artifactId>jackson-dataformat-xml</artifactId>
+ <version>${fasterxml.version}</version>
+ </dependency>
+
+ <!-- 用于计算文件 MD5(生成 ETag,可选但推荐,SDK 8.3.7 不自带该工具类) -->
+ <dependency>
+ <groupId>commons-codec</groupId>
+ <artifactId>commons-codec</artifactId>
+ <version>${commons.version}</version>
+ </dependency>
+
<!-- Forest -->
<dependency>
<groupId>com.dtflys.forest</groupId>
--
Gitblit v1.9.3