From b98c83582b6bbe1128044b14ea3d03e44c3bebd2 Mon Sep 17 00:00:00 2001 From: Audit Bot Date: Mon, 29 Jun 2026 22:11:22 +0800 Subject: [PATCH 1/3] =?UTF-8?q?fix:=20=E4=B8=8A=E4=BC=A0=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E5=A4=A7=E5=B0=8F=E9=99=90=E5=88=B6=E4=BB=8E=20800MB=20?= =?UTF-8?q?=E6=8F=90=E5=8D=87=E8=87=B3=202000MB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit P0 修复: - config.py: OSS_DIRECT_UPLOAD_MAX_MB 默认值从 800 改为 2000 - config.py: 添加 AliasChoices 支持 MAX_UPLOAD_SIZE_MB 环境变量 (.env.production 中 MAX_UPLOAD_SIZE_MB=2000 现在可被正确读取) P1 修复: - infra/nginx/xiaoxia-saas.conf: client_max_body_size 800m → 2g(3处) - infra/docker/nginx-production.conf: client_max_body_size 800m → 2g --- apps/api/app/config.py | 7 +++++-- infra/docker/nginx-production.conf | 2 +- infra/nginx/xiaoxia-saas.conf | 6 +++--- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/apps/api/app/config.py b/apps/api/app/config.py index 811decbcc..00f6c9b92 100755 --- a/apps/api/app/config.py +++ b/apps/api/app/config.py @@ -1,7 +1,7 @@ import os from typing import Optional -from pydantic import field_validator +from pydantic import AliasChoices, Field, field_validator from pydantic_settings import BaseSettings, SettingsConfigDict @@ -83,7 +83,10 @@ class Settings(BaseSettings): OSS_ACCESS_KEY_ID: str = "" OSS_ACCESS_KEY_SECRET: str = "" OSS_BUCKET_NAME: str = "xiaoxia-autocut" - OSS_DIRECT_UPLOAD_MAX_MB: int = 800 + OSS_DIRECT_UPLOAD_MAX_MB: int = Field( + default=2000, + validation_alias=AliasChoices("OSS_DIRECT_UPLOAD_MAX_MB", "MAX_UPLOAD_SIZE_MB"), + ) OSS_DIRECT_UPLOAD_EXPRESS_SECRET: int = 900 LOG_LEVEL: str = "INFO" diff --git a/infra/docker/nginx-production.conf b/infra/docker/nginx-production.conf index 3b07b0be7..c13b9e0a4 100644 --- a/infra/docker/nginx-production.conf +++ b/infra/docker/nginx-production.conf @@ -8,7 +8,7 @@ server { gzip_vary on; gzip_min_length 1024; gzip_types text/plain text/css text/xml text/javascript application/javascript application/json application/xml+rss; - client_max_body_size 800m; + client_max_body_size 2g; location /api/ { proxy_pass http://xiaoxia-api-production:8000/api/; diff --git a/infra/nginx/xiaoxia-saas.conf b/infra/nginx/xiaoxia-saas.conf index c1996de6b..969caf864 100644 --- a/infra/nginx/xiaoxia-saas.conf +++ b/infra/nginx/xiaoxia-saas.conf @@ -28,7 +28,7 @@ server { include /etc/letsencrypt/options-ssl-nginx.conf; ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; - client_max_body_size 800m; + client_max_body_size 2g; location / { proxy_pass http://127.0.0.1:3002/; @@ -51,7 +51,7 @@ server { include /etc/letsencrypt/options-ssl-nginx.conf; ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; - client_max_body_size 800m; + client_max_body_size 2g; location /api/ { proxy_pass http://127.0.0.1:8001; @@ -112,7 +112,7 @@ server { include /etc/letsencrypt/options-ssl-nginx.conf; ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; - client_max_body_size 800m; + client_max_body_size 2g; location / { proxy_pass http://127.0.0.1:3000/; -- 2.54.0 From b5e27a5e9e9871f69e9032f04bd62e63ef3c3a4a Mon Sep 17 00:00:00 2001 From: Audit Bot Date: Mon, 29 Jun 2026 22:13:08 +0800 Subject: [PATCH 2/3] 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

)} -- 2.54.0 From 304354208a319c80cd601cf02bdb2504d79d1e7f Mon Sep 17 00:00:00 2001 From: Audit Bot Date: Tue, 30 Jun 2026 00:07:27 +0800 Subject: [PATCH 3/3] fix(assets): add project_id to OSS direct upload and align size limit to 2048MB P1: prepareDirectUpload and completeDirectUpload now require project_id, uploadAssetDirect calls getOrCreateDefaultProject() to obtain it automatically. P2: MAX_FILE_SIZE changed from 2*1024^3 to 2048*1024*1024 to align with backend OSS_DIRECT_UPLOAD_MAX_MB=2000. --- apps/web/src/api/assets.ts | 7 +++++++ apps/web/src/pages/assets/AssetLibrary.tsx | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/apps/web/src/api/assets.ts b/apps/web/src/api/assets.ts index b95c736fb..d68bd13af 100644 --- a/apps/web/src/api/assets.ts +++ b/apps/web/src/api/assets.ts @@ -154,6 +154,7 @@ export const uploadAsset = async ( /** 预签名直传准备 */ export const prepareDirectUpload = async (data: { + project_id: string; library_id: string; filename: string; content_type: string; @@ -172,6 +173,7 @@ export const prepareDirectUpload = async (data: { /** 直传完成确认 */ export const completeDirectUpload = async (data: { + project_id: string; library_id: string; storage_key: string; }): Promise<{ storage_key: string; ingest_job_id: string }> => { @@ -184,7 +186,11 @@ export const uploadAssetDirect = async (data: { file: File; library_id: string; }): Promise<{ storage_key: string; ingest_job_id: string }> => { + // 后端要求 project_id,前端自动获取默认项目 + const project = await getOrCreateDefaultProject(); + const prepared = await prepareDirectUpload({ + project_id: project.id, library_id: data.library_id, filename: data.file.name, content_type: data.file.type || 'application/octet-stream', @@ -206,6 +212,7 @@ export const uploadAssetDirect = async (data: { } return completeDirectUpload({ + project_id: project.id, library_id: data.library_id, storage_key: prepared.storage_key, }); diff --git a/apps/web/src/pages/assets/AssetLibrary.tsx b/apps/web/src/pages/assets/AssetLibrary.tsx index a11472957..f764f3a1e 100644 --- a/apps/web/src/pages/assets/AssetLibrary.tsx +++ b/apps/web/src/pages/assets/AssetLibrary.tsx @@ -44,8 +44,8 @@ import { getOrCreateDefaultProject } from '@/api/projects'; const { Title, Text } = Typography; const { Dragger } = Upload; -/** 最大文件大小:2GB */ -const MAX_FILE_SIZE = 2 * 1024 * 1024 * 1024; // 2147483648 bytes +/** 最大文件大小:2048MB(与后端 OSS_DIRECT_UPLOAD_MAX_MB=2000 对齐,留少量余量) */ +const MAX_FILE_SIZE = 2048 * 1024 * 1024; // 2147483648 bytes /** 大文件阈值:100MB,超过此值走 OSS 直传通道 */ const LARGE_FILE_THRESHOLD = 100 * 1024 * 1024; // 104857600 bytes -- 2.54.0