update 初次提交分片上传
This commit is contained in:
parent
b61865f45f
commit
7d2e98cb05
@ -48,7 +48,8 @@
|
||||
"vue-json-pretty": "2.4.0",
|
||||
"vue-router": "4.4.5",
|
||||
"vue-types": "5.1.3",
|
||||
"vxe-table": "4.5.22"
|
||||
"vxe-table": "4.5.22",
|
||||
"hash-wasm": "^4.12.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "9.15.0",
|
||||
|
104
src/components/PartUpload/index.vue
Normal file
104
src/components/PartUpload/index.vue
Normal file
@ -0,0 +1,104 @@
|
||||
<template>
|
||||
<div>
|
||||
<a href='https://gitee.com/dromara/RuoYi-Vue-Plus/pulls/522'>分支地址</a>
|
||||
<Progress :status='uploadStatus' :percent='percent' />
|
||||
<input type='file' @change='handleFileChange' />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang='ts'>
|
||||
import { md5 } from 'hash-wasm';
|
||||
import { httpInstance, multipartUpload, type PartUploadList } from '@/utils/partUpload';
|
||||
import { message, Progress } from 'ant-design-vue';
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
/**
|
||||
* 直接运行项目需要放行multipart接口 否则需要token
|
||||
* // @SaCheckPermission("system:oss:multipart")
|
||||
@PostMapping(value = "/multipart")
|
||||
@SaIgnore
|
||||
public R<?> multipart(@RequestBody MultipartBo multipartBo) {
|
||||
*/
|
||||
const uploadDone = ref(false);
|
||||
const uploadStatus = computed(() => (uploadDone.value ? 'success' : 'active'));
|
||||
const percent = ref(0);
|
||||
|
||||
// 切片
|
||||
const CHUNK_SIZE = 1024 * 1024 * 5; // 5MB
|
||||
async function handleFileChange(e: any) {
|
||||
// 拿到上传的文件
|
||||
const file = e.target.files[0]! as File;
|
||||
|
||||
const fileSize = file.size;
|
||||
if (fileSize <= CHUNK_SIZE) {
|
||||
message.error('文件大小不能小于5MB');
|
||||
return;
|
||||
}
|
||||
|
||||
const chunks: Blob[] = [];
|
||||
for (let i = 0; i < file.size; i += CHUNK_SIZE) {
|
||||
const chunk = file.slice(i, i + CHUNK_SIZE);
|
||||
chunks.push(chunk);
|
||||
}
|
||||
// chunk0转uint8array
|
||||
const chunk0 = await chunks[0].arrayBuffer();
|
||||
const uint8array0 = new Uint8Array(chunk0);
|
||||
// 第一片的md5
|
||||
const firstChunkMd5 = await md5(uint8array0);
|
||||
|
||||
/**
|
||||
* 初始化分片请求 拿到uploadId
|
||||
*/
|
||||
const resp = await multipartUpload({
|
||||
ossStatus: 'initiate',
|
||||
originalName: file.name,
|
||||
md5Digest: firstChunkMd5
|
||||
});
|
||||
const uploadId = resp.data.data.uploadId;
|
||||
|
||||
const partUploadList: PartUploadList[] = [];
|
||||
for (let i = 0; i < chunks.length; i++) {
|
||||
const chunk = chunks[i];
|
||||
// const uint8array = new Uint8Array(await chunk.arrayBuffer());
|
||||
// const md5Digest = await md5(uint8array);
|
||||
const uploadResp = await multipartUpload({
|
||||
ossStatus: 'upload',
|
||||
/***
|
||||
* 大坑 这里不能传md5 否则上传失败
|
||||
*/
|
||||
// md5Digest,
|
||||
uploadId,
|
||||
partNumber: i + 1
|
||||
});
|
||||
|
||||
// 拿到上传地址 privateUrl
|
||||
const privateUrl = uploadResp.data.data.privateUrl;
|
||||
const minioResp = await httpInstance.put(privateUrl, chunk, {
|
||||
headers: {
|
||||
'Content-Type': 'application/octet-stream'
|
||||
// 'Content-MD5': md5Digest,
|
||||
}
|
||||
});
|
||||
|
||||
// 从headers拿到etag
|
||||
const eTag = minioResp.headers['etag'];
|
||||
partUploadList.push({
|
||||
partNumber: i + 1,
|
||||
entryTag: eTag
|
||||
});
|
||||
|
||||
// 上传进度
|
||||
percent.value = Math.round(((i + 1) / chunks.length) * 100);
|
||||
console.log(`上传进度: ${percent.value}%`);
|
||||
}
|
||||
|
||||
// 合并
|
||||
await multipartUpload({
|
||||
ossStatus: 'complete',
|
||||
uploadId,
|
||||
partUploadList
|
||||
});
|
||||
|
||||
message.success('上传成功');
|
||||
}
|
||||
</script>
|
74
src/utils/partUpload.ts
Normal file
74
src/utils/partUpload.ts
Normal file
@ -0,0 +1,74 @@
|
||||
import axios from 'axios';
|
||||
|
||||
export const httpInstance = axios.create({
|
||||
baseURL: '/api',
|
||||
timeout: 10000
|
||||
});
|
||||
|
||||
export interface MultipartBo {
|
||||
|
||||
/**
|
||||
* 分片类型(必传)
|
||||
*/
|
||||
ossStatus: 'initiate' | 'upload' | 'query' | 'complete';
|
||||
|
||||
/**
|
||||
* 文件原名(分片初始化的时候使用)
|
||||
*/
|
||||
originalName?: string;
|
||||
|
||||
/**
|
||||
* 用于分片上传任务的 Upload ID
|
||||
* 在初始化分片上传时获取,并在后续的分片上传和完成上传过程中使用
|
||||
*/
|
||||
uploadId?: string;
|
||||
|
||||
/**
|
||||
* 分片编号(从1开始递增)
|
||||
*/
|
||||
partNumber?: number;
|
||||
|
||||
/**
|
||||
* 内容的 MD5 摘要
|
||||
* initiate初始化需要第一片的md5值(或者直接计算整体的md5)用来判断断点续传,以及秒传
|
||||
*/
|
||||
md5Digest?: string;
|
||||
|
||||
/**
|
||||
* 最大返回的分片数(默认为1000,最大值1000)
|
||||
* 最多分片一万,一次性返回会造成前端性能问题,需要前端多次校验
|
||||
*/
|
||||
maxParts?: number;
|
||||
|
||||
/**
|
||||
* 分片编号的标记,用于分页查询(默认为0,表示从第一个分片开始查询)
|
||||
*/
|
||||
partNumberMarker?: number;
|
||||
|
||||
partUploadList?: PartUploadList[];
|
||||
}
|
||||
|
||||
export interface PartUploadList {
|
||||
|
||||
/**
|
||||
* 分片编号(从1开始递增)
|
||||
*/
|
||||
partNumber: number;
|
||||
|
||||
/**
|
||||
* 从上传部分的内容生成的实体标签
|
||||
*/
|
||||
entryTag: string;
|
||||
}
|
||||
|
||||
export interface Data {
|
||||
filename: string;
|
||||
originalName: string;
|
||||
md5Digest: string;
|
||||
uploadId: string;
|
||||
suffix: string;
|
||||
}
|
||||
|
||||
export function multipartUpload(multipartBo: MultipartBo) {
|
||||
return httpInstance.post('/resource/oss/multipart', multipartBo);
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user