飞书审批重复到达,如何只创建一条招聘需求
幂等键解决重复建单,但不能自动修复后续关联失败。沿着并发、唯一约束、事务边界与重试恢复,分析审批联动的可靠性。
ERP 把已通过的飞书招聘审批转换为招聘需求。同步任务重试、重复事件和并发处理,都可能让同一份审批多次进入建单逻辑。目标是让这些处理最终关联到同一条招聘需求,并在中途失败后能够恢复。
我参与系统建设,负责招聘模块的功能迭代,并独立负责审批模块。下面围绕审批建单链路说明问题、方案和取舍。
问题:先查询,再创建,仍然可能重复
假设两个处理任务同时收到审批 A。任务一查询,没有对应岗位;任务二也查询,没有对应岗位。随后两边各创建一条记录。即使每次创建前都查询,两个查询之间仍存在并发窗口。
另一个情况更隐蔽:招聘需求已经创建,但更新审批过程记录时失败。下次同步看到审批仍未完成,如果只检查过程状态,就可能再次建单。
因此,需要一种跨重试保持稳定的业务标识,以及数据库层面的约束。
方案:稳定请求键与唯一约束共同兜底
处理首先检查审批状态与启用时间规则。符合条件的已通过审批,才进入自动建单。接着匹配申请人、部门并校验字段,使用审批实例标识生成确定性的请求键。
数据库对“创建人 + 请求键”设置复合唯一约束。相同创建人与相同审批实例派生的键,在重复处理时保持一致。
处理链路可以概括为:
- 已成功处理且关联了岗位,直接返回已有结果。
- 生成稳定请求键,在事务前和事务内查询已有岗位。
- 在事务中创建岗位、协同关系与审计记录。
- 如果出现预期的唯一约束冲突,按相同业务键读回已有岗位。
- 更新审批过程状态,关联到这条岗位记录。
前置查询减少重复工作的成本;数据库唯一约束负责抵御并发竞争。冲突后读回,让并发请求最终拿到同一个业务结果,而不是把其中一个请求简单地显示为失败。
恢复:岗位创建成功,过程关联失败
招聘需求创建与审批过程更新没有放进同一个大事务。这留下了一个中间窗口:业务记录已经存在,但审批过程还没有关联它。
恢复时再次使用原来的请求键查询。找到已有需求后,就补全过程记录的关联,不再创建第二条需求。这里的关键是保留“重试同一份审批”的身份,而不是每次请求都随机生成新键。
这也是把业务状态和页面提示区分开的原因。页面显示处理失败,并不必然意味着数据库里没有产生任何业务记录;需要按稳定键检查后,再决定继续哪一步。
失败类型不同,恢复动作也不同
申请人无法匹配时,流程等待人工补全映射;部门或字段校验失败时,记录失败原因,修正后重试。尚未通过的审批不建单,启用前的审批按既定规则忽略。
已建单后撤销审批,当前规则不会自动删除岗位。自动创建的岗位也仍需后续分配招聘负责人,不能把“需求已创建”解释成招聘流程已经全部就绪。
站内演示提供正常处理和失败重试两个场景。正常场景可以再次投递同一审批,观察需求数量保持为一条;失败场景展示部门映射修正后的恢复。切换场景会重置演示记录,避免把不同事件轨迹混在一起。
取舍:明确保证范围,比笼统承诺可靠更重要
复合唯一键包含创建人。因此,它保证的是同创建人与同审批派生键范围内的去重。若重试时申请人映射改变,不能把原有约束解释为跨任意身份变化的全局保证。
过程关联单独更新,让失败恢复依赖稳定键和读回机制;代价是必须承认并处理那个中间状态。重复审批返回路径也没有进行新旧内容哈希一致性检查,因此不能进一步声称实现了审批内容冲突检测。
数据库中的幂等建单,不等于飞书等外部系统的所有副作用都恰好执行一次。每一条通知和外部调用,都需要自己的投递与恢复设计。
验证边界与下一步
这篇文章依据当前代码链路整理。站内教学演示在浏览器内运行,展示状态变化,并不执行真实数据库并发事务。
后续集成验证应覆盖两个并发任务处理同一审批、建单后过程更新失败再重试,以及映射修正后的恢复。检查对象不仅是返回状态,还应包括最终需求数量、过程关联和关联写入的一致性。
审批幂等的核心,是为同一业务动作保留稳定身份,用数据库约束守住唯一性,再为中间失败设计明确的恢复路径。
参考
- RFC 9110, §9.2.2 Idempotent Methods:幂等描述重复请求对预期服务端效果的影响;这不等同于所有附带副作用只执行一次。
- PostgreSQL 16: INSERT:说明 ON CONFLICT 如何处理唯一约束冲突,以及冲突动作的并发语义。本文项目实现以 handoff 与代码为准,并非使用 PostgreSQL 专属语法的声明。