【代码模板】文件存储
Kiml Lv5
  • 前言
    项目中常用到文件上传的功能。

  • 更新

1
24.05.30 初始记录(Minio,阿里云OSS)

文件上传

  1. 校验

    • 文件是否为空
    • 文件的格式
    • 文件的大小
  2. 文件重命名

  3. 文件目录分离

    • 使用目录分离算法 (Hash 把 hash 值作为目录名称)
    • 按照日期分目录 (每天创建一个新的目录)
    • 按照用户名分目录
  4. 图片保存地点

    • web 服务器(项目所运行在的电脑)
    • 云存储服务商
    • 自己搭建文件服务器(FastDFS Minio)

通用部分代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/**  
* 文件上传
*
* @param file 文件
* @return Boolean
*/
@RestController
@RequestMapping("common")
public class CommonController {
@PostMapping("upload")
public R uploadPic(MultipartFile file) throws IOException {
// 校验
// 校验文件是否为空
if (ObjectUtil.isNull(file)) return R.error("上传文件不能为空");
// 校验文件的格式
if (!StrUtil.containsAnyIgnoreCase(FileUtil.getSuffix(file.getOriginalFilename()), "jpg", "png")) {
return R.error("上传文件格式错误");
}
// 校验文件的大小
if (file.getSize() / 1024 / 1024 > 2) return R.error("文件过大,限制在2MB以内");

// 给文件进行重命名
String fileName = UUID.fastUUID().toString() + "." + FileUtil.getSuffix(file.getOriginalFilename());
// 给文件目录分离

// 图片保存地点(不同保存地点实现方式不同)
.....
return R.success(fileName);
}
}

不同的保存地点

web 服务器(项目所运行在的电脑)

  1. 文件上传部分代码

1
2
// 这种情况下,文件存储路径为服务器上的路径名称
file.transferTo(new File("D:\\images\\" + fileName));
  1. 下载

  • 前端直接访问服务器域名 + 存储地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 这种情况下,代码中需要添加访问资源映射地址
// 在拦截器中配置
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Autowired
private AuthInterceptor authInterceptor;

private static final String DWG = "file:" + "D:/images/";

@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// 添加访问资源的地址
registry.addResourceHandler("/virtual/**").addResourceLocations(DWG);
}

@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(authInterceptor)
.addPathPatterns("/**")
.excludePathPatterns("/**/login/**", "/**/*.html", "/**/*.js", "/**/*.css", "/doc.*",
"/swagger-ui.*",
"/swagger-resources",
"/webjars/**",
"/v2/api-docs/**",
"/virtual/**"
);
}
}
  • 前端通过接口下载文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@GetMapping("download")
public void downloadPic(String name, HttpServletResponse response) throws IOException {
FileInputStream fileInputStream = new FileInputStream("D:\\images\\" + name);
ServletOutputStream outputStream = response.getOutputStream();

byte[] bytes = new byte[1024];
int len = 0;
while ((len = fileInputStream.read(bytes)) != -1) {
outputStream.write(bytes, 0, len);
}

fileInputStream.close();
outputStream.close();
}

云存储服务商

通过开通云存储服务商的 OSS 服务,调用提供的 API,实现文件存储。一般后面两种情况会直接编写 Util 类方便多处调用。

阿里云

Maven 依赖
1
2
3
4
5
<dependency>  
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.4.2</version>
</dependency>
配置文件
1
2
3
4
5
6
7
8
aliyun:  
oss:
endpoint: oss-cn-hangzhou.aliyuncs.com
accessKeyId: xxxxxxxxxxxxxxxx
accessKeySecret: xxxxxxxxxxxxxxxx
bucket: xxxxxxxxxxxxxxxx
userFolder: user/
managerFolder: manager/
代码
  • OssAutoConfiguration.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Configuration  
@ConditionalOnClass({OSSClient.class})
@EnableConfigurationProperties(OssProperties.class)
public class OssAutoConfiguration {

private final OssProperties ossProperties;

public OssAutoConfiguration(final OssProperties ossProperties) {
this.ossProperties = ossProperties;
}

@Bean
public OssClientFactoryBean ossClientFactoryBean() {
final OssClientFactoryBean factoryBean = new OssClientFactoryBean();
factoryBean.setEndpoint(this.ossProperties.getEndpoint());
factoryBean.setAccessKeyId(this.ossProperties.getAccessKeyId());
factoryBean.setAccessKeySecret(this.ossProperties.getAccessKeySecret());
return factoryBean;
}
}
  • OssClientFactoryBean.class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
public class OssClientFactoryBean implements FactoryBean<OSSClient>, InitializingBean, DisposableBean {  

private OSSClient ossClient;
private String endpoint;
private String accessKeyId;
private String accessKeySecret;

@Override
public OSSClient getObject() throws Exception {
return this.ossClient;
}

@Override
public Class<?> getObjectType() {
return OSSClient.class;
}

@Override
public boolean isSingleton() {
return true;
}

@Override
public void destroy() throws Exception {
if (this.ossClient != null) {
this.ossClient.shutdown();
}
}

@Override
public void afterPropertiesSet() throws Exception {
Assert.notNull(this.endpoint, "'aliyun.oss.endpoint' must be not null");
Assert.notNull(this.accessKeyId, "'aliyun.oss.accessKeyId' must be not null");
Assert.notNull(this.accessKeySecret, "'aliyun.oss.accessKeySecret' must be not null");
this.ossClient = new OSSClient(this.endpoint, this.accessKeyId, this.accessKeySecret);
}

public void setEndpoint(final String endpoint) {
this.endpoint = endpoint;
}

public void setAccessKeyId(final String accessKeyId) {
this.accessKeyId = accessKeyId;
}

public void setAccessKeySecret(final String accessKeySecret) {
this.accessKeySecret = accessKeySecret;
}
}
  • OssProperties.java

1
2
3
4
5
6
7
8
9
10
11
@Getter  
@Setter
@ConfigurationProperties(prefix = "aliyun.oss")
public class OssProperties {
private String endpoint;
private String accessKeyId;
private String accessKeySecret;
private String bucket;
private String userFolder;
private String managerFolder;
}
  • OssUtil.class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
@Slf4j  
@Component
public class OssUtil {
@Autowired
private OSSClient ossClient;

@Autowired
private OssProperties ossProperties;

/**
* 上传文件
* @param folder "需要加/ 如"xxx/""
* @return 文件地址
*/
public String upload(MultipartFile file, String folder) {
String bucketName = ossProperties.getBucket();

// 判断Bucket的存在
if (!ossClient.doesBucketExist(bucketName)) {
// 创建存储空间
Bucket bucket = ossClient.createBucket(bucketName);
bucketName = bucket.getName();
}

String objectName = "";
try {
// 以输入流的形式上传文件
InputStream is = file.getInputStream();
// 文件名
String originalFilename = file.getOriginalFilename();
if (ObjectUtil.isNull(originalFilename)) {
throw new LogicException("文件名称获取失败");
}
val suffix = originalFilename.substring(originalFilename.lastIndexOf("."));
String fileName = UUID.fastUUID().toString() + suffix.toLowerCase();
objectName = folder + DateUtil.format(new Date(), "yy-MM/dd") + "/" + fileName;

// 文件大小
long fileSize = file.getSize();

// 创建上传Object的Metadata
ObjectMetadata metadata = new ObjectMetadata();
// 上传的文件的长度
metadata.setContentLength(is.available());
// 指定该Object被下载时的网页的缓存行为
metadata.setCacheControl("no-cache");
// 指定该Object下设置Header
metadata.setHeader("Pragma", "no-cache");
// 指定该Object被下载时的内容编码格式
metadata.setContentEncoding("utf-8");
// 文件的MIME,定义文件的类型及网页编码,决定浏览器将以什么形式、什么编码读取文件。如果用户没有指定则根据Key或文件名的扩展名生成,
// 如果没有扩展名则填默认值application/octet-stream
metadata.setContentType(AliyunOssFileTypeEnum.getText(suffix.toLowerCase()));
// 指定该Object被下载时的名称(指示MINME用户代理如何显示附加的文件,打开或下载,及文件名称)
metadata.setContentDisposition("filename/filesize=" + originalFilename + "/" + fileSize + "Byte.");
// 上传文件 (上传文件流的形式)
PutObjectResult putResult = ossClient.putObject(bucketName, objectName, is, metadata);
// 解析结果
String resultStr = putResult.getETag();
log.info("唯一MD5数字签名:" + resultStr);
} catch (OSSException oe) {
// oe.printStackTrace();
log.error("上传阿里云OSS服务器异常." + oe.getMessage(), oe);
} catch (ClientException ce) {
// ce.printStackTrace();
log.error("阿里云OSS服务连接异常." + ce.getMessage(), ce);
} catch (IOException e) {
// e.printStackTrace();
log.error("文件流异常." + e.getMessage(), e);
}
return "https://" + ossProperties.getBucket() + "." + ossProperties.getEndpoint() + "/" + objectName;
}

/**
* 上传文件 byte ()
* @param folder "需要加/ 如"xxx/""
* @return 文件地址
*/
public String imgUploadByByte(byte[] bytes, String folder) {
String bucketName = ossProperties.getBucket();

// 判断Bucket的存在
if (!ossClient.doesBucketExist(bucketName)) {
// 创建存储空间
Bucket bucket = ossClient.createBucket(bucketName);
bucketName = bucket.getName();
}

String objectName = "";
try {
InputStream inputStream = new ByteArrayInputStream(bytes);

val suffix = "jpg";
String fileName = UUID.fastUUID().toString() +suffix;
objectName = folder + DateUtil.format(new Date(), "yy-MM/dd") + "/" + fileName;

// 创建上传Object的Metadata
ObjectMetadata metadata = new ObjectMetadata();
// 上传的文件的长度
metadata.setContentLength(inputStream.available());
// 指定该Object被下载时的网页的缓存行为
metadata.setCacheControl("no-cache");
// 指定该Object下设置Header
metadata.setHeader("Pragma", "no-cache");
// 指定该Object被下载时的内容编码格式
metadata.setContentEncoding("utf-8");
// 文件的MIME,定义文件的类型及网页编码,决定浏览器将以什么形式、什么编码读取文件。如果用户没有指定则根据Key或文件名的扩展名生成,
// 如果没有扩展名则填默认值application/octet-stream
metadata.setContentType(AliyunOssFileTypeEnum.getText(suffix));
// 上传文件 (上传文件流的形式)
PutObjectResult putResult = ossClient.putObject(bucketName, objectName, inputStream, metadata);
// 解析结果
String resultStr = putResult.getETag();
log.info("唯一MD5数字签名:" + resultStr);
} catch (OSSException oe) {
// oe.printStackTrace();
log.error("上传阿里云OSS服务器异常." + oe.getMessage(), oe);
} catch (ClientException ce) {
// ce.printStackTrace();
log.error("阿里云OSS服务连接异常." + ce.getMessage(), ce);
} catch (IOException e) {
// e.printStackTrace();
log.error("文件流异常." + e.getMessage(), e);
}
return "https://" + ossProperties.getBucket() + "." + ossProperties.getEndpoint() + "/" + objectName;
}

/**
* 根据objectName删除OSS服务器上的文件
*
* @param objectName 模拟文件夹名 如"qj_nanjing/" + Bucket下的文件的路径名+文件名 如:"upload/cake.jpg"
*/
public Boolean deleteFile(String objectName) {
String bucketName = ossProperties.getBucket();
ossClient.deleteObject(bucketName, objectName);
log.info("删除" + bucketName + "下的文件" + objectName + "成功");
return true;
}

/**
* 根据url获取objectName
* * @param url url
*/
public String getObjectNameByUrl(String url) {
// url = url.replace("https://" + ossProperties.getBucket() + "." + ossProperties.getEndpoint() + "/", "");
int firstIndex = url.indexOf("/");
int secondIndex = url.indexOf("/", firstIndex + 1);
int thirdIndex = url.indexOf("/", secondIndex + 1);
int suffix = url.lastIndexOf("?") > 0 ? url.lastIndexOf("?") : url.length();
url = url.substring(thirdIndex + 1, suffix);
return url;
}

/**
* 获取上传文件url (非公共读写时)
* * @param objectName 模拟文件夹名 如"qj_nanjing/" + Bucket下的文件的路径名+文件名 如:"upload/cake.jpg"
* @return url
*/
public String getUrl(String objectName) {
//设置URl过期时间为99年:3600L*1000*24*365*99
Date expiration = new Date(new Date().getTime() + 3600L * 1000 * 24 * 365 * 99);
GeneratePresignedUrlRequest generatePresignedUrlRequest = new GeneratePresignedUrlRequest(ossProperties.getBucket(), objectName);
generatePresignedUrlRequest.setExpiration(expiration);
URL url = ossClient.generatePresignedUrl(generatePresignedUrlRequest);
return url.toString();
}
}

Minio

服务启动
  • windows 启动

1
C:\Develop\minio\bin>minio.exe server C:\Develop\minio\data --address 127.0.0.1:9005
Maven 依赖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<dependencies>  
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>7.1.0</version>
</dependency>
<!--SPI-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
配置文件
1
2
3
4
5
minio:  
endpoint: xxxxxxxxxxxxxxxxxxxxxx #MinIO服务所在地址
bucketName: xxxxxxxxxxx #存储桶名称
accessKey: xxxxxxxxxxxxxxxx #访问的key
secretKey: xxxxxxxxxxxxxxxxxxx #访问的秘钥
代码
  • MinioProp.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
@ConfigurationProperties(prefix = "minio")  
public class MinioProp {
private String endpoint;
private String accessKey;
private String secretKey;
private String bucketName;

public String getEndpoint() {
return endpoint;
}

public void setEndpoint(String endpoint) {
this.endpoint = endpoint;
}

public String getAccessKey() {
return accessKey;
}

public void setAccessKey(String accessKey) {
this.accessKey = accessKey;
}

public String getSecretKey() {
return secretKey;
}

public void setSecretKey(String secretKey) {
this.secretKey = secretKey;
}

public String getBucketName() {
return bucketName;
}

public void setBucketName(String bucket) {
this.bucketName = bucket;
}
}
  • OSSFile.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Data  
public class OSSFile {
/**
* 文件名
*/
private String fileName;
/**
* 文件存储地址
*/
private String url;
/**
* 文件ObjectName
*/
private String objectName;
}
  • MinioUtil.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
@Component  
@Slf4j
@EnableConfigurationProperties(MinioProp.class)
public class MinioUtil {
@Resource
private MinioProp prop;

private MinioClient minioClient;

/**
* 创建minioClient
*/ @PostConstruct
public void createMinioClient() {
try {
if (null == minioClient) {
log.info("minioClient create start");
minioClient = MinioClient.builder().endpoint(prop.getEndpoint())
.credentials(prop.getAccessKey(), prop.getSecretKey())
.build();
createBucket();
log.info("minioClient create end");
}
} catch (Exception e) {
log.error("连接MinIO服务器异常:" + e);
}
}

/**
* 初始化Bucket
* * @throws Exception 异常
*/
private void createBucket()
throws IOException, InvalidKeyException, InvalidResponseException, InsufficientDataException, NoSuchAlgorithmException, ServerException, InternalException, XmlParserException, InvalidBucketNameException, ErrorResponseException, RegionConflictException {
if (!minioClient.bucketExists(BucketExistsArgs.builder().bucket(prop.getBucketName()).build())) {
minioClient.makeBucket(MakeBucketArgs.builder().bucket(prop.getBucketName()).build());
}
}

/**
* 查看存储bucket是否存在
* @return boolean
*/
public Boolean bucketExists(String bucketName) {
Boolean found;
try {
found = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
} catch (Exception e) {
e.printStackTrace();
return false;
}
return found;
}

/**
* 创建存储bucket
* @return Boolean
*/
public Boolean makeBucket(String bucketName) {
try {
if (!bucketExists(bucketName)) {
minioClient.makeBucket(MakeBucketArgs.builder()
.bucket(bucketName)
.build());
String policyJson = "{\n" +
"\t\"Version\": \""+new SimpleDateFormat("yyyy-mm-dd").format(System.currentTimeMillis())+"\",\n" +
"\t\"Statement\": [{\n" +
"\t\t\"Effect\": \"Allow\",\n" +
"\t\t\"Principal\": {\n" +
"\t\t\t\"AWS\": [\"*\"]\n" +
"\t\t},\n" +
"\t\t\"Action\": [\"s3:GetBucketLocation\", \"s3:ListBucket\", \"s3:ListBucketMultipartUploads\"],\n" +
"\t\t\"Resource\": [\"arn:aws:s3:::" + bucketName + "\"]\n" +
"\t}, {\n" +
"\t\t\"Effect\": \"Allow\",\n" +
"\t\t\"Principal\": {\n" +
"\t\t\t\"AWS\": [\"*\"]\n" +
"\t\t},\n" +
"\t\t\"Action\": [\"s3:AbortMultipartUpload\", \"s3:DeleteObject\", \"s3:GetObject\", \"s3:ListMultipartUploadParts\", \"s3:PutObject\"],\n" +
"\t\t\"Resource\": [\"arn:aws:s3:::" + bucketName + "/*\"]\n" +
"\t}]\n" +
"}\n";
minioClient.setBucketPolicy(SetBucketPolicyArgs.builder().bucket(bucketName).config(policyJson).build());
log.info("buckets:【{}】,创建[readwrite]策略成功!", bucketName);
} else {
log.info("minio bucket->>>【{}】already exists", bucketName);
}
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
/**
* 删除存储bucket
* @return Boolean
*/
public Boolean removeBucket(String bucketName) {
try {
minioClient.removeBucket(RemoveBucketArgs.builder()
.bucket(bucketName)
.build());
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
/**
* 获取全部bucket
*/
public List<Bucket> getAllBuckets() {
try {
List<Bucket> buckets = minioClient.listBuckets();
return buckets;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}

/**
* 文件上传
*
* @param file 文件
* @return Boolean
*/
public OSSFile upload(MultipartFile file) {
String originalFilename = file.getOriginalFilename();
if (StrUtil.isBlank(originalFilename)){
throw new RuntimeException();
}
// 重命名
String fileName = UUID.fastUUID() + originalFilename.substring(originalFilename.lastIndexOf("."));
String objectName = DateUtil.format(new Date(), "yy-MM/dd") + "/" + fileName;
try {
PutObjectArgs objectArgs = PutObjectArgs.builder().bucket(prop.getBucketName()).object(objectName)
.stream(file.getInputStream(), file.getSize(), -1).contentType(file.getContentType()).build();
//文件名称相同会覆盖
minioClient.putObject(objectArgs);
} catch (Exception e) {
e.printStackTrace();
return null;
}

OSSFile ossFile = new OSSFile();
ossFile.setFileName(originalFilename);
ossFile.setObjectName(objectName);
ossFile.setUrl(prop.getEndpoint() + "/" + prop.getBucketName() + "/" + objectName);
return ossFile;
}

/**
* 预览图片
* @param fileName
* @return
*/
public String preview(String fileName){
// 查看文件地址
GetPresignedObjectUrlArgs build = new GetPresignedObjectUrlArgs().builder().bucket(prop.getBucketName()).object(fileName).method(Method.GET).build();
try {
String url = minioClient.getPresignedObjectUrl(build);
return url;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}

/**
* 文件下载
* @param fileName 文件名称
* @param res response
* @return Boolean
*/
public void download(String fileName, HttpServletResponse res) {
GetObjectArgs objectArgs = GetObjectArgs.builder().bucket(prop.getBucketName())
.object(fileName).build();
try (InputStream response = minioClient.getObject(objectArgs)){
byte[] buf = new byte[1024];
int len;
try (FastByteArrayOutputStream os = new FastByteArrayOutputStream()){
while ((len=response.read(buf))!=-1){
os.write(buf,0,len);
}
os.flush();
byte[] bytes = os.toByteArray();
res.setCharacterEncoding("utf-8");
// 设置强制下载不打开
// res.setContentType("application/force-download");
res.addHeader("Content-Disposition", "attachment;fileName=" + fileName);
try (ServletOutputStream stream = res.getOutputStream()){
stream.write(bytes);
stream.flush();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}

/**
* 查看文件对象
* @return 存储bucket内文件对象信息
*/
public List<Item> listObjects() {
Iterable<Result<Item>> results = minioClient.listObjects(
ListObjectsArgs.builder().bucket(prop.getBucketName()).build());
List<Item> items = new ArrayList<>();
try {
for (Result<Item> result : results) {
items.add(result.get());
}
} catch (Exception e) {
e.printStackTrace();
return null;
}
return items;
}

/**
* 删除
* @param fileName
* @return
* @throws Exception
*/
public boolean remove(String fileName){
try {
minioClient.removeObject( RemoveObjectArgs.builder().bucket(prop.getBucketName()).object(fileName).build());
}catch (Exception e){
return false;
}
return true;
}
}
拓展

Minio 这个被写成了 SPI 加载到别的服务中,这边再加一下关于 SPI 的知识。

image

如图 resources 文件夹下的 META-INF 文件夹下的 spring.factories 文件。

如果要把 MinioUtil 这个方法加载到别的项目中,可以这么写。

1
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.hzjzxx.minio.utils.MinioUtil

关于 SPI 的知识:

内链:[[面试-SPI]]
外链:SPI相关

 评论
评论插件加载失败
正在加载评论插件
由 Hexo 驱动 & 主题 Keep
访客数 访问量