From 8d7a0688f16d42247857a96b7f004b8c07b0ce4d Mon Sep 17 00:00:00 2001 From: yanghao05 Date: Tue, 8 Apr 2025 20:42:34 +0800 Subject: [PATCH] feat: upload success --- backend/config.toml | 4 +- backend/providers/ali/credential.go | 6 +- .../admin/src/api/get_oss_upload_token.json | 11 ++ frontend/admin/src/api/mediaService.js | 8 + frontend/admin/src/pages/MediaPage.vue | 143 +++++++++++++++--- 5 files changed, 144 insertions(+), 28 deletions(-) create mode 100644 frontend/admin/src/api/get_oss_upload_token.json diff --git a/backend/config.toml b/backend/config.toml index cac9c1f..0de8043 100644 --- a/backend/config.toml +++ b/backend/config.toml @@ -28,6 +28,6 @@ DB = 0 AccessKeyId = "LTAI5t86SjiP9zRd3q2w7jQN" AccessKeySecret = "hV7spvJuWh8w0EEIXj8NFi2uBlF4aS" Bucket ="rogee-test" -Host ="abc" +#Host ="abc" Region ="cn-beijing" -CallbackURL = "https://localhost" +CallbackURL = "https://www.baidu.com" diff --git a/backend/providers/ali/credential.go b/backend/providers/ali/credential.go index a9111ac..7461251 100644 --- a/backend/providers/ali/credential.go +++ b/backend/providers/ali/credential.go @@ -33,11 +33,11 @@ func (c *Config) GetToken(path string) (*PolicyToken, error) { SetType("access_key"). SetAccessKeyId(c.AccessKeyId). SetAccessKeySecret(c.AccessKeySecret). - SetPolicy("") + SetPolicy(""). + SetRoleSessionExpiration(3600) // SetType("ram_role_arn"). // SetRoleArn(os.Getenv("OSS_STS_ROLE_ARN")). // SetRoleSessionName("Role_Session_Name"). - // SetRoleSessionExpiration(3600) // 根据配置创建凭证提供器 provider, err := credentials.NewCredential(config) @@ -105,7 +105,7 @@ func (c *Config) GetToken(path string) (*PolicyToken, error) { callbackParam.CallbackBodyType = "application/x-www-form-urlencoded" callback_str, err := json.Marshal(callbackParam) if err != nil { - fmt.Println("callback json err:", err) + return nil, errors.Wrap(err, "callback json err:") } callbackBase64 := base64.StdEncoding.EncodeToString(callback_str) // 构建返回给前端的表单 diff --git a/frontend/admin/src/api/get_oss_upload_token.json b/frontend/admin/src/api/get_oss_upload_token.json new file mode 100644 index 0000000..e0dd3c7 --- /dev/null +++ b/frontend/admin/src/api/get_oss_upload_token.json @@ -0,0 +1,11 @@ +{ + "policy": "eyJjb25kaXRpb25zIjpbeyJidWNrZXQiOiJyb2dlZS10ZXN0In0seyJ4LW9zcy1zaWduYXR1cmUtdmVyc2lvbiI6Ik9TUzQtSE1BQy1TSEEyNTYifSx7Ingtb3NzLWNyZWRlbnRpYWwiOiJMVEFJNXQ4NlNqaVA5elJkM3EydzdqUU4vMjAyNTA0MDgvY24tYmVpamluZy9vc3MvYWxpeXVuX3Y0X3JlcXVlc3QifSx7Ingtb3NzLWRhdGUiOiIyMDI1MDQwOFQxMTUxMzZaIn0seyJ4LW9zcy1zZWN1cml0eS10b2tlbiI6IiJ9XSwiZXhwaXJhdGlvbiI6IjIwMjUtMDQtMDhUMTI6NTE6MzYuNjEzWiJ9", + "security_token": "", + "x_oss_signature_version": "OSS4-HMAC-SHA256", + "x_oss_credential": "LTAI5t86SjiP9zRd3q2w7jQN/20250408/cn-beijing/oss/aliyun_v4_request", + "x_oss_date": "20250408T115136Z", + "signature": "e18146794197eb767ad4910b35997192ea04a006fbff58252f1ed66aaae1bdfd", + "host": "abc", + "dir": "quyun/", + "callback": "eyJjYWxsYmFja1VybCI6Imh0dHBzOi8vbG9jYWxob3N0IiwiY2FsbGJhY2tCb2R5IjoiZmlsZW5hbWU9JHtvYmplY3R9XHUwMDI2c2l6ZT0ke3NpemV9XHUwMDI2bWltZVR5cGU9JHttaW1lVHlwZX1cdTAwMjZoZWlnaHQ9JHtpbWFnZUluZm8uaGVpZ2h0fVx1MDAyNndpZHRoPSR7aW1hZ2VJbmZvLndpZHRofSIsImNhbGxiYWNrQm9keVR5cGUiOiJhcHBsaWNhdGlvbi94LXd3dy1mb3JtLXVybGVuY29kZWQifQ==" +} \ No newline at end of file diff --git a/frontend/admin/src/api/mediaService.js b/frontend/admin/src/api/mediaService.js index 282fcc3..461d75e 100644 --- a/frontend/admin/src/api/mediaService.js +++ b/frontend/admin/src/api/mediaService.js @@ -6,4 +6,12 @@ export const mediaService = { params: { page, limit } }); }, + + getUploadToken() { + return httpClient.get('/admin/uploads/token'); + }, + + createMedia(mediaInfo) { + return httpClient.post('/admin/medias', mediaInfo); + } }; \ No newline at end of file diff --git a/frontend/admin/src/pages/MediaPage.vue b/frontend/admin/src/pages/MediaPage.vue index dc22acb..22e97c3 100644 --- a/frontend/admin/src/pages/MediaPage.vue +++ b/frontend/admin/src/pages/MediaPage.vue @@ -22,6 +22,9 @@ const toast = useToast(); // Add ref for upload dialog visibility const uploadDialogVisible = ref(false); +// Add ref for FileUpload component +const fileUploadRef = ref(null); + // Function to open upload dialog const openUploadDialog = () => { uploadDialogVisible.value = true; @@ -63,17 +66,97 @@ const totalPages = computed(() => { // Sample data - in a real app, this would come from an API const mediaFiles = ref([]); -// File upload handling -const onUpload = (event) => { - toast.add({ severity: 'success', summary: '成功', detail: '文件上传成功', life: 3000 }); - // In a real app, you would process the files from event.files and update the mediaFiles list - // Here we're just showing a success message +// Add OSS upload token state +const ossConfig = ref(null); - // Close the dialog after successful upload - closeUploadDialog(); +// Add method to get OSS token +const getOssToken = async () => { + try { + const response = await mediaService.getUploadToken(); + ossConfig.value = response; + } catch (error) { + toast.add({ severity: 'error', summary: '错误', detail: '获取上传凭证失败', life: 3000 }); + } +}; - // Refresh the media files list - fetchMediaFiles(); +const uploadProgress = ref(0); +const isUploading = ref(false); +const currentFile = ref(null); + +// Add custom uploader method +const customUploader = async (event) => { + const { files } = event; + + try { + for (const file of files) { + currentFile.value = file; + isUploading.value = true; + uploadProgress.value = 0; + + // Get fresh token for each file + await getOssToken(); + + 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 + file.name); + formData.append('x-oss-security-token', ossConfig.value.security_token); + formData.append('callback', ossConfig.value.callback); + formData.append('file', file); + + const xhr = new XMLHttpRequest(); + xhr.upload.addEventListener('progress', (e) => { + if (e.lengthComputable) { + uploadProgress.value = (e.loaded * 100) / e.total; + } + }); + + await new Promise((resolve, reject) => { + xhr.onreadystatechange = () => { + if (xhr.readyState === 4) { + if (xhr.status >= 200 && xhr.status < 300) { + resolve(xhr.response); + } else { + reject(new Error(`Upload failed with status: ${xhr.status}`)); + } + } + }; + + xhr.open('POST', ossConfig.value.host, true); + xhr.send(formData); + }); + + // Create media record after successful upload + // await mediaService.createMedia({ + // name: file.name, + // file_size: file.size, + // file_type: file.type, + // url: `${ossConfig.value.host}/${ossConfig.value.dir}${file.name}` + // }); + } + + toast.add({ severity: 'success', summary: '成功', detail: '文件上传成功', life: 3000 }); + closeUploadDialog(); + fetchMediaFiles(); + } catch (error) { + toast.add({ severity: 'error', summary: '错误', detail: '文件上传失败', life: 3000 }); + // Use ref to clear the upload queue + fileUploadRef.value.clear(); + } finally { + isUploading.value = false; + currentFile.value = null; + } +}; + +// Add beforeUpload handler +const onBeforeUpload = async (event) => { + if (!ossConfig.value) { + await getOssToken(); + } }; // Preview file @@ -131,6 +214,7 @@ const onPage = (event) => { onMounted(() => { fetchMediaFiles(); + getOssToken(); }); // File type badge severity mapping @@ -217,7 +301,7 @@ const formatFileSize = (bytes) => { - + - + - + - + @@ -269,19 +353,32 @@ const formatFileSize = (bytes) => { - - - +
{{ Math.round(uploadProgress) }}%
+ +