大文件上传概述:
之前项目做了一个文件上传的功能,最近看到有面试会具体的问这个上传功能的细节,把之前做的项目拿过来总结一下,自己写的一个文件上传服务,如果是单体项目直接就是 file-model,微服务可以给文件单独搞一个服务。
对于大的文件上传由于网络带宽的限制非常的耗时间,可以将文件进行切分,类似于IP数据包重组和分片。
类似于百度网盘一秒上传文件,这个如何实现的呢? 相同的文件上传过一次之后使用了MD5加密的算法,下次就不用等待上传了。
对于海量的文件,如何去存储呢? 这些都是需要去考虑的内容。可以使用轻量级的分布式存储minio来存储文件。复杂一点可以使用 fastDFS 来存储文件。
文件存储 sql 表:
create table file_url_table
(
id int auto_increment
primary key,
file_name varchar(255) not null,
file_type varchar(30) not null,
file_md5 varchar(32) not null,
file_url varchar(512) not null,
endpoint varchar(255) not null,
buck_name varchar(255) not null,
object_name varchar(255) not null,
created_at timestamp default CURRENT_TIMESTAMP null,
updated_at timestamp default CURRENT_TIMESTAMP null on update CURRENT_TIMESTAMP
);
minio分布式文件存储
后端文件的存储使用分布式文件存储系统minio
一个minio的地址包括:
endpoint:节点的信息 http:ip地址+:端口号
buckName: 桶的基础信息
objectName: 文件夹的信息+文件的名称。
项目中使用 minio
导入minio maven依赖:
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>8.5.7</version>
</dependency>
配置minoConfig,使用Configuration注解,spring在启动的时候会扫描这个类。@bean会创建一个MinioClient,交给Spring容器去管理。
@Configuration
public class MinioConfig {
@Value("${minio.accessKey}")
private String accessKey;
@Value("${minio.secretKey}")
private String secretKey;
@Value("${minio.endpoint}")
private String endPoint;
@Bean
public MinioClient minioClient() {
return MinioClient.builder().endpoint(endPoint)
.credentials(accessKey, secretKey).build();
}
}
**@Bean**
注解定义了一个 Spring Bean 方法,用来初始化并提供配置好的MinioClient
。- 通过自动注入,Spring Boot 会将配置文件中的
minio.accessKey
,minio.secretKey
,minio.endPoint
自动注入到类的字段中,并将配置好的MinioClient
实例作为 Bean 提供给整个应用使用。 - spring启动的时候会扫描到configuration这个包。
断点续传:
通常视频文件都比较大,所以对于媒资系统上传文件的需求要满足大文件的上传要求。http协议本身对上传文件大小没有限制,但是客户的网络环境质量、电脑硬件环境等参差不齐,如果一个大文件快上传完了网断了没有上传完成,需要客户重新上传,用户体验非常差,所以对于大文件上传的要求最基本的是断点续传。
什么是断点续传:
引用百度百科:断点续传指的是在下载或上传时,将下载或上传任务(一个文件或一个压缩包)人为的划分为几个部分,每一个部分采用一个线程进行上传或下载,如果碰到网络故障,可以从已经上传或下载的部分开始继续上传下载未完成的部分,而没有必要从头开始上传下载,断点续传可以提高节省操作时间,提高用户体验性。
使用的一些技术
因为上传的结构传进来的都是MultipartFile,但是文件合并完成之后是File类型的,需要将File转为MultipartFile。
//将木桶的文件 转换成 MultipartFile
MultipartFile multipartFile = new MockMultipartFile(
"file", // 表单中文件参数的名字
fileName, // 文件名
fileType, // 文件类型
new FileInputStream(outputFile) // 文件输入流
);
MockMultipartFile
的构造函数有四个参数:
- 第一个参数:
"file"
- 这是表单中用于接收文件的字段名。通常是前端表单中的
<input type="file" name="file">
中的name
属性值。 - 例如,在 HTML 表单中,字段可能长这样:
- 这是表单中用于接收文件的字段名。通常是前端表单中的
<input type="file" name="file">
- 后端通过 `"file"` 来访问上传的文件。
- 第二个参数:
fileName
- 这是文件的名字,即上传文件时的文件名。它将作为文件在服务器上存储时的名称,或者用作处理文件时的标识符。
- 例如,
fileName
可能是"document.pdf"
或"image.jpg"
等。
- 第三个参数:
fileType
- 这是文件的 MIME 类型(文件类型)。常见的文件类型如
"application/pdf"
,"image/jpeg"
,"text/plain"
等。 - 在这里,它指定了文件的格式/类型,告诉服务器如何处理该文件。
- 这是文件的 MIME 类型(文件类型)。常见的文件类型如
- 第四个参数:
new FileInputStream(outputFile)
- 这是文件内容的输入流。
**FileInputStream**
** 用于读取文件内容并将其传递给**MockMultipartFile**
。** outputFile
是一个File
对象,表示要上传的实际文件。通过new FileInputStream(outputFile)
,你可以将文件内容转换为字节流,使得它可以被传递到MockMultipartFile
中。
- 这是文件内容的输入流。
MultipartFile
提供了几个常用方法,比如:
getName()
: 获取文件的字段名。getOriginalFilename()
: 获取文件的原始文件名。getBytes()
: 获取文件的字节内容。getInputStream()
: 获取文件的输入流。
校验MD5的逻辑
对于已经存在的文件,文件的 md5 值都是相同的。文件上传前端解析 md5,查询数据库中是否有数据,如果没有再实现文件上传。
//去数据库中查询这个文件的哈希值是否存在,如果存在就不用上传了,直接拿到这个文件的地址
@GetMapping("/md5")
public AjaxResult getMd5(@RequestParam("md5") String md5) {
Map<String, Object> map=fileService.getMd5(md5);
return AjaxResult.success(map);
}
@SneakyThrows
@Override
public Map<String, Object> getMd5(String md5) {
// 从数据库查询文件信息
FileEntity file = fileMapper.getMd5(md5);
// 如果文件不存在,直接返回 null
if (file == null) {
return null;
}
// 获取文件相关信息
String bucketName = file.getBuckName();
String objectName = file.getObjectName();
String fileUrl = file.getFileUrl();
String fileType = file.getFileType()