# xiwen-document **Repository Path**: johnnie_walker/xiwen-document ## Basic Information - **Project Name**: xiwen-document - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-11-01 - **Last Updated**: 2026-11-01 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 析文 · 文档智能平台 析文是面向文档的 AI 效率平台:把 PDF、Office、OFD、图片等原件变成可核对、可导出、可调用的结构化数据。它以「解析 → 抽取 → 校对 → 交付」四段能力组织,既能人工逐字段校对,也能通过批量队列与开放接口交给外部系统调用。 上传电子文件并进入文件列表 → 预览原件并按需解析 → 按页、工作表或幻灯片圈定范围 → 套用行业领域模板或交给模型 → 逐字段核对 → 导出 CSV / JSON、回写原件或通过接口交付。 前端 React + Vite,后端 Go + SQLite;桌面版使用 Wails v3 封装同一套前后端能力。每种格式使用自己的解析与预览管线:PDF.js / Docling PDF、Flyfish File Viewer 的 Word / Spreadsheet / Presentation / OFD renderer 等。字段抽取接入 OpenAI 兼容大模型;前后端通过 REST/JSON 通信,生产环境由 Go 单进程同时提供 API 与静态资源。 ## 定位与能力矩阵 文档进入平台后,解析、抽取、校对、交付与对外调用共用同一套文档模型,提取只是平台的一项能力。 | 能力 | 说明 | 入口 | |---|---|---| | 解析 | 上传与解析分离,按格式使用 PDF、Office、OFD、表格、幻灯片与图像专用管线 | 工作台 | | 双引擎抽取 | 模板字段与模型抽取两条路径,未配置模型时自动降级为规则 | 工作台 · 模板中心 | | 逐字段校对 | 置信度、原文证据与版本留存,改完可回写原件 | 工作区 | | 校验规则 | 必填、正则、数值范围、枚举、日期与跨字段关系;错误阻断、警告标记 | 模板编辑 · 工作区 · 提取记录 | | 批量队列 | 解析与提取都显式触发,逐份执行并回看结果 | 工作台 | | 流水线 | 模板 + 校验 + 投递编排,按批运行并记录每次结果 | 流水线 | | 投递中心 | webhook 状态码、耗时、响应留痕,失败可带原载荷重试 | 投递中心 | | 交付与调用 | CSV / JSON 导出、原件回写、开放接口与 Webhook | 提取记录 · 开发者 | ## 技术栈与选型 | 层 | 选型 | 理由 | |---|---|---| | 后端语言 | Go 1.26 | 单二进制、低内存、原生并发,部署无需运行时 | | HTTP 路由 | 标准库 `net/http`(1.22+ 增强路由) | `GET /api/documents/{id}` 这类方法+路径模式已覆盖需求,不引入框架减少依赖与升级成本 | | 数据库 | SQLite(`modernc.org/sqlite`) | 纯 Go 实现,**免 CGO**,Windows 无需 gcc;单文件库便于备份与迁移 | | 数据访问 | `database/sql` + 手写 SQL | 查询简单、可读,避免 ORM 隐藏执行计划;事务显式书写 | | 文档解析 | [`github.com/unitedrhino/docling`](https://github.com/unitedrhino/docling) | 纯 Go 的 Docling 文档模型,覆盖 PDF/Office/EPUB/邮件/CAD,内置 OCR 与视觉钩子,不拖 Python 服务 | | AI 能力 | OpenAI 兼容 chat/completions | 一套客户端同时承担扫描页 OCR、版面视觉识别与字段抽取;未配置 key 时自动降级到规则 | | 前端 | React 18 + Vite 5 | 已有组件资产可直接复用,Vite 代理解决开发期跨域 | | 前后端契约 | REST + JSON | 资源模型清晰、可缓存、易调试;本项目无实时推送与强类型契约需求,gRPC/GraphQL 会带来不必要的工具链负担 | | 桌面端 | Wails v3 + WebView2 | 复用 Go 后端与 React 界面,前端资产和 API 同进程运行,无需另起浏览器服务 | | 部署 | 多阶段 Docker → Alpine | 前端产物内嵌进镜像,单容器对外只需 8080 端口 | **为什么不是 GraphQL / gRPC**:接口数量少、调用关系线性(列表 → 详情 → 提取),GraphQL 的灵活查询与 gRPC 的强类型代码生成收益低于其引入的复杂度。若后续接入多端 SDK 或需要流式提取进度,再引入 SSE 或 gRPC 更合适。 ## 目录结构 ``` index.html Vite 入口 vite.config.js 开发代理 /api → 127.0.0.1:8080 Dockerfile 前端构建 + 后端构建 + 运行时三阶段 docker-compose.yml 一键启动,数据卷持久化 Makefile 常用命令 src/ 前端 App.jsx 数据加载、视图切换、提取编排 lib/api.js 带超时与统一错误结构的 API 客户端 lib/exporters.js CSV 下载 / JSON 复制 components/ 侧栏、总览、工作台、工作区、模板中心、记录、流水线、投递、弹窗 styles/app.css 设计令牌与全部样式 server/ 后端 main.go 配置加载、日志、优雅关闭 internal/config/ 环境变量配置与校验 internal/store/ SQLite 连接、建表、种子数据、查询、流水线与投递 internal/detect/ 内容驱动格式识别与解析器规范化 internal/formatcap/ 格式能力、内容单元、选区与预览声明 internal/ingest/ 独立解析队列与失败状态 internal/parse/ Docling 与 OFD 等格式专用解析适配 internal/extract/ 字段抽取规则(正则优先、标签锚点兜底) internal/ai/ OpenAI 兼容客户端:OCR 钩子 + 结构化字段抽取 internal/validate/ 字段与跨字段校验规则引擎 internal/webhook/ Webhook 推送、签名与状态回执 internal/runtime/ 批处理执行:抽取 → 校验 → 投递 internal/api/ 路由、处理器、中间件、静态资源托管 desktop/ Wails v3 桌面应用 main.go 窗口生命周期、内嵌界面与本地 API 服务 backend.go SQLite、解析器、任务队列与运行时初始化 scripts/ 前端暂存、开发、构建和便携包脚本 build/ 图标与 Windows 版本资源 legacy-prototype.html 最早的单文件原型(对照用) ``` ## 快速开始 需要 Go ≥ 1.26、Node ≥ 20。国内网络建议先设置代理: ```bash go env -w GOPROXY=https://goproxy.cn,direct ``` **1. 启动后端** ```bash cd server go mod download go run . ``` 服务监听 `:8080`,首次启动自动建表并写入 9 个内置模板;文件列表、提取记录、流水线、投递与活动流保持为空。数据库文件位于 `server/data/xiwen.db`。 内置模板:采购合同关键条款、增值税专用发票、医学检验报告、招投标公告、财务报表指标、简历信息解析、党政公文要素、国家标准著录项、**通用内容提取**。 「通用内容提取」不绑定文档类型,从任意文件或图片中抽取标题、摘要、关键信息、日期、金额、联系方式与全文,可作为未知格式的兜底模板。 **模板分内置与自定义两类**。内置模板随二进制发布,只读,每次启动按 `seed.json` 同步(新增的内置模板会自动补齐,字段定义会刷新,不影响你创建的自定义模板)。自定义模板在模板中心的「新建模板」里维护:可定义字段中文名、英文标识、类型(文本/日期/数值/金额/枚举/列表)、定位序号(页/Sheet/Slide)、取值说明与正则规则,还能写一段模板级「抽取指引」约束整体取数口径,也可以把任意内置模板一键复制成自定义模板再改。 **2. 启动前端** ```bash npm install npm run dev ``` 打开 http://localhost:5173 。开发服务器把 `/api` 代理到 `127.0.0.1:8080`,无需配置跨域。 **3. 桌面应用** 需要 PowerShell 7、Wails v3 与 WebView2 Runtime: ```powershell go install github.com/wailsapp/wails/v3/cmd/wails3@v3.0.0 cd desktop wails3 task dev wails3 task build ``` 桌面数据保存在 `%AppData%\Xiwen\data`,可用 `XIWEN_DATA_DIR` 指定其它目录。旧版测试数据会在首次升级启动时归档到 `%AppData%\Xiwen\legacy-data-*`。构建产物为 `desktop/bin/xiwen-desktop.exe`,运行: ```powershell ./desktop/bin/xiwen-desktop.exe ``` **4. 单进程部署** ```bash npm run build # 产出 dist/ cd server && go run . # 自动托管 dist/,访问 http://localhost:8080 ``` ## 平台能力 **解析**(`internal/parse`)通过格式专用适配器共享同一套输出契约,不要求各格式内部遵循相同算法: | 格式 | 说明 | |---|---| | PDF | 文本层 + 页面结构 + 表格;扫描页交给视觉模型 | | DOC / DOCX | 段落、表格、标题层级 | | PPT / PPTX | 每张幻灯片一页;PPTX 由 Go 解析,旧版 PPT 由浏览器预览引擎回填文字与形状边界 | | XLS / XLSX / CSV | 每个工作表一页,保留单元格内容 | | HTML / Markdown / AsciiDoc / EPUB / EML | 正文与标题结构 | | 图片(PNG / JPEG / BMP / WEBP) | 需要 OCR,交给视觉模型 | | DWG / DXF / GLB | 图框文字与三维结构元数据 | 解析结果按内容单元展开成版式块存入 `document_pages`,同时在 `documents.units` 保存真实定位名称:PDF 为页,XLSX 为工作表名,PPTX 为幻灯片序号。`/api/formats` 返回每种格式的解析器、预览器、内容单元和选区能力。没有文本层时标记 `parseStatus: ocr_required`,前端工具栏显示「需 OCR」。 各类开源解析、预览方案、许可证、限制,以及 `flyfish-dev/file-viewer` 的评估结论,见 [`docs/FORMAT_RESEARCH.md`](docs/FORMAT_RESEARCH.md)。 **AI 能力**(`internal/ai`)走 OpenAI 兼容协议,配置 `OPENAI_API_KEY` 后自动启用: 1. **扫描页与疑难版面**:作为 Docling 的 `OCRHook` / `PDFVisualHook`,逐页交给多模态模型识别,超过 `AI_MAX_OCR_PAGES` 的页数交回纯 Go 兜底。 2. **字段抽取**:把选中内容单元(页、工作表或幻灯片)、真实 Sheet 名、模板字段清单、字段类型、字段说明、模板级「抽取指引」以及是否存在框选/单元格范围交给模型,要求只输出 JSON,命中字段置信度记 0.92。提示词会按表格、幻灯片、图片和普通分页文档分别加入格式约束,禁止跨 Sheet、跨 Slide 或越过选区猜测。 思考模型(Qwen3、DeepSeek-R1 等)默认会先输出一大段推理过程再给答案。本项目默认发送 `enable_thinking: false` 关闭它:同一份简历抽取实测从 14.8 秒降到 1.2 秒,字段结果完全一致。若服务端不认识该参数会返回 400,客户端会自动去掉参数重试一次并记住结果,因此换用其它模型也不受影响。 抽取优先级:文档自带已知字段 → 大模型 → 模板正则(0.95)→ 标签锚点(0.8)。模型报错或输出非 JSON 时整体回退到规则,请求不会失败。每条记录保存 `engine`(`ai` / `rule`),前端在结果标题旁标注「AI 抽取」或「规则抽取」。 未配置 API Key 时,解析与抽取全部走规则路径,服务功能完整可用。 ## 校验、流水线与投递 **校验规则**(`internal/validate`)在每次提取与校对后自动执行,结果写回字段与记录: | 规则 | 配置位置 | 说明 | |---|---|---| | 必填 | 字段 `required` | 空值记 error | | 正则 | 字段 `rule` | 不匹配记 error | | 数值范围 | 字段 `min` / `max` | 超出区间记 error | | 枚举 | 字段 `enum` | 不在候选集内记 error | | 跨字段关系 | 模板 `rules` | `equal` / `not_before` / `sum`,不满足记 warning | 字段级 error 会在提取记录中计入阻断数,warning 单独计数;问题随字段修改即时重算,不阻塞既有数据读取。 **流水线**(`internal/store/pipelines.go`)把模板、校验与投递串成一次批处理:选取文档或复用最近记录,按模板重新提取并校验,最后按需投递 webhook。运行结果逐条留存指标(成功、失败、警告、耗时),支持手动触发全部或选中项,暂不提供定时调度。 **投递中心**(`internal/store/deliveries.go` + `internal/webhook`)记录每次 webhook 调用的目标、状态码、耗时与响应摘要;失败条目保留原始载荷,可在界面一键重试。系统设置中的全局 Webhook 与流水线内的独立地址都走同一套留痕。 ## 环境变量 下面的变量是启动时的默认值。大模型参数、上传上限与 Webhook 可以在界面「系统设置」中修改并立即生效,保存到 `data/settings.json`;优先级为界面设置 > 环境变量。监听地址、数据目录、日志格式、任务并发数仍需改环境变量并重启。 | 变量 | 默认值 | 说明 | |---|---|---| | `ADDR` | `:8080` | 监听地址 | | `DB_PATH` | `data/xiwen.db` | SQLite 文件路径 | | `UPLOAD_DIR` | `data/uploads` | 上传文件落盘目录 | | `WEB_DIR` | `dist` | 前端产物目录,不存在则只提供 API | | `CORS_ORIGINS` | `http://localhost:5173,http://127.0.0.1:5173` | 允许的跨域来源,逗号分隔,`*` 表示全部 | | `MAX_UPLOAD_MB` | `50` | 单文件上传上限 | | `WRITE_TIMEOUT_SECONDS` | `300` | 写超时。提取是同步请求,需覆盖模型耗时,不能低于 30 秒 | | `LOG_LEVEL` | `info` | `debug` / `info` / `warn` / `error` | | `LOG_FORMAT` | `text` | `text` 便于本地阅读,`json` 便于日志采集 | | `OPENAI_BASE_URL` | `https://api.openai.com/v1` | OpenAI 兼容服务地址,可指向自建网关或本地模型 | | `OPENAI_API_KEY` | 空 | 配置后启用大模型解析与抽取 | | `AI_MODEL` | `gpt-4o-mini` | 文本抽取模型 | | `AI_VISION_MODEL` | 同 `AI_MODEL` | 图片识别模型,需支持 `image_url`;未配置时复用 `AI_MODEL` | | `JOB_WORKERS` | `1` | 批量队列并发数,取值 1–4 | | `WEBHOOK_URL` | 空 | 任务完成后的推送地址,留空则关闭 | | `WEBHOOK_SECRET` | 空 | 推送签名密钥,设置后附带 `X-Xiwen-Signature` | | `AI_TIMEOUT_SECONDS` | `120` | 单次模型调用超时 | | `AI_MAX_OCR_PAGES` | `12` | 单文档最多交给模型的 OCR 页数 | | `AI_EXTRACT` | `true` | 是否启用大模型字段抽取 | | `AI_THINKING` | `off` | 思考模型推理开关:`off` 关闭、`on` 开启、`auto` 不发送该参数 | ## API 所有接口前缀 `/api`,响应为 JSON;错误统一为 `{"error":{"code","message","requestId"}}`,每个响应带 `X-Request-Id` 便于链路排查。 | 方法 | 路径 | 说明 | |---|---|---| | GET | `/api/health` | 健康检查,含数据库连通性、版本与模型启用状态 | | GET | `/api/formats` | 格式能力矩阵:解析器、预览器、页/Sheet/Slide 单元、选区与限制 | | GET | `/api/settings` | 读取大模型、运行参数与只读运行环境(密钥仅返回掩码) | | PUT | `/api/settings` | 保存设置并热更新,body 同读取结果中的 `values`;`apiKey` 留空表示不修改 | | POST | `/api/settings/test` | 用给定参数测试模型连通性,返回 `{ok,model,latencyMs,message}` | | GET | `/api/documents` | 轻量文档列表(状态、格式、page/Sheet/Slide 数量与 units,不含正文) | | POST | `/api/documents` | 只上传并登记文件(multipart,字段名 `file`);返回 201 和 `parseStatus: uploaded` | | GET | `/api/documents/{id}` | 单份文档详情(含逐单元版式与字段取值) | | POST | `/api/documents/{id}/parse` | 显式开始解析或重新解析;后台执行时返回 202 和 `parseStatus: queued` | | POST | `/api/documents/{id}/browser-parse` | 回填旧版 PPT 预览引擎提取的文字与形状边界 | | GET | `/api/documents/{id}/file` | 读取原件(PDF / 图片直接用于预览,`?download=1` 下载) | | POST | `/api/ocr` | 识别前端裁切的页面区域(multipart,字段名 `file`),返回 `{text}` | | GET | `/api/documents/{id}/crop?x=&y=&w=&h=` | 按归一化坐标裁切图片,返回 PNG | | PUT | `/api/documents/{id}/pages/{page}` | 用识别结果替换某页文字层,body:`{text}` | | GET | `/api/templates` | 模板列表与字段定义(含 `builtin` 标记) | | POST | `/api/templates` | 新建自定义模板,body:`{name,category,docType,desc,guideline,fields[]}` | | PUT | `/api/templates/{id}` | 更新自定义模板(内置模板返回 409) | | DELETE | `/api/templates/{id}` | 删除自定义模板(被提取记录引用时返回 409) | | POST | `/api/templates/{id}/duplicate` | 复制模板为自定义模板 | | GET | `/api/extractions?limit=&offset=&q=&status=&sort=` | 提取记录检索,返回 `{items,total,limit,offset}` | | POST | `/api/extractions/export` | 批量导出 Excel,body:`{ids:[]}` | | GET | `/api/extractions/{id}/revisions` | 字段修改历史 | | PUT | `/api/extractions/{id}/results/{key}/evidence` | 保存字段的归一化坐标,body:`{page,bbox:[x1,y1,x2,y2],origin}` | | POST | `/api/jobs` | 加入批量提取队列 | | GET | `/api/jobs` | 队列列表 | | POST | `/api/jobs/{id}/retry` | 重试失败或取消的任务 | | DELETE | `/api/jobs/{id}` | 取消排队中的任务 | | GET | `/api/templates/{id}/export` | 导出模板 JSON | | POST | `/api/templates/import` | 导入模板 JSON,同名自动加后缀 | | POST | `/api/extractions` | 执行提取,body:`{documentId,templateId,pages,region,pageFrom,pageTo}`;`pages` 为页/Sheet/Slide 序号清单,`region` 为文本、单元格或视觉选区 | | GET | `/api/extractions/{id}` | 单条记录含字段明细 | | PATCH | `/api/extractions/{id}/results/{key}` | 保存校对值,body:`{value}`;改回原值自动取消校对标记 | | DELETE | `/api/extractions/{id}/results/{key}` | 还原为模型原值 | | GET | `/api/extractions/{id}/export` | 导出校对后的原件(保留版式) | | GET | `/api/overview` | 平台总览:文档、记录、问题与投递统计,附最近活动 | | GET | `/api/pipelines` | 流水线列表 | | POST | `/api/pipelines` | 新建流水线,body:`{name,templateId,webhookUrl,failOnError,notifyOnWarning}` | | PUT | `/api/pipelines/{id}` | 更新流水线 | | DELETE | `/api/pipelines/{id}` | 删除流水线 | | POST | `/api/pipelines/{id}/run` | 运行流水线,body:`{documentIds?}`,省略则取最近文档 | | GET | `/api/pipelines/{id}/runs` | 单个流水线的运行记录 | | GET | `/api/pipeline-runs` | 全部运行记录,`?limit=` 控制条数 | | GET | `/api/pipeline-runs/{id}` | 单次运行详情 | | GET | `/api/deliveries` | 投递记录,`?limit=` 控制条数 | | POST | `/api/deliveries/{id}/retry` | 用原载荷重试一次投递 | 状态码:`201` 创建成功、`202` 解析已排队、`204` 删除成功、`400` 参数或范围非法、`404` 资源不存在、`409` 模板只读/文档已在解析、`413` 文件超限、`422` 模板定义或原件不可用、`503` 数据库或解析队列不可用。 ## PDF 预览 PDF 不再拆成文字块重排,而是由浏览器用 PDF.js 按原件渲染:每页绘制到 Canvas,叠加透明文本层。因此字体、分栏、表格、印章、扫描图像都与原件一致,缩放和翻页也不会失真。 - 缩略图是真实页面截图,不是示意条。 - 框选时从文本层取相交的文字,随提取请求提交,服务端只使用框选范围内的文本。 - 扫描版 PDF 没有文本层,框选后自动裁切画面并调用视觉模型识别(`/api/ocr`),识别结果作为该页文本参与抽取。 - 扫描页右下角会出现「识别本页」按钮:整页交给视觉模型识别后写回该页文字层,解析状态同步更新,随后可直接用模板提取;识别失败会明确提示,不会写入空内容。 - 缩放条右侧的「识别空白页」会先扫描全文找出没有文本层的页,再逐页识别并写回,过程中显示进度、可随时取消;已识别的页面不再显示单页按钮。 - 点击提取结果会跳到对应页,并在文本层中高亮该值;定位不到时只跳页,不伪造高亮。 - 页面按可视区域懒加载,长文档不会一次性渲染全部页面。 ## 格式支持 | 格式 | 预览 | 提取 | 说明 | |---|---|---|---| | PDF | 原件渲染 | 支持 | PDF.js + 文本层,扫描页可框选走视觉识别 | | DOCX | 原件渲染 | 支持 | Flyfish File Viewer Word renderer;改名的 OOXML 按真实容器处理 | | WPS 文字(`.wps` / `.wpt`) | 原件渲染 | 支持 | 内容为 OOXML 时自动按 DOCX 解析、预览与回写 | | WPS 表格(`.et` / `.ett`) | 表格网格 | 支持 | 内容为 OOXML 时按 XLSX 解析与回写 | | WPS 演示(`.dps` / `.dpt`) | 幻灯片渲染 | 支持 | 内容为 OOXML 时按 PPTX 解析与回写 | | DOC | 原件渲染 | 支持 | Flyfish File Viewer Word renderer;Go 侧提取旧版 DOC 段落文字 | | XLSX / XLS / CSV | 表格网格 + 单元格选区 | 支持 | Flyfish File Viewer Spreadsheet renderer;按工作表定位,复用虚拟表格原生选区 | | PPT / PPTX | 幻灯片渲染 + 区域选区 | 支持 | Flyfish File Viewer Presentation renderer;旧版 PPT 使用独立二进制引擎,按幻灯片定位并框选文字 | | OFD | OFD 版式渲染 + 文字块选区 | 支持 | Flyfish File Viewer OFD renderer 负责预览,Go 解析 `TextCode` 坐标负责选区与提取;扫描件标记需 OCR | | HTML | 沙箱 iframe | 支持 | 脚本被禁用,避免上传件执行任意代码 | | TXT / Markdown | 文字重排 | 支持 | 按段落切块 | | 图片 | 原图 | 支持 | 视觉模型识别文字后抽取 | PDF、DOCX、DOC、OFD 与文字重排页按文字块框选;表格按单元格范围选择,PPT/PPTX 按幻灯片区域选择,图片按视觉矩形框选。HTML 与无法可靠还原几何的旧版格式只支持整节范围。 DOCX 与 DOC 统一使用 Flyfish File Viewer Word renderer:DOCX 按 OOXML 页面、页眉页脚、表格与编号排版,DOC 使用 Word 风格纸张阅读面;两者均支持搜索、缩放和文本节点框选。Go 后端负责 DOCX/DOC 的结构化文字提取,浏览器负责视觉还原。TXT / Markdown 等纯文本格式保持 HTML 重排。 PDF 预览右上角提供缩放控件(− / 百分比 / + / 适应宽度),工具栏页码改为可直接输入的跳转框,回车或失焦即跳页。 ## 提取范围 三种范围可以组合使用: - **指定内容单元**:对 PDF 是页码,对 XLSX 是 Sheet 序号,对 PPTX 是 Slide 序号;API 仍以统一的 `pages: [1,3,5]` 传递,界面显示真实单元名称。输入 `1,3,5-7` 支持逗号、区间与全角逗号。 - **框选与单元格范围**:PDF、DOCX、OFD 与文字重排页按相交文字块取值;表格拖选单元格后保留行列与制表符结构;PPTX 抽取与选区相交的标题、正文和表格文本。选区所在单元会自动成为当前提取范围。 - **图片文档**:解析时交给视觉模型识别文字(`AI_VISION_MODEL`,未配置时用 `AI_MODEL`),识别结果按行写入版式块;预览仍显示原图。框选图片时会把选区裁成 PNG 交给视觉模型,只提取框内内容,识别失败会明确提示而不是退回整图。 ## 准确率评测 `eval/dataset.json` 定义评测样本:每条包含文件、模板、页码与期望字段值。运行: ```bash make eval # 或 cd server && go run ./cmd/eval -dataset ../eval/dataset.json ``` 输出逐样本的字段命中、准确率与耗时,并给出总体数字;`-min 0.9` 可让准确率低于阈值时以非零状态退出,适合接 CI。`-json` 输出机器可读结果,`-ai` 启用大模型抽取(默认只跑规则引擎,结果稳定可复现)。 基线对比: ```bash make eval-save # 把当前结果存为 eval/baseline.json make eval-check # 与基线对比,出现回退字段时退出码非零 ``` 对比会给出准确率变化,并逐条列出「新命中」「回退」「值变化」的字段。`-save`、`-baseline`、`-fail-on-regression` 也可单独组合使用。 真实样本: ```bash make eval-draft # 扫描 server/data/uploads,按 contract 模板生成草稿数据集 ``` 草稿会把当前提取结果预填为期望值,**必须人工校对后再当基线**,否则只是自证。校对完成后把文件改成 `eval/dataset.json` 或单独用 `-dataset` 指定即可。 当前内置 3 条演示样本,全部命中——但这只证明评测链路可运行,不代表真实文件上的准确率。要得到可信数字,需要把 `eval/fixtures` 换成真实脱敏文件并补充期望值。 `eval/samples/` 下另有 7 份公开真实文件(政府 PDF/OFD、Apache POI 的 DOCX/XLSX/PPTX,以及 `ppt-codec` 生成的两页二进制 PPT),来源与许可见 `eval/samples/manifest.json`,可直接用于解析、预览与草稿生成。 ## 集成与推送 `/api/openapi.json` 提供 OpenAPI 3.1 描述,覆盖文档、模板、提取、任务、校对与导出接口,可直接导入 Postman、Apifox 或生成 SDK。 配置 `WEBHOOK_URL` 后,每个批量任务结束都会向该地址 POST 一条事件: ```json { "event": "job.done", "jobId": "job_xxx", "documentId": "doc_xxx", "documentName": "合同.pdf", "templateId": "contract", "templateName": "采购合同关键条款", "status": "done", "extractionId": "ex_xxx", "fieldCount": 12, "timestamp": "2026-10-31T09:00:58Z" } ``` 失败任务推送 `job.failed`,并带上 `error`。设置 `WEBHOOK_SECRET` 后,请求头会附带 `X-Xiwen-Signature: sha256=`,接收方按同样算法校验即可。推送失败只记日志,不影响任务结果。 ## 上传与解析解耦 上传接口只负责落盘与登记。文件写入磁盘后立即返回 201 和 `parseStatus: uploaded`,同时出现在文件列表;不会隐式提交解析,也不会把用户锁在等待页。已上传文件的 PDF 页数、XLSX 工作表或 PPTX 幻灯片可以先从原件预览中读取。 用户在文件行或工作区点击「开始解析」后,前端调用 `POST /documents/{id}/parse`。服务端用原子状态迁移提交到解析 worker,返回 202 和 `queued`;前端只轮询该文档到 `parsed / partial / ocr_required / unsupported / failed`。排队中或解析中的文档重复提交会返回 409,失败或已完成文档可以显式重新解析。 格式识别按内容判定(ZIP 容器结构、CFB 流名、magic),而不是只看扩展名,因此 `.wps` / `.et` / `.dps` 与改名的 OOXML 都能归到正确解析器。旧版二进制与加密文档也会先进入文件列表,用户触发解析后再得到明确的不支持或加密提示。 ## 批量任务与记录中心 上传支持一次选择多份文件(单次最多 20 份),所有文件上传完成后都进入同一文件列表,不自动解析或抽取。上传过程显示逐文件进度条,可中途取消单个上传;列表支持单份解析和「解析待处理」批量提交。解析完成后,用户再显式选择模板与范围进入提取队列。刷新页面后文档解析状态与提取任务状态仍在。 记录中心支持按文件名或模板搜索、按状态筛选、按时间或命中字段数排序,每页 20 条可翻页,并支持多选后合并导出 Excel。工作簿包含「概览」与「明细」两个工作表:概览一行一条记录,明细按字段展开,带置信度与是否已校对。 ## 模板流通与修订历史 模板可以导出为 JSON 并重新导入,字段类型、说明、抽取指引完整保留;同名导入自动加后缀,不会覆盖现有模板。这为团队之间交换模板、以及后续模板市场打底。 每个命中字段都会保存包含它的原文整行,作为「原文证据」显示在结果下方,并随 Excel 导出。表格拼接出来的值会进一步定位到具体行,显示「第 2 页 · 表格第 1–2 行:…」,不再退回整段文本。 PDF 还会保存字段的归一化坐标:点击结果时,前端在文本层里找到命中片段,算出它在页面上的矩形并写回服务端,同时在页面上画出高亮框。坐标以 0–1 归一化存储,与缩放无关;后续对接方可以直接用这个矩形做二次标注或比对。坐标是在第一次点击时计算并保存的,不是提取瞬间生成——Docling 的纯 Go PDF 解析不返回版面几何。 OFD 则在提取阶段就带坐标:解析 `TextCode` 的 `X` / `Y` / `Size`,结合页面 `PhysicalBox` 归一化后写入结果,不需要点击。文字宽度按字号估算,不是精确字形宽度。 DOCX 的结果会在页面上画出证据框,但坐标只用于当前视图,没有写回服务端——它的视觉分页与解析分页可能不一致,直接存页码会污染来源页码。 PDF 结果行在拿到坐标后会出现「截图」按钮:按页面原始分辨率重新渲染,裁出证据区域并画出边框,导出 PNG。字段没有坐标时按钮不出现,不会导出整页充数。 结果区还有「导出交付包」:把原件、Excel 结果表、结果 JSON 与证据截图打成一个 ZIP。原件导出失败不会中断打包,会在包内附一份说明;证据截图只包含已经生成坐标的 PDF 字段。 字段每次人工修改都会写入修订记录(原值、新值、时间、来源),结果面板里可展开查看;还原操作也会留痕,不会抹掉历史。 ## 校对与原样导出 提取结果支持逐字段人工校对:结果行右侧进入编辑,保存后记录 `edited` 与模型原值,可随时还原。校对值不会另存为新文档,而是**写回上传的原件**。 导出按格式处理,全部使用开源库与标准库,不依赖任何商业组件: | 格式 | 处理方式 | 版式 | |---|---|---| | DOCX | 重写 `word/document.xml`、页眉页脚中的文字节点 | 完全保留字体、段落、表格、编号 | | XLSX | 重写 `sharedStrings.xml`、工作表内联文字与唯一数值单元格 | 保留样式与公式 | | PPTX | 重写幻灯片与备注页的文字节点 | 保留母版与版式 | | TXT / MD / CSV | 文本替换 | 不适用 | | PDF | 未修改时原样返回;有修改时返回 409 并说明原因 | 见下方限制 | 实现上只替换 XML 文本节点,其余 zip 条目与属性原样拷贝,因此不会出现"重新排版"导致的样式丢失。同名字段按出现顺序逐条替换;若模型未识别到某字段而由人工补填,该值没有可定位的原文位置,不会写入原件。 **PDF 的边界**:PDF 是版式文件,没有段落与文本流的概念,改字需要重排引擎。当前不使用商业 SDK,也不引入 OnlyOffice / LibreOffice 这类重型服务,因此对"有修改的 PDF"明确返回 `409 pdf_read_only`,而不是生成一个看似成功、实际错位的文件。未修改的 PDF 可原样导出。 ## 数据库 SQLite 单文件,启用 WAL 与外键约束,启动时幂等建表。 - `documents` / `document_pages` / `document_fields`:文档元信息(含原件路径)、逐页版式块(JSON)、字段取值 - `templates` / `template_fields`:模板与字段定义(内置标记、字段键、类型、取值说明、页码提示、置信度、正则) - `extractions` / `extraction_results`:提取任务与字段明细(含校对标记与模型原值) `server/internal/store/seed.json` 通过 `go:embed` 打包进二进制,每次启动只同步内置模板并按 ID 更新字段定义。示例文档与演示提取仅供测试或显式开发调用,不会写入首次安装的数据目录。 ## 测试 ```bash cd server && go test ./... ``` 覆盖:Docling 解析(TXT / DOCX / 图片)、大模型客户端协议(httptest 打桩,含 JSON 围栏、非 JSON 回退、多模态请求体)、AI 优先与规则兜底、种子数据一致性、页码过滤、非法范围、HTTP 处理器与 CORS 预检。 前端构建校验: ```bash npm run build ``` ## Docker 部署 ```bash docker compose up --build -d ``` 访问 http://localhost:8080 。数据持久化在 `xiwen-data` 卷(`/data/xiwen.db` 与 `/data/uploads`),容器以非 root 用户运行,内置 `wget` 健康检查。 ## 已知边界 1. **大模型需要自备 Key**:未配置 `OPENAI_API_KEY` 时运行在规则模式,功能完整但抽取质量取决于模板规则;扫描件在规则模式下只能标记 `ocr_required`。 2. **OCR 依赖多模态模型**:纯文本模型无法识别扫描页与图片,需选用支持 `image_url` 输入的模型。 3. **单机 SQLite**:适合单实例部署与中小数据量。需要多副本水平扩展时应迁移到 PostgreSQL,`database/sql` 层改动集中在 `internal/store`。 4. **未包含鉴权**:当前面向内网/单租户场景。对外暴露前需加 API Key 或 OIDC 网关,并在 `internal/api/middleware.go` 增加鉴权中间件。 5. **解析与提取使用不同任务粒度**:解析已经异步化并可由 Go worker 恢复排队任务;单次字段提取仍是同步请求,因为它需要把结构化结果即时返回给校对界面。超大模型抽取需要更长写超时,后续可扩展为 SSE 进度流。 ## 开发命令 ```bash make dev-api # 启动后端 make dev-web # 启动前端 make desktop-dev # 启动 Wails v3 桌面开发窗口 make desktop # 构建桌面 EXE make test # 后端测试 make build # 前后端构建 make docker # 容器化启动 ```