🧾
发票管理系统 invoice-management
发票上传、识别、核验与管理一体化系统。完整流水线:上传 → 按文件 MD5 去重 → 识别 → 自动核验 → 存储 → 列表/详情展示。Vue3 前端 + FastAPI 后端,另提供 RabbitMQ 异步核验对接。
Vue 3 + Vite
FastAPI
motor / MongoDB
MinIO
RabbitMQ
:8000 / :5173
# 一、架构
┌─────────────┐ HTTP/JSON ┌──────────────────┐
│ Vue 3 SPA │ ───────────────────> │ FastAPI 后端 │
│ (Vite + TS) │ <─────────────────── │ │
└─────────────┘ │ ┌────────────┐ │ PARSE_MODE 二选一:
│ │ 识别服务 │──┼──> single : Parse API(:7500)
│ │ 核验服务 │ │ └> two_stage: OCR API → 通义千问 LLM
│ │ 编排服务 │──┼──> 核验 API (chromePa)
│ └────────────┘ │
└───┬──────────┬───┘
│ │
┌──────▼───┐ ┌───▼──────┐
│ MongoDB │ │ MinIO │
│ 元数据/ │ │ 原文件/ │
│ 核验记录 │ │ 缩略图 │
└──────────┘ └──────────┘
后端FastAPI(异步)+ motor(MongoDB)+ MinIO SDK + httpx + Pillow + PyMuPDF(PDF 渲染)+ aio-pika(MQ)
前端Vue 3 + Vite + TypeScript SPA
存储MongoDB 三集合 invoices / verifications / verification_index;MinIO 存原始文件 + 缩略图
MongoDB 用库 invoice、MinIO 用桶 invoice,与舆情系统的 deep-research 库物理同集群但逻辑独立(同一 Mongo/MinIO 实例,不同库/桶)。
# 二、处理流水线
上传文件(图片/PDF)
│ PDF 渲染首页用于识别
▼
按文件 MD5 去重 ── 命中 → 直接返回已有记录
│ 未命中
▼
存 MinIO(原文件 + 缩略图) → 落库 stage=parsing
▼
识别(按 PARSE_MODE 二选一,约 1~2 分钟)
├─ single(当前) : Parse API 一次返回结构化发票
└─ two_stage(回退): OCR API 取原文 → 通义千问 LLM 提结构化
│ parse_success
▼
自动核验(调核验 API)
│
├─ verified=true → 完成
└─ verified=false → 记录失败原因(验证码失败/次数超限/发票无效等)
上传接口立即返回 stage=parsing,识别 + 核验在后台异步进行。识别较慢(LLM 约 1~2 分钟),同步接口客户端超时建议 ≥180s。
# 三、REST 接口
| 方法 | 路径 | 说明 |
| POST | /api/invoices/upload | 上传单个发票(图片/PDF),立即返回 stage=parsing,后台识别+核验 |
| GET | /api/invoices?page=1&size=20 | 发票列表(分页,卡片字段) |
| GET | /api/invoices/{id} | 发票详情(识别信息 + 核验记录列表) |
| POST | /api/invoices/{id}/verify | 手动重新核验(追加一条核验记录) |
| POST | /api/invoices/{id}/reparse | 重新识别(失败重试,成功后自动核验) |
| GET | /api/verifications | 按 fpdm/fphm/kprq/kjje 查询核验记录 |
| GET | /api/files/{object} | 后端代理返回 MinIO 文件/缩略图 |
| POST | /api/verify-invoice | 对外开放:上传文件→识别→核验,返回核验原始 JSON(无需鉴权) |
| POST | /api/verify-invoice-url | 对外开放:按 URL 下载文件走完整核验,返回结果(附带 url) |
对外接口响应(/api/verify-invoice)
{
"success": true, // 识别且核验均通过
"parse_success": true, // 识别是否成功
"verified": true, // 核验是否通过
"invoice_id": "6a21...",
"verify_result": { ... }, // 核验接口完整原始 JSON(失败为 null)
"error": "" // 失败原因
}
健康检查 GET /api/health。接口前缀统一 /api,后端会自动挂载 frontend/dist/ 提供 SPA。
# 四、RabbitMQ 异步核验对接
除 REST 外,系统提供 MQ 方式做批量/异步核验。两个队列:
| 队列名 | 方向 | 说明 |
invoice.verify.queue | 调用方 → 本系统 | 投递待核验消息 { "url": "..." } |
invoice.verify.result.queue | 本系统 → 调用方 | 投递核验结果 |
结果消息关键字段
url原样返回请求的 url,用于对应
success最终是否成功(识别成功且核验通过才 true)
verified / parse_success核验是否通过 / 识别是否成功
invoice_category发票类型(如"电子发票(增值税专用发票)")
verify_result核验接口原始返回
error失败原因
失败分类与重试
| 场景 | error 文案 | 自动重试 |
| 成功 | "" | — |
| 文件下载不了 | 文件不可用 | 否 |
| 文件识别失败 | 文件识别错误 | 否 |
| 核验没通过(技术性失败) | 核验失败 | 是 |
| 发票无效(税务判定) | 税务返回的具体原因 | 否 |
保证必达 + 幂等:每条能解析出 url 的消息最终一定有结果。核验失败 会在工作日 8:00–18:00 每 10 分钟自动重跑(上限默认 6 次),同一 url 可能收到多条结果,以 success=true 或最新一条为准。下游需按 url 关联并做幂等。
# 五、外部依赖
下列为系统外部的第三方服务(图谱中标「外部依赖」角标),地址由 .env 配置:
识别走双模式二选一(PARSE_MODE 开关,入口 parse_service.parse_invoice 分发):single(当前 .env 与代码默认)只调 Parse API 一次返回结构化;two_stage(可回退)才走 OCR API → 通义千问 LLM。所以当前实际生效的只有 Parse API,OCR API 与 LLM 是备用分支,不同时调用。
| 依赖 | 配置项 / 地址 | 用途 | 当前是否启用 |
| Parse 识别 API | PARSE_API_URL · :7500/api/v1/parse | 单段式:一次返回结构化发票 | ✅ single 模式(当前) |
| OCR API | OCR_API_URL · sino-bridge.com:18012/ocr | 两段式第一步:取原文 | ⏸ 仅 two_stage 回退 |
| LLM(通义千问) | LLM_BASE_URL · 阿里云 DashScope · qwen | 两段式第二步:OCR 后提结构化 | ⏸ 仅 two_stage 回退 |
| 核验 API | VERIFY_API_URL · sino-bridge.com/chromePa/invoice_verification | 发票查验,路径经由采集端 chrome-pa | ✅ 启用 |
| MinIO 外网入口 | OSS_PUBLIC_ENDPOINT · sino-bridge.com:82 | 文件对外访问地址 | ✅ 启用 |
| 企微告警 | MONITOR_WEBHOOK_URL · 企业微信机器人 | 服务监控告警(MONITOR_ENABLED 开关) | ✅ 启用 |
为何做成开关:两段式 LLM 对某些票会漏字段(如开票日期空、发票号只截到 11 位)导致必填校验失败;单段式实测能补全,故切 single 并保留 two_stage 可回退。两条分支返回结构统一,编排/核验/前端无感知。
核验 API 的路径带
chromePa,指向
采集后端 暴露的发票查验能力——而采集端在查验税务平台时又依赖
验证码识别服务破解彩色验证码。因此"核验失败"里可能包含"验证码识别失败"这类上游原因。
# 六、部署
运行依赖
| 依赖 | 类别 | 端口 / 标识 | 必需性 | 说明 |
| MongoDB | 数据库 | 27017 · 库 invoice | ✅ 必需 | 发票元数据 + 核验记录(集合 invoices/verifications/verification_index) |
| MinIO | 对象存储 | 9000(控制台 9001)· 桶 invoice | ✅ 必需 | 原始文件 + 缩略图 |
| RabbitMQ | 消息队列 | 5672 · vhost langwell | ⚙ 默认开(MQ_ENABLED=true) | 异步核验:消费 invoice.verify.queue,结果发 invoice.verify.result.queue。连外部已有实例,不在本地 compose 内 |
| Parse 识别 API | 外部服务 | :7500(PARSE_API_URL) | ✅ single 模式必需 | 当前默认 single 模式一次返回结构化发票 |
| 核验 API | 外部服务 | VERIFY_API_URL(/chromePa) | ✅ 必需 | 发票查验,经采集端 chrome-pa |
| OCR API + 通义千问 | 外部服务 | OCR_API_URL / LLM_* | ⏸ 仅 two_stage | 回退模式才用,详见外部依赖 |
本地 docker-compose.yml 只拉起 MongoDB + MinIO 两个容器,不含 RabbitMQ。而后端 MQ_ENABLED 默认 true、会去连外部 RabbitMQ 实例(示例 MQ_HOST=192.168.104.126、vhost langwell)。只跑 REST、不需要 MQ 异步核验时,设 MQ_ENABLED=false 即可跳过消费者启动。
启动步骤
# 1. 启动本地基础设施(仅 MongoDB + MinIO,RabbitMQ 连外部)
docker compose up -d
# MongoDB :27017 MinIO :9000(控制台 :9001,minioadmin/minioadmin)
# 2. 后端
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
cp config/config.example.env .env # 按需改;连外部 Mongo/MinIO/MQ 时填各自地址与凭据
python -m app.main # http://localhost:8000,前缀 /api
# 3. 前端
cd frontend && npm install && npm run dev # http://localhost:5173,/api 代理到 :8000
npm run build # 产物 frontend/dist/,后端自动挂载
前置:Python 3.10+、Node.js 18+、Docker(本地起 MongoDB + MinIO)。关键 Python 依赖见 requirements.txt:fastapi / uvicorn / motor(MongoDB)/ minio / httpx / pillow / pymupdf(PDF 渲染)/ aio-pika(RabbitMQ)。测试 pytest tests/ -v 用 mongomock + fake MinIO + mock 外部接口,无需真实外部依赖(含 MQ)即可运行。