From 1f6e8a56efec18d9343f6ac0f93d4cf21e9ab668 Mon Sep 17 00:00:00 2001
From: zhaowenyu <35589726@qq.com>
Date: Wed, 04 Feb 2026 09:45:54 +0800
Subject: [PATCH] 2026-02-04 zwy 机场媒体文件上传minio的模拟接口优化,可建目录、上传文件
---
ard-admin/src/main/java/com/ard/web/controller/sample/media/MinioProxyUploadController.java | 130 ++++++++++++++++++++----------------------
1 files changed, 62 insertions(+), 68 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
index f709229..462192f 100644
--- 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
@@ -3,9 +3,8 @@
import com.ard.common.config.OssConfiguration;
import com.ard.oss.model.CredentialsDTO;
import com.ard.oss.service.impl.OssServiceContext;
-import com.ard.sample.common.model.ResponseResult;
-import com.ard.sample.media.model.FileUploadDTO;
-import com.ard.sample.utils.S3PutObjectResponse;
+import com.ard.sample.media.Response.S3ErrorResponse;
+import com.ard.sample.media.Response.S3PutObjectResponse;
import io.minio.MinioClient;
import io.minio.PutObjectArgs;
import io.minio.credentials.StaticProvider;
@@ -13,6 +12,7 @@
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
@@ -20,6 +20,7 @@
import org.springframework.web.bind.annotation.*;
import java.io.ByteArrayInputStream;
+import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.time.OffsetDateTime;
@@ -35,100 +36,93 @@
@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 响应(返回给大疆,衔接后续流程)
- */
- @ApiOperation("minio上传")
- @PutMapping("/{bucketName}/{objectName}")
- public ResponseEntity<S3PutObjectResponse> proxyUpload(
+ @ApiOperation("minio上传/创建目录")
+ // 关键修改:{objectName:.*} 支持路径中包含斜杠,适配多层级文件/目录
+ @PutMapping(value = "/{bucketName}/{objectName:.*}", produces = MediaType.APPLICATION_XML_VALUE)
+ public ResponseEntity<?> proxyUpload(
@PathVariable String bucketName,
@PathVariable String objectName,
- @RequestBody byte[] fileContent,
+ // 允许请求体为空(目录创建场景)
+ @RequestBody(required = false) byte[] fileContent,
@RequestHeader(value = "Content-Type", defaultValue = "application/octet-stream") String contentType
) {
- try {
- log.info("开始中转上传文件到 MinIO,收到参数:bucketName={}, objectName={}",bucketName,objectName);
- // 步骤 1:获取 MinIO HTTPS 临时凭证(复用你的已有工具类,已验证可行)
+ // 基础参数校验
+ if (bucketName.isBlank() || objectName.isBlank()) {
+ return buildS3Error("InvalidRequest", "存储桶/对象名称不能为空", "/" + bucketName + "/" + objectName);
+ }
+
+ // 标识:是否为目录创建请求(对象名以 / 结尾)
+ boolean isCreateDirectory = objectName.endsWith("/");
+ // 处理空数据:目录创建/空请求体时,使用空字节数组
+ byte[] actualContent = (fileContent == null) ? new byte[0] : fileContent;
+
+ log.info("开始代理操作 | 存储桶:{} | 对象路径:{} | 操作类型:{}",
+ bucketName, objectName, isCreateDirectory ? "创建目录" : "上传文件");
+
+ try (ByteArrayInputStream inputStream = new ByteArrayInputStream(actualContent)) {
+ // ===================== 保留你原有逻辑:获取临时凭证 =====================
CredentialsDTO credentials = ossService.getCredentials();
if (credentials == null || credentials.getAccessKeyId() == null || credentials.getSecurityToken() == null) {
- return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(null);
+ log.error("MinIO临时凭证获取失败");
+ return buildS3Error("InvalidCredentials", "临时凭证无效", "/" + bucketName + "/" + objectName);
}
- // 步骤 2:构建 MinioClient(适配 SDK 8.3.7,忽略私有 SSL 证书,使用临时凭证)
+ // ===================== 保留你原有逻辑:构建MinioClient =====================
MinioClient minioClient = MinioClient.builder()
- .endpoint(OssConfiguration.endpoint) // 指定 MinIO HTTPS 原生地址
+ .endpoint(OssConfiguration.endpoint)
.credentialsProvider(new StaticProvider(
credentials.getAccessKeyId(),
credentials.getAccessKeySecret(),
- credentials.getSecurityToken() // SDK 8.3.7 支持临时凭证的 sessionToken 参数
+ credentials.getSecurityToken()
))
.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 类型
+ .bucket(bucketName)
+ .object(objectName)
+ .stream(inputStream, actualContent.length, -1)
+ .contentType(contentType)
.build()
);
- log.info("文件上传成功,文件名:{}",objectName);
- // 步骤 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)
- );
- log.info("构造回复大疆机场的响应信息-S3PutObjectResponse:{}",successResponse);
+ // 生成标准响应参数
+ String eTag = "\"" + DigestUtils.md5Hex(actualContent) + "\"";
+ String lastModified = OffsetDateTime.now().format(DateTimeFormatter.ISO_OFFSET_DATE_TIME);
+ S3PutObjectResponse successResponse = new S3PutObjectResponse(eTag, lastModified);
- // 步骤 5:返回 200 OK + XML 格式响应(Spring Boot 自动转换实体类为 XML)
- return ResponseEntity.status(HttpStatus.OK)
+ log.info("操作成功 | 路径:{} | ETag:{}", objectName, eTag);
+ return ResponseEntity.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 (MinioException e) {
+ log.error("MinIO服务异常: {}", e.getMessage(), e);
+ return buildS3Error("ClientError", e.getMessage(), "/" + bucketName + "/" + objectName);
+ } catch (InvalidKeyException | NoSuchAlgorithmException e) {
+ log.error("凭证/加密异常: {}", e.getMessage(), e);
+ return buildS3Error("CredentialError", "凭证校验失败", "/" + bucketName + "/" + objectName);
+ } catch (IOException e) {
+ log.error("文件流处理异常: {}", e.getMessage(), e);
+ return buildS3Error("IOError", "文件数据流处理失败", "/" + bucketName + "/" + objectName);
} catch (Exception e) {
- // 其他通用异常
- e.printStackTrace();
- log.info("MinIO 异常:{}",e.getMessage());
- return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(null);
+ log.error("通用服务异常: {}", e.getMessage(), e);
+ return buildS3Error("InternalError", "服务器内部异常", "/" + bucketName + "/" + objectName);
}
}
- @ApiOperation("测试")
- @PostMapping("/test")
- public ResponseResult fastUpload() {
- return ResponseResult.success();
+ /**
+ * 构造S3标准XML错误响应
+ */
+ private ResponseEntity<S3ErrorResponse> buildS3Error(String code, String message, String resource) {
+ S3ErrorResponse errorResponse = new S3ErrorResponse(code, message, resource);
+ return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
+ .contentType(MediaType.APPLICATION_XML)
+ .body(errorResponse);
}
-}
+}
\ No newline at end of file
--
Gitblit v1.9.3