Skip to content

Commit 2e4fb8c

Browse files
committed
feat(file): 实现Minio文件上传功能
- 实现minioFileUpload方法,支持文件上传至Minio Signed-off-by: LiggMax <wenzhouli06@gmail.com>
1 parent 7a2891d commit 2e4fb8c

8 files changed

Lines changed: 354 additions & 12 deletions

File tree

common/pom.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,12 @@
3333
<artifactId>thumbnailator</artifactId>
3434
</dependency>
3535

36+
<!--minio-->
37+
<dependency>
38+
<groupId>io.minio</groupId>
39+
<artifactId>minio</artifactId>
40+
</dependency>
41+
3642
<!-- 邮件服务 -->
3743
<dependency>
3844
<groupId>org.springframework.boot</groupId>
@@ -45,6 +51,7 @@
4551
<artifactId>spring-boot-starter-thymeleaf</artifactId>
4652
</dependency>
4753

54+
<!-- 验证码 -->
4855
<dependency>
4956
<groupId>cn.hutool</groupId>
5057
<artifactId>hutool-captcha</artifactId>
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package com.ligg.common.config;
2+
3+
import io.minio.MinioClient;
4+
import lombok.Data;
5+
import org.springframework.boot.context.properties.ConfigurationProperties;
6+
import org.springframework.context.annotation.Bean;
7+
import org.springframework.context.annotation.Configuration;
8+
9+
/**
10+
* @author Ligg
11+
* @create_time 2025/11/10 11:00
12+
* @update_time 2025/11/10 11:00
13+
**/
14+
@Data
15+
@Configuration
16+
@ConfigurationProperties(prefix = "minio")
17+
public class MinioConfig {
18+
private String endpoint;
19+
private String accessKey;
20+
private String secretKey;
21+
private String bucketName;
22+
23+
@Bean
24+
public MinioClient minioClient() {
25+
return MinioClient.builder()
26+
.endpoint(endpoint)
27+
.credentials(accessKey, secretKey)
28+
.build();
29+
}
30+
}

common/src/main/java/com/ligg/common/service/FileService.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,15 @@ public interface FileService {
1515
*/
1616
String uploadImage(MultipartFile imageFile, String FilePath);
1717

18+
/**
19+
* 上传图片文件到Minio
20+
*
21+
* @param imageFile 图片文件
22+
* @param filePath 指定文件保存的路径(前缀)
23+
* @return 图片访问路径
24+
*/
25+
String minioFileUpload(MultipartFile imageFile, String filePath);
26+
1827
/**
1928
* 获取多种图片输入流
2029
*

common/src/main/java/com/ligg/common/service/impl/FileServiceImpl.java

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,15 @@
44
**/
55
package com.ligg.common.service.impl;
66

7+
import com.ligg.common.config.MinioConfig;
8+
import com.ligg.common.enums.BusinessStates;
79
import com.ligg.common.enums.ImageType;
810
import com.ligg.common.constants.Constant;
911
import com.ligg.common.service.FileService;
12+
import io.minio.*;
13+
import io.minio.errors.*;
14+
import io.minio.http.Method;
15+
import lombok.RequiredArgsConstructor;
1016
import lombok.extern.slf4j.Slf4j;
1117
import net.coobird.thumbnailator.Thumbnails;
1218
import org.springframework.beans.factory.annotation.Value;
@@ -21,16 +27,24 @@
2127
import java.nio.file.Files;
2228
import java.nio.file.Path;
2329
import java.nio.file.Paths;
30+
import java.security.InvalidKeyException;
31+
import java.security.NoSuchAlgorithmException;
2432
import java.util.UUID;
2533

2634
@Slf4j
2735
@Service
36+
@RequiredArgsConstructor
2837
public class FileServiceImpl implements FileService {
2938

3039
// 图片存储根路径
3140
@Value("${file.image.base-path}")
3241
private String IMAGE_PATH;
3342

43+
private final MinioConfig minioConfig;
44+
45+
private final MinioClient minioClient;
46+
47+
3448
@Override
3549
public String uploadImage(MultipartFile imageFile, String path) {
3650
if (imageFile.isEmpty()) {
@@ -76,6 +90,75 @@ public String uploadImage(MultipartFile imageFile, String path) {
7690
}
7791
}
7892

93+
@Override
94+
public String minioFileUpload(MultipartFile imageFile, String filePath) {
95+
String bucketName = minioConfig.getBucketName();
96+
String datePath = java.time.LocalDate.now().toString();
97+
String fullFilePath = String.join("/", (filePath != null ? filePath : ""), datePath, UUID.randomUUID() + "_" + imageFile.getOriginalFilename());
98+
//创建存储桶(如果不存在)
99+
try {
100+
if (!minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build())) {
101+
minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
102+
//设置访问权限
103+
minioClient.setBucketPolicy(SetBucketPolicyArgs.builder()
104+
.bucket(bucketName)
105+
.config(getPublicReadPolicy(bucketName))
106+
.build());
107+
}
108+
} catch (Exception e) {
109+
log.error("创建存储桶失败: {}", e.getMessage());
110+
throw new RuntimeException(BusinessStates.INTERNAL_SERVER_ERROR.getMessage());
111+
}
112+
113+
//上传文件
114+
try (InputStream inputStream = imageFile.getInputStream()) {
115+
minioClient.putObject(PutObjectArgs.builder()
116+
.bucket(bucketName)
117+
.object(fullFilePath)
118+
.stream(inputStream, imageFile.getSize(), -1)
119+
.contentType(imageFile.getContentType())
120+
.build());
121+
} catch (Exception e) {
122+
log.error("上传文件失败: {}", e.getMessage());
123+
}
124+
125+
return minioConfig.getEndpoint() + "/" + bucketName + "/" + fullFilePath;
126+
}
127+
128+
/**
129+
* url签名
130+
*/
131+
private String getUrlSignature(String bucketName, String objectName) throws ServerException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
132+
return minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
133+
.bucket(bucketName)
134+
.object(objectName)
135+
.method(Method.GET)
136+
.build());
137+
}
138+
139+
/**
140+
* 存储桶配置
141+
* "Principal": "*" 表示允许所有用户访问。
142+
* "Action": "s3:GetObject" 表示允许下载/读取对象
143+
* "Action": "s3:PutObject" 表示允许上传/写入对象
144+
* %s 是格式化占位符,会被传入的 bucketName 替换
145+
*/
146+
private String getPublicReadPolicy(String bucketName) {
147+
return """
148+
{
149+
"Version":"2012-10-17",
150+
"Statement":[
151+
{
152+
"Effect":"Allow",
153+
"Principal":"*",
154+
"Action":["s3:GetObject"],
155+
"Resource":["arn:aws:s3:::%s/*"]
156+
}
157+
]
158+
}
159+
""".formatted( bucketName);
160+
}
161+
79162
/**
80163
* 获取多质量图片输入流
81164
*
@@ -153,7 +236,7 @@ public StreamingResponseBody getImageInputStream(String path, String date, Strin
153236
@Override
154237
@Deprecated
155238
public void deleteFile(String filePath) {
156-
Path imagePath = Paths.get(getBasePath(), filePath).toAbsolutePath().normalize() ;
239+
Path imagePath = Paths.get(getBasePath(), filePath).toAbsolutePath().normalize();
157240
//检查文件是否存在
158241
if (Files.exists(imagePath)) {
159242
try {
@@ -189,6 +272,7 @@ public void deleteFileAsync(String filePath) {
189272

190273
/**
191274
* 获取基础路径
275+
*
192276
* @return 绝对路径
193277
*/
194278
private String getBasePath() {

entrance/src/main/resources/application-dev.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,10 @@ mybatis-plus:
5151
file:
5252
image:
5353
base-path: ${user.dir}/resources/image # 用相对路径(相对于项目运行目录)
54+
55+
minio:
56+
endpoint: ${ENDPOINT}
57+
access-key: ${ACCESS_KEY}
58+
secret-key: ${SECRET_KEY}
59+
bucket-name: e-commerce
60+

0 commit comments

Comments
 (0)