初步完成框架
Some checks failed
Android CI / build (push) Has been cancelled

- 实时图传:WebSocket JPEG 帧发送 + 帧率控制 + PC 浏览器预览
- PDF 上传与处理:上传/处理分离,支持 ocrpdf 和 markdown 两种类型
- MinerU 真实接入:markdown 处理 + images ZIP 打包
- OCRmyPDF 接入:ocrpdf 生成可搜索双层 PDF
- 手机端任务管理面板:轮询状态 + SAF 目录选择下载
- PC 管理面板:/dashboard 文件与任务管理
- 网络层:OkHttp 客户端、WebSocket 图传、局域网发现占位

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
MobKBK
2026-06-04 17:03:18 +08:00
parent dd8002009d
commit 1848a88fcf
72 changed files with 6281 additions and 163 deletions

789
requirements/pc-api-spec.md Normal file
View File

@@ -0,0 +1,789 @@
# FairScan PC 端统一接口规范(草案 v0.1
> 本文档定义 FairScan 手机端与 PC 端之间的最小稳定接口契约。
>
> 适用对象:
>
> - 人工开发者
> - Claude Code
> - 其他 AI 编码代理
>
> 设计目标:
>
> - 让不同执行者都能按同一接口实现,不因上下文差异而跑偏
> - 优先稳定协议与字段,而不是优先绑定具体内部实现
> - 允许 PC 端先做“接口占位实现”,后续再逐步接入真实 MinerU / OCRmyPDF
---
## 1. 设计范围
本文档覆盖以下能力:
1. 局域网服务发现配套信息
2. 健康检查接口
3. 实时图传接口
4. PDF 上传接口
5. 统一处理任务接口
6. 任务状态查询接口
7. 处理产物查询接口
8. 处理产物下载接口
本文档**不**约束以下内容:
- PC 端内部具体使用什么库执行 MinerU
- PC 端内部具体使用什么方式调用 OCRmyPDF
- PC 端图传画面最终是显示在网页、桌面窗口还是其他 UI 中
- Android 端 UI 的具体布局样式
也就是说:
- **本文档约束的是“外部协议”**
- **不强制约束“内部实现”**
---
## 2. 核心原则
### 2.1 图传与文档处理解耦
- 实时图传只负责低延迟画面预览
- 正式文档处理只基于手机本地生成的 PDF
- 图传流不得直接作为 MinerU / OCRmyPDF 的正式输入
### 2.2 统一处理接口
PC 端后处理统一使用一套任务接口。
支持的处理类型:
- `markdown`
- `ocrpdf`
差异只体现在:
- `processType`
- 返回产物的 MIME 类型
### 2.3 手机主动下载结果
“PC 处理后结果回到手机”在工程上定义为:
- 手机查询任务状态
- 手机获取产物列表
- 手机主动下载产物
不要求 PC 主动回连手机进行推送。
### 2.4 允许占位实现
第一阶段允许 PC 端:
- 返回 mock 任务
- 返回 mock 产物
- 先不真正接入 MinerU / OCRmyPDF
只要对外接口契约稳定即可。
---
## 3. 术语定义
### 3.1 File
指手机上传到 PC 的原始 PDF 文件。
### 3.2 Task
指 PC 端异步处理任务。
### 3.3 Artifact
指任务完成后可下载的结果文件。
### 3.4 Primary Artifact
指该处理类型最核心的主产物:
- `markdown` -> `.md`
- `ocrpdf` -> `.pdf`
### 3.5 Auxiliary Artifact
指附加产物,例如:
- 资源图片
- 日志文件
- JSON 中间结果
- 识别报告
---
## 4. 协议总览
| 能力 | 方法 | 路径 | 说明 |
|---|---|---|---|
| 健康检查 | GET | `/health` | 检查服务可用性与能力 |
| 实时图传 | WS | `/stream` | 接收手机实时图像帧 |
| 上传 PDF | POST | `/upload/pdf` | 上传正式文档 PDF |
| 创建处理任务 | POST | `/tasks/process` | 发起统一处理任务 |
| 查询任务状态 | GET | `/tasks/{taskId}` | 查询任务执行状态 |
| 查询任务产物 | GET | `/tasks/{taskId}/artifacts` | 获取结果文件列表 |
| 下载产物 | GET | `/artifacts/{artifactId}/download` | 下载结果文件 |
| 下载原始文件 | GET | `/files/{fileId}/download` | 下载已上传的原始 PDF |
默认基础地址示例:
```text
http://{host}:{port}
```
例如:
```text
http://192.168.1.10:8080
```
---
## 5. 通用约定
### 5.1 编码与格式
- JSON 请求与响应统一使用 UTF-8
- 除下载接口外,默认返回 `application/json`
- 图传 WebSocket 使用二进制消息承载 JPEG 帧
### 5.2 ID 规则
以下字段都视为**不透明字符串**
- `fileId`
- `taskId`
- `artifactId`
客户端不得依赖这些 ID 的内部结构。
### 5.3 时间字段
如果服务返回时间字段,建议使用 RFC 3339 / ISO 8601例如
```text
2026-06-04T12:34:56Z
```
时间字段不是第一阶段强制要求,但如果提供,应统一格式。
### 5.4 状态枚举
任务状态建议使用以下枚举:
```text
queued
running
completed
failed
```
如后续需要,可扩展:
```text
canceled
```
### 5.5 错误返回格式
推荐所有错误统一返回:
```json
{
"error": {
"code": "INVALID_REQUEST",
"message": "processType is required"
}
}
```
推荐错误码:
- `INVALID_REQUEST`
- `UNSUPPORTED_PROCESS_TYPE`
- `FILE_NOT_FOUND`
- `TASK_NOT_FOUND`
- `ARTIFACT_NOT_FOUND`
- `PROCESSING_FAILED`
- `SERVICE_UNAVAILABLE`
### 5.6 版本兼容原则
- 第一阶段不强制引入 `/api/v1` 路径前缀
- 通过 `apiVersion` 字段表达协议版本
- 后续如需重大变更,再评估路径版本化
---
## 6. 局域网发现配套约定
### 6.1 mDNS 服务标识
- service type`_fairscan._tcp`
- service instance name`FairScan-PC-{deviceName}`
### 6.2 推荐 TXT Record 字段
- `name`:设备显示名
- `features``stream,upload,process,download`
- `apiVersion`:如 `1`
- `version`PC 服务版本
### 6.3 关于 `process` 能力
这里建议广播能力使用:
- `process`
而不是直接广播多个内部工具名。
原因:
- 发现层只需表达“能不能处理”
- 具体支持哪些 `processType`,可通过 `/health` 返回
- 这样后续新增其他处理器时不需要修改发现层语义
---
## 7. 健康检查接口
## 7.1 GET `/health`
### 作用
- 判断服务是否在线
- 返回最小能力信息
- 返回支持的处理类型
### 请求
无请求体。
### 成功响应示例
```json
{
"name": "FairScan-PC-Office",
"status": "ok",
"version": "0.1.0",
"apiVersion": "1",
"features": ["stream", "upload", "process", "download"],
"processTypes": ["markdown", "ocrpdf"]
}
```
### 字段说明
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `name` | string | 是 | 设备显示名 |
| `status` | string | 是 | 固定为 `ok` |
| `version` | string | 否 | PC 服务版本 |
| `apiVersion` | string | 是 | 接口版本 |
| `features` | string[] | 是 | 服务能力 |
| `processTypes` | string[] | 否 | 当前支持的处理类型 |
### 状态码
- `200 OK`
---
## 8. 实时图传接口
## 8.1 WS `/stream`
### 作用
接收手机端发送的实时画面帧。
### 连接方式
- 客户端发起 WebSocket 连接
- 连接成功后开始发送二进制帧
- 每条二进制消息代表**一张完整 JPEG 图像**
### 帧格式
- 二进制消息
- 内容JPEG 文件完整字节流
- 一条消息 = 一帧
### 服务端要求
- 服务端可只保留最新帧
- 服务端不要求逐帧确认
- 服务端允许丢弃旧帧以保证实时性
### 客户端要求
- 不得无限积压待发送帧
- 若上一帧尚未发完,允许直接丢弃当前帧
- 连接断开后由客户端自行决定是否重连
### 第一阶段最小可接受行为
- 服务端只需能接收 JPEG 帧并显示或缓存最新一帧
- 不要求复杂多端会话管理
- 不要求录像、回放、时间轴等高级功能
### 状态码
- WebSocket Upgrade 成功即视为可用
---
## 9. PDF 上传接口
## 9.1 POST `/upload/pdf`
### 作用
上传手机端正式生成的 PDF 文件。
### 请求类型
```text
multipart/form-data
```
### 表单字段
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `file` | file | 是 | PDF 文件 |
### 约束
- `file` 的 MIME 类型应为 `application/pdf`
- 服务端可根据需要限制上传大小
- 若文件过大,建议返回 `413 Payload Too Large`
### 成功响应示例
```json
{
"fileId": "file-123",
"fileName": "Scan 2026-06-04 12.34.56.pdf",
"mimeType": "application/pdf",
"sizeBytes": 1048576
}
```
### 字段说明
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `fileId` | string | 是 | 服务端文件标识 |
| `fileName` | string | 是 | 保存后的文件名 |
| `mimeType` | string | 是 | 固定为 `application/pdf` |
| `sizeBytes` | number | 是 | 文件字节大小 |
### 状态码
- `201 Created`
- `400 Bad Request`
- `413 Payload Too Large`
- `500 Internal Server Error`
---
## 10. 统一处理任务接口
## 10.1 POST `/tasks/process`
### 作用
使用统一接口发起后处理任务。
### 请求示例
```json
{
"fileId": "file-123",
"processType": "markdown",
"options": {}
}
```
### 请求字段说明
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `fileId` | string | 是 | 由上传接口返回的文件标识 |
| `processType` | string | 是 | `markdown``ocrpdf` |
| `options` | object | 否 | 预留扩展字段,首版可为空对象 |
### 处理类型定义
| `processType` | 含义 | 预期主产物 |
|---|---|---|
| `markdown` | 执行 Markdown 转换 | `text/markdown` |
| `ocrpdf` | 执行 OCR PDF 处理 | `application/pdf` |
### 成功响应示例
```json
{
"taskId": "task-123",
"status": "queued",
"processType": "markdown",
"fileId": "file-123"
}
```
### 状态码
- `202 Accepted`
- `400 Bad Request`
- `404 Not Found``fileId` 不存在)
- `422 Unprocessable Entity``processType` 不支持时可选)
- `500 Internal Server Error`
### 第一阶段占位实现要求
如果真实 MinerU / OCRmyPDF 尚未接入,允许这样实现:
- 接口正常收请求
- 正常返回 `taskId`
- 任务状态可直接从 `queued` -> `completed`
- 产物可先返回 mock 文件或占位文件
这样做的目标是:
- 先稳定客户端协议
- 先打通 Android 联调链路
- 后续再逐步替换成真实处理器
---
## 11. 查询任务状态接口
## 11.1 GET `/tasks/{taskId}`
### 作用
返回单个任务的当前状态。
### 成功响应示例
```json
{
"taskId": "task-123",
"status": "running",
"processType": "markdown",
"fileId": "file-123",
"progress": 50,
"message": "processing",
"artifactsAvailable": false
}
```
### 字段说明
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `taskId` | string | 是 | 任务标识 |
| `status` | string | 是 | `queued` / `running` / `completed` / `failed` |
| `processType` | string | 是 | 任务处理类型 |
| `fileId` | string | 是 | 输入文件标识 |
| `progress` | number | 否 | 建议 0~100 |
| `message` | string | 否 | 当前状态说明 |
| `artifactsAvailable` | boolean | 否 | 是否已有可下载产物 |
### 失败响应示例
```json
{
"error": {
"code": "TASK_NOT_FOUND",
"message": "task not found"
}
}
```
### 状态码
- `200 OK`
- `404 Not Found`
---
## 12. 查询任务产物接口
## 12.1 GET `/tasks/{taskId}/artifacts`
### 作用
列出某个任务已经生成的所有产物。
### 成功响应示例
#### Markdown 任务示例
```json
[
{
"artifactId": "artifact-1",
"fileName": "result.md",
"mimeType": "text/markdown",
"role": "primary",
"sizeBytes": 2048
}
]
```
#### OCR PDF 任务示例
```json
[
{
"artifactId": "artifact-2",
"fileName": "result.pdf",
"mimeType": "application/pdf",
"role": "primary",
"sizeBytes": 3145728
}
]
```
### 字段说明
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `artifactId` | string | 是 | 产物标识 |
| `fileName` | string | 是 | 文件名 |
| `mimeType` | string | 是 | MIME 类型 |
| `role` | string | 是 | `primary` / `auxiliary` / `log` |
| `sizeBytes` | number | 否 | 文件大小 |
### 约束
-`markdown`,应至少存在一个 `role=primary``mimeType=text/markdown` 的产物
-`ocrpdf`,应至少存在一个 `role=primary``mimeType=application/pdf` 的产物
### 状态码
- `200 OK`
- `404 Not Found`
---
## 13. 产物下载接口
## 13.1 GET `/artifacts/{artifactId}/download`
### 作用
下载指定产物文件。
### 响应
- 响应体为二进制文件流
- `Content-Type` 应与产物 `mimeType` 一致
- `Content-Disposition` 建议包含文件名
### 成功行为示例
- 下载 Markdown`Content-Type: text/markdown`
- 下载 OCR PDF`Content-Type: application/pdf`
### 状态码
- `200 OK`
- `404 Not Found`
---
## 14. 原始文件下载接口
## 14.1 GET `/files/{fileId}/download`
### 作用
下载已上传但尚未处理的原始 PDF 文件。
### 响应
- 响应体为二进制文件流
- `Content-Type: application/pdf`
- `Content-Disposition` 包含原始文件名
### 典型用途
- PC 管理面板中直接下载查看手机上传的原始 PDF
- 手机端重新获取已上传的文件
### 状态码
- `200 OK`
- `404 Not Found`(文件 ID 不存在或文件已从磁盘删除)
---
## 15. 两类处理任务的差异说明
## 15.1 `processType=markdown`
### 目标
把 PDF 处理为 Markdown 文档。
### 最低要求
- 至少返回一个 `.md` 主产物
### 可选附加产物
- 图片资源
- 日志
- JSON 中间结果
## 15.2 `processType=ocrpdf`
### 目标
把 PDF 处理为 OCR 后的可搜索 PDF。
### 最低要求
- 至少返回一个 `.pdf` 主产物
### 可选附加产物
- 日志
- 识别报告
---
## 16. 典型调用流程
## 16.1 实时图传流程
1. 手机发现并选择 PC 主机
2. 手机调用 `/health` 确认支持 `stream`
3. 手机建立 `WS /stream`
4. 手机按抽帧策略发送 JPEG 帧
5. PC 实时显示最新帧
## 16.2 文档处理流程
1. 手机本地生成 PDF
2. 手机 `POST /upload/pdf`
3. 手机获得 `fileId`
4. 手机 `POST /tasks/process`
5. 手机获得 `taskId`
6. 手机轮询 `GET /tasks/{taskId}`
7. 任务完成后,手机调用 `GET /tasks/{taskId}/artifacts`
8. 手机调用 `GET /artifacts/{artifactId}/download`
9. 手机保存、打开或分享结果
---
## 17. 第一阶段可接受的占位实现
如果当前目标只是让 Android 端和 PC 端先联调通,这一阶段允许:
### 17.1 `markdown` 占位实现
- 收到 `processType=markdown`
- 直接生成一个示例 `.md` 文件
- 任务短时间内进入 `completed`
### 17.2 `ocrpdf` 占位实现
- 收到 `processType=ocrpdf`
- 直接复制输入 PDF 为新文件,或生成一个占位 PDF
- 任务短时间内进入 `completed`
### 17.3 为什么允许这样做
这样可以先验证:
- 接口字段是否稳定
- Android 端状态流是否完整
- 下载逻辑是否可用
- 不同 `mimeType` 的本地处理是否正确
等这些都稳定后,再接入真实处理器更安全。
---
## 18. 对执行者的约束说明
本节适用于任何执行这份接口文档的人或 AI。
### 18.1 必须遵守的约束
- 不要为 `markdown``ocrpdf` 设计两套独立任务协议
- 不要让 Android 端依赖 PC 内部执行器实现细节
- 不要把图传流直接作为正式文档处理输入
- 不要把“结果回到手机”实现成 PC 主动推送手机的唯一方式
### 18.2 优先级建议
如果执行资源有限,优先实现:
1. `/health`
2. `/upload/pdf`
3. `/tasks/process`
4. `/tasks/{id}`
5. `/tasks/{id}/artifacts`
6. `/artifacts/{artifactId}/download`
7. `WS /stream`
说明:
- 如果当前主要目标是联调文档处理链路,可先暂缓图传 UI
- 如果当前主要目标是实时性验证,可先实现 `WS /stream`
- 但无论如何,统一处理接口契约应保持不变
---
## 19. 与 Android 端实现的对应关系
PC 接口与 Android 模块建议对应如下:
| PC 接口 | Android 模块 |
|---|---|
| `/health` | discovery / server endpoint |
| `WS /stream` | stream client |
| `/upload/pdf` | upload client |
| `/tasks/process` | task client |
| `/tasks/{id}` | task polling logic |
| `/tasks/{id}/artifacts` | artifact query logic |
| `/artifacts/{artifactId}/download` | artifact download client |
| `/files/{fileId}/download` | raw file download client |
---
## 20. 后续扩展预留
后续如果需要扩展,可在不破坏主契约的情况下增加:
- 更多 `processType`
- 更多 `options` 字段
- 任务取消接口
- 批量任务接口
- 任务日志查询接口
- 结果 ZIP 打包下载接口
但第一阶段不建议过早加入这些扩展。
---
## 21. 一句话总结
这份接口规范的核心思想是:
- **实时图传走一条轻量、低延迟链路**
- **文档处理走一条统一任务接口链路**
- **MinerU 与 OCRmyPDF 共用同一处理协议,只通过 `processType` 区分**
- **允许先用占位实现把联调跑通,再逐步接入真实处理器**