生产环境实践:基于 Docker 二阶段构建的 MinerU + 千问异步处理架构
一、核心问题
这类场景的本质不是“怎么调用 AI”,而是:
如何把 OCR + LLM 这种长耗时链路稳定接入业务系统
典型问题:
- PDF 解析耗时长(分钟级)
- LLM 不稳定(超时 / 输出异常)
- HTTP 不能阻塞
- 任务需要可追踪
二、核心解法:异步化 + 本地执行 + 云模型
整体架构很简单:
1 | 请求 → 落库 → 异步任务 |
三、关键实现(结合代码)
3.1 入口:只触发,不执行
以过程文档接口为例:
1 | pdRouter.POST("/uploadAndParse", pdApi.UploadAndParse) |
设计点:
HTTP 只做:
- 入参校验
- 创建任务
- 返回成功
实际处理放到 goroutine
👉 避免接口阻塞,这是第一原则
3.2 状态控制:防重复执行
审批场景里做了关键控制:
1 | Where(SmartAuditStatusNEQ(InProgress)). |
核心思想:
用数据库保证“只有一个任务在跑”
效果:
- 避免重复触发
- 并发安全(比加锁更可靠)
3.3 异步主流程(核心链路)
以智能审核为例,实际执行逻辑可以抽象为:
1 | go func() { |
这里有三个关键点:
✅ 1. MinerU 用子进程执行
1 | exec.CommandContext(...) |
原因:
- Python 环境隔离
- 不污染 Go 进程
- 可超时控制
✅ 2. LLM 只做“理解”,不做解析
1 | 输入:markdown |
约束模型输出:
- 固定 JSON 格式
- 后端严格解析
👉 避免“模型自由发挥”
✅ 3. DB 写入单独超时
不要用主 ctx:
1 | ctxDB, cancel := context.WithTimeout(context.Background(), 5*time.Second) |
原因:
- 防止 MinerU/LLM 卡死导致状态写不进去
四、Docker 设计(关键)
核心 Dockerfile:
1 | FROM xxx/alpine-builder AS builder |
为什么这么设计?
1️⃣ MinerU 不自己装
直接用基础镜像:
- 避免 Python + OCR 依赖地狱
- 环境统一
2️⃣ Go 只保留二进制
- 镜像小
- 启动快
- 无运行时依赖
3️⃣ 本地执行 MinerU
配置:
1 | mineru_path: /usr/bin/mineru |
👉 容器内路径固定,避免环境差异
六、工程上的几个关键点
6.1 并发控制
- DB 状态控制
- 避免重复执行
五、总结(核心就三点)
这套方案真正有价值的不是“用了 AI”,而是:
1️⃣ 异步化
避免接口阻塞,提升系统稳定性
2️⃣ 本地 + 云分层
- MinerU 本地执行
- 千问云端推理
3️⃣ 容器固化环境
通过 Docker 保证:
- 依赖一致
- 部署简单
一句话总结
用容器封装 OCR,用云模型做理解,用异步任务串联整条链路