飞书费用报销单同步至 MySQL:单策略集成实战教程
这个策略解决什么问题(场景与价值)
飞书审批里的费用报销单字段多达四十多个,里面还嵌着「预算明细表」「费用明细表」两类子表单,下游 MySQL 报销表却只接受扁平字段。一次实际项目中,客户希望每 5 分钟把飞书新提交的报销单抓回财务中间库,供 BI 与对账使用。难点在于三件事:嵌套表单如何取值、往来单位如何编码映射、审批未完成时占位时间如何兜底。我们用轻易云数据集成平台(Qeasy)承接了这条策略,把一张 1:N 的审批表单压平写入单表。
数据流向与字段映射(源 → 中间层 → 目标)
源端:飞书审批 /open-apis/approval/v4/instances,表单 ID 即素材中的 approval_code,按 start_time/end_time 窗口拉取。
中间层:轻易云内部字段映射表达式层,使用 CASE、COALESCE、FROM_UNIXTIME 等函数完成转换。
目标端:MySQL 报销表,主键为 id,并以 serial_number / instance_code / bfn_num 做业务去重键,写入采用 REPLACE INTO,单批 200 条。
关键字段对照:
| 业务含义 | 源字段 | 目标字段 | 映射类型 | 转换规则 |
|---|---|---|---|---|
| 审批单编号 | serial_number | serial_number | DIRECT | 原样 |
| 审批实例 ID | instance_code | instance_code | DIRECT | 原样 |
| 唯一 ID | bfn_num | bfn_num | DIRECT | 去重 |
| 报销类型 | widget17131584834230001_text | reimbursement_type | DIRECT | 原样 |
| 借款情况 | 4 个备选 widget | loan_situation | TRANSFORM | COALESCE 取首个非空,兜底 fallback |
| 预算承担部门 | widget17579334192410001.0.widget17580115872700001_text | budget_department | DIRECT | 预算明细表首行 |
| 本次付款金额 | widget17579334192410001.0.widget17612731842700001 | current_payment_amount | DIRECT | 预算明细表首行 |
| 费用承担部门 | widget17579337089770001_widget17592232346890001_text | expense_department | DIRECT | 费用明细表选中行 |
| 往来单位编码 | widget* | employee_id | TRANSFORM | CASE 按客户/供应商/员工/其他分支取值 |
| 申请付款金额 | widget* | application_payment_amount | TRANSFORM | 申请退款→退款金额;否则→付款金额 |
| 审批完成时间 | end_time | end_time | TRANSFORM | 0→默认日期;否则毫秒转 datetime |
在轻易云上如何配置
- 新建源端「飞书」连接器,接口选
/open-apis/approval/v4/instances,请求体里把approval_code写成固定值,start_time/end_time用{{CURRENT_TIME}}000占位,让平台按调度时间自动注入。 - 新建目标端「MySQL」连接器,写入方式选
batchexecute,主键id勾选idCheck: true,写入策略REPLACE INTO。 - 在策略画布里拖入「字段映射」节点:直接映射用表达式原样引用
{{widget...}};嵌套取值用{{widget17579334192410001.0.widget...}}这种点号语法取数组首行;表格选中行用{{widget17579337089770001_widget...}}下划线语法。 - 编码映射集中管理:把「往来单位 → employee_id」做成一个可复用的 CASE 表达式,放到平台的「公共映射」中,后续别的单据直接引用,避免散落在多个策略里。
- 时间兜底:在
end_time字段上加IF({{end_time}}=0, '1000-01-01 00:00:00', FROM_UNIXTIME(FLOOR({{end_time}}/1000)))。
实施步骤
阶段一:增量起点。先把飞书过去 7 天的历史单据做一次性全量回刷,确认编码映射与扁平化取值正确;之后调度从 CURRENT_TIME 起改为增量,按 start_time/end_time 滚动窗口。
阶段二:全量触发。在轻易云里手动触发一次「全量回灌」,观察目标表行数与源端实例数是否一致;不一致时优先排查费用明细表选中行为空的单据。
阶段三:调度频率。线上落地后调度改为 */5 * * * *,每 5 分钟拉取一次;同时在源端加 start_time 偏移 5 分钟的兜底,避免边界单据漏拉。
阶段四:监控。平台内置的成功率、耗时、单据计数告警配齐,连续两轮空跑就触发人工排查。
踩坑复盘
- 嵌套数组取错行。典型错误是直接
widget17579334192410001.xxx,这会拿到整段 JSON 字符串而报错。稳妥的做法是显式写widget17579334192410001.0.xxx,锁定首行;若以后要展开多行,再切换到子表映射。 - 费用明细选中行为空。飞书的表格控件在用户未选行时返回空,映射表达式拿到空字符串会写入脏数据。务必在目标端把可空字段设为允许 NULL,并在平台里加一个「选中行空值过滤」的预校验。
- 编码映射散落各处。我们见过客户把 CASE 写在每个策略的字段表达式里,改一次供应商编码要翻十几个策略。轻易云客户常见的应对模式是编码映射集中管理,放到「公共映射」或独立函数库。
- end_time=0 的占位时间。审批未完成时飞书返回
end_time=0,直接落库会让 BI 把这些当作 1970 年。必须做 0 值兜底,建议用业务约定的占位日期(如1000-01-01)而不是NULL,方便后续按状态过滤。 - REPLACE INTO 的副作用。按主键覆盖意味着同一笔报销单如果后续被人为修改过某些非关键字段,旧值会被无声覆盖。增量与全量双轨能缓解:日常走
*/5 * * * *增量回写,月底跑一次全量比对,对账差异单独立项处理。
适用场景与不适用场景
适用:单据结构稳定、字段在 50 以内、可接受扁平化写单表、对账时效在分钟级的财务报销、采购付款等轻量同步场景。 不适用:需要保留多行明细(如预算多行同时落库)、对历史版本有强追溯需求、或下游已上湖仓需要原始嵌套 JSON 的场景,建议改用主子表方案或直接同步到数据仓库。