修改task_id生成逻辑

This commit is contained in:
xunbu
2025-07-15 13:55:03 +08:00
parent 8745329d2c
commit 93004e9838
2 changed files with 187 additions and 235 deletions

View File

@@ -6,6 +6,7 @@ import logging
import os
import socket
import time
import uuid
from contextlib import asynccontextmanager, closing
from pathlib import Path
from typing import List, Dict, Any, Optional, Literal, Union
@@ -281,11 +282,11 @@ DocuTranslate 后端服务 API提供文档翻译、状态查询、结果下
**注意**: 所有任务状态都保存在服务进程的内存中,服务重启将导致所有任务信息丢失。
### 主要工作流程:
1. **`POST /service/translate`**: 提交文件和翻译参数,启动一个后台任务,并获取 `task_id`。
2. **`GET /service/status/{{task_id}}`**: 使用 `task_id` 轮询此端点,获取任务的实时状态。
1. **`POST /service/translate`**: 提交文件和翻译参数,启动一个后台任务。服务会自动生成并返回一个唯一的 `task_id`。
2. **`GET /service/status/{{task_id}}`**: 使用获取到的 `task_id` 轮询此端点,获取任务的实时状态。
3. **`GET /service/logs/{{task_id}}`**: (可选) 获取实时的翻译日志。
4. **`GET /service/download/{{task_id}}/{{file_type}}`**: 任务完成后 (当 `download_ready` 为 `true` 时),通过此端点下载结果文件。
5. **`GET /service/download_content/{{task_id}}/{{file_type}}`**: 任务完成后以JSON格式获取文件内容。
5. **`GET /service/content/{{task_id}}/{{file_type}}`**: 任务完成后(当 `download_ready` 为 `true` 时)以JSON格式获取文件内容。
6. **`POST /service/cancel/{{task_id}}`**: (可选) 取消一个正在进行的任务。
7. **`POST /service/release/{{task_id}}`**: (可选) 当任务不再需要时,释放其在服务器上占用的所有资源。
@@ -302,14 +303,9 @@ app.mount("/static", StaticFiles(directory=STATIC_DIR), name="static")
# ===================================================================
# --- Pydantic Models for Service API ---
# --- Pydantic Models for Service API (MODIFIED) ---
# ===================================================================
class TranslateServiceRequest(BaseModel):
task_id: str = Field(
default="0",
description="任务的唯一标识符。用于后续跟踪任务状态和结果。",
examples=["task-b2865b93"]
)
base_url: str = Field(
...,
description="LLM API的基础URL例如 OpenAI, deepseek, 或任何兼容OpenAI的接口。",
@@ -386,7 +382,6 @@ class TranslateServiceRequest(BaseModel):
class Config:
json_schema_extra = {
"example": {
"task_id": "task-b2865b93-85d7-40a8-b118-a61048698585",
"base_url": "https://api.openai.com/v1",
"apikey": "sk-your-api-key-here",
"model_id": "gpt-4o",
@@ -407,7 +402,7 @@ class TranslateServiceRequest(BaseModel):
# ===================================================================
# --- Service Endpoints (/service) ---
# --- Service Endpoints (/service) (MODIFIED) ---
# ===================================================================
@service_router.post(
@@ -417,20 +412,20 @@ class TranslateServiceRequest(BaseModel):
接收一个包含文件内容Base64编码和翻译参数的JSON请求启动一个后台翻译任务。
- **异步处理**: 此端点会立即返回,不会等待翻译完成。
- **任务ID**: 成功启动后,返回任务ID (`task_id`)。
- **任务ID**: 成功启动后,服务会自动生成并返回任务ID (`task_id`)。
- **后续步骤**: 客户端应使用返回的 `task_id` 轮询 `/service/status/{task_id}` 接口来获取任务进度和结果。
""",
responses={
200: {
"description": "翻译任务成功启动。",
"content": {"application/json": {"example": {"task_started": True, "task_id": "task-b2865b93",
"content": {"application/json": {"example": {"task_started": True, "task_id": "b2865b93",
"message": "翻译任务已成功启动,请稍候..."}}}
},
400: {"description": "请求体中的Base64文件内容无效。",
"content": {"application/json": {"example": {"detail": "无效的Base64文件内容: Incorrect padding"}}}},
429: {"description": "同一任务ID已在进行中无法重复提交", "content": {
429: {"description": "服务器内部任务冲突,请重试", "content": {
"application/json": {
"example": {"task_started": False, "message": "任务ID 'task-b2865b93' 正在进行中,请稍后再试。"}}}},
"example": {"task_started": False, "message": "任务ID 'b2865b93' 正在进行中,请稍后再试。"}}}},
500: {"description": "服务器内部错误,导致任务启动失败。",
"content": {
"application/json": {
@@ -440,25 +435,27 @@ class TranslateServiceRequest(BaseModel):
async def service_translate(request: TranslateServiceRequest = Body(..., description="翻译任务的详细参数和文件内容。")):
"""
提交一个文件进行翻译,并启动一个后台任务。
文件内容需以Base64编码。
返回任务ID后续可凭此ID查询状态和下载结果。
文件内容需以Base64编码任务ID将由后端自动生成并返回
后续可凭此ID查询状态和下载结果。
"""
task_id = uuid.uuid4().hex[:8]
try:
file_contents = base64.b64decode(request.file_content)
except (binascii.Error, TypeError) as e:
raise HTTPException(status_code=400, detail=f"无效的Base64文件内容: {e}")
params = request.model_dump(exclude={'file_name', 'file_content', 'task_id'})
params = request.model_dump(exclude={'file_name', 'file_content'})
try:
response_data = await _start_translation_task(
task_id=request.task_id,
task_id=task_id,
params=params,
file_contents=file_contents,
original_filename=request.file_name
)
return JSONResponse(content=response_data)
except HTTPException as e:
# Re-raise as JSONResponse to fit the documented response model
# 重新包装为JSONResponse以匹配文档中的响应模型
if e.status_code == 429:
return JSONResponse(status_code=e.status_code, content={"task_started": False, "message": e.detail})
if e.status_code == 500:
@@ -488,7 +485,7 @@ async def service_translate(request: TranslateServiceRequest = Body(..., descrip
}
)
async def service_cancel_translate(
task_id: str = FastApiPath(..., description="要取消的任务的ID", example="task-b2865b93")):
task_id: str = FastApiPath(..., description="要取消的任务的ID", example="b2865b93")):
"""根据任务ID取消一个正在进行的翻译任务。"""
try:
response_data = _cancel_translation_logic(task_id)
@@ -511,7 +508,7 @@ async def service_cancel_translate(
200: {
"description": "任务资源已成功释放。",
"content": {
"application/json": {"example": {"released": True, "message": "任务 'task-b2865b93' 的资源已释放。"}}
"application/json": {"example": {"released": True, "message": "任务 'b2865b93' 的资源已释放。"}}
}
},
404: {
@@ -522,7 +519,7 @@ async def service_cancel_translate(
}
)
async def service_release_task(
task_id: str = FastApiPath(..., description="要释放资源的任务的ID", example="task-b2865b93")
task_id: str = FastApiPath(..., description="要释放资源的任务的ID", example="b2865b93")
):
"""根据任务ID释放其占用的所有服务器资源。"""
if task_id not in tasks_state:
@@ -575,7 +572,7 @@ async def service_release_task(
"processing": {
"summary": "处理中",
"value": {
"task_id": "task-b2865b93",
"task_id": "b2865b93",
"is_processing": True,
"status_message": "正在翻译: 15/50 块",
"error_flag": False,
@@ -594,7 +591,7 @@ async def service_release_task(
"completed": {
"summary": "已完成",
"value": {
"task_id": "task-b2865b93",
"task_id": "b2865b93",
"is_processing": False,
"status_message": "翻译成功!用时 123.45 秒。",
"error_flag": False,
@@ -604,16 +601,16 @@ async def service_release_task(
"task_start_time": 1678886400.123,
"task_end_time": 1678886523.573,
"downloads": {
"markdown": "/service/download/task-b2865b93/markdown",
"markdown_zip": "/service/download/task-b2865b93/markdown_zip",
"html": "/service/download/task-b2865b93/html"
"markdown": "/service/download/b2865b93/markdown",
"markdown_zip": "/service/download/b2865b93/markdown_zip",
"html": "/service/download/b2865b93/html"
}
}
},
"error": {
"summary": "出错",
"value": {
"task_id": "task-b2865b93",
"task_id": "b2865b93",
"is_processing": False,
"status_message": "翻译过程中发生错误 (用时 45.67 秒): APIConnectionError(...)",
"error_flag": True,
@@ -640,7 +637,7 @@ async def service_release_task(
}
)
async def service_get_status(
task_id: str = FastApiPath(..., description="要查询状态的任务的ID", example="task-b2865b93")):
task_id: str = FastApiPath(..., description="要查询状态的任务的ID", example="b2865b93")):
"""根据任务ID获取任务的当前状态和结果下载链接。"""
task_state = tasks_state.get(task_id)
if not task_state:
@@ -692,7 +689,7 @@ async def service_get_status(
}
)
async def service_get_logs(
task_id: str = FastApiPath(..., description="要获取日志的任务的ID", example="task-b2865b93")):
task_id: str = FastApiPath(..., description="要获取日志的任务的ID", example="b2865b93")):
"""获取指定任务ID自上次查询以来的新日志。"""
if task_id not in tasks_log_queues:
raise HTTPException(status_code=404, detail=f"找不到任务ID '{task_id}' 的日志队列。")
@@ -730,7 +727,7 @@ FileType = Literal["markdown", "markdown_zip", "html"]
}
)
async def service_download_file(
task_id: str = FastApiPath(..., description="已完成任务的ID", example="task-b2865b93"),
task_id: str = FastApiPath(..., description="已完成任务的ID", example="b2865b93"),
file_type: FileType = FastApiPath(..., description="要下载的文件类型。", example="html")
):
"""根据任务ID和文件类型下载翻译结果。"""
@@ -756,7 +753,7 @@ async def service_download_file(
@service_router.get(
"/download_content/{task_id}/{file_type}",
"/content/{task_id}/{file_type}",
summary="下载翻译结果内容 (JSON)",
description="""
根据任务ID和文件类型以JSON格式返回翻译结果的内容。该接口总是返回一个JSON对象。
@@ -778,7 +775,7 @@ async def service_download_file(
"summary": "Markdown 内容",
"value": {
"file_type": "markdown",
"filename": "my_doc_translated.md",
"original_filename": "my_doc.pdf",
"content": "# 标题\n\n这是翻译后的Markdown内容..."
}
},
@@ -786,7 +783,7 @@ async def service_download_file(
"summary": "HTML 内容",
"value": {
"file_type": "html",
"filename": "my_doc_translated.html",
"original_filename": "my_doc.pdf",
"content": "<h1>标题</h1><p>这是翻译后的HTML内容...</p>"
}
},
@@ -794,7 +791,7 @@ async def service_download_file(
"summary": "ZIP 内容 (Base64)",
"value": {
"file_type": "markdown_zip",
"filename": "my_doc_translated.zip",
"filename": "my_doc.pdf",
"content": "UEsDBBQAAAAIA... (base64-encoded string)"
}
}
@@ -808,8 +805,8 @@ async def service_download_file(
},
}
)
async def service_download_content(
task_id: str = FastApiPath(..., description="已完成任务的ID", example="task-b2865b93"),
async def service_content(
task_id: str = FastApiPath(..., description="已完成任务的ID", example="b2865b93"),
file_type: FileType = FastApiPath(..., description="要获取内容的文件类型。", example="html")
):
"""根据任务ID和文件类型以JSON格式返回内容。zip文件会进行Base64编码。"""
@@ -821,10 +818,9 @@ async def service_download_content(
raise HTTPException(status_code=404, detail="内容尚未准备好。")
content_map = {
"markdown": (task_state.get("markdown_content"), f"{task_state['original_filename_stem']}_translated.md"),
"markdown_zip": (task_state.get("markdown_zip_content"),
f"{task_state['original_filename_stem']}_translated.zip"),
"html": (task_state.get("html_content"), f"{task_state['original_filename_stem']}_translated.html"),
"markdown": (task_state.get("markdown_content"), task_state['original_filename']),
"markdown_zip": (task_state.get("markdown_zip_content"),task_state['original_filename']),
"html": (task_state.get("html_content"), task_state['original_filename']),
}
raw_content, filename = content_map.get(file_type, (None, None))
@@ -837,7 +833,7 @@ async def service_download_content(
return JSONResponse(content={
"file_type": file_type,
"filename": filename,
"original_filename": filename,
"content": final_content
})
@@ -871,7 +867,7 @@ async def service_get_engin_list():
responses={
200: {
"description": "成功返回任务ID列表。",
"content": {"application/json": {"example": ["task-b2865b93", "task-another-one", "0"]}}
"content": {"application/json": {"example": ["b2865b93", "f4e2a1c8"]}}
}
}
)
@@ -1021,10 +1017,11 @@ def run_app(port: int | None = None):
if port_to_use != initial_port: print(f"端口 {initial_port} 被占用,将使用端口 {port_to_use} 代替")
print(f"正在启动 DocuTranslate WebUI 版本号:{__version__}")
print(f"请用浏览器访问 http://127.0.0.1:{port_to_use}")
print(f"服务接口文档: http://127.0.0.1:{port_to_use}/docs")
uvicorn.run(app, host=None, port=port_to_use, workers=1)
except Exception as e:
print(f"启动失败: {e}")
if __name__ == "__main__":
run_app()
run_app()