From 5ca3280ddc50d596ecd1cd25310014c29cc031e4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E8=B5=B5=E5=BF=A0=E6=9E=97?= <170083662@qq.com>
Date: Fri, 7 Aug 2026 03:46:49 +0800
Subject: [PATCH] =?UTF-8?q?feat(storage):=20=E6=94=AF=E6=8C=81=20MinIO=20?=
=?UTF-8?q?=E5=AD=98=E5=82=A8=E5=B9=B6=E4=BC=98=E5=8C=96=E4=B8=8A=E4=BC=A0?=
=?UTF-8?q?=E4=B8=8E=E5=88=A0=E9=99=A4=E9=80=BB=E8=BE=91?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 引入 MinIO 官方 Java SDK,替代 aliyun OSS SDK 处理与 MinIO 的交互
- 兼容 MinIO 的 AWS4-HMAC-SHA256 签名及 path-style 访问
- 区分上传和删除操作中 MinIO 与阿里云 OSS 的客户端实例创建及调用
- 统一文件路径处理,调整图片处理策略以兼容 MinIO 不支持的参数
- 增加对 MinIO 客户端异常的捕获与兼容处理
- 在 pom.xml 中添加对 okhttp 和 okio 依赖的版本锁定,解决版本冲突问题
---
pom.xml | 32 +++++++++++
.../system/controller/AliOssController.java | 57 ++++++++++---------
.../service/impl/FileRecordServiceImpl.java | 35 ++++++------
3 files changed, 79 insertions(+), 45 deletions(-)
diff --git a/pom.xml b/pom.xml
index 77b3ea5..220a8ff 100644
--- a/pom.xml
+++ b/pom.xml
@@ -26,7 +26,31 @@
UTF-8
+
+
+
+
+ com.squareup.okhttp3
+ okhttp
+ 4.12.0
+
+
+ com.squareup.okio
+ okio
+ 3.6.0
+
+
+
+
+
+
+ com.squareup.okhttp3
+ okhttp
+ 4.12.0
+
+
org.springframework.boot
@@ -297,6 +321,14 @@
3.17.4
+
+
+ io.minio
+ minio
+ 8.5.7
+
+
com.aliyun
diff --git a/src/main/java/com/gxwebsoft/common/system/controller/AliOssController.java b/src/main/java/com/gxwebsoft/common/system/controller/AliOssController.java
index 3611f9e..a3b016b 100644
--- a/src/main/java/com/gxwebsoft/common/system/controller/AliOssController.java
+++ b/src/main/java/com/gxwebsoft/common/system/controller/AliOssController.java
@@ -37,6 +37,8 @@ import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
+import io.minio.MinioClient;
+import io.minio.UploadObjectArgs;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
@@ -115,26 +117,18 @@ public class AliOssController extends BaseController {
// 上传文件结果
FileRecord result;
- CredentialsProvider credentialsProvider = new DefaultCredentialProvider(accessKeyId, accessKeySecret);
- // 创建OSSClient实例。
- // MinIO 兼容 S3 协议,需开启 PathStyle 访问(endpoint/bucket/key)并使用 V4 签名
- OSS ossClient;
- if ("minio".equals(uploadMethod)) {
- ClientBuilderConfiguration minioConfig = new ClientBuilderConfiguration();
- // MinIO 兼容 S3:V4 签名 + path-style。自定义域名无法从 endpoint 推导 region,
- // 必须显式指定(MinIO 默认 us-east-1),否则 OSSV4Signer.getRegion() 为 null 触发 NPE。
- minioConfig.setSignatureVersion(SignVersion.V4);
- minioConfig.setExtractSettingFromEndpoint(true);
- // 关闭 CNAME:默认 true 会让 SDK 把自定义域名当 CNAME,走 bucket.endpoint 虚拟主机风格,
- // 触发 *.minio-api.websoft.top 证书不匹配。关闭后强制 path-style(endpoint/bucket/key)。
- minioConfig.setSupportCname(false);
- ossClient = OSSClientBuilder.create()
+ boolean isMinio = "minio".equals(uploadMethod);
+ // MinIO 用官方 minio-java SDK(原生 AWS4-HMAC-SHA256 + path-style);
+ // aliyun OSS SDK 无 AWS4 签名器,对接不了 MinIO,因此 minio 分支独立使用 MinioClient。
+ OSS ossClient = null;
+ MinioClient minioClient = null;
+ if (isMinio) {
+ minioClient = MinioClient.builder()
.endpoint(endpoint)
- .credentialsProvider(credentialsProvider)
- .clientConfiguration(minioConfig)
- .region("us-east-1")
+ .credentials(accessKeyId, accessKeySecret)
.build();
} else {
+ CredentialsProvider credentialsProvider = new DefaultCredentialProvider(accessKeyId, accessKeySecret);
ossClient = new OSSClientBuilder().build(endpoint, credentialsProvider);
}
@@ -145,16 +139,18 @@ public class AliOssController extends BaseController {
String path = upload.getAbsolutePath().replace("\\", "/").substring(dir.length());
String originalName = file.getOriginalFilename();
- // 创建PutObjectRequest对象。
- PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, path, upload);
- // 如果需要上传时设置存储类型和访问权限,请参考以下示例代码。
- // ObjectMetadata metadata = new ObjectMetadata();
- // metadata.setHeader(OSSHeaders.OSS_STORAGE_CLASS, StorageClass.Standard.toString());
- // metadata.setObjectAcl(CannedAccessControlList.Private);
- // putObjectRequest.setMetadata(metadata);
-
- // 上传文件。
- PutObjectResult ossResult = ossClient.putObject(putObjectRequest);
+ // 上传文件到对象存储。
+ if (isMinio) {
+ // minio-java:path 此时无前导 "/",作为对象名(如 2026/08/07/xxx.png),默认 region=us-east-1
+ minioClient.uploadObject(UploadObjectArgs.builder()
+ .bucket(bucketName)
+ .object(path)
+ .filename(upload.getAbsolutePath())
+ .build());
+ } else {
+ PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, path, upload);
+ ossClient.putObject(putObjectRequest);
+ }
// 保存记录并返回
result = new FileRecord();
@@ -185,7 +181,7 @@ public class AliOssController extends BaseController {
// 根据文件类型设置缩略图和预览图
// MinIO 不支持阿里云 x-oss-process 图片处理参数,统一返回原图 URL
- boolean isMinio = "minio".equals(uploadMethod);
+ isMinio = "minio".equals(uploadMethod);
if (FileServerUtil.isImage(contentType)) {
if (isMinio) {
result.setThumbnail(bucketDomain + path);
@@ -236,10 +232,15 @@ public class AliOssController extends BaseController {
+ "a serious internal problem while trying to communicate with OSS, "
+ "such as not being able to access the network.");
System.out.println("Error Message:" + ce.getMessage());
+ } catch (Exception e) {
+ // 兼容 minio-java 抛出的异常(MinioException / IOException 等)
+ System.out.println("Caught an upload Exception: " + e.getMessage());
+ e.printStackTrace();
} finally {
if (ossClient != null) {
ossClient.shutdown();
}
+ // MinioClient 内部复用 HttpClient 连接池,无需显式关闭
}
return fail("上传失败", null);
}
diff --git a/src/main/java/com/gxwebsoft/common/system/service/impl/FileRecordServiceImpl.java b/src/main/java/com/gxwebsoft/common/system/service/impl/FileRecordServiceImpl.java
index 2ed5bfb..2f64f73 100644
--- a/src/main/java/com/gxwebsoft/common/system/service/impl/FileRecordServiceImpl.java
+++ b/src/main/java/com/gxwebsoft/common/system/service/impl/FileRecordServiceImpl.java
@@ -22,6 +22,8 @@ import com.gxwebsoft.common.system.param.FileRecordParam;
import com.gxwebsoft.common.system.service.CompanyService;
import com.gxwebsoft.common.system.service.FileRecordService;
import com.gxwebsoft.common.system.service.SettingService;
+import io.minio.MinioClient;
+import io.minio.RemoveObjectArgs;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
@@ -96,32 +98,31 @@ public class FileRecordServiceImpl extends ServiceImpl 0) {
Company company = companyService.getById(fileRecord.getCompanyId());