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 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); } }