跳到正文

外呼任务生命周期

本文说明 callout-server 从任务调度到通话终态的完整边界。实现入口位于 src/services/outbound-task/;公共域入口只暴露 dispatchCampaignBatchrunCalloutScheduler

入口与执行时序

  • 启动入口:管理端把任务状态更新为 running 前先调用预检;后端在状态更新事务内再次锁定 任务并校验统一工作量,覆盖并发变化和直接调用 API 的情况。
  • 自动入口:后台 scheduler 扫描 campaigns.status=running 的任务,先刷新完成度,再检查策略、 工作时段、调度间隔和当前可派发联系人后派发。
  • 手动入口:POST /api/campaigns/:id/dispatch 完成权限、参数和审计处理后,调用同一个 dispatchCampaignBatch
  • 两个入口都使用 campaign 级 Redis 锁;领取事务还使用 campaign/line 行锁、cell 容量锁和 FOR UPDATE SKIP LOCKED,多实例之间不会重复领取同一联系人。

originate 被 FreeSWITCH 接受只代表提交成功,不代表响铃、接通或业务完成。接通后的通话控制 归 callflow-eslcallout-server 继续保存任务、尝试和最终结果。

四类核心记录

记录职责关键生命周期
campaigns任务配置、策略入口、并发上限与整体进度draft → running → paused/completed/archived
contacts一个任务内的被叫工作项,也是并发与重试统计真值pending → dialing → submitted → completed/failed
callout_batches一次领取和提交循环的汇总、来源与计数running → completed/cancelled/failed
call_attempts单个联系人一次 originate 的请求快照和通话结果accepted/rejected/error 加独立 callStatus

当前为兼容历史数据,事务内预创建 attempt 时仍写 status=accepted,即使真正的 originate 尚未 提交。该字段语义后续可通过独立 schema 迁移引入 prepared,不在本次结构重构中改变。

领取、容量与提交边界

  1. claim.ts 在一个短事务内校验任务状态、计划时间、策略、线路和 cell,计算任务/线路/ 号码池/cell/严格监控容量。
  2. 可派发联系人包括 pending,以及未耗尽重试次数且 nextRetryAt 已到的 failed
  3. 事务使用 FOR UPDATE SKIP LOCKED 领取联系人,写入 dialing,创建 batch 和 attempt,返回 PreparedAttempt[] 后立即提交事务。
  4. originate.ts 在事务外并行调用本批所有 OutboundCallGateway;批次数量已经受请求上限、 任务、线路/号码池、cell 和严格盯屏容量共同裁剪。每路结果独立落库并原子增加 batch 的 submittedCount / failedCount,全部完成后再以汇总结果校准最终计数,避免长事务包围网络调用。
  5. progress.ts 用统一口径统计 pending、active、retryable、dispatchable 和 remaining work, 在无剩余工作且无活跃通话时完成 campaign。

启动、等待与完成边界

  • PATCH /api/campaigns/:id 切换到 running 时,必须存在 pending、未耗尽重试次数的 failed,或 dialing/submitted 活跃联系人;否则返回 HTTP 409,事务整体回滚并保留原状态。
  • draftpausedcompletedrunning 均可执行启动预检;archived 不可启动。 completed 任务只有在后来新增待处理联系人后才能重新启动。
  • 完成条件固定为 remainingWork === 0 && active === 0。全部成功、全部失败且重试耗尽,或 联系人池为空,都会在 running 任务的下一轮调度扫描中推进为 completed
  • 尚未到 nextRetryAt 的失败联系人属于 remaining work;dialing/submitted 属于 active。 这两类任务继续保持 running,当前轮不派发,也不创建空批次。
  • 预检中的 pending_contacts 检查编码为兼容现有调用方保持不变,但提示同时展示当前可派发、 等待重试和活跃通话数量。

三个终态来源

来源典型场景写入内容完成度处理
POST /api/call-resultscallflow-esl 正常完成业务通话状态、时间、录音、转写、业务结果、DNC/情绪分析标记刷新 campaign 完成度
FreeSWITCH 通道事件接通或挂机事件先到、业务未回写answered/completed/no_answer/busy/failed 与挂机原因、时长挂机终态后刷新完成度
生命周期超时对账contact 长期卡在 dialing/submittedattempt/contact 标记失败并安排重试按受影响 campaign 刷新完成度

三条路径都应幂等地更新同一 attempt/contact。业务层 payload.result 与 originate 快照合并,不能 覆盖 outboundPayload。如果某次失败仍满足重试条件,campaign 保持 running,等待 nextRetryAt 到期后再次领取。

模块导航

文件职责
index.ts外呼任务域唯一公共入口
scheduler.tsrunning campaign 扫描、工作时段与间隔
dispatch.tsRedis 锁及领取、提交、收尾编排
claim.ts短事务领取与 PreparedAttempt 创建
capacity.tscell、线路、号码池和监控容量
originate.tsFreeSWITCH 网关、提交结果和持久化
progress.tscampaign 工作量与完成度统一统计
state.tsattempt/contact 状态补丁与 payload 合并

文档与代码在同一仓库维护,现有 Markdown 是唯一内容源。