fix: 上传文件大小限制从 800MB 提升至 2000MB #117

Merged
xiaoxia merged 3 commits from fix/upload-size-limit-2000mb into develop 2026-06-30 07:50:46 +08:00
5 changed files with 59 additions and 16 deletions
+5 -2
View File
@@ -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"
+7
View File
@@ -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,
});
+43 -10
View File
@@ -37,12 +37,18 @@ import {
getAssets,
deleteAsset,
uploadAsset,
uploadAssetDirect,
} from '@/api/assets';
import { getOrCreateDefaultProject } from '@/api/projects';
const { Title, Text } = Typography;
const { Dragger } = Upload;
/** 最大文件大小: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
/** 素材库类型图标 */
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 }}
>
<p className="ant-upload-drag-icon">
@@ -226,7 +259,7 @@ const AssetLibrary: React.FC = () => {
{uploading ? '正在上传,请稍候...' : '点击或拖拽文件到此区域上传'}
</p>
<p className="ant-upload-hint">
{kindLabel[currentLib?.kind || 'video']}
{kindLabel[currentLib?.kind || 'video']} 2GB
</p>
</Dragger>
)}
+1 -1
View File
@@ -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/;
+3 -3
View File
@@ -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/;