From 7c7fe2e1c186371076fd7dea49ffc650790c1d8b Mon Sep 17 00:00:00 2001 From: b2894lxlx <517289602@qq.com> Date: Tue, 13 Oct 2026 22:14:50 +0800 Subject: [PATCH] =?UTF-8?q?:bug:=20=E4=BF=AE=E5=A4=8D=20MinIO=20endpoint?= =?UTF-8?q?=20=E5=90=AB=20path=20=E5=AF=BC=E8=87=B4=20blade-transport=20?= =?UTF-8?q?=E5=90=AF=E5=8A=A8=E5=A4=B1=E8=B4=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Cursor --- .../transport/config/VoucherMinioConfig.java | 39 +++++++++- .../impl/VoucherManageServiceImpl.java | 71 ++++++++++++++++++- doc/nacos/blade-prod.yaml | 6 +- 3 files changed, 109 insertions(+), 7 deletions(-) diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/config/VoucherMinioConfig.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/config/VoucherMinioConfig.java index 08ed5e38..5c0db187 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/config/VoucherMinioConfig.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/config/VoucherMinioConfig.java @@ -5,19 +5,28 @@ package org.springblade.transport.config; import io.minio.MinioClient; +import lombok.extern.slf4j.Slf4j; +import org.springblade.core.log.exception.ServiceException; +import org.springblade.core.tool.utils.StringUtil; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import java.net.URI; + /** * 凭证图片 MinIO 客户端配置。 *

* 连接参数由 Nacos 的 file.storage.minio 配置提供。 + *

* 未配置 {@code file.storage.minio.endpoint} 时不注册该客户端, - * 以免凭证上传功能缺失配置导致整个 blade-transport 服务无法启动; - * 此时凭证相关接口会在调用时给出明确提示,而非启动即失败。 + * 以免凭证上传功能缺失配置导致整个 blade-transport 服务无法启动。 */ +@Slf4j @Configuration @ConditionalOnProperty(prefix = "file.storage.minio", name = "endpoint") public class VoucherMinioConfig { @@ -27,6 +36,30 @@ public class VoucherMinioConfig { @Value("${file.storage.minio.endpoint:${minio.endpoint:}}") String endpoint, @Value("${file.storage.minio.access-key-id:${minio.access-key:}}") String accessKey, @Value("${file.storage.minio.access-key-secret:${minio.secret-key:}}") String secretKey) { - return MinioClient.builder().endpoint(endpoint).credentials(accessKey, secretKey).build(); + String clientEndpoint = resolveClientEndpoint(endpoint); + if (!clientEndpoint.equals(endpoint == null ? null : endpoint.trim())) { + log.warn("MinIO endpoint 含 path,已剥离为 {}(原值={})。服务端读写请配置内网 endpoint,外网请用 transform-endpoint", + clientEndpoint, endpoint); + } + return MinioClient.builder().endpoint(clientEndpoint).credentials(accessKey, secretKey).build(); + } + + /** + * MinIO SDK 不允许 endpoint 带 path(例如 https://host/oss)。 + */ + public static String resolveClientEndpoint(String endpoint) { + if (StringUtil.isBlank(endpoint)) { + throw new ServiceException("MinIO endpoint 不能为空"); + } + URI uri = URI.create(endpoint.trim()); + if (StringUtil.isBlank(uri.getScheme()) || StringUtil.isBlank(uri.getHost())) { + throw new ServiceException("MinIO endpoint 配置不正确: " + endpoint); + } + StringBuilder origin = new StringBuilder(); + origin.append(uri.getScheme()).append("://").append(uri.getHost()); + if (uri.getPort() > 0) { + origin.append(':').append(uri.getPort()); + } + return origin.toString(); } } diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/VoucherManageServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/VoucherManageServiceImpl.java index 12e2c34e..16d35e08 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/VoucherManageServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/VoucherManageServiceImpl.java @@ -88,6 +88,10 @@ public class VoucherManageServiceImpl extends BaseServiceImpl minioClientProvider; private final String minioBucketName; private final String minioRootDirectory; + private final String minioTransformEndpoint; + private final String minioAccessKey; + private final String minioSecretKey; + private volatile MinioClient transformPresignClient; public VoucherManageServiceImpl(VoucherWaybillBatchMapper voucherWaybillBatchMapper, IProjectApplyService projectApplyService, IWaybillService waybillService, @@ -95,7 +99,10 @@ public class VoucherManageServiceImpl extends BaseServiceImpl minioClientProvider, @Value("${file.storage.minio.bucket-name:${minio.bucket-name:}}") String minioBucketName, - @Value("${file.storage.minio.root-directory:${minio.root-directory:}}") String minioRootDirectory) { + @Value("${file.storage.minio.root-directory:${minio.root-directory:}}") String minioRootDirectory, + @Value("${file.storage.minio.transform-endpoint:${minio.transform-endpoint:}}") String minioTransformEndpoint, + @Value("${file.storage.minio.access-key-id:${minio.access-key:}}") String minioAccessKey, + @Value("${file.storage.minio.access-key-secret:${minio.secret-key:}}") String minioSecretKey) { this.voucherWaybillBatchMapper = voucherWaybillBatchMapper; this.projectApplyService = projectApplyService; this.waybillService = waybillService; @@ -106,6 +113,9 @@ public class VoucherManageServiceImpl extends BaseServiceImpl 0) { + result.append(':').append(url.getPort()); + } + result.append(pathPrefix).append(path); + if (Func.isNotEmpty(url.getRawQuery())) { + result.append('?').append(url.getRawQuery()); + } + return result.toString(); + } + + private String normalizePathPrefix(String path) { + if (Func.isEmpty(path) || "/".equals(path)) { + return ""; + } + String normalized = path.endsWith("/") ? path.substring(0, path.length() - 1) : path; + return normalized.startsWith("/") ? normalized : "/" + normalized; + } + private String normalizePlateNo(String plateNo) { return plateNo == null ? null : plateNo.replaceAll("[\\s-]", "").toUpperCase(); } diff --git a/doc/nacos/blade-prod.yaml b/doc/nacos/blade-prod.yaml index 889576ad..4a8b3e3e 100644 --- a/doc/nacos/blade-prod.yaml +++ b/doc/nacos/blade-prod.yaml @@ -120,11 +120,13 @@ baidu: request-timeout: 30s token-refresh-advance: 1m -# 凭证分片上传走 blade-file,与 /resource/oss 库表配置相互独立;外网地址必须在此配置并发布到 Nacos 后重启 blade-file -# 不要使用可被空环境变量覆盖的 ${VAR:default},否则仍会签发 http://内网IP 预签名导致 Mixed Content +# 对象存储(blade-file 分片上传 + blade-transport 凭证读写共用) +# endpoint:内网地址,禁止带 path,供服务端 put/get(如 http://tms-prod-oss:9000 或 http://IP:19000) +# transform-endpoint:外网 HTTPS,可带 /oss,仅用于浏览器预签名;切勿把该值填到 endpoint file: storage: minio: + # endpoint: http://内网MinIO地址:端口 transform-endpoint: https://tms-prod.xinghy56.com/oss powerjob: