From d961c1a4a56e25ed63158b145e6d50130779f52e Mon Sep 17 00:00:00 2001 From: yanghao05 Date: Thu, 17 Apr 2025 20:13:12 +0800 Subject: [PATCH] feat: complete upload progress bar --- backend/app/http/admin/uploads.go | 2 +- backend/providers/ali/oss_client.go | 5 +- frontend/admin/src/api/mediaService.js | 7 +-- frontend/admin/src/pages/MediaUploadPage.vue | 62 +++++++------------- 4 files changed, 27 insertions(+), 49 deletions(-) diff --git a/backend/app/http/admin/uploads.go b/backend/app/http/admin/uploads.go index 09a1858..6e65cee 100644 --- a/backend/app/http/admin/uploads.go +++ b/backend/app/http/admin/uploads.go @@ -35,7 +35,7 @@ type PreCheckResp struct { func (up *uploads) PreUploadCheck(ctx fiber.Ctx, md5, ext string) (*PreCheckResp, error) { _, err := models.Medias.GetByHash(ctx.Context(), md5) if err != nil && errors.Is(err, qrm.ErrNoRows) { - preSign, err := up.oss.PreSignUpload(ctx.Context(), fmt.Sprintf("%s%s", md5, ext)) + preSign, err := up.oss.PreSignUpload(ctx.Context(), fmt.Sprintf("%s.%s", md5, ext)) if err != nil { return nil, err } diff --git a/backend/providers/ali/oss_client.go b/backend/providers/ali/oss_client.go index d537e48..2fc0776 100644 --- a/backend/providers/ali/oss_client.go +++ b/backend/providers/ali/oss_client.go @@ -19,8 +19,9 @@ func (c *OSSClient) GetClient() *oss.Client { func (c *OSSClient) PreSignUpload(ctx context.Context, path string) (*oss.PresignResult, error) { request := &oss.PutObjectRequest{ - Bucket: oss.Ptr(c.config.Bucket), - Key: oss.Ptr("quyun/" + strings.Trim(path, "/")), + Bucket: oss.Ptr(c.config.Bucket), + Key: oss.Ptr("quyun/" + strings.Trim(path, "/")), + ContentType: oss.Ptr("multipart/form-data"), } log.Printf("%+v", request) return c.client.Presign(ctx, request) diff --git a/frontend/admin/src/api/mediaService.js b/frontend/admin/src/api/mediaService.js index 551a1b9..0625f38 100644 --- a/frontend/admin/src/api/mediaService.js +++ b/frontend/admin/src/api/mediaService.js @@ -11,11 +11,8 @@ export const mediaService = { return httpClient.post('/admin/medias', mediaInfo); }, - getUploadToken() { - return httpClient.get('/admin/uploads/token'); - }, - preUploadedCheck(md5) { - return httpClient.get(`/admin/uploads/pre-uploaded-check/${md5}`); + preUploadedCheck(md5, ext) { + return httpClient.get(`/admin/uploads/pre-uploaded-check/${md5}.${ext}`); }, uploadedSuccess(data) { diff --git a/frontend/admin/src/pages/MediaUploadPage.vue b/frontend/admin/src/pages/MediaUploadPage.vue index fa3b04d..79ef0ea 100644 --- a/frontend/admin/src/pages/MediaUploadPage.vue +++ b/frontend/admin/src/pages/MediaUploadPage.vue @@ -15,7 +15,6 @@ const dropZone = ref(null); const uploadProgress = ref(0); const isUploading = ref(false); const currentFile = ref(null); -const ossConfig = ref(null); const uploadHistory = ref([]); const STORAGE_KEY = 'media_upload_history'; @@ -56,19 +55,10 @@ const addToHistory = (file, status = 'uploading') => { // Clear completed uploads const clearCompleted = () => { - uploadHistory.value = uploadHistory.value.filter(item => item.status !== 'completed'); + uploadHistory.value = []; saveUploadHistory(); }; -const getOssToken = async () => { - try { - const response = await mediaService.getUploadToken(); - ossConfig.value = response.data; - } catch (error) { - toast.add({ severity: 'error', summary: '错误', detail: '获取上传凭证失败', life: 3000 }); - } -}; - // Add pending uploads state const uploadQueue = ref([]); const currentUploadIndex = ref(-1); @@ -107,10 +97,12 @@ const processNextUpload = async () => { try { const md5Hash = await calculateMD5(nextFile.file); + // get file ext + const fileExt = nextFile.file.name.split('.').pop(); // Check if file exists before upload - const checkResult = await mediaService.preUploadedCheck(md5Hash); - console.log(checkResult) - if (checkResult.data === 'exists') { + const checkResult = await mediaService.preUploadedCheck(md5Hash, fileExt); + + if (checkResult.data.exists) { // Skip upload and mark as completed uploadQueue.value.shift(); addToHistory(nextFile.file, 'completed'); @@ -121,8 +113,8 @@ const processNextUpload = async () => { life: 3000 }); } else { - // Proceed with upload - await uploadFile(nextFile.file, md5Hash); + // Proceed with upload using pre-signed URL + await uploadFile(nextFile.file, md5Hash, checkResult.data.pre_sign); uploadQueue.value.shift(); addToHistory(nextFile.file, 'completed'); } @@ -175,34 +167,15 @@ const calculateMD5 = (file) => { }); }; -// Modify uploadFile function -const uploadFile = async (file, md5Hash) => { +// Modify uploadFile function to use pre-signed URL +const uploadFile = async (file, md5Hash, preSign) => { try { currentFile.value = file; isUploading.value = true; uploadProgress.value = 0; - if (!ossConfig.value) { - await getOssToken(); - } - - const fileExt = file.name.split('.').pop(); - const newFileName = `${md5Hash}.${fileExt}`; - await new Promise((resolve, reject) => { const xhr = new XMLHttpRequest(); - const formData = new FormData(); - - formData.append('success_action_status', '200'); - formData.append('policy', ossConfig.value.policy); - formData.append('x-oss-signature', ossConfig.value.signature); - formData.append('x-oss-signature-version', 'OSS4-HMAC-SHA256'); - formData.append('x-oss-credential', ossConfig.value.x_oss_credential); - formData.append('x-oss-date', ossConfig.value.x_oss_date); - formData.append('key', ossConfig.value.dir + newFileName); - formData.append('x-oss-security-token', ossConfig.value.security_token); - formData.append('callback', ossConfig.value.callback); - formData.append('file', file); xhr.upload.onprogress = (e) => { if (e.lengthComputable) { @@ -230,8 +203,16 @@ const uploadFile = async (file, md5Hash) => { } }; - xhr.open('POST', ossConfig.value.host, true); - xhr.send(formData); + xhr.open(preSign.Method, preSign.URL, true); + + // Add signed headers if provided + if (preSign.SignedHeaders) { + Object.entries(preSign.SignedHeaders).forEach(([key, value]) => { + xhr.setRequestHeader(key, value); + }); + } + + xhr.send(file); }); toast.add({ severity: 'success', summary: '成功', detail: '文件上传成功', life: 3000 }); @@ -346,8 +327,7 @@ onMounted(() => {