From b5e27a5e9e9871f69e9032f04bd62e63ef3c3a4a Mon Sep 17 00:00:00 2001 From: Audit Bot Date: Mon, 29 Jun 2026 22:13:08 +0800 Subject: [PATCH] feat(assets): add 2GB file size validation and OSS direct upload for large files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add MAX_FILE_SIZE (2GB) validation in handleUpload with friendly error message - Route files >100MB through OSS presigned direct upload (uploadAssetDirect) - Keep small file form upload (uploadAsset) for files <=100MB - Update upload hint text to show '单文件不超过 2GB' limit - Add directUploadMutation with proper loading state tracking Co-Authored-By: Claude Fable 5 --- apps/web/src/pages/assets/AssetLibrary.tsx | 53 ++++++++++++++++++---- 1 file changed, 43 insertions(+), 10 deletions(-) diff --git a/apps/web/src/pages/assets/AssetLibrary.tsx b/apps/web/src/pages/assets/AssetLibrary.tsx index 173f620b4..a11472957 100644 --- a/apps/web/src/pages/assets/AssetLibrary.tsx +++ b/apps/web/src/pages/assets/AssetLibrary.tsx @@ -37,12 +37,18 @@ import { getAssets, deleteAsset, uploadAsset, + uploadAssetDirect, } from '@/api/assets'; import { getOrCreateDefaultProject } from '@/api/projects'; const { Title, Text } = Typography; const { Dragger } = Upload; +/** 最大文件大小:2GB */ +const MAX_FILE_SIZE = 2 * 1024 * 1024 * 1024; // 2147483648 bytes +/** 大文件阈值:100MB,超过此值走 OSS 直传通道 */ +const LARGE_FILE_THRESHOLD = 100 * 1024 * 1024; // 104857600 bytes + /** 素材库类型图标 */ const KindIcon: React.FC<{ kind: string }> = ({ kind }) => { switch (kind) { @@ -99,7 +105,7 @@ const AssetLibrary: React.FC = () => { onError: (err: any) => { if (!err?.__msgShown) message.error('创建失败') }, }); - // 上传素材 + // 上传素材(小文件表单上传) const uploadMutation = useMutation({ mutationFn: uploadAsset, onSuccess: () => { @@ -110,6 +116,17 @@ const AssetLibrary: React.FC = () => { onError: (err: any) => { if (!err?.__msgShown) message.error('上传失败') }, }); + // 大文件直传(OSS 预签名) + const directUploadMutation = useMutation({ + mutationFn: uploadAssetDirect, + onSuccess: () => { + message.success('上传成功'); + queryClient.invalidateQueries({ queryKey: ['assets', activeLibrary] }); + queryClient.invalidateQueries({ queryKey: ['asset-libraries'] }); + }, + onError: (err: any) => { if (!err?.__msgShown) message.error('上传失败') }, + }); + // 删除素材 const deleteMutation = useMutation({ mutationFn: deleteAsset, @@ -127,16 +144,32 @@ const AssetLibrary: React.FC = () => { message.warning('请先选择素材库'); return false; } + + // 文件大小校验:上限 2GB + if (file.size > MAX_FILE_SIZE) { + message.error('文件大小不能超过 2GB'); + return false; + } + setUploading(true); try { - const project = await getOrCreateDefaultProject(); - const formData = new FormData(); - formData.append('file', file); - formData.append('library_id', activeLibrary); - formData.append('project_id', project.id); - await uploadMutation.mutateAsync(formData); + if (file.size > LARGE_FILE_THRESHOLD) { + // 大文件(>100MB)走 OSS 预签名直传通道 + await directUploadMutation.mutateAsync({ + file, + library_id: activeLibrary, + }); + } else { + // 小文件走表单上传 + const project = await getOrCreateDefaultProject(); + const formData = new FormData(); + formData.append('file', file); + formData.append('library_id', activeLibrary); + formData.append('project_id', project.id); + await uploadMutation.mutateAsync(formData); + } } catch { - // uploadMutation.onError 已处理错误提示 + // mutation.onError 已处理错误提示 } finally { setUploading(false); } @@ -216,7 +249,7 @@ const AssetLibrary: React.FC = () => { beforeUpload={handleUpload} showUploadList={false} multiple - disabled={uploading || uploadMutation.isPending} + disabled={uploading || uploadMutation.isPending || directUploadMutation.isPending} style={{ marginBottom: 24 }} >

@@ -226,7 +259,7 @@ const AssetLibrary: React.FC = () => { {uploading ? '正在上传,请稍候...' : '点击或拖拽文件到此区域上传'}

- 支持 {kindLabel[currentLib?.kind || 'video']} 格式文件 + 支持 {kindLabel[currentLib?.kind || 'video']} 格式文件,单文件不超过 2GB

)}