# V1 事件、消费者与任务操作登记表 本文档冻结 V1 领域事件、Outbox 消费者、队列 Job 和定时监听器之间的唯一映射。它解决四个问题:事实由谁产生、谁消费、重复执行如何判定、失败后由谁补偿。 ## 一、固定架构 ```text 业务事务 → eb_farm_domain_outbox → FarmOutboxDispatchListen → PublishFarmDomainEventJob → 领域消费者 → 领域 Service / 新 Outbox app/event.php create_timer → crmeb/listens/farm/timer/*Listen.php → crmeb/jobs/farm/*Job.php → 领域 Service ``` - 业务事实、状态迁移和 Outbox 必须同事务提交。 - 定时监听器只抢占 `job_name + run_key` 并投递队列,不执行业务批量。 - 队列 Job 使用游标、租约、超时、重试和业务结果键。 - 消费幂等唯一键为 `event_id + consumer_name`;业务结果还必须有自己的确定性唯一键。 - 定时任务幂等由 `job_name + run_key` 和表内业务结果键共同保证,不伪造 HTTP `request_id`。 ## 二、事件信封 所有事件使用版本化信封: ```json { "event_id": "01J...", "event_type": "farm.cloud.order.paid", "event_version": 1, "aggregate_type": "cloud_order_item", "aggregate_id": "42001", "aggregate_version": 3, "occurred_at": "2026-07-30 21:03:20", "trace_id": "01J...", "source": "http|payment_callback|event|job", "payload": {} } ``` `payload` 只保存消费者定位和校验所需的稳定 ID、状态、版本、数量或金额快照编号。姓名、手机号、地址、证件、支付报文、富文本和附件 URL 不进入事件;消费者凭 ID 按权限内服务读取。 ## 三、消费者文件 | 消费者名 | 推荐文件 | 负责范围 | | --- | --- | --- | | `CloudActivityConsumer` | `crmeb/listens/farm/domain/CloudActivityDomainListen.php` | 活动状态、未抢库存闭合、批次准备 | | `CloudSupplyConsumer` | `crmeb/listens/farm/domain/CloudSupplyDomainListen.php` | 供货、交付、验收、退回和平台承接 | | `CloudPrimaryOrderConsumer` | `crmeb/listens/farm/domain/CloudPrimaryOrderDomainListen.php` | 首次支付、去向、运费、自提、完成和退款 | | `CloudResaleConsumer` | `crmeb/listens/farm/domain/CloudResaleDomainListen.php` | FEFO 分配、观察期、有效销售和退款冲正 | | `CloudBatchConsumer` | `crmeb/listens/farm/domain/CloudBatchDomainListen.php` | 批次启动、节点、到期、回购和关闭 | | `FarmFinanceConsumer` | `crmeb/listens/farm/domain/FarmFinanceDomainListen.php` | 用户账本、商户货款、正式入账和冲正 | | `FarmRightsConsumer` | `crmeb/listens/farm/domain/FarmRightsDomainListen.php` | 租地/认养付款、预留、分配、替换 | | `FarmProductionConsumer` | `crmeb/listens/farm/domain/FarmProductionDomainListen.php` | 生产批次、任务、过程记录和产出 | | `FarmFulfillmentConsumer` | `crmeb/listens/farm/domain/FarmFulfillmentDomainListen.php` | 地址、运费、包裹、发货和完成 | | `FarmExceptionConsumer` | `crmeb/listens/farm/domain/FarmExceptionDomainListen.php` | 异常影响、方案、步骤和闭合 | | `FarmTraceConsumer` | `crmeb/listens/farm/domain/FarmTraceDomainListen.php` | 溯源审核、发布、撤回和公开缓存 | | `FarmNotificationConsumer` | `crmeb/listens/farm/FarmNotificationListener.php` | 把需通知事件转换为通知 Job | | `FarmAuditProjectionConsumer` | `crmeb/listens/farm/domain/FarmAuditProjectionListen.php` | 非关键列表摘要、统计和审计投影 | 消费者类只做事件版本判断、消费幂等、调用 Application Service 和记录结果;领域计算不得复制进 Listener。 ## 四、云仓事件登记 ### 0. 商品农业资料与管理员范围 | 事件 v1 | 生产操作/事务 | 主消费者 | 业务结果键 | 失败策略 | | --- | --- | --- | --- | --- | | `farm.product.agriculture.submitted` | `PRODUCT_AGRI_SUBMIT_ADMIN` | `FarmAuditProjectionConsumer` | `product_id + submitted_version + actor_type=admin` | 审核待办和摘要可重建;通知独立重试 | | `farm.product.agriculture.submitted` | `PRODUCT_AGRI_SUBMIT_MERCHANT` | `FarmAuditProjectionConsumer` | `product_id + submitted_version + actor_type=merchant` | 审核待办和摘要可重建;通知独立重试 | | `farm.product.agriculture.approved` | `PRODUCT_AGRI_REVIEW` | `FarmAuditProjectionConsumer` | `product_id + audit_version + approved` | 供货资格投影重试;交易守卫仍实时读取事实 | | `farm.product.agriculture.rejected` | `PRODUCT_AGRI_REVIEW` | `FarmAuditProjectionConsumer` | `product_id + audit_version + rejected` | 审核结果投影/通知重试 | | `farm.product.material.expiring` | `ProductMaterialExpiryJob` | `FarmNotificationConsumer` | `product_material_id + warning_stage` | 30/7 天提醒可重试 | | `farm.product.material.expired` | `PRODUCT_MATERIAL_EXPIRY_APPLY` | `FarmAuditProjectionConsumer` | `product_id + material_set_version + expire_stage` | 整体重算材料集合并关闭新供货资格;失败进入业务告警 | | `farm.admin.scope.changed` | `FARM_ADMIN_SCOPE_SAVE` | `FarmAuditProjectionConsumer` | `admin_id + scope_set_version` | 清理权限缓存、通知重新加载;实时鉴权不依赖消费成功 | ### 1. 活动与供货 | 事件 v1 | 生产操作/事务 | 主消费者 | 业务结果键 | 失败策略 | | --- | --- | --- | --- | --- | | `farm.cloud.activity.published` | `CW_ACTIVITY_PUBLISH` | `CloudActivityConsumer` | `activity_id + publish_version` | 重试缓存/预热;快照冲突转异常 | | `farm.cloud.activity.closed` | `CW_ACTIVITY_CLOSE` | `CloudActivityConsumer` | `activity_id + close_version` | 重试闭合检查 | | `farm.cloud.supply.submitted` | `CW_SUPPLY_SUBMIT` | `CloudSupplyConsumer` | `supply_id + submitted_version` | 审核待办可重建 | | `farm.cloud.supply.approved` | `CW_SUPPLY_AUDIT` | `CloudSupplyConsumer` | `supply_id + audit_version + source=merchant` | 交付任务可重建;库存差异不可盲重试 | | `farm.cloud.supply.approved` | `CW_PLATFORM_SUPPLY_CREATE` | `CloudSupplyConsumer` | `supply_id + create_version + source=platform` | 平台来源预占已提交;交付任务可重建 | | `farm.cloud.supply.dispatched` | `CW_SUPPLY_DELIVERY_DISPATCH` | `CloudSupplyConsumer` | `delivery_id + dispatch_version` | 在途投影重试 | | `farm.cloud.supply.received` | `CW_SUPPLY_RECEIVE` | `CloudSupplyConsumer` | `delivery_id + receive_version` | 待检任务重试 | | `farm.cloud.supply.inspection_completed` | `CW_SUPPLY_INSPECTION_COMPLETE` | `CloudSupplyConsumer` | `inspection_id + complete_version` | 库存结果已在生产事务;仅重试投影/任务 | | `farm.cloud.supply.shortage_confirmed` | `CW_SUPPLY_RECEIVE` | `FarmExceptionConsumer` | `delivery_id + shortage_version` | 创建一次异常;重复合并证据 | | `farm.cloud.supply.reservation_released` | `CW_SUPPLY_RELEASE` | `CloudSupplyConsumer` | `supply_id + release_version + source=manual` | 来源投影重试 | | `farm.cloud.supply.reservation_released` | `SYS_CW_INVENTORY_CLOSE` | `CloudSupplyConsumer` | `supply_id + release_version + source=activity_close` | 来源投影重试 | | `farm.cloud.supply.return_pending` | `SYS_CW_INVENTORY_CLOSE` | `CloudSupplyConsumer` | `return_id` | 退回任务重试,不先恢复来源库存 | | `farm.cloud.supply.returned` | `CW_SUPPLY_RETURN_COMPLETE` | `CloudSupplyConsumer` | `return_id + complete_version` | 来源库存事实已提交;重试投影 | | `farm.cloud.supply.platform_acquired` | `CW_SUPPLY_ACQUIRE_EXECUTE` | `CloudSupplyConsumer` | `acquisition_id + execute_version` | 平台库存事实已提交;重试投影 | ### 2. 首次订单、去向和运费 | 事件 v1 | 生产操作/事务 | 主消费者 | 业务结果键 | 失败策略 | | --- | --- | --- | --- | --- | | `farm.cloud.order.created` | `CW_ORDER_CREATE` | `CloudPrimaryOrderConsumer` | `cloud_order_item_id` | 支付超时跟踪可重建 | | `farm.cloud.order.paid` | `CRM_ORDER_PAID_ADAPT` | `CloudPrimaryOrderConsumer` | `cloud_order_item_id + paid_version` | 状态修复 Job 补偿 | | `farm.cloud.order.pay_expired` | `CRM_ORDER_CANCEL_ADAPT` | `CloudPrimaryOrderConsumer` | `cloud_order_item_id + cancel_version` | 库存释放修复 Job | | `farm.cloud.choice.confirmed` | `CW_CHOICE_CONFIRM` | `CloudPrimaryOrderConsumer` | `cloud_order_item_id + disposition_version + source=direct` | 履约/持仓投影重试 | | `farm.cloud.choice.confirmed` | `CW_FREIGHT_PAID` | `CloudPrimaryOrderConsumer` | `cloud_order_item_id + disposition_version + source=freight` | 履约投影重试 | | `farm.cloud.freight.created` | `CW_CHOICE_CONFIRM` | `CloudPrimaryOrderConsumer` | `freight_order_id` | 支付跟踪可重建 | | `farm.cloud.freight.paid` | `CW_FREIGHT_PAID` | `FarmFulfillmentConsumer` | `freight_order_id + provider_tx_id` | 业务事实已提交;重试仓储待办 | | `farm.cloud.freight.expired` | `SYS_CW_FREIGHT_EXPIRE` | `CloudPrimaryOrderConsumer` | `freight_order_id + expired_version` | 渠道关单事务后重试 | | `farm.cloud.freight.late_paid` | `CW_FREIGHT_PAID` | `CloudPrimaryOrderConsumer` | `freight_order_id + provider_tx_id` | 必须创建固定退款单;失败告警 | | `farm.cloud.freight.refund_requested` | `SYS_CW_FREIGHT_REFUND` | `RefundCloudFreightJob` | `freight_refund_no` | 渠道技术失败按固定曲线重试 | | `farm.cloud.freight.refunded` | `SYS_CW_FREIGHT_REFUND` | `FarmFinanceConsumer` | `freight_refund_id + completed_version` | 通知/对账重试 | | `farm.cloud.freight.refund_failed` | `SYS_CW_FREIGHT_REFUND` | `FarmExceptionConsumer` | `freight_refund_id + failed_version` | 人工复核,不自动新建退款单 | | `farm.cloud.choice.auto_consigned` | `SYS_CW_CHOICE_EXPIRE` | `CloudPrimaryOrderConsumer` | `cloud_order_item_id + deadline_version` | 与人工选择互斥;冲突判幂等/过期 | | `farm.cloud.primary.quantity_completed` | 签收、每次自提核销/异常闭合或持仓入批次 | `FarmFinanceConsumer` | `cloud_order_item_id + completion_type + completion_fact_id` | 按数量建立商户货款资格候选 | | `farm.cloud.primary.completed` | 明细全部数量闭合 | `CloudPrimaryOrderConsumer` | `cloud_order_item_id + completed_version` | 汇总状态和通知补偿 | | `farm.cloud.primary.refunded` | `CRM_REFUND_AFTER_ADAPT` | `FarmFinanceConsumer` | `refund_order_id + cloud_order_item_id` | 冲减/冻结使用原退款结果键 | ### 3. 活动闭合、批次和二次零售 | 事件 v1 | 生产操作/事务 | 主消费者 | 业务结果键 | 失败策略 | | --- | --- | --- | --- | --- | | `farm.cloud.activity.ended` | `SYS_CW_ACTIVITY_ADVANCE` | `CloudActivityConsumer` | `activity_id + end_version` | 重试关闭未支付订单 | | `farm.cloud.activity.unpaid_closed` | 未支付关闭汇总事务 | `CloudActivityConsumer` | `activity_id + unpaid_close_version` | 扫描任务可重算 | | `farm.cloud.activity.inventory_released` | `SYS_CW_INVENTORY_CLOSE` | `CloudActivityConsumer` | `activity_sku_id + close_version + release` | 库存流水唯一键兜底 | | `farm.cloud.activity.return_pending` | `SYS_CW_INVENTORY_CLOSE` | `CloudSupplyConsumer` | `activity_sku_id + close_version + return` | 退回任务重试 | | `farm.cloud.activity.inventory_closed` | `SYS_CW_INVENTORY_CLOSE` | `CloudBatchConsumer` | `activity_sku_id + close_version` | 批次准备重试 | | `farm.cloud.batch.ready` | 闭合检查事务 | `CloudBatchConsumer` | `batch_id + ready_version` | 启动任务重试 | | `farm.cloud.batch.started` | `SYS_CW_BATCH_START` | `CloudBatchConsumer` | `batch_id + start_version` | 二次库存桥接重试 | | `farm.cloud.resale.allocated` | `SYS_CW_RESALE_ALLOCATE` | `CloudResaleConsumer` | `secondary_order_product_id + allocation_version` | 已在订单事务;重试投影 | | `farm.cloud.resale.released` | `CRM_ORDER_CANCEL_ADAPT` | `CloudResaleConsumer` | `allocation_id + release_version` | 释放 Job 补偿 | | `farm.cloud.resale.observing` | 核心订单完成适配 | `CloudResaleConsumer` | `allocation_id + completion_version` | 观察期任务补偿 | | `farm.cloud.resale.effective` | `SYS_CW_RESALE_EFFECTIVE` | `CloudBatchConsumer` | `allocation_id + effective_version` | 进度/节点任务补偿 | | `farm.cloud.resale.reversed` | `CRM_REFUND_AFTER_ADAPT` | `CloudBatchConsumer` | `refund_id + allocation_id` | 账本冻结/冲正使用原退款键 | | `farm.cloud.milestone.reached` | `SYS_CW_MILESTONE` | `FarmFinanceConsumer` | `batch_id + highest_milestone + calculation_version` | 只计算最高新节点的一组累计差额 | | `farm.cloud.batch.sold_out` | `SYS_CW_MILESTONE` | `CloudBatchConsumer` | `batch_id + sold_out_version` | 最终结算任务重试 | | `farm.cloud.batch.matured` | `SYS_CW_MATURITY_START` | `CloudBatchConsumer` | `batch_id + maturity_version` | 停止新分配并开始在途处置宽限 | | `farm.cloud.batch.ownership_cutover` | `SYS_CW_MATURITY_FINALIZE` | `CloudBatchConsumer` | `batch_id + cutover_version` | 固化整数未售/平台承接在途量并生成待审回购预案 | | `farm.cloud.buyback.approved` | `CW_BUYBACK_REVIEW` | `ExecuteCloudBuybackJob` | `buyback_id + audit_version` | 执行前重新预检 | | `farm.cloud.buyback.rejected` | `CW_BUYBACK_REVIEW` | `CloudBatchConsumer` | `buyback_id + reject_version` | 冻结并生成重算待办 | | `farm.cloud.buyback.completed` | `CW_BUYBACK_EXECUTE` | `FarmFinanceConsumer` | `buyback_id + execute_version` | 正式入账/通知分别重试 | | `farm.cloud.batch.closed` | 批次闭合事务 | `FarmAuditProjectionConsumer` | `batch_id + close_version` | 报表/归档可重建 | ### 4. 用户与商户财务 | 事件 v1 | 生产操作/事务 | 主消费者 | 业务结果键 | 失败策略 | | --- | --- | --- | --- | --- | | `farm.cloud.user_ledger.created` | `SYS_CW_MILESTONE` | `FarmFinanceConsumer` | `ledger_business_key + source=milestone` | 公式差异转人工复核 | | `farm.cloud.user_ledger.created` | `SYS_CW_BUYBACK_EXECUTE` | `FarmFinanceConsumer` | `ledger_business_key + source=buyback` | 公式差异转人工复核 | | `farm.cloud.user_ledger.eligible` | `SYS_CW_USER_SETTLE` | `PostFarmUserLedgerJob` | `ledger_id + eligibility_version + source=auto` | 入账 Job 重试 | | `farm.cloud.user_ledger.eligible` | `CW_USER_LEDGER_REVIEW` | `PostFarmUserLedgerJob` | `ledger_id + eligibility_version + source=review` | 入账 Job 重试 | | `farm.cloud.user_ledger.settled` | `CW_USER_POSTING` | `FarmNotificationConsumer` | `posting_key` | 通知失败不回滚资金 | | `farm.cloud.user_ledger.frozen` | 退款/差异/争议事务 | `FarmFinanceConsumer` | `ledger_id + freeze_version` | 人工复核 | | `farm.cloud.merchant_ledger.eligible` | `SYS_CW_MERCHANT_ELIGIBLE` | `FarmFinanceConsumer` | `merchant_ledger_id + eligibility_version` | 结单任务补偿 | | `farm.cloud.merchant_statement.generated` | `SYS_CW_STATEMENT_GENERATE` | `FarmFinanceConsumer` | `merchant_id + statement_date + currency` | 自动/人工审核 | | `farm.cloud.merchant_ledger.settled` | `CW_MERCHANT_POSTING` | `FarmNotificationConsumer` | `posting_key` | 通知/对账重试 | | `farm.financial_posting.failed` | 统一入账服务 | `FarmExceptionConsumer` | `posting_id + failed_version` | 可恢复走修复 Job;业务冲突人工复核 | | `farm.financial_posting.recovery_pending` | 冲正时余额不足 | `FarmFinanceConsumer` | `adjustment_id + recovery_version` | 冻结后续农业入账,等待追偿 | | `farm.cloud.ledger.adjusted` | 冲正审核执行 | `FarmFinanceConsumer` | `adjustment_id + execute_version` | 重新对账 | ## 五、农业经营事件登记 ### 1. 权益、资源分配和生产 | 事件 v1 | 生产操作/事务 | 主消费者 | 业务结果键 | 失败策略 | | --- | --- | --- | --- | --- | | `farm.land.order.paid` | `CRM_ORDER_PAID_ADAPT` | `FarmRightsConsumer` | `land_order_id + paid_version` | 分配修复 Job | | `farm.land.plot.reserved` | `LAND_ALLOCATION_RESERVE` | `FarmRightsConsumer` | `reservation_no` | 过期任务释放 | | `farm.land.plot.allocated` | `LAND_ALLOCATION_CONFIRM` | `FarmProductionConsumer` | `land_order_id + allocation_version` | 生产准备重试 | | `farm.land.plot.reservation_released` | `LAND_ALLOCATION_RELEASE` | `FarmRightsConsumer` | `reservation_no + release_version + source=manual` | 日历投影可重建 | | `farm.land.plot.reservation_released` | `SYS_FARM_RESERVE_EXPIRE` | `FarmRightsConsumer` | `reservation_no + release_version + source=expiry` | 日历投影可重建 | | `farm.adoption.order.paid` | `CRM_ORDER_PAID_ADAPT` | `FarmRightsConsumer` | `adoption_order_id + paid_version` | 分配修复 Job | | `farm.adoption.asset.reserved` | `ADOPTION_ALLOCATION_RESERVE` | `FarmRightsConsumer` | `reservation_no` | 过期任务释放 | | `farm.adoption.asset.allocated` | `ADOPTION_ALLOCATION_CONFIRM` | `FarmProductionConsumer` | `adoption_order_id + allocation_version` | 生产准备重试 | | `farm.adoption.asset.reservation_released` | `ADOPTION_ALLOCATION_RELEASE` | `FarmRightsConsumer` | `reservation_no + release_version + source=manual` | 容量投影可重建 | | `farm.adoption.asset.reservation_released` | `SYS_FARM_RESERVE_EXPIRE` | `FarmRightsConsumer` | `reservation_no + release_version + source=expiry` | 容量投影可重建 | | `farm.adoption.asset.replaced` | `SYS_FARM_EXCEPTION_STEP` | `FarmRightsConsumer` | `exception_id + plan_version + replace_step` | 原子替换失败转异常步骤失败 | | `farm.animal.imported` | `ANIMAL_IMPORT_CONFIRM` | `FarmAssetProjectionConsumer`、`FarmNotificationConsumer` | `confirm_operation_no + validation_operation_no + content_hash` | 列表/容量摘要可重建;通知失败独立重试 | | `farm.production.batch.created` | `PRODUCTION_BATCH_CREATE` | `FarmProductionConsumer` | `production_batch_id + create_version` | 任务规划重试 | | `farm.production.batch.started` | `PRODUCTION_BATCH_START` | `FarmProductionConsumer` | `production_batch_id + start_version` | 现场任务投递重试 | | `farm.production.batch.paused` | `PRODUCTION_BATCH_PAUSE` | `FarmProductionConsumer` | `production_batch_id + pause_version` | 待执行任务投影重试 | | `farm.production.batch.resumed` | `PRODUCTION_BATCH_RESUME` | `FarmProductionConsumer` | `production_batch_id + resume_version` | 任务恢复重试 | | `farm.production.batch.completed` | `PRODUCTION_BATCH_COMPLETE` | `FarmTraceConsumer` | `production_batch_id + complete_version` | 权益/溯源汇总重试 | | `farm.task.assigned` | `FARM_TASK_ASSIGN` | `FarmNotificationConsumer` | `task_id + assignment_version + action=assign` | 提醒可重建 | | `farm.task.assigned` | `FARM_TASK_REASSIGN` | `FarmNotificationConsumer` | `task_id + assignment_version + action=reassign` | 提醒可重建 | | `farm.task.started` | `FARM_TASK_START` | `FarmProductionConsumer` | `task_id + start_version` | 生产/履约现场进度投影重试 | | `farm.task.completed` | `FARM_TASK_COMPLETE` | `FarmProductionConsumer` | `task_id + complete_version` | 批次/履约进度重算 | | `farm.production.event.submitted` | `PRODUCTION_EVENT_SUBMIT` | `FarmProductionConsumer` | `event_version_id` | 审核待办可重建 | | `farm.production.event.published` | `PRODUCTION_EVENT_PUBLISH` | `FarmTraceConsumer` | `event_version_id + publish_version` | 用户时间线/溯源重建 | ### 2. 产出、履约、异常和溯源 | 事件 v1 | 生产操作/事务 | 主消费者 | 业务结果键 | 失败策略 | | --- | --- | --- | --- | --- | | `farm.output.batch.submitted` | `OUTPUT_BATCH_SUBMIT` | `FarmProductionConsumer` | `output_batch_id + submit_version` | 质量待办可重建 | | `farm.output.batch.accepted` | `OUTPUT_BATCH_QUALITY` | `FarmProductionConsumer` | `output_batch_id + quality_version` | 自动分配 Job 补偿 | | `farm.output.allocated` | `OUTPUT_ALLOCATION_CONFIRM` | `FarmFulfillmentConsumer` | `output_batch_id + calculation_version` | 履约创建可重建 | | `farm.fulfillment.ready` | `FULFILLMENT_CREATE` | `FarmNotificationConsumer` | `fulfillment_id + ready_version` | 地址提醒重试 | | `farm.fulfillment.address_confirmed` | `FULFILLMENT_ADDRESS_CONFIRM` | `FarmFulfillmentConsumer` | `fulfillment_id + address_version` | 运费/仓储待办重建 | | `farm.fulfillment.freight_paid` | `FULFILLMENT_FREIGHT_PAID` | `FarmFulfillmentConsumer` | `freight_order_id + provider_tx_id` | 支付修复 Job | | `farm.fulfillment.shipped` | `FULFILLMENT_SHIP` | `FarmNotificationConsumer` | `package_id + ship_version` | 通知失败不回滚出库 | | `farm.fulfillment.completed` | `FULFILLMENT_TAKE` | `FarmProductionConsumer` | `fulfillment_id + complete_version + source=user` | 权益汇总重算 | | `farm.fulfillment.completed` | `SYS_FARM_FULFILL_AUTO` | `FarmProductionConsumer` | `fulfillment_id + complete_version + source=auto` | 权益汇总重算 | | `farm.exception.created` | `FARM_EXCEPTION_CREATE` | `FarmExceptionConsumer` | `exception_id` | 冻结/通知按严重度重试 | | `farm.exception.plan_submitted` | `FARM_EXCEPTION_PLAN_CREATE` | `FarmExceptionConsumer` | `plan_version_id` | 审核待办可重建 | | `farm.exception.plan_approved` | `FARM_EXCEPTION_PLAN_REVIEW` | `FarmExceptionConsumer` | `plan_version_id + audit_version` | 需要确认则建立待确认,否则投递执行 | | `farm.exception.plan_confirmed` | `FARM_EXCEPTION_USER_CONFIRM` | `ExecuteFarmExceptionStepJob` | `plan_version_id + confirmation_version` | 执行前再预检 | | `farm.exception.plan_confirmation_timed_out` | `SYS_FARM_CONFIRM_EXPIRE` | `FarmExceptionConsumer` | `plan_version_id + timeout_version` | 等价方案转执行;降级/减量/超期方案转退款 | | `farm.exception.step_completed` | `SYS_FARM_EXCEPTION_STEP` | `FarmExceptionConsumer` | `exception_id + plan_version + step_code` | 推进下一步骤 | | `farm.exception.step_failed` | `SYS_FARM_EXCEPTION_STEP` | `FarmExceptionConsumer` | 同上 + `failed_version` | 冻结、告警、人工修复 | | `farm.exception.resolved` | 恒等式闭合检查 | `FarmExceptionConsumer` | `exception_id + resolved_version` | 通知和归档重试 | | `farm.exception.closed` | `FARM_EXCEPTION_CLOSE` | `FarmAuditProjectionConsumer` | `exception_id + close_version` | 报表可重建 | | `farm.trace.version.submitted` | `TRACE_VERSION_SUBMIT` | `FarmTraceConsumer` | `trace_version_id + submit_version` | 审核待办可重建 | | `farm.trace.version.approved` | `TRACE_VERSION_REVIEW` | `FarmTraceConsumer` | `trace_version_id + audit_version` | 只开放发布动作,不自动公开 | | `farm.trace.version.published` | `TRACE_VERSION_PUBLISH` | `FarmTraceConsumer` | `archive_id + public_version` | 二维码/缓存任务重试 | | `farm.trace.version.withdrawn` | `TRACE_VERSION_WITHDRAW` | `FarmTraceConsumer` | `archive_id + withdraw_version` | 公开缓存更新重试 | ## 六、事件消费结果规则 | 结果 | `eb_farm_event_consume_log.status` | 后续 | | --- | --- | --- | | 已成功处理 | `succeeded` | 保存 `result_business_key`,重复事件直接返回 | | 事件版本未知 | `failed` | 不重试,立即告警并阻断部署兼容性检查 | | 聚合已是目标状态 | `succeeded` | 记录 `idempotent_noop` | | 聚合已进入更新且不兼容状态 | `ignored` 或 `failed` | 只有明确的过期事件可忽略;资金/库存事件必须失败并人工复核 | | 技术暂时失败 | `retry_pending` | 10 秒、1 分钟、5 分钟、15 分钟、1 小时 | | 业务恒等式失败 | `failed` | 建立/关联异常,不自动重复计算到“成功”为止 | ## 七、定时监听器与 Job 登记 ### 1. 运行通则 | 项目 | 冻结值 | | --- | --- | | 时区 | `Asia/Shanghai`;数据库时间按项目既有约定统一,业务日边界显式转换 | | 游标 | 优先 `last_due_at + last_id` 或 `last_id`,批次成功后推进 | | 租约 | `lease_until` 至少为超时时间的 2 倍;实例使用唯一 `lease_owner` | | 默认重试 | 5 次:10 秒、1 分钟、5 分钟、15 分钟、1 小时 | | 不可重试 | 状态前置失败、归属错误、规则快照缺失、金额/数量不闭合、事件版本未知 | | 告警 | 单条最终失败、连续三轮有失败、租约超时、扫描积压超过两个周期 | | 修复 | 后台查看事实、预演重放、按原结果键确认重放;不得直接改汇总 | ### 2. 云仓任务 | Timer Listen | Job 文件 | 频率/批量/超时 | 选择器与结果键 | 领域动作 | | --- | --- | --- | --- | --- | | `CloudActivityStatusListen.php` | `CloudActivityStatusJob.php` | 1 分钟/200/60 秒 | `status + start_at/end_at + id`;`activity_id + target_status + version` | 预热、开始、结束 | | `CloudActivityInventoryCloseListen.php` | `CloudActivityInventoryCloseJob.php` | 1 分钟/100/120 秒 | 已结束且未闭合 SKU;`activity_sku_id + close_version` | 释放、退回待办、平台承接、闭合 | | `CloudUnpaidReleaseListen.php` | `CloudUnpaidReleaseJob.php` | 1 分钟/200/90 秒 | 已关闭未释放明细;`order_item_id + cancel_version` | 修复首次库存 | | `CloudPaidRepairListen.php` | `CloudPaidRepairJob.php` | 1 分钟/200/90 秒 | CRMEB 已付且农业待付;`order_item_id + paid_version` | 修复付款事实 | | `CloudChoiceReminderListen.php` | `CloudChoiceReminderJob.php` | 10 分钟/500/120 秒 | `status=paid_pending_choice` + `choice_deadline_at` 的 24h/6h/1h 窗口;`cloud_order_item_id + reminder_stage` | 幂等提醒 | | `CloudChoiceExpireListen.php` | `CloudChoiceExpireJob.php` | 1 分钟/200/120 秒 | `status in (paid_pending_choice,delivery_pending_freight)` + `choice_deadline_at <= now`;`cloud_order_item_id + choice_version` | 自动整条代销;有未付运费单时同事务先本地取消 | | `CloudFreightExpireListen.php` | `CloudFreightExpireJob.php` | 1 分钟/200/120 秒 | `pay_status in (pending,paying)` + `expired_at <= now`;`freight_order_id + expired_version` | 回待选/自动代销、事务后关单 | | `CloudFreightPaymentRepairListen.php` | `CloudFreightPaymentRepairJob.php` | 2 分钟/100/180 秒 | paying/late/refund_pending;`freight_id + repair_stage + provider_fact` | 支付、关单和退款事实修复 | | `CloudBatchStartListen.php` | `CloudBatchStartJob.php` | 1 分钟/100/180 秒 | `status=consign_ready` + `consign_start_at <= now`;`batch_id + start_version` | 冻结选择并启动 | | `CloudPickupOverdueListen.php` | `CloudPickupOverdueJob.php` | 10 分钟/200/120 秒 | 自提表 `status in (pending,partially_verified)` + `expires_at <= now`;`pickup_id + overdue_version` | 建立协商异常 | | `CloudResaleAllocationRepairListen.php` | `CloudResaleAllocationRepairJob.php` | 1 分钟/200/180 秒 | cloud_only 明细无分配;`order_product_id + allocation_version` | 修复 FEFO 分配或冻结订单 | | `CloudResaleReleaseListen.php` | `CloudResaleReleaseJob.php` | 1 分钟/200/120 秒 | 关闭订单仍有锁定;`allocation_id + release_version` | 释放二次库存 | | `CloudResaleEffectiveListen.php` | `CloudResaleEffectiveJob.php` | 5 分钟/200/180 秒 | `status=observing` + `observation_end_at <= now`;`allocation_id + effective_version` | 有效销售与进度 | | `CloudRefundSyncListen.php` | `CloudRefundSyncJob.php` | 5 分钟/200/180 秒 | CRMEB 退款与农业影响不一致;`refund_id + impact_type` | 首次/二次冲正 | | `CloudMilestoneListen.php` | `CloudMilestoneJob.php` | 10 分钟/100/180 秒 | 批次进度高于已结节点;`holding_id + highest_milestone + calculation_version + ledger_type` | 一组累计差额账本 | | `CloudUserSettlementListen.php` | `CloudUserSettlementJob.php` | 5 分钟/200/180 秒 | eligible 账本;`posting_key` | 调用统一用户入账 | | `CloudMerchantEligibilityListen.php` | `CloudMerchantEligibilityJob.php` | 10 分钟/200/180 秒 | 观察期到且无退款/冻结;`merchant_ledger_id + eligibility_version` | 货款可结资格 | | `CloudMerchantStatementListen.php` | `CloudMerchantStatementJob.php` | 每日 02:00/商户分片/600 秒 | 上一自然日 eligible;`merchant_id + date + currency` | 不可变结单 | | `CloudMerchantPostingListen.php` | `CloudMerchantPostingJob.php` | 02:05 + 10 分钟/100/300 秒 | approved/auto 结单;`posting_key` | 商户余额和流水 | | `FarmFinancialPostingRepairListen.php` | `FarmFinancialPostingRepairJob.php` | 5 分钟/200/180 秒 | processing 租约过期或 retry_pending;`posting_key` | 回收租约、原键重试 | | `CloudMaturityListen.php` | `CloudMaturityJob.php` | 10 分钟/100/180 秒 | consigning + maturity_at;`batch_id + maturity_version` | 停止新分配、固化宽限截止 | | `CloudMaturityFinalizeListen.php` | `CloudMaturityFinalizeJob.php` | 10 分钟/100/300 秒 | matured + maturity_grace_deadline_at;`batch_id + cutover_version` | 固化所有权切换、整数未售量和待审预案 | | `CloudBuybackListen.php` | `ExecuteCloudBuybackJob.php` | 10 分钟 + 事件/100/300 秒 | approved + precheck valid;`buyback_id + audit_version` | 执行回购 | | `CloudReconciliationListen.php` | `CloudReconciliationJob.php` | 每日 02:10/分片/900 秒 | 业务日 + 分片;`reconcile_date + shard` | 五段对账 | | `CloudSupplyInventoryReconcileListen.php` | `CloudSupplyInventoryReconcileJob.php` | 10 分钟/200/300 秒 | 活跃/近期供货;`supply_id + reconcile_version` | 供货数量守恒 | ### 3. 农业、履约和基础任务 | Timer Listen | Job 文件 | 频率/批量/超时 | 选择器与结果键 | 领域动作 | | --- | --- | --- | --- | --- | | `FarmAllocationRepairListen.php` | `FarmAllocationRepairJob.php` | 5 分钟/100/180 秒 | 已支付待分配超过阈值;`order_type + order_id + repair_version` | 候选检查/生成待办 | | `FarmAllocationReservationExpireListen.php` | `FarmAllocationReservationExpireJob.php` | 1 分钟/200/120 秒 | reserved + lock_expires_at;`reservation_no + release_version` | 释放地块/个体/份额 | | `FarmTaskReminderListen.php` | `FarmTaskReminderJob.php` | 每小时/500/180 秒 | assigned/in_progress + due_at;`task_id + reminder_stage` | 现场提醒 | | `FarmOutputAllocationListen.php` | `FarmOutputAllocationJob.php` | 10 分钟/100/300 秒 | accepted + available_qty;`output_id + calculation_version` | 权益分配 | | `FarmExceptionExecutionListen.php` | `ExecuteFarmExceptionStepJob.php` | 5 分钟 + 事件/100/300 秒 | approved/executing 可执行步骤;`exception_id + plan_version + step_code` | 顺序执行 | | `FarmExceptionConfirmationExpireListen.php` | `FarmExceptionConfirmationExpireJob.php` | 10 分钟/200/120 秒 | `status=pending_user_confirmation` + `confirmation_deadline_at <= now`;`plan_version_id + timeout_version` | 按等价/降级超时策略推进 | | `FarmExceptionSlaListen.php` | `FarmExceptionSlaJob.php` | 10 分钟/200/120 秒 | 未关闭 + sla_due_at;`exception_id + sla_stage` | 预警/升级 | | `FarmFulfillmentReminderListen.php` | `FarmFulfillmentReminderJob.php` | 每小时/500/180 秒 | `status in (pending_address,partially_shipped,shipped)` + `next_reminder_at <= now`;阶段分别读取 `address_due_at/receive_due_at`;`fulfillment_id + reminder_stage + due_at` | 用户/仓储提醒,成功后推进 `next_reminder_at` | | `FarmFulfillmentFreightRepairListen.php` | `RepairFarmFulfillmentFreightJob.php` | 2 分钟/100/180 秒 | paying/late/refund_pending;`freight_id + repair_stage + provider_fact` | 农业运费修复 | | `ProductMaterialExpiryListen.php` | `ProductMaterialExpiryJob.php` | 10 分钟/500/300 秒 | 已批准 profile 关联的当前材料 `status=active` + `expire_at`;按 `product_id` 去重;`product_id + material_set_version + warning_or_expire_stage` | 30/7 天预警;到期调用 `PRODUCT_MATERIAL_EXPIRY_APPLY` 整体重算资格 | | `TraceMaterialExpiryListen.php` | `TraceMaterialExpiryJob.php` | 每日 03:10/500/300 秒 | `status=active` + `expire_at <= stage_at`;`trace_material_id + warning_stage` | 30/7/0 天预警 | | `TracePublicCacheRepairListen.php` | `TracePublicCacheRepairJob.php` | 10 分钟/200/180 秒 | published/withdrawn 与公开指针不一致;`archive_id + public_version` | 缓存/二维码解析修复 | | `FarmUnboundAttachmentCleanupListen.php` | `CleanupFarmUnboundAttachmentJob.php` | 每日 03:30/500/600 秒 | unbound + expires_at;`attachment_id + cleanup_version` | 删除临时对象;业务证据永不删 | | `FarmOutboxDispatchListen.php` | `PublishFarmDomainEventJob.php` | 5 秒/200/60 秒 | pending/retry + next_retry_at;`event_id + consumer_name` | 投递 Outbox | | `FarmFailedJobAlertListen.php` | `FarmFailedJobAlertJob.php` | 5 分钟/200/120 秒 | final_failed/unalerted;`job_run_id + alert_stage` | 技术/业务负责人告警 | ### 4. 仅事件触发的队列 Job | Job 文件 | 触发事件/命令 | 业务结果键 | 重试与失败 | | --- | --- | --- | --- | | `SendFarmNotificationJob.php` | `FarmNotificationConsumer` | `template_code + recipient + business_type + business_id + stage` | 渠道失败重试;模板/接收人非法不重试 | | `PostFarmUserLedgerJob.php` | `farm.cloud.user_ledger.eligible` | `posting_key` | 账户/恒等式冲突人工复核 | | `GenerateMerchantSupplyStatementJob.php` | 手工命令或定时 Job 内分片 | `merchant_id + statement_date + currency` | 候选版本冲突重新生成预览 | | `PostMerchantSupplyStatementJob.php` | 已审核/自动结单 | `posting_key` | 与用户入账同一恢复规则 | | `RefundCloudFreightJob.php` | `farm.cloud.freight.refund_requested` | `freight_refund_no` | 渠道技术失败重试;金额冲突立即告警 | | `GenerateTraceQrCodeJob.php` | `farm.trace.version.published` | `archive_id + public_version` | 生成/对象存储失败重试,稳定二维码入口不变 | | `ValidateAnimalImportJob.php` | `ANIMAL_IMPORT_VALIDATE` | `validation_operation_no + content_hash` | 文件读取/解析技术失败重试;业务行错误写预览后正常结束 | | `ConfirmAnimalImportJob.php` | `ANIMAL_IMPORT_CONFIRM` | `confirm_operation_no + validation_operation_no` | 范围/版本/哈希变化不重试;死锁可按原结果键重试,资产写入全有或全无 | ## 八、队列和隔离 | 队列 | Job | 目的 | | --- | --- | --- | | `farm-critical` | 支付事实修复、用户/商户入账、退款、回购、异常执行 | 与通知/导出隔离,低并发严格锁 | | `farm-business` | 活动、供货、批次、分配、履约 | 默认业务队列 | | `farm-notification` | 通知和提醒 | 渠道拥塞不阻塞业务 | | `farm-maintenance` | 对账、缓存修复、附件清理、失败告警 | 可限速、可暂停 | 同一业务键只能由一个 Job 持有租约;扩容通过分片和并发消费者完成,不通过取消行锁或绕过唯一键提升吞吐。 ## 九、发布前验证 - `app/event.php` 中每个 Timer Listen 恰好注册一次,间隔与本表一致。 - 每个 Listen、Job、Consumer 和 Domain Service 的类名与 `21`、`35` 完全一致。 - 每种事件都能在测试中验证:生产事务回滚不留 Outbox、提交后至少一次投递、重复消费不重复产生业务结果。 - 每个 Job 都有空扫描、单条成功、部分失败、租约争抢、游标续跑、最终失败告警和人工原键重放测试。 - 对账/修复任务只生成差异、方案或调用既有命令,不直接覆盖业务汇总、余额或库存。 ## 十、关联文档 - [20-v1-events-jobs-permissions-notifications.md](20-v1-events-jobs-permissions-notifications) - [21-v1-code-change-blueprint.md](21-v1-code-change-blueprint) - [31-v1-write-operation-contract-registry.md](31-v1-write-operation-contract-registry) - [33-v1-state-exception-transaction-matrix.md](33-v1-state-exception-transaction-matrix) - [35-v1-page-api-file-trace.md](35-v1-page-api-file-trace)