编排入口
order-parse-service.ts 负责:加锁 → 建批次 → Gmail → 解析 → 事务写入 → 写日志。
一、Parse 库是什么?
Parse 库不是单独的 npm 包,而是项目里负责「派送表自动解析」的一组模块,核心职责:
| 职责 | 说明 |
|---|---|
| 搜邮件 | 按柜号在 Gmail 中检索 |
| 下附件 | 下载 Excel/CSV 二进制 |
| 解析表 | 动态识别表头、映射字段、标注 warning |
| 入库 | 写 delivery_items、warehouse_summaries、attachments |
| 留痕 | 每步写入 parse_logs,失败可追查 |
| 控并发 | 同一订单/柜号禁止重复 parsing |
入口有两条链路(编排层不同,底层能力复用):
1 | |
二、模块地图
1 | |
文件清单
| 文件 | 角色 | 一句话 |
|---|---|---|
order-parse-service.ts |
编排 A | 从订单出发,创建 containers 记录,走 Gmail 全流程 |
container-parse-service.ts |
编排 B | 从柜号出发,更新 container_parse_meta,支持 Gmail/手动/上传 |
delivery-excel-parser.ts |
解析引擎 | 表头检测、字段映射、warning、仓库汇总计算 |
gmail.ts |
外部 IO | Gmail OAuth、搜邮件、选最优邮件、下载附件 |
batch-no.ts |
批次号 | batch_no = String(containers.id) |
parse-log.ts |
日志层 | safeWriteParseLog 事务外写 parse_logs |
parse-db-error.ts |
错误层 | 事务回滚后的独立日志 + 状态更新 + 分类 + 告警 |
parse-lock.ts |
并发层 | 禁止同一订单/柜号并发 parsing |
parse-result-columns.ts |
展示层 | 解析结果页列定义(与解析流程无直接耦合) |
delivery-items-export.ts |
导出层 | 按原模板格式导出 CSV/XLSX |
三、一次完整解析的生命周期
以 订单检索(链路 A)为例:
1 | |
关键设计原则:
- 重 IO 在事务外:Gmail 搜索、附件下载、Excel 解析都不进
$transaction - 日志在事务外:
parse_logs用safeWriteParseLog,业务回滚不影响失败日志 - 失败必留痕:
handleParseDbWriteFailure默认把containers标为failed
四、核心模块详解
4.1 delivery-excel-parser.ts — 解析引擎
输入:附件 Buffer + 可选文件名
输出:DeliveryParseResult
1 | |
表头检测(detectHeaderRow):
- 扫描前 30 行
- 每行统计命中
HEADER_KEYWORDS的列数 - ≥ 2 个命中 → 认定为表头行
字段映射(FIELD_ALIASES):
| Excel 表头变体 | 数据库字段 |
|---|---|
| 柜号 | container_no |
| SO/客户代码/唛头 | customer_code |
| FBA ID | fba_id |
| Reference ID / PO | reference_id |
| 仓库代码 | warehouse_code |
| 箱数 | carton_count |
Warning 规则:
- 柜号为空 → 仍入库,
is_warning=true,warning="柜号为空" - 仓库代码为空 → 仍入库,追加 warning
- 合计行(含「合计」「总计」「total」)→ 自动跳过
对外 API:
1 | |
4.2 gmail.ts — 邮件与附件
| 函数 | 作用 |
|---|---|
searchEmailsByContainer |
from:发件人 柜号 → fallback 仅 柜号 |
pickBestEmailForParse |
多封邮件时选最优(有附件优先、snippet 更长、附件更大) |
getEmailDetail |
读正文 + 附件列表 |
downloadAttachmentBuffer |
Gmail API 下载 → Buffer |
4.3 batch-no.ts — 批次号
1 | |
同一轮解析中,containers、attachments、delivery_items、parse_logs 共用同一 batch_no,便于在「解析日志」页按批次追溯。
4.4 parse-log.ts — 日志层
两种写入方式:
| 函数 | 使用场景 | 是否在事务内 |
|---|---|---|
writeParseLog(tx, ...) |
短事务内与状态更新绑定(遗留,逐步减少) | ✅ |
safeWriteParseLog(meta) |
推荐:所有解析步骤日志 | ❌ 独立连接 |
标准 step 值:
| step | 含义 |
|---|---|
search_email |
Gmail 搜索 |
check_attachment |
检查是否有可解析附件 |
download_attachment |
下载附件 |
parse_excel |
Excel/CSV 解析 |
save_database |
业务数据入库 |
upload_attachment |
本地上传 |
parse_pipeline |
管线级失败(container 链路) |
status 枚举(log_status):success | failed | warning
查看入口:页面 /parse-logs,API GET /api/v1/parse-logs
4.5 parse-db-error.ts — 错误层
当 Prisma $transaction 抛出异常、业务数据已回滚时,由本模块接手:
1 | |
错误分类与用户提示:
| 分类 | 典型 Prisma 码 | 用户看到 |
|---|---|---|
retryable |
P2034 死锁、P1001 连接失败、timeout | 数据库暂时不可用,请稍后重试 |
business |
P2002 唯一约束 | 数据冲突,请勿重复操作 |
fatal |
其他 | 数据库写入失败,事务已回滚 |
上下文 ctx 字段:
1 | |
4.6 parse-lock.ts — 并发控制
问题:用户连点「检索」,会创建多条 parsing 记录,重复写历史数据。
方案:
1 | |
逻辑:
- 先释放超时(默认 10 分钟)的 stale
parsing→ 自动标failed - 若仍有活跃
parsing→throw Error("正在解析中,请稍后再试")
环境变量:PARSE_LOCK_STALE_MS=600000
4.7 两条编排链路对比
链路 A:order-parse-service |
链路 B:container-parse-service |
|
|---|---|---|
| 触发 | /orders 检索 / 批量检索 |
/cargo-sheet 解析 / 上传 / 选手动附件 |
| 前置 | orders 表有记录 |
cargo_sheet 有柜号 |
| containers | 每次检索 新建 一条 | 复用已有或从 sheet 同步 |
| 批次号 | containers.id |
containers.id |
| 附件存储 | 写入 attachments.file_content |
不存附件(仅解析) |
| 状态表 | 直接写 containers.parse_status |
同时写 container_parse_meta |
| CSV 支持 | ✅ parseDeliveryFileBuffer |
仅 Excel(parseDeliveryExcelBuffer) |
五、事务边界(最重要)
✅ 正确模式
1 | |
❌ 错误模式(已修复)
1 | |
六、历史版本机制(is_history)
重复解析同一柜号时 不物理删除 旧数据:
1 | |
列表、汇总、导出均过滤 is_history = false。
七、如何调试 Parse 问题
7.1 用户报告「检索失败」
- 打开
/containers找对应柜号的parse_status和error_message - 点击「解析日志」→
/parse-logs?containerNo=XXX - 看
step在哪一步failed:search_email→ Gmail 搜不到 / 授权问题parse_excel→ 附件格式/表头问题save_database→ 数据库事务失败(看 message 里的[retryable]等分类)
7.2 用户报告「一直解析中」
- 检查
containers.parse_status = parsing - 可能原因:进程 crash 未更新状态
- 解决:等 10 分钟 stale 自动释放,或手动改
parse_status = failed
7.3 本地复现
1 | |
7.4 告警
配置 webhook 后,以下情况会 POST 到 PARSE_ALERT_WEBHOOK_URL:
parse_log_write_failed— 连失败日志都写不进去container_status_update_failed— 失败后状态更新失败attachment_status_update_failed— 附件状态更新失败
八、扩展指南
新增一个解析步骤
- 在编排层(
order-parse-service或container-parse-service)添加逻辑 - 成功/失败均调用
safeWriteParseLog({ step: "your_step", ... }) - 若涉及 DB 写入,放在
$transaction内,不要在事务内写 log
新增 Excel 列映射
编辑 delivery-excel-parser.ts:
1 | |
新增解析入口(如新 API)
1 | |
九、与环境变量的关系
| 变量 | 影响模块 |
|---|---|
DATABASE_URL |
全部 |
GOOGLE_CLIENT_ID/SECRET |
gmail.ts |
GMAIL_DEFAULT_SENDER |
gmail.ts 默认发件人 |
PARSE_LOCK_STALE_MS |
parse-lock.ts 锁超时 |
PARSE_ALERT_WEBHOOK_URL |
parse-db-error.ts 告警 |
十、一图总结
1 | |
相关文档:交付文档 · Gmail 检索与解析说明
系列导航
| 篇 | 主题 |
|---|---|
| 上一篇 | 04 Excel 解析 |
| 本篇 | 解析 ETL 流水线 |
| 下一篇 | 06 可编辑大表 |
| 索引 | 专栏首页 |