Promise 对象代表一个异步操作,有三种状态:
只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态
有了 Promise 对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数
- //定义异步操作
- var promise = new Promise (function (resolve,reject)) {
- // ... some code
- if(/* 异步操作成功 */){
- resolve(value)
- } else {
- reject(error)
- }
- }
-
- //异步操作的回调(写法一)
- promise.then( function( result ) {
- // result ==上面的value
- console.log("fulfilled:",val)
- }).catch(function (error){
- // error==上面的error
- console.log('rejected',err)
- });
-
- //异步操作的回调(写法二)
- promise.then((result) => console.log("fulfilled:",result)).catch((error) =>console.log('rejected',error));
Promise.all()
除了串行执行若干异步任务外,Promise还可以并行执行异步任务。
Promise.all() 可以将多个Promise实例包装成一个新的Promise实例;
const p = Promise.all([p1, p2, p3]);
p
的状态由p1
、p2
、p3
决定,分成两种情况。(1)只有
p1
、p2
、p3
的状态都变成fulfilled
,p
的状态才会变成fulfilled
,此时p1
、p2
、p3
的返回值组成一个数组,传递给p
的回调函数。(2)只要
p1
、p2
、p3
之中有一个被rejected
,p
的状态就变成rejected
,此时第一个被reject
的实例的返回值,会传递给p
的回调函数。
曾经困扰我的一个问题
<button @click="bbb">异步调用button>
异步的写法
- //异步的
- bbb() {
- let arr = [];
- for (let index = 0; index < 15; index++) {
- arr.push(this.aaa(index));
- }
- //使用之前不希望调用
- console.log(arr);
- },
- aaa(i) {
- return new Promise((resolve, reject) => {
- setTimeout(() => {
- console.log("i", i);
- resolve(i);
- this.a++;
- }, 200 - 5 * i);
- });
- },
同步的写法
- bbb() {
- let arr = [];
- let p = undefined;
- for (let index = 0; index < 15; index++) {
- if (index == 0) {
- p = this.aaa(index);
- } else {
- //获取上一个
- p = this.aaa(index, p);
- }
- }
- //使用之前不希望调用
- console.log(p);
- },
- aaa(i, promise) {
- if (i == 0) {
- return new Promise((resolve, reject) => {
- setTimeout(() => {
- console.log("i", i);
- resolve(i);
- this.a++;
- }, 200 - 5 * i);
- });
- } else {
- return promise.then((value) => {
- return new Promise((resolve, reject) => {
- setTimeout(() => {
- console.log("i", i);
- resolve(i);
- this.a++;
- }, 200 - 5 * i);
- });
- });
- }
- },
File
对象Blob对象
Blob对象有一个slice方法,返回一个新的
Blob
对象,包含了源Blob对象中制定范围内的数据。
var blob = instanceOfBlob.slice([start [, end [, contentType]]]};
参数说明:
start: 可选,代表 Blob 里的下标,表示第一个会被会被拷贝进新的 Blob 的字节的起始位置。如果传入的是一个负数,那么这个偏移量将会从数据的末尾从后到前开始计算。
end: 可选,代表的是 Blob 的一个下标,这个下标-1的对应的字节将会是被拷贝进新的Blob 的最后一个字节。如果你传入了一个负数,那么这个偏移量将会从数据的末尾从后到前开始计算。
contentType
可选给新的 Blob 赋予一个新的文档类型。这将会把它的 type 属性设为被传入的值。它的默认值是一个空的字符串。
通过slice方法,从blob1中创建出一个新的blob对象,size等于3。
- var data = "abcdef";
- var blob1 = new Blob([data]);
- var blob2 = blob1.slice(0,3);
-
- console.log(blob1); //输出:Blob {size: 6, type: ""}
- console.log(blob2); //输出:Blob {size: 3, type: ""}
File继承字Blob,因此我们可以调用slice方法对大文件进行分片上传。
FileReader的使用方式非常简单分为三个步骤:
1.创建FileReader
对象 2.读取文件 3.处理文件读取事件(完成,中断,错误等...)
创建FileReader对象
let reader = new FileReader();
读取文件
FileReader 的实例拥有 4 个方法,其中 3 个用以读取文件,另一个用来中断读取。
上面的表格列出了这些方法以及他们的参数和功能,需要注意的是 ,无论读取成功或失败,方法并不会返回读取结果, 这一结果存储在 result属性中。
方法名 参数 描述 abort 无 中断读取 readAsBinaryString file 将文件读取为二进制码 readAsDataURL file 将文件读取为 DataURL readAsText file, [文件编码] 将文件读取为文本
//1.将文件读取为二进制数据 reader.readAsBinaryString(file); //2.将文件读取为文本数据 reader.readAsText(file,"UTF-8")
读取事件
FileReader
包含了一套完整的事件模型,用于捕获读取文件时的状态,下面这个表格归纳了这些事件。
事件 描述 onabort 中断时触发 onerror 出错时触发 onload 文件读取成功完成时触发 onloadend 读取完成触发,无论成功或失败 onloadstart 读取开始时触发 onprogress 读取中
完整读取示例
- //1.获取文件对象
- let file = data.file;
- //2.创建FileReader对象,用于读取文件
- let reader = new FileReader();
- //3.将文件读取为二进制码
- reader.readAsBinaryString(file);
- //4.处理读取事件
- //4.1读取完成事件,获取数据
- reader.onload = (e) => {
- //获取数据
- const data = e.currentTarget.result;
- };
- //4.2 //读取中断事件
- reader.onabort = () => {
- console.log('读取中断了');
- };
MD5计算将整个文件或者字符串,通过其不可逆的字符串变换计算,产生文件或字符串的MD5散列值。
因此MD5常用于校验文件,以防止文件被“篡改”。因为如果文件、字符串的MD5散列值不一样,说明文件内容也是不一样的
安装依赖
npm install spark-md5 --save
导包
import SparkMD5 from 'spark-md5'
要配合js的 FileReader 函数来使用 SparkMD5
- const getFileMD5 = (file:File) => {
- return new Promise((resolve, reject) => {
- const spark = new SparkMD5.ArrayBuffer()
- const fileReader = new FileReader()
- fileReader.onload = (e:any) => {
- spark.append(e.target.result)
- resolve(spark.end())
- }
- fileReader.onerror = () => {
- reject('')
- }
- fileReader.readAsArrayBuffer(file)
- })
- }
6、
minio:对象存储服务。一个对象文件可以是任意大小,从几kb到最大5T不等
中文网站: MinIO | 高性能分布式存储,私有云存储
下载后:新建一个minioData文件夹用来存储上传的文件
在minio.exe文件夹的路径处输入cmd进入命令行界面(该exe文件不能双击运行)
输入命令:minio.exe server E:\server\minio\install\minioData
用户名和密码都是minioadmin
Object:存储到minio中的基本对象,如文件、字节流
Bucket:存放文件的顶层目录,起到一个隔离的作用
Drive:存储数据的磁盘
MinIO 提供客户端工具访问和操作服务端。MinIO 客户端工具 mc(minio client)
提供了类似 unix 的命令去操作服务端。mc 相关命令列表如下所示(请查看英文官网):
- ls 列出文件和文件夹。
- mb 创建一个存储桶或一个文件夹。
- cat 显示文件和对象内容。
- pipe 将一个STDIN重定向到一个对象或者文件或者STDOUT。
- share 生成用于共享的URL。
- cp 拷贝文件和对象。
- mirror 给存储桶和文件夹做镜像。
- find 基于参数查找文件。
- diff 对两个文件夹或者存储桶比较差异。
- rm 删除文件和对象。
- events 管理对象通知。
- watch 监听文件和对象的事件。
- policy 管理访问策略。
- session 为cp命令管理保存的会话。
- config 管理mc配置文件。
- update 检查软件更新。
- version 输出版本信息。
查看 minio服务端
mc config host ls
添加服务端
mc config host add xiayumao_minio http://127.0.0.1:9000 minioadmin minioadmin
再次查看
删除服务端
mc config host remove xiayumao_minio
mc config host add minio-server http://127.0.0.1:9000 minioadmin minioadmin
查看
ls命令列出文件、对象和存储桶。
-----------------
创建桶
mc mb minio-server/sringcloud
递归创建桶,对吗?
mc mb minio-server/nacos/2023/9/23
- <dependency>
- <groupId>io.miniogroupId>
- <artifactId>minioartifactId>
- <version>8.2.1version>
- dependency>
application.yml 配置信息
- minio:
- endpoint: http://127.0.0.1:9000 #Minio服务所在地址
- accessKey: minioadmin #账号
- secretKey: minioadmin #密码
关于 MinIO 的一切操作都得通过 MinioClient
对象来进行
- @Data
- @Configuration
- @ConfigurationProperties(prefix = "minio")
- public class MinioConfig {
- private String endpoint;
- private String accessKey;
- private String secretKey;
- @Bean
- public MinioClient minioClient() {
- return MinioClient.builder()
- .endpoint(endpoint)//服务地址
- .credentials(accessKey, secretKey)//账号 密码
- .build();
- }
- }
- @Component
- @Slf4j
- public class MinioUtil {
- @Resource
- private MinioClient minioClient;
-
- /**
- * 查看存储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 {
- minioClient.makeBucket(MakeBucketArgs.builder()
- .bucket(bucketName)
- .build());
- } 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
getAllBuckets() { - try {
- return minioClient.listBuckets();
- } catch (Exception e) {
- e.printStackTrace();
- }
- return null;
- }
-
- /**
- * 列出存储桶中的所有对象
- *
- * @param bucketName 存储桶名称
- * @return
- * @throws Exception
- */
- public Iterable
> listObjects(String bucketName) { - boolean flag = bucketExists(bucketName);
- if (flag) {
- return minioClient.listObjects(ListObjectsArgs.builder().bucket(bucketName).build());
- }
- return null;
- }
-
-
- /**
- * 递归查询桶下对象
- *
- * @param bucketName 存储桶名称
- * @return
- * @throws Exception
- */
- public Iterable
> recursiveListObjects(String bucketName) { - boolean flag = bucketExists(bucketName);
- if (flag) {
- return minioClient.listObjects(ListObjectsArgs.builder().bucket(bucketName).recursive(true).build());
- }
- return null;
- }
-
- /**
- * 列出某个桶中的所有文件名
- * 文件夹名为空时,则直接查询桶下面的数据,否则就查询当前桶下对于文件夹里面的数据
- *
- * @param bucketName 桶名称
- * @param folderName 文件夹名
- * @param isDeep 是否递归查询
- */
- public Iterable
> getBucketAllFile(String bucketName, String folderName, Boolean isDeep) { - if (!StringUtils.hasLength(folderName)) {
- folderName = "";
- }
- System.out.println(folderName);
- Iterable
> listObjects = minioClient.listObjects( - ListObjectsArgs
- .builder()
- .bucket(bucketName)
- .prefix(folderName + "/")
- .recursive(isDeep)
- .build());
-
- return listObjects;
- }
-
- /**
- * 创建文件夹
- *
- * @param bucketName 桶名
- * @param folderName 文件夹名称
- * @return
- * @throws Exception
- */
- public ObjectWriteResponse createBucketFolder(String bucketName, String folderName) throws Exception {
- if (!bucketExists(bucketName)) {
- throw new RuntimeException("必须在桶存在的情况下才能创建文件夹");
- }
- if (!StringUtils.hasLength(folderName)) {
- throw new RuntimeException("创建的文件夹名不能为空");
- }
- PutObjectArgs putObjectArgs = PutObjectArgs.builder()
- .bucket(bucketName)
- .object(folderName + "/")
- .stream(new ByteArrayInputStream(new byte[0]), 0, 0)
- .build();
- ObjectWriteResponse objectWriteResponse = minioClient.putObject(putObjectArgs);
- return objectWriteResponse;
- }
- }
通过MinioClient
对象,指定 Bucket 即可进行相应的操作。
桶操作接口测试
- @Slf4j
- @RestController
- @RequestMapping(value = "product/file")
- public class FileController {
-
- @Autowired
- private MinioUtil minioUtil;
- @Autowired
- private MinioConfig prop;
-
- //查看存储bucket是否存在
- @GetMapping("/bucketExists")
- public Boolean bucketExists(@RequestParam("bucketName") String bucketName) {
- return minioUtil.bucketExists(bucketName);
- }
-
- //创建存储bucket
- @GetMapping("/makeBucket")
- public Boolean makeBucket(String bucketName) {
- return minioUtil.makeBucket(bucketName);
- }
-
- //删除存储bucket
- @GetMapping("/removeBucket")
- public Boolean removeBucket(String bucketName) {
- return minioUtil.removeBucket(bucketName);
- }
- }
新创建桶ipadmini6下,创建文件夹
//创建文件夹 @GetMapping("/createBucketFolder") public void createBucketFolder(String bucketName) throws Exception { String folderName = "aa/bb"; ObjectWriteResponse response = minioUtil.createBucketFolder(bucketName, folderName); }递归查询桶ipadmini6下所有对象
查询所有的桶
//获取全部bucket @GetMapping("/getAllBuckets") public List getAllBuckets() { ListallBuckets = minioUtil.getAllBuckets(); allBuckets.forEach(bucket -> { System.out.printf("存储桶名:%s,创建时间:%s \n", bucket.name(), bucket.creationDate()); }); return allBuckets; }
查询桶下所有对象
//查询桶下所有对象 @GetMapping("/listObjects") public void listObjects(@RequestParam("bucketName") String bucketName) throws Exception { Iterable> listObjects = minioUtil.listObjects(bucketName); for (Result- result : listObjects) {
Item item = result.get(); System.out.println(item.objectName() + "\t" + item.size()); } }控制台打印:
递归查询桶下所有对象
//递归查询桶下所有对象 @GetMapping("/recursiveListObjects") public void recursiveListObjects(@RequestParam("bucketName") String bucketName) throws Exception { Iterable> listObjects = minioUtil.recursiveListObjects(bucketName); for (Result- result : listObjects) {
Item item = result.get(); System.out.println(item.objectName() + "\t" + item.size()); } }控制台打印:
指定前缀查询桶下所有对象
//指定前缀查询 @GetMapping("/getBucketAllFile") public void getBucketAllFile(@RequestParam("bucketName") String bucketName) throws Exception { Iterable> listObjects = minioUtil.getBucketAllFile(bucketName,"兰兰/img",true); for (Result- result : listObjects) {
Item item = result.get(); System.out.println(item.objectName() + "\t" + item.size()); } }
OSS没有文件夹的概念,所有资源都是以文件来存储,但您可以通过创建一个以正斜线(/)结尾,大小为0的Object来创建模拟文件夹。
MinioUtil中继续新增
- /**
- * 上传本地文件,根据路径上传
- * minio 采用文件内容上传,可以换成上面的流上传
- *
- * @param filePath 上传本地文件路径
- * @Param bucketName 上传至服务器的桶名称
- */
- public boolean uploadPath(String filePath, String bucketName) throws Exception {
- File file = new File(filePath);
- if (!file.isFile()) {
- throw new RuntimeException("上传文件为空,请重新上传");
- }
- if (!StringUtils.hasLength(bucketName)) {
- throw new RuntimeException("传入桶名为空,请重新上传");
- }
- if (!this.bucketExists(bucketName)) {
- throw new RuntimeException("当前操作的桶不存在!");
- }
- String minioFilename = UUID.randomUUID().toString() + "_" + file.getName();//获取文件名称
- String fileType = minioFilename.substring(minioFilename.lastIndexOf(".") + 1);
- minioClient.uploadObject(
- UploadObjectArgs.builder()
- .bucket(bucketName)
- .object(minioFilename)//文件存储在minio中的名字
- .filename(filePath)//上传本地文件存储的路径
- .contentType(fileType)//文件类型
- .build());
- return this.getBucketFileExist(minioFilename, bucketName);
- }
上传后,结果只能在桶下吗
略微修改一下objectName
再上传,就能看到
MinioUtil中继续新增
使用的是建造者模式,创建PutObjectArgs参数对象
- /**
- * 根据MultipartFile file上传文件
- * minio 采用文件流上传,可以换成下面的文件上传
- *
- * @param file 上传的文件
- * @param bucketName 上传至服务器的桶名称
- */
- public boolean uploadFile(MultipartFile file, String bucketName) throws Exception {
-
- if (file == null || file.getSize() == 0 || file.isEmpty()) {
- throw new RuntimeException("上传文件为空,请重新上传");
- }
-
- if (!this.bucketExists(bucketName)) {
- throw new RuntimeException("当前操作的桶不存在!");
- }
-
- // 获取上传的文件名
- String filename = file.getOriginalFilename();
- assert filename != null;
- //可以选择生成一个minio中存储的文件名称
- String minioFilename = UUID.randomUUID().toString() + "_" + filename;
-
- InputStream inputStream = file.getInputStream();
- long size = file.getSize();
- String contentType = file.getContentType();
-
- // Upload known sized input stream.
- minioClient.putObject(
- PutObjectArgs.builder()
- .bucket(bucketName) //上传到指定桶里面
- .object(minioFilename)//文件在minio中存储的名字
- //p1:上传的文件流;p2:上传文件总大小;p3:上传的分片大小
- .stream(inputStream, size, -1) //上传分片文件流大小,如果分文件上传可以采用这种形式
- .contentType(contentType) //文件的类型
- .build());
-
- return this.getBucketFileExist(minioFilename, bucketName);
- }
再去尝试上传一个很大的(433MB)文件,报错
报错的原因是: springBoot项目自带的tomcat对上传的文件大小有默认的限制,SpringBoot官方文档中展示:每个文件的配置最大为1Mb,单次请求的文件的总数不能大于10Mb。
重新指定最大限制
- spring:
- servlet:
- multipart:
- enabled: true
- max-file-size: 500MB
- max-request-size: 500MB
重新调用 上传文件(MultipartFile)
MinioUtil中继续新增
- /**
- * 文件下载到指定路径
- *
- * @param downloadPath 下载到本地路径
- * @param bucketName 下载指定服务器的桶名称
- * @param objectName 下载的文件名称
- */
- public void downloadPath(String downloadPath, String bucketName, String objectName) throws Exception {
- if (downloadPath.isEmpty() || !StringUtils.hasLength(bucketName) || !StringUtils.hasLength(objectName)) {
- throw new RuntimeException("下载文件参数不全!");
- }
- if (!new File(downloadPath).isDirectory()) {
- throw new RuntimeException("本地下载路径必须是一个文件夹或者文件路径!");
- }
- if (!this.bucketExists(bucketName)) {
- throw new RuntimeException("当前操作的桶不存在!");
- }
- downloadPath += objectName;
- minioClient.downloadObject(
- DownloadObjectArgs.builder()
- .bucket(bucketName) //指定是在哪一个桶下载
- .object(objectName)//是minio中文件存储的名字;本地上传的文件是user.xlsx到minio中存储的是user-minio,那么这里就是user-minio
- .filename(downloadPath)//需要下载到本地的路径,一定是带上保存的文件名;如 d:\\minio\\user.xlsx
- .build());
- }
在分片上传的时候如果没有设置分片类型,会默认设置分片类型为application/octet-stream。这样的话不能在线观看,只能够进行下载再查看资源,从而不利于前端直接引用地址。
思路:
- 前端对文件进行切片,并且记录切片总数
- 访问后端预上传接口,该接口仅仅处理目标文件上传url并且返回给前端,没有太多的资源占用。
- 前端获取到返回的预上传url后,循环分片进行上传。
- 在前端上传完分片后,请求后端文件合并接口对目标分片进行合并。
这样可以最大限度的利用前端的性能,而不是只发请求到后端,并且实现了前端直接对接minio服务器的功能。
- public class CustomMinioClient extends MinioClient {
- public CustomMinioClient(MinioClient client) {
- super(client);
- }
-
- /**
- * 创建分片上传请求
- *
- * @param bucket 存储桶
- * @param region 区域
- * @param object 对象名
- * @param headers 消息头
- * @param extraQueryParams 额外查询参数
- */
- public String initMultiPartUpload(String bucket, String region, String object, Multimap
headers, Multimap extraQueryParams) throws Exception { - CreateMultipartUploadResponse response = this.createMultipartUpload(bucket, region, object, headers, extraQueryParams);
- return response.result().uploadId();
- }
-
- /**
- * 完成分片上传,执行合并文件
- *
- * @param bucketName 存储桶
- * @param region 区域
- * @param objectName 对象名
- * @param uploadId 上传ID
- * @param parts 分片
- * @param extraHeaders 额外消息头
- * @param extraQueryParams 额外查询参数
- */
- public ObjectWriteResponse mergeMultipartUpload(String bucketName, String region, String objectName, String uploadId, Part[] parts, Multimap
extraHeaders, Multimap extraQueryParams) throws Exception { - return this.completeMultipartUpload(bucketName, region, objectName, uploadId, parts, extraHeaders, extraQueryParams);
- }
-
- /**
- * 查询分片数据
- *
- * @param bucketName 存储桶
- * @param region 区域
- * @param objectName 对象名
- * @param uploadId 上传ID
- * @param extraHeaders 额外消息头
- * @param extraQueryParams 额外查询参数
- */
- public ListPartsResponse listMultipart(String bucketName, String region, String objectName, Integer maxParts, Integer partNumberMarker, String uploadId, Multimap
extraHeaders, Multimap extraQueryParams) throws Exception { - return this.listParts(bucketName, region, objectName, maxParts, partNumberMarker, uploadId, extraHeaders, extraQueryParams);
- }
- }
在构建InputStream时,会进行分片操作,我们可以了解到上传文件大小的一些限制:
分片规则如下:
- // 参数为 文件大小objectSize、分片大小partSize,分片数我们传入的是-1,表示使用默认配置
- protected long[] getPartInfo(long objectSize, long partSize) {
- // 1. 校验大小,如果设置的分片大小 小于5M或者大于5GB,报错不支持
- // 对象大小超过5TiB,报错不支持
- this.validateSizes(objectSize, partSize);
- if (objectSize < 0L) {
- return new long[]{partSize, -1L};
- } else {
- // 2. 没有设置分片数据大小,怎按照默认的5M进行分割
- if (partSize <= 0L) {
- double dPartSize = Math.ceil((double)objectSize / 10000.0D);
- dPartSize = Math.ceil(dPartSize / 5242880.0D) * 5242880.0D;
- partSize = (long)dPartSize;
- }
- if (partSize > objectSize) {
- partSize = objectSize;
- }
-
- long partCount = partSize > 0L ? (long)Math.ceil((double)objectSize / (double)partSize) : 1L;
- // 3. 分片数量不能超过10000
- if (partCount > 10000L) {
- throw new IllegalArgumentException("object size " + objectSize + " and part size " + partSize + " make more than " + 10000 + "parts for upload");
- } else {
- // 4. 返回一个数组,第一个值为分片数据大小,第二个为分片数量
- return new long[]{partSize, partCount};
- }
- }
- }
createMultipartUpload方法会创建分块请求,根据对象名和存储桶名去Minio获取上传当前对象的uploadId。
uploadId在循环中使用的都是同一个,说明分片上传的时候都会使用同一个uploadId,最后合并同一个uploadId的文件。
用户调用初始化接口,后端调用minio初始化,得到uploadId,生成每个分片的minio上传url
- /**
- * 初始化分片上传
- *
- * @param bucket 桶
- * @param objectName 文件全路径名称
- * @param partCount 分片数量
- * @param contentType 类型,如果类型使用默认流会导致无法预览
- * @return /
- */
- public Map
initMultiPartUpload(String bucket, String objectName, int partCount, String contentType) { - Map
result = new HashMap<>(); - try {
- if (StrUtil.isBlank(contentType)) {
- contentType = "application/octet-stream";
- }
- HashMultimap
headers = HashMultimap.create(); - headers.put("Content-Type", contentType);
- String uploadId = customMinioClient.initMultiPartUpload(bucket, null, objectName, headers, null);
-
- result.put("uploadId", uploadId);
- List
partList = new ArrayList<>(); -
- Map
reqParams = new HashMap<>(); - //reqParams.put("response-content-type", "application/json");
- reqParams.put("uploadId", uploadId);
- for (int i = 1; i <= partCount; i++) {
- reqParams.put("partNumber", String.valueOf(i));
- String uploadUrl = customMinioClient.getPresignedObjectUrl(
- GetPresignedObjectUrlArgs.builder()
- .method(Method.PUT)
- .bucket(bucket)
- .object(objectName)
- .expiry(1, TimeUnit.DAYS)
- .extraQueryParams(reqParams)
- .build());
- partList.add(uploadUrl);
- }
- result.put("uploadUrls", partList);
- } catch (Exception e) {
- e.printStackTrace();
- return null;
- }
-
- return result;
- }
测试,我们把partCount给成4
获取到了uploadId以后,就会执行上传操作,调用uploadPart方法,uploadPart最终也是调用execute,可以看到该方法,是调用的OkHttpClient 去执行的。
MinioUtil中继续新增
- /**
- * 分片上传完后合并
- *
- * @param objectName 文件全路径名称
- * @param uploadId 返回的uploadId
- * @return /
- */
- public boolean mergeMultipartUpload(String bucket, String objectName, String uploadId) {
- try {
- //TODO::目前仅做了最大1000分片
- Part[] parts = new Part[1000];
- ListPartsResponse partResult = customMinioClient.listMultipart(bucket, null, objectName, 1000, 0, uploadId, null, null);
- int partNumber = 1;
- for (Part part : partResult.result().partList()) {
- parts[partNumber - 1] = new Part(partNumber, part.etag());
- partNumber++;
- }
- customMinioClient.mergeMultipartUpload(bucket, null, objectName, uploadId, parts, null, null);
- } catch (Exception e) {
- e.printStackTrace();
- return false;
- }
- return true;
- }
思路:
- 前端上传文件到web后台。
- 后端对文件进行切割,并且记录切割段数。
- 后端调用minio上传api。
- 等待分片全部上传后再调用合并文件
分片上传和断点续传的实现过程中,需要在Minio内部记录已上传的分片文件。
这些分片文件将以文件md5作为父目录,分片文件的名字按照01,02,...的顺序进行命名。同时,还必须知道当前文件的分片总数,这样就能够根据总数来判断文件是否上传完毕了。
比如,一个文件被分成了10片,所以总数是10。当前端发起上传请求时,把一个个文件分片依次上传,Minio 服务器中存储的临时文件依次是01、02、03 等等。
假设前端把05分片上传完毕了之后断开了连接,由于 Minio 服务器仍然存储着01~05的分片文件,因此前端再次上传文件时,只需从06序号开始上传分片,而不用从头开始传输。这就是所谓的断点续传。