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.media.Response.S3ErrorResponse;
|
import com.ard.sample.media.Response.S3PutObjectResponse;
|
import io.minio.MinioClient;
|
import io.minio.PutObjectArgs;
|
import io.minio.credentials.StaticProvider;
|
import io.minio.errors.MinioException;
|
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;
|
import org.springframework.http.ResponseEntity;
|
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;
|
import java.time.format.DateTimeFormatter;
|
|
/**
|
* 适配 MinIO SDK 8.3.7 + 服务器 RELEASE.2024-08-03
|
* 承接大疆 PUT 上传请求,中转上传到 MinIO HTTPS 接口
|
*/
|
@Api(tags = "机场上传minio")
|
@Slf4j
|
@RestController
|
@RequestMapping("/minioTransfer") // 根路径,匹配 Nginx 转发的 /dock/、/kmz/ 路径
|
public class MinioProxyUploadController {
|
|
// 注入你的凭证服务(保持原有注入方式不变)
|
@Autowired
|
private OssServiceContext ossService;
|
|
@ApiOperation("minio上传/创建目录")
|
// 关键修改:{objectName:.*} 支持路径中包含斜杠,适配多层级文件/目录
|
@PutMapping(value = "/{bucketName}/{objectName:.*}", produces = MediaType.APPLICATION_XML_VALUE)
|
public ResponseEntity<?> proxyUpload(
|
@PathVariable String bucketName,
|
@PathVariable String objectName,
|
// 允许请求体为空(目录创建场景)
|
@RequestBody(required = false) byte[] fileContent,
|
@RequestHeader(value = "Content-Type", defaultValue = "application/octet-stream") String contentType
|
) {
|
// 基础参数校验
|
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) {
|
log.error("MinIO临时凭证获取失败");
|
return buildS3Error("InvalidCredentials", "临时凭证无效", "/" + bucketName + "/" + objectName);
|
}
|
|
// ===================== 保留你原有逻辑:构建MinioClient =====================
|
MinioClient minioClient = MinioClient.builder()
|
.endpoint(OssConfiguration.endpoint)
|
.credentialsProvider(new StaticProvider(
|
credentials.getAccessKeyId(),
|
credentials.getAccessKeySecret(),
|
credentials.getSecurityToken()
|
))
|
.build();
|
|
// ===================== 核心上传逻辑:兼容文件/目录/多层级路径 =====================
|
minioClient.putObject(
|
PutObjectArgs.builder()
|
.bucket(bucketName)
|
.object(objectName)
|
.stream(inputStream, actualContent.length, -1)
|
.contentType(contentType)
|
.build()
|
);
|
|
// 生成标准响应参数
|
String eTag = "\"" + DigestUtils.md5Hex(actualContent) + "\"";
|
String lastModified = OffsetDateTime.now().format(DateTimeFormatter.ISO_OFFSET_DATE_TIME);
|
S3PutObjectResponse successResponse = new S3PutObjectResponse(eTag, lastModified);
|
|
log.info("操作成功 | 路径:{} | ETag:{}", objectName, eTag);
|
return ResponseEntity.ok()
|
.contentType(MediaType.APPLICATION_XML)
|
.body(successResponse);
|
|
} 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) {
|
log.error("通用服务异常: {}", e.getMessage(), e);
|
return buildS3Error("InternalError", "服务器内部异常", "/" + bucketName + "/" + objectName);
|
}
|
}
|
|
/**
|
* 构造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);
|
}
|
}
|