From 665228a58f110a5dbc58cfa871aea041654c5b3b Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Thu, 20 Aug 2026 16:22:52 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E5=88=A0=E9=99=A4=20uploadAsset?= =?UTF-8?q?=EF=BC=8C=E7=BB=9F=E4=B8=80=E4=BD=BF=E7=94=A8=20uploadAssetDire?= =?UTF-8?q?ct=20=E7=9B=B4=E4=BC=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 前端: - 删除 uploadAsset 函数(upload.ts)和 export(index.ts) - 删除 UploadResult type export - CloneModal/useCloneSubmit 改用 uploadAssetDirect - DirectUploadCompleteResult 新增 url 字段 - 清理测试文件中 uploadAsset 引用 后端: - DirectUploadCompleteResponse 新增 url 字段 - complete_direct_upload 返回 OSS 文件 URL - 修复 FFmpeg 超时日志 300s → 900s --- apps/api/app/api/routes/upload.py | 3 ++- apps/api/app/schemas/upload.py | 1 + apps/web/src/api/assets/index.ts | 3 +-- apps/web/src/api/assets/types.ts | 1 + apps/web/src/api/assets/upload.ts | 11 +---------- .../src/components/voice/CloneModal/index.tsx | 15 +++++++++++---- .../components/voice/hooks/useCloneSubmit.ts | 14 +++++++------- apps/web/src/test/api/assets.test.ts | 17 ----------------- .../test/components/voice/CloneModal.test.tsx | 4 +++- 9 files changed, 27 insertions(+), 42 deletions(-) diff --git a/apps/api/app/api/routes/upload.py b/apps/api/app/api/routes/upload.py index c67add1eb..687c0db8f 100644 --- a/apps/api/app/api/routes/upload.py +++ b/apps/api/app/api/routes/upload.py @@ -206,6 +206,7 @@ async def complete_direct_upload( ingest_job_id="", duplicated=True, asset_id=existing.id, + url=storage_service.get_url(normalized_key), ) job = _submit_ingest_job( @@ -215,7 +216,7 @@ async def complete_direct_upload( ingest_job_repository=ingest_job_repository, file_hash=request.file_hash, ) - return DirectUploadCompleteResponse(storage_key=normalized_key, ingest_job_id=job.id) + return DirectUploadCompleteResponse(storage_key=normalized_key, ingest_job_id=job.id, url=storage_service.get_url(normalized_key)) @router.post( diff --git a/apps/api/app/schemas/upload.py b/apps/api/app/schemas/upload.py index b9763606c..c6d798288 100644 --- a/apps/api/app/schemas/upload.py +++ b/apps/api/app/schemas/upload.py @@ -39,6 +39,7 @@ class DirectUploadCompleteResponse(BaseModel): ingest_job_id: str duplicated: bool = Field(default=False, description="是否为重复素材(命中去重)") asset_id: str = Field(default="", description="重复素材的 asset_id(duplicated=true 时返回)") + url: str = Field(default="", description="Public URL of uploaded file") class UploadAssetResponse(BaseModel): diff --git a/apps/web/src/api/assets/index.ts b/apps/web/src/api/assets/index.ts index ba460863d..5c536f2c2 100644 --- a/apps/web/src/api/assets/index.ts +++ b/apps/web/src/api/assets/index.ts @@ -13,7 +13,6 @@ export type { ClassificationJob, AssetDiagnosis, BatchOperationResult, - UploadResult, DirectUploadPrepareResult, DirectUploadCompleteResult, } from "./types" @@ -40,7 +39,7 @@ export { } from "./assets" // 上传 -export { uploadAsset, prepareDirectUpload, completeDirectUpload, uploadAssetDirect } from "./upload" +export { prepareDirectUpload, completeDirectUpload, uploadAssetDirect } from "./upload" // 任务 export { getIngestJob, submitClassificationJob, getClassificationJob } from "./jobs" diff --git a/apps/web/src/api/assets/types.ts b/apps/web/src/api/assets/types.ts index 9e64a942e..e98274a18 100644 --- a/apps/web/src/api/assets/types.ts +++ b/apps/web/src/api/assets/types.ts @@ -135,4 +135,5 @@ export interface DirectUploadPrepareResult { export interface DirectUploadCompleteResult { storage_key: string ingest_job_id: string + url: string } diff --git a/apps/web/src/api/assets/upload.ts b/apps/web/src/api/assets/upload.ts index 874861f1f..12304db42 100644 --- a/apps/web/src/api/assets/upload.ts +++ b/apps/web/src/api/assets/upload.ts @@ -3,16 +3,7 @@ */ import apiClient from "../client" import { getOrCreateDefaultProject } from "../projects" -import type { UploadResult, DirectUploadPrepareResult, DirectUploadCompleteResult } from "./types" - -/** 表单上传素材(小文件) */ -export const uploadAsset = async (formData: FormData): Promise => { - const response = await apiClient.post("/upload", formData, { - headers: { "Content-Type": "multipart/form-data" }, - timeout: 30 * 60 * 1000, - }) - return response.data -} +import type { DirectUploadPrepareResult, DirectUploadCompleteResult } from "./types" /** 预签名直传准备 */ export const prepareDirectUpload = async (data: { diff --git a/apps/web/src/components/voice/CloneModal/index.tsx b/apps/web/src/components/voice/CloneModal/index.tsx index 5aab11555..28f2beee9 100644 --- a/apps/web/src/components/voice/CloneModal/index.tsx +++ b/apps/web/src/components/voice/CloneModal/index.tsx @@ -1,7 +1,8 @@ import React, { useState, useCallback, useRef, useEffect } from "react" import { Modal, Button } from "@/components/ui" import { createVoiceClone, toVoiceClone } from "@/api/voice-clone" -import { uploadAsset } from "@/api/assets" +import { uploadAssetDirect, ensureDefaultLibrary } from "@/api/assets" +import { getOrCreateDefaultProject } from "@/api/projects" import { PROGRESS_STEPS, ACCEPTED_MIME } from "./constants" import { validateFile } from "./utils" import { useAudioRecorder } from "./hooks/useAudioRecorder" @@ -181,9 +182,15 @@ const CloneModal: React.FC = ({ open, onClose, onSuccess }) => }) } - const formData = new FormData() - formData.append("file", fileToUpload) - const uploadResult = await uploadAsset(formData) + // 获取默认项目和素材库 + const project = await getOrCreateDefaultProject() + const library = await ensureDefaultLibrary({ project_id: project.id, kind: "voice" }) + + // 直传到 OSS + const uploadResult = await uploadAssetDirect({ + file: fileToUpload, + library_id: library.id, + }) // 组件已卸载则中止后续操作 if (!isMountedRef.current) return diff --git a/apps/web/src/components/voice/hooks/useCloneSubmit.ts b/apps/web/src/components/voice/hooks/useCloneSubmit.ts index f578f5fe2..dd38e52cc 100755 --- a/apps/web/src/components/voice/hooks/useCloneSubmit.ts +++ b/apps/web/src/components/voice/hooks/useCloneSubmit.ts @@ -1,6 +1,6 @@ import { useRef, useCallback, useEffect } from "react" import { createVoiceClone, toVoiceClone } from "@/api/voice-clone" -import { uploadAsset, ensureDefaultLibrary } from "@/api/assets" +import { uploadAssetDirect, ensureDefaultLibrary } from "@/api/assets" import { getOrCreateDefaultProject } from "@/api/projects" import type { VoiceClone } from "@/api/voice-clone" @@ -62,15 +62,15 @@ export function useCloneSubmit({ }) } - // 获取默认项目和素材库(后端 /upload 接口必填) + // 获取默认项目和素材库 const project = await getOrCreateDefaultProject() const library = await ensureDefaultLibrary({ project_id: project.id, kind: "voice" }) - const formData = new FormData() - formData.append("file", fileToUpload) - formData.append("project_id", project.id) - formData.append("library_id", library.id) - const uploadResult = await uploadAsset(formData) + // 直传到 OSS + const uploadResult = await uploadAssetDirect({ + file: fileToUpload, + library_id: library.id, + }) // 阶段 2:克隆 setPhase("cloning") diff --git a/apps/web/src/test/api/assets.test.ts b/apps/web/src/test/api/assets.test.ts index 4745fcdf5..71f5788d7 100644 --- a/apps/web/src/test/api/assets.test.ts +++ b/apps/web/src/test/api/assets.test.ts @@ -10,7 +10,6 @@ import { updateAsset, updateAssetReviewStatus, deleteAsset, - uploadAsset, prepareDirectUpload, completeDirectUpload, uploadAssetDirect, @@ -222,22 +221,6 @@ describe("assets API", () => { }) }) - describe("uploadAsset", () => { - it("should resolve successfully", async () => { - await expect(uploadAsset(new FormData())).resolves.not.toThrow() - }) - - it("should reject on API error", async () => { - mockGet.mockRejectedValue(new Error("Network error")) - mockPost.mockRejectedValue(new Error("Network error")) - mockPut.mockRejectedValue(new Error("Network error")) - mockDelete.mockRejectedValue(new Error("Network error")) - mockPatch.mockRejectedValue(new Error("Network error")) - - await expect(uploadAsset(new FormData())).rejects.toThrow() - }) - }) - describe("prepareDirectUpload", () => { it("should resolve successfully", async () => { await expect(prepareDirectUpload({ name: "test-item" })).resolves.not.toThrow() diff --git a/apps/web/src/test/components/voice/CloneModal.test.tsx b/apps/web/src/test/components/voice/CloneModal.test.tsx index 91847c07c..47fcf8345 100755 --- a/apps/web/src/test/components/voice/CloneModal.test.tsx +++ b/apps/web/src/test/components/voice/CloneModal.test.tsx @@ -10,7 +10,9 @@ vi.mock("@/api/voice-clone", () => ({ })) vi.mock("@/api/assets", () => ({ - uploadAsset: vi.fn(), + uploadAssetDirect: vi + .fn() + .mockResolvedValue({ storage_key: "test", ingest_job_id: "test", url: "http://test" }), })) vi.mock("@/components/ui", () => ({