fix: 上传文件大小限制从 800MB 提升至 2000MB #117
@@ -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"
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
@@ -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/;
|
||||
|
||||
@@ -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/;
|
||||
|
||||
Reference in New Issue
Block a user