一.文件本地上传
1.1 文件上传目录
服务端接收上传的目的是提供文件的访问服务,那么对于SpringBoot而言,有哪些可以提供文件访问的静态资源目录呢?
-
classpath:/META-INF/resources/
, -
classpath:/static/
, -
classpath:/public/
, - `classpath:/resources/
以上目录都在工程里面,用来存储动态上传的文件是有很多问题的。spring boot 为我们提供了使用spring.web.resources.static-locations
配置自定义静态文件的位置。
web:
upload-path: E:/data/
spring:
web:
resources:
static-locations: classpath:/META-INF/resources/,classpath:/resources/,classpath:/static/,classpath:/public/,file:${web.upload-path}
- 配置
web.upload-path
为与项目代码分离的静态资源路径,即:文件上传保存根路径 - 配置
spring.web.resources.static-locations
,除了带上Spring Boot默认的静态资源路径之外,加上file:${web.upload-path}指向外部的文件资源上传路径。该路径下的静态资源可以直接对外提供HTTP访问服务。
1.2 文件上传的Controller实现
@RestController
public class FileUploadController {
@Value("${web.upload-path}")
private String uploadPath;
@PostMapping("/upload")
public String upload(MultipartFile uploadFile, HttpServletRequest request) throws IOException {
// 在 uploadPath 文件夹中通过日期对上传的文件归类保存
// 比如:/2019/06/06/cf13891e-4b95-4000-81eb-b6d70ae44930.png
SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd/");
String format = sdf.format(new Date());
File folder = new File(uploadPath + format);
if (!folder.isDirectory()) {
folder.mkdirs();
}
// 对上传的文件重命名,避免文件重名
String oldName = uploadFile.getOriginalFilename();
String newName = UUID.randomUUID().toString() + oldName.substring(oldName.lastIndexOf("."));
// 文件保存到本地
File destFile = new File(folder, newName);
uploadFile.transferTo(destFile);
// 返回上传文件的访问路径
//https://localhost:8888/2020/10/18/a9a05df4-6615-4bb5-b859-a3f9bf4bfae0.jpg
String filePath = request.getScheme() + "://" + request.getServerName()
+ ":" + request.getServerPort() + "/" + format + newName;
return filePath;
}
}
二.分布式文件系统MinIO
2.1 MinIO简介
MinIO 是一个基于Apache License v2.0开源协议的对象存储服务。它兼容亚马逊S3云存储服务接口,非常适合于存储大容量非结构化的数据,例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等,而一个对象文件可以是任意大小,从几kb到最大5T不等。
MinIO官网:https://min.io
2.2 Docker下安装MinIO
1.创建目录
一个用来存放配置,一个用来存储上传文件的目录。启动前需要先创建Minio外部挂载的配置文件(如:E:/minio/config),和存储上传文件的目录(如:E:/minio/data)
2.拉取MinIO的Docker镜像:
docker pull minio/minio
3.创建Minio容器并运行
docker run -p 9000:9000 -p 9090:9090 --name minio -d --restart=always -e MINIO_ACCESS_KEY=minioadmin -e MINIO_SECRET_KEY=minioadmin -v E:/minio/data:/data -v E:/minio/config:/root/.minio minio/minio server /data --console-address ":9090" --address ":9000"
- 9000: S3-API端口,用于程序访问;
- 9090:控制台端口,用户通过浏览器访问;
- MINIO_ACCESS_KEY:指定 minio 默认的 access_key ,同时也是管理服务的登录用户名,这里指定的是minioadmin;
- MINIO_SECRET_KEY:指定 minio 默认的 secret_key ,同时也是管理服务的的登录密码,这里指定的是minioadmin;
4.访问MinIO控制台
本机:http://127.0.0.1:9090或实际IP地址访问
三.整合MinIO的JavaSDK
3.1 整合MinIO
1.pom.xml引入:
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>8.4.5</version>
<exclusions>
<exclusion>
<artifactId>okhttp</artifactId>
<groupId>com.squareup.okhttp3</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.9.0</version>
</dependency>
2.application.yml,服务信息要和我们安装的MinIO服务一致,这样才能正常连接测试:
# MinIo文件服务器
minio:
endpoint: http://127.0.0.1:9000
accessKey: minioadmin
secretKey: minioadmin
3.MinIoProperties.java 配置实体,将上文配置文件属性装载到实体配置对象中:
@Data
@ConfigurationProperties(prefix = "minio")
public class MinIOProperties {
private String endpoint;
private String accessKey;
private String secretKey;
}
4.MinIO提供了MinioClient对bucket、文件等进行操作,我们这里再封装一个工具类:
@Component
@Configuration
@EnableConfigurationProperties({MinIOProperties.class})
public class MinIOTemplate {
private MinIOProperties minIo;
public MinIOTemplate(MinIOProperties minIo) {
this.minIo = minIo;
}
private MinioClient instance;
//minio操作对象实例化
@PostConstruct
public void init() {
instance = MinioClient.builder()
.endpoint(minIo.getEndpoint())
.credentials(minIo.getAccessKey(), minIo.getSecretKey())
.build();
}
/**
* 判断 bucket是否存在
*/
public boolean bucketExists(String bucketName)
throws IOException, InvalidKeyException, InvalidResponseException,
InsufficientDataException, NoSuchAlgorithmException,
ServerException, InternalException, XmlParserException, ErrorResponseException {
return instance.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
}
/**
* 创建 bucket
*/
public void makeBucket(String bucketName) throws IOException, InvalidResponseException,
InvalidKeyException, NoSuchAlgorithmException, ServerException,
ErrorResponseException, XmlParserException, InsufficientDataException, InternalException {
boolean isExist = this.bucketExists(bucketName);
if(!isExist) {
instance.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
}
}
/**
* 文件上传
* @param bucketName bucket名称
* @param objectName 对象名称,文件名称
* @param filepath 文件路径
*/
public ObjectWriteResponse putObject(String bucketName, String objectName, String filepath)
throws IOException, InvalidKeyException, InvalidResponseException,
InsufficientDataException, NoSuchAlgorithmException, ServerException,
InternalException, XmlParserException, ErrorResponseException {
return instance.uploadObject(
UploadObjectArgs.builder().bucket(bucketName).object(objectName).filename(filepath).build());
}
/**
* 文件上传
* @param bucketName bucket名称
* @param objectName 对象名称,文件名称
* @param inputStream 文件输入流
*/
public ObjectWriteResponse putObject(String bucketName, String objectName, InputStream inputStream)
throws IOException, InvalidKeyException, InvalidResponseException,
InsufficientDataException, NoSuchAlgorithmException, ServerException,
InternalException, XmlParserException, ErrorResponseException {
return instance.putObject(
PutObjectArgs.builder()
.bucket(bucketName)
.object(objectName).stream(
inputStream, -1, 10485760)
.build());
}
/**
* 删除文件
* @param bucketName bucket名称
* @param objectName 对象名称
*/
public void removeObject(String bucketName, String objectName)
throws IOException, InvalidKeyException, InvalidResponseException, InsufficientDataException,
NoSuchAlgorithmException, ServerException, InternalException, XmlParserException, ErrorResponseException {
instance.removeObject(RemoveObjectArgs.builder()
.bucket(bucketName)
.object(objectName)
.build());
}
public String getObjectUrl(String bucketName, String objectName)
throws IOException, InvalidKeyException, InvalidResponseException, InsufficientDataException,
NoSuchAlgorithmException, ServerException, InternalException, XmlParserException, ErrorResponseException {
return instance.getPresignedObjectUrl(
GetPresignedObjectUrlArgs.builder()
.method(Method.GET)
.bucket(bucketName)
.object(objectName)
// .expiry(2, TimeUnit.HOURS)
.build());
}
}
3.2 测试
对1.2 文件上传的Controller实现进行改造,增加minio上传的代码:
@RestController
public class FileUploadController {
@Resource
private MinIOTemplate minIOTemplate;
@Value("${web.upload-path}")
private String uploadPath;
@PostMapping("/upload")
public String upload(MultipartFile uploadFile, HttpServletRequest request) throws IOException {
// 在 uploadPath 文件夹中通过日期对上传的文件归类保存
// 比如:/2019/06/06/cf13891e-4b95-4000-81eb-b6d70ae44930.png
SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd/");
String format = sdf.format(new Date());
File folder = new File(uploadPath + format);
if (!folder.isDirectory()) {
folder.mkdirs();
}
// 对上传的文件重命名,避免文件重名
String oldName = uploadFile.getOriginalFilename();
String newName = UUID.randomUUID().toString() + oldName.substring(oldName.lastIndexOf("."));
// 文件保存到本地
File destFile = new File(folder, newName);
uploadFile.transferTo(destFile);
//保存文件到minIO
uploadToMinIO("test", format + newName, destFile.getAbsolutePath());
// 返回上传文件的访问路径
//https://localhost:8888/2020/10/18/a9a05df4-6615-4bb5-b859-a3f9bf4bfae0.jpg
String filePath = request.getScheme() + "://" + request.getServerName()
+ ":" + request.getServerPort() + "/" + format + newName;
return filePath;
}
private void uploadToMinIO(String bucketName, String objectName, String filePath) {
try {
minIOTemplate.makeBucket(bucketName);
ObjectWriteResponse response = minIOTemplate.putObject(bucketName, objectName, filePath);
System.out.println(response.object());
//获取文件访问路径
String url = minIOTemplate.getObjectUrl("test", objectName);
System.out.println(url);
} catch (Exception e) {
e.printStackTrace();
}
}
}
注意:MinIO默认获取到的文件范围URL有效期为7天,可以通过在控制台bucket里面进行规制配置,实现永久访问。
网友评论