🐛 修复未配置 MinIO 时 transport 服务无法启动的问题

凭证图片 MinIO 客户端此前为无条件装配,缺少 file.storage.minio.endpoint
配置时会在启动阶段直接抛 IllegalArgumentException,导致整个
blade-transport 服务起不来,并在关闭失败上下文时连带抛出
nacosGracefulShutdownDelegate 的 BeanCreationNotAllowedException,
掩盖了真正的失败原因。

- VoucherMinioConfig 增加 @ConditionalOnProperty,未配置 endpoint 时不注册客户端
- ProcessConfigController、VoucherManageServiceImpl 改用 ObjectProvider
  可选注入,避免构造器注入因 bean 缺失而启动失败
- 新增 minioClient() 访问器,在真正调用时给出"未配置"的明确提示,
  与已有 validateMinioConfig() 的提示风格保持一致

未配置 MinIO 时服务可正常启动,凭证相关接口仅在使用时提示配置缺失;
已配置 MinIO 时行为不变。
This commit is contained in:
2026-09-20 19:31:58 +08:00
parent 304061774e
commit af1430eaee
3 changed files with 51 additions and 15 deletions
@@ -6,14 +6,20 @@ package org.springblade.transport.config;
import io.minio.MinioClient;
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;
/**
* 凭证图片 MinIO 客户端配置。
* <p>
* 连接参数由 Nacos 的 file.storage.minio 配置提供。
* 未配置 {@code file.storage.minio.endpoint} 时不注册该客户端,
* 以免凭证上传功能缺失配置导致整个 blade-transport 服务无法启动;
* 此时凭证相关接口会在调用时给出明确提示,而非启动即失败。
*/
@Configuration
@ConditionalOnProperty(prefix = "file.storage.minio", name = "endpoint")
public class VoucherMinioConfig {
@Bean
@@ -52,6 +52,7 @@ import org.springblade.transport.pojo.entity.Waybill;
import org.springblade.transport.pojo.vo.BusinessRemoveResultVO;
import org.springblade.transport.pojo.vo.ProcessConfigVO;
import org.springblade.transport.service.IProcessConfigService;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
@@ -83,20 +84,34 @@ public class ProcessConfigController extends BladeController {
private final VoucherFileMapper voucherFileMapper;
private final VoucherImageMapper voucherImageMapper;
private final VoucherManageMapper voucherManageMapper;
private final MinioClient minioClient;
private final ObjectProvider<MinioClient> minioClientProvider;
@Value("${file.storage.minio.bucket-name:${minio.bucket-name:}}")
private String minioBucketName;
public ProcessConfigController(IProcessConfigService processConfigService, WaybillMapper waybillMapper,
VoucherFileMapper voucherFileMapper, VoucherImageMapper voucherImageMapper,
VoucherManageMapper voucherManageMapper,
MinioClient minioClient) {
ObjectProvider<MinioClient> minioClientProvider) {
this.processConfigService = processConfigService;
this.waybillMapper = waybillMapper;
this.voucherFileMapper = voucherFileMapper;
this.voucherImageMapper = voucherImageMapper;
this.voucherManageMapper = voucherManageMapper;
this.minioClient = minioClient;
this.minioClientProvider = minioClientProvider;
}
/**
* 获取 MinIO 客户端。
* <p>
* 未配置 file.storage.minio.endpoint 时该客户端不会被注册,
* 此时凭证预览地址无法生成,抛出明确提示而非启动即失败。
*/
private MinioClient minioClient() {
MinioClient minioClient = minioClientProvider.getIfAvailable();
if (minioClient == null) {
throw new IllegalStateException("Nacos 未配置 file.storage.minio.endpoint,凭证文件功能不可用");
}
return minioClient;
}
@GetMapping("/detail")
@@ -147,7 +162,7 @@ public class ProcessConfigController extends BladeController {
result.put("waybillNo", image.getWaybillNo());
result.put("objectKey", image.getObjectKey());
try {
result.put("url", minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
result.put("url", minioClient().getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
.method(Method.GET).bucket(minioBucketName).object(image.getObjectKey())
.expiry(1, TimeUnit.HOURS).build()));
} catch (Exception e) {
@@ -270,7 +285,7 @@ public class ProcessConfigController extends BladeController {
result.put("objectKey", image.objectKey());
result.put("matched", image.matched());
try {
result.put("url", minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
result.put("url", minioClient().getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
.method(Method.GET).bucket(minioBucketName).object(image.objectKey())
.expiry(1, TimeUnit.HOURS).build()));
} catch (Exception exception) {
@@ -42,6 +42,7 @@ import io.minio.PutObjectArgs;
import io.minio.RemoveObjectArgs;
import io.minio.http.Method;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Value;
import java.time.format.DateTimeFormatter;
@@ -79,14 +80,14 @@ public class VoucherManageServiceImpl extends BaseServiceImpl<VoucherManageMappe
private final VoucherImageMapper voucherImageMapper;
private final VoucherFileMapper voucherFileMapper;
private final ApplicationEventPublisher eventPublisher;
private final MinioClient minioClient;
private final ObjectProvider<MinioClient> minioClientProvider;
private final String minioBucketName;
private final String minioRootDirectory;
public VoucherManageServiceImpl(VoucherWaybillBatchMapper voucherWaybillBatchMapper, IProjectApplyService projectApplyService,
IWaybillService waybillService,
VoucherImageMapper voucherImageMapper, VoucherFileMapper voucherFileMapper,
ApplicationEventPublisher eventPublisher, MinioClient minioClient,
ApplicationEventPublisher eventPublisher, ObjectProvider<MinioClient> minioClientProvider,
@Value("${file.storage.minio.bucket-name:${minio.bucket-name:}}") String minioBucketName,
@Value("${file.storage.minio.root-directory:${minio.root-directory:}}") String minioRootDirectory) {
this.voucherWaybillBatchMapper = voucherWaybillBatchMapper;
@@ -95,7 +96,7 @@ public class VoucherManageServiceImpl extends BaseServiceImpl<VoucherManageMappe
this.voucherImageMapper = voucherImageMapper;
this.voucherFileMapper = voucherFileMapper;
this.eventPublisher = eventPublisher;
this.minioClient = minioClient;
this.minioClientProvider = minioClientProvider;
this.minioBucketName = minioBucketName;
this.minioRootDirectory = minioRootDirectory;
}
@@ -251,7 +252,7 @@ public class VoucherManageServiceImpl extends BaseServiceImpl<VoucherManageMappe
List<String> candidateObjectKeys = resolvedObjectKey.equals(objectKey)
? List.of(objectKey) : List.of(resolvedObjectKey, objectKey);
for (String candidateObjectKey : candidateObjectKeys) {
try (InputStream source = minioClient.getObject(GetObjectArgs.builder().bucket(minioBucketName).object(candidateObjectKey).build());
try (InputStream source = minioClient().getObject(GetObjectArgs.builder().bucket(minioBucketName).object(candidateObjectKey).build());
OutputStream target = Files.newOutputStream(archivePath)) {
source.transferTo(target);
return archivePath;
@@ -427,7 +428,7 @@ public class VoucherManageServiceImpl extends BaseServiceImpl<VoucherManageMappe
String waybillNo = matchedWaybill == null ? "unmatched" : safePathPart(matchedWaybill.getWaybillNo());
String objectKey = buildObjectKey(voucher.getId(), waybillNo, plateNo, entryName);
String contentType = contentType(fileName);
minioClient.putObject(PutObjectArgs.builder().bucket(minioBucketName).object(objectKey)
minioClient().putObject(PutObjectArgs.builder().bucket(minioBucketName).object(objectKey)
.stream(zipInputStream, entry.getSize(), 10 * 1024 * 1024)
.contentType(contentType).build());
VoucherFile file = new VoucherFile();
@@ -490,7 +491,7 @@ public class VoucherManageServiceImpl extends BaseServiceImpl<VoucherManageMappe
String waybillNo = matchedWaybill == null ? "unmatched" : safePathPart(matchedWaybill.getWaybillNo());
String objectKey = buildObjectKey(voucher.getId(), waybillNo, plateNo, entryName);
String contentType = contentType(fileName);
minioClient.putObject(PutObjectArgs.builder().bucket(minioBucketName).object(objectKey)
minioClient().putObject(PutObjectArgs.builder().bucket(minioBucketName).object(objectKey)
.stream(zipInputStream, entry.getSize(), 10 * 1024 * 1024)
.contentType(contentType).build());
VoucherFile file = new VoucherFile();
@@ -537,7 +538,7 @@ public class VoucherManageServiceImpl extends BaseServiceImpl<VoucherManageMappe
String entryName = plateNo + "/" + System.currentTimeMillis() + "_" + fileName;
String objectKey = buildObjectKey(voucher.getId(), matchedWaybill == null ? "unmatched" : safePathPart(matchedWaybill.getWaybillNo()), plateNo, entryName);
try (InputStream inputStream = imageFile.getInputStream()) {
minioClient.putObject(PutObjectArgs.builder().bucket(minioBucketName).object(objectKey)
minioClient().putObject(PutObjectArgs.builder().bucket(minioBucketName).object(objectKey)
.stream(inputStream, imageFile.getSize(), 10 * 1024 * 1024)
.contentType(Func.isEmpty(imageFile.getContentType()) ? contentType(fileName) : imageFile.getContentType()).build());
}
@@ -606,7 +607,7 @@ public class VoucherManageServiceImpl extends BaseServiceImpl<VoucherManageMappe
return Func.isEmpty(normalizedPlateNo) ? "未识别车牌" : normalizedPlateNo;
}
private void deleteObjectQuietly(String objectKey) { try { minioClient.removeObject(RemoveObjectArgs.builder().bucket(minioBucketName).object(objectKey).build()); } catch (Exception exception) { log.warn("删除凭证对象失败 objectKey={}", objectKey, exception); } }
private void deleteObjectQuietly(String objectKey) { try { minioClient().removeObject(RemoveObjectArgs.builder().bucket(minioBucketName).object(objectKey).build()); } catch (Exception exception) { log.warn("删除凭证对象失败 objectKey={}", objectKey, exception); } }
@Override
@Transactional(rollbackFor = Exception.class)
@@ -838,7 +839,7 @@ public class VoucherManageServiceImpl extends BaseServiceImpl<VoucherManageMappe
String objectKey = buildObjectKey(voucher.getId(), waybillNo,
Func.isEmpty(plateNo) ? "root" : plateNo, entryName);
String contentType = contentType(fileName);
minioClient.putObject(PutObjectArgs.builder().bucket(minioBucketName).object(objectKey)
minioClient().putObject(PutObjectArgs.builder().bucket(minioBucketName).object(objectKey)
.stream(zipInputStream, entry.getSize(), 10 * 1024 * 1024)
.contentType(contentType).build());
VoucherFile file = new VoucherFile();
@@ -1052,6 +1053,20 @@ public class VoucherManageServiceImpl extends BaseServiceImpl<VoucherManageMappe
}
}
/**
* 获取 MinIO 客户端。
* <p>
* 未配置 file.storage.minio.endpoint 时该客户端不会被注册,
* 此处给出与 {@link #validateMinioConfig()} 一致的明确提示。
*/
private MinioClient minioClient() {
MinioClient minioClient = minioClientProvider.getIfAvailable();
if (minioClient == null) {
throw new ServiceException("Nacos 未配置 file.storage.minio.endpoint,凭证文件功能不可用");
}
return minioClient;
}
private String buildObjectKey(Long voucherId, String waybillNo, String plateNo, String entryName) {
String objectKey = voucherId + "/" + waybillNo + "/" + safePathPart(plateNo) + "/" + safeArchivePath(entryName);
if (Func.isEmpty(minioRootDirectory)) {
@@ -1127,7 +1142,7 @@ public class VoucherManageServiceImpl extends BaseServiceImpl<VoucherManageMappe
private String buildVoucherFileUrl(Long fileId, String objectKey) {
try {
return minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
return minioClient().getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
.method(Method.GET).bucket(minioBucketName).object(objectKey).expiry(1, java.util.concurrent.TimeUnit.HOURS).build());
} catch (Exception exception) {
log.warn("生成凭证文件预览地址失败 voucherFileId={}, objectKey={}", fileId, objectKey, exception);