# 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` 区分** - **允许先用占位实现把联调跑通,再逐步接入真实处理器**