文档翻译
文档上传、异步翻译任务、进度轮询、译文 / 原文下载(MinIO 预签名 URL)与删除。
4.1 功能与限制
app.document.supported-formats 配置)
上传限制:单文件 ≤ 50MB(spring.servlet.multipart.max-file-size),
单请求 ≤ 100MB;超限抛 MaxUploadSizeExceededException → HTTP 413 FILE_TOO_LARGE。
解析器按扩展名自动选择:PDF 用 Apache PDFBox 3,Office 系列用 Apache POI 5.2,txt 按行读取。
usedPoints = 页数 × 1 // document-points-per-page = 1,即 1 点 / 页 // 扣费时机:POST /start/{id} 启动翻译时一次性预扣(写入 usedPointsSnapshot) // 点数不足且会员额度不足:HTTP 402 INSUFFICIENT_POINTS,任务不会启动
正常链路:PENDING → PROCESSING → COMPLETED;任一步骤重试后仍失败则置 FAILED(含 errorMessage)。
翻译类型可选 STANDARD 标准翻译 / PROFESSIONAL 专业翻译 / FAST 快速翻译 / ACCURATE 精准翻译。
4.2 接口总览(前缀 /api/document)
4.3 接口详情
/api/document/upload
上传文档到 MinIO 并创建翻译任务记录(此时状态为 PENDING,尚未翻译、尚未扣费)。请求为 multipart/form-data。
| 字段 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
file | File | 必填 | — | 文档文件,扩展名须在 8 种白名单内,≤ 50MB |
sourceLanguage | String | 必填 | — | 源语言(可为 auto) |
targetLanguage | String | 必填 | — | 目标语言 |
translationType | String | 可选 | STANDARD | 翻译类型枚举名 |
translationEngine | String | 可选 | QWEN | 文档翻译默认走通义千问 |
useTerminology | Boolean | 可选 | false | 是否应用个人术语库 |
priority | Integer | 可选 | — | 任务优先级 |
响应 data 为 DocumentTranslation 实体,关键字段:
id、originalFilename、fileType、fileSize、sourceLanguage / targetLanguage、
status(PENDING)、progress、downloadCount、characterCount、createdAt 等。
/api/document/start/{id}
启动异步翻译任务:按页数预扣点数(1 点 / 页),提交至 document.translation.executor 线程池(4 核心 / 8 最大 / 50 队列)后立即返回。
{
"id": 55,
"originalFilename": "产品手册.docx",
"fileSize": 1048576,
"sourceLanguage": "zh",
"targetLanguage": "en",
"translationEngine": "QWEN",
"status": "PROCESSING", // 枚举名
"progress": 0,
"createdAt": "2026-09-06T10:00:00",
"usedPoints": 12, // 本次预扣点数(= 页数),无快照时为 0
"pointsBalance": 88 // 扣减后余额,可为 null
}
/api/document/list
分页查询当前用户的文档翻译任务。响应 data 为 MyBatis-Plus 分页对象(0 起始 page)。
| 参数 | 必填 | 说明 |
|---|---|---|
page | 可选 | 默认 0(0 起始) |
size | 可选 | 每页条数,默认 10 |
/api/document/progress/{id}
轮询翻译进度。前端建议每 2–3 秒调用一次,直至 status 为 COMPLETED 或 FAILED。
{
"documentId": 55,
"taskId": "doc-task-55",
"fileName": "产品手册.docx",
"progress": 65, // 0–100 百分比
"status": "PROCESSING",
"statusMessage": "正在翻译第 8/12 页",
"totalPages": 12,
"processedPages": 8,
"currentPage": 8,
"estimatedCompletionTime": "2026-09-06T10:05:00",
"processingTime": 120000, // 已耗时(毫秒)
"remainingTime": 60000, // 预计剩余(毫秒)
"errorMessage": null,
"qualityScore": null,
"useTerminology": false,
"translationEngine": "QWEN"
}
/api/document/download-url/{id}
获取译文文件的 MinIO 预签名下载链接,有效期 600 秒(内部 60 秒 × 10 次重放)。
{
"url": "http://minio:9002/translation-dev/translated/xxx.docx?X-Amz-...",
"filename": "产品手册_翻译.docx"
}
⚠️ 任务未完成或无译文文件时抛 IllegalArgumentException("翻译结果不存在") → HTTP 400。
链接过期后需重新调用本接口获取。
/api/document/original-url/{id}
获取原始文件的预签名下载链接,结构与 download-url 一致(FileUrlResponse)。
/api/document/download/{id}
由服务端代理,直接以二进制流下载译文文件(响应为 byte[],不经过 ApiResponse 统一包装)。适合无法访问 MinIO 直链的内网环境。
/api/document/delete/{id}
删除文档翻译任务记录。
{ "id": 55, "success": true }
4.4 异步任务生命周期
- 上传 upload
校验格式与大小 → 原文上传 MinIO → 创建
DocumentTranslation记录(PENDING,未扣费)。 - 启动 start
统计总页数 → 按 1 点 / 页预扣点数(快照到
usedPointsSnapshot)→ 状态置 PROCESSING → 提交线程池异步执行。 - 解析与翻译 按扩展名选择解析器提取文本行 → 分批调用翻译引擎(带术语库上下文)→ 单批失败重试,重试仍失败保留原文占位不中断整体任务。
- 回写与上传 译文按原格式回写文档字节(PDFBox / POI)→ 译文上传 MinIO → 状态置 COMPLETED、progress 100。
- 轮询与下载
客户端轮询
progress/{id}至终态 → 调download-url/{id}取 600 秒预签名链接下载,或直接走download/{id}流式下载。
全程接口均为「提交即返回」模式:start 不等待翻译结束,进度只能通过 progress/{id} 轮询获得(本系统无文档级 SSE 推送)。
4.5 完整调用示例
# 1. 上传文档 DOC_ID=$(curl -s -X POST http://localhost:8090/api/document/upload \ -H "Authorization: Bearer $TOKEN" \ -F "file=@./产品手册.docx" \ -F "sourceLanguage=zh" \ -F "targetLanguage=en" \ -F "translationEngine=QWEN" \ | jq -r '.data.id') # 2. 启动翻译(预扣点数) curl -X POST http://localhost:8090/api/document/start/$DOC_ID \ -H "Authorization: Bearer $TOKEN" # 3. 轮询进度直至 COMPLETED / FAILED curl http://localhost:8090/api/document/progress/$DOC_ID \ -H "Authorization: Bearer $TOKEN" # 4. 获取译文下载链接(600 秒有效) curl http://localhost:8090/api/document/download-url/$DOC_ID \ -H "Authorization: Bearer $TOKEN"
async function translateDocument(token, file) { // 1. 上传 const form = new FormData(); form.append('file', file); form.append('sourceLanguage', 'zh'); form.append('targetLanguage', 'en'); const uploaded = await fetch('/api/document/upload', { method: 'POST', headers: { 'Authorization': `Bearer ${token}` }, // 浏览器勿手动设 Content-Type body: form }).then(r => r.json()); const docId = uploaded.data.id; // 2. 启动 await fetch(`/api/document/start/${docId}`, { method: 'POST', headers: { 'Authorization': `Bearer ${token}` } }); // 3. 轮询进度 while (true) { const p = await fetch(`/api/document/progress/${docId}`, { headers: { 'Authorization': `Bearer ${token}` } }).then(r => r.json()); console.log(p.data.progress + '%'); if (['COMPLETED', 'FAILED'].includes(p.data.status)) return p.data; await new Promise(r => setTimeout(r, 3000)); } }