# Feature Specification: 闪送订单状态变更消息推送 **Feature Branch**: `028-flash-delivery-push` **Created**: 2026-09-17 **Status**: Draft **Input**: User description: "闪送订单状态变更消息推送:骑手抢单、已取件、已送达推送给用户端;用户/平台取消推送给骑手端。2026-09-18 追加:立即闪送单创建后通知骑手;预约闪送单创建时不通知,进入可抢单时间窗口后再开放展示并通知;外卖和闪送使用不同的新单文案。加小费不推送。" ## User Scenarios & Testing *(mandatory)* ### User Story 1 - 寄件用户收到骑手接单通知 (Priority: P1) 寄件用户下单后处于等待状态,骑手抢单成功的瞬间,用户收到一条推送,告知订单已被骑手接单、骑手正在前来取件。 **Why this priority**: 用户下单后最焦虑的时段是"有没有人接单";接单通知是整个推送体系里价值最高的单条消息,没有它用户只能反复刷新订单页。 **Independent Test**: 创建一笔待接单的闪送订单,骑手账号执行抢单,验证寄件用户收到"骑手已接单"推送。 **Acceptance Scenarios**: 1. **Given** 订单状态为 WAITING_ACCEPTANCE,**When** 骑手抢单成功(状态转为 ACCEPTED),**Then** 寄件用户在短时间内收到推送,内容表明骑手已接单并包含订单标识。 2. **Given** 抢单因并发失败(订单已被他人抢走或已取消),**When** 抢单请求落空,**Then** 不产生任何推送。 --- ### User Story 2 - 寄件用户收到取件与送达通知 (Priority: P1) 履约中段,骑手确认取件、确认送达两个节点,寄件用户分别收到推送,掌握物品在途与签收进度。 **Why this priority**: 与接单通知共同构成完整的履约进度感知;送达通知同时提示用户可去确认收货。 **Independent Test**: 对一笔已接单订单依次执行取件(附凭证)、送达(附凭证),验证寄件用户先后收到两条对应推送。 **Acceptance Scenarios**: 1. **Given** 订单状态为 ACCEPTED,**When** 骑手确认取件成功(状态转为 PICKED_UP),**Then** 寄件用户收到"骑手已取件"推送。 2. **Given** 订单状态为 PICKED_UP,**When** 骑手确认送达成功(状态转为 DELIVERED),**Then** 寄件用户收到"已送达"推送。 3. **Given** 取件或送达操作因校验失败未改变订单状态,**When** 操作报错返回,**Then** 不产生推送。 --- ### User Story 3 - 骑手收到订单取消通知 (Priority: P2) 订单被取消时,已接单的骑手收到推送,及时停止履约,避免白跑。 **Why this priority**: 取消是低频事件,但漏通知会导致骑手空跑、产生纠纷;相比履约进度通知优先级略低。 **Independent Test**: 对一笔已接单订单分别走用户取消、平台取消两条路径,验证骑手收到取消推送;对未接单订单执行取消,验证不产生骑手推送。 **Acceptance Scenarios**: 1. **Given** 订单已被骑手接单(ACCEPTED/PICKED_UP),**When** 寄件用户取消订单成功(状态转为 CANCELLED),**Then** 该骑手收到"订单已取消"推送,内容含订单标识。 2. **Given** 订单已被骑手接单,**When** 平台取消订单成功,**Then** 该骑手收到取消推送。 3. **Given** 订单尚未被任何骑手接单(WAITING_ACCEPTANCE,含过期预约单自动取消),**When** 订单被取消,**Then** 不产生骑手推送(无推送对象)。 4. **Given** 用户取消请求因状态校验失败,**When** 取消未生效,**Then** 不产生推送。 --- ### User Story 4 - 骑手及时收到可抢闪送新单通知 (Priority: P1) 符合配送资格且在线的骑手在闪送订单真正可抢时收到新单推送。立即单创建后直接开放;预约单创建时隐藏,进入预约取件时间窗后再开放并推送。 **Why this priority**: 新单通知决定骑手是否能及时发现任务;预约单必须保证“列表可见”和“推送触发”使用同一个原子开放动作,避免骑手先看到订单、稍后才收到推送。 **Independent Test**: 分别创建立即单和预约单,验证立即单 `isDisplay=true` 且创建后推送;预约单初始 `isDisplay=false` 且不推送,在时间窗内由任务原子切为 `true` 后只推送一次。 **Acceptance Scenarios**: 1. **Given** 创建一笔立即闪送单,**When** 订单保存事务提交,**Then** 订单立即可见,并向符合配送类型、车型、距离和接单互斥规则的在线骑手发送“有新的闪送订单”。 2. **Given** 创建一笔尚未进入时间窗的预约闪送单,**When** 创建事务提交,**Then** `isDisplay=false`,骑手列表不可见且不产生新单推送。 3. **Given** 预约单进入 `scheduledPickupStartAt <= 当前时间 < scheduledPickupEndAt`,**When** 独立任务执行,**Then** 以条件更新原子切换 `isDisplay=false -> true`,同一次成功切换后发送新单推送。 4. **Given** 多个任务实例或连续执行同时处理同一预约单,**When** 竞争开放资格,**Then** 只有条件更新成功的一次触发推送,其他执行不重复发送。 5. **Given** 外卖单与闪送单分别发布,**When** 骑手收到新单推送,**Then** 文案分别为“有新的外卖订单”和“有新的闪送订单”。 ### Edge Cases - 接收方没有有效的设备推送标识(未登录过 App / 标识为空)时:跳过云端推送,但消息记录仍正常生成,且不影响订单操作结果。 - 推送服务调用失败或超时:只记录日志,不重试阻断,订单状态流转结果不受影响。 - 接收方语言设置为越南语/繁中/英语/简中之外或未设置:按平台默认语言回落(与现有订单推送行为一致)。 - 骑手抢单与用户取消并发(一方成功另一方必然失败):只对实际改变了订单状态的一方产生推送。 - 平台连续取消多笔订单:每笔独立推送,互不影响。 - 预约单进入窗口后的任务存在亚秒级调度延迟:在 `isDisplay` 原子切换前订单保持不可见,因此不会出现先看到订单、后收到推送的一分钟级错位。 ## Requirements *(mandatory)* ### Functional Requirements - **FR-001**: 骑手抢单成功(WAITING_ACCEPTANCE → ACCEPTED)后,系统 MUST 向寄件用户发送"骑手已接单"推送通知。 - **FR-002**: 骑手确认取件成功(ACCEPTED → PICKED_UP)后,系统 MUST 向寄件用户发送"骑手已取件"推送通知。 - **FR-003**: 骑手确认送达成功(PICKED_UP → DELIVERED)后,系统 MUST 向寄件用户发送"已送达"推送通知。 - **FR-004**: 寄件用户取消订单成功(状态转为 CANCELLED)且订单已有接单骑手时,系统 MUST 向该骑手发送"订单已取消"推送通知。 - **FR-005**: 平台取消订单成功且订单已有接单骑手时,系统 MUST 向该骑手发送"订单已取消"推送通知。 - **FR-006**: 所有推送文案 MUST 支持四种语言(vi、zh、tw、en),按接收方用户的语言设置呈现。 - **FR-007**: 每条推送内容 MUST 包含订单标识(订单号),使接收方无需打开 App 搜索即可定位订单。 - **FR-008**: 每条推送 MUST 同步生成消息记录(复用现有消息中心机制),用户/骑手可在消息列表中回看。 - **FR-009**: 推送环节的任何失败(无设备标识、推送服务异常)MUST NOT 影响订单状态流转本身,也 MUST NOT 向操作方返回错误。 - **FR-010**: 以下事件明确不推送:加小费不产生推送;超时自动完成(DELIVERED → COMPLETED)不推送;过期预约单自动取消不推送;预约单创建时不推送。 - **FR-011**: 推送 MUST 仅在对应状态变更实际成功后触发;操作失败或未改变状态时不得产生推送。 - **FR-012**: 立即闪送单创建时 `isDisplay` MUST 为 true,并在事务提交后触发一次骑手新单通知。 - **FR-013**: 预约闪送单创建时 `isDisplay` MUST 为 false,且在开放前不得出现在骑手可抢列表、详情或抢单接口中。 - **FR-014**: 预约单 MUST 仅在 `scheduledPickupStartAt <= 当前时间 < scheduledPickupEndAt` 时通过条件更新原子切换 `isDisplay=false -> true`;仅更新成功者有资格发送通知。 - **FR-015**: 预约开放任务 MUST 使用独立任务类每秒执行,不与自动完成或预约过期取消任务耦合。 - **FR-016**: 新单候选骑手 MUST 在线、具备 FLASH 配送类型、车型匹配、位于配置距离内,并遵守普通/加急闪送现有接单互斥规则。 - **FR-017**: 外卖和闪送的新单推送 MUST 使用不同文案,分别为 `no.message.push.new.food.order` 与 `no.message.push.new.flash.order`。 ### Key Entities *(include if feature involves data)* - **推送通知(Push Notification)**: 一次状态变更事件产生的一条通知,属性包括接收方(寄件用户或接单骑手)、事件类型(接单/取件/送达/取消)、标题与正文(按语言)、关联订单标识;复用现有消息记录实体落地,不新增独立数据模型。 ## Success Criteria *(mandatory)* ### Measurable Outcomes - **SC-001**: 任一在推送范围内的事件(抢单、取件、送达、取消)成功发生后,接收方在 10 秒内收到推送(推送服务正常的前提下)。 - **SC-002**: 四种语言设置的用户各抽检一批推送,文案语言与用户设置一致的比例为 100%。 - **SC-003**: 抽检的全部推送均含订单号,接收方可凭推送内容直接定位订单,达标率 100%。 - **SC-004**: 推送服务整体不可用时,抢单/取件/送达/取消接口的成功率与推送上线前持平(推送故障零传导)。 - **SC-005**: 预约单进入可抢窗口后 2 秒内开放展示并开始异步推送,且同一订单开放通知最多触发一次。 ## Assumptions - 复用项目现有推送通道(App 端云函数推送 + 消息入库),不新建推送基础设施,不新增 Android 通道。 - 推送在订单状态变更成功后异步触发,不放在订单操作的事务内。 - 平台取消按需求仅推送骑手端;寄件用户通过订单列表感知取消结果,如需同时通知用户可后续追加需求。 - 超时自动完成、过期预约单自动取消、预约单创建和加小费均不推送(FR-010);立即单创建和预约单开放属于新单通知触发点。 - 推送点击后的客户端跳转(payload 路由到闪送订单详情页)由客户端处理;服务端只保证推送携带订单标识与业务类型。 - 报文文案沿用现有订单推送的文案风格(简短状态描述 + 订单号后缀)。