
【寻迹校园 HarmonyOS NEXT 实战 29】只允许改期一次、24 小时自动过期交接安排的防拖延状态机这是“寻迹校园 HarmonyOS NEXT 实战”系列第 29 篇。本文结合HandoffService.reschedule()、shouldExpire()、expiresAt与本地时间注入测试说明如何限制无限改期、重置确认状态并准确区分“访问时检查过期”和“后台定时任务”。上图为原创生成的交接时间线插画不是应用截图。一个交接安排只有一次改期机会每次新安排都有独立 24 小时确认窗口过期后释放物品资源。一、为什么“随时可以再约”会让流程永远结束不了如果交接双方可以无限改期系统会长期保留ACCEPTEDClaim 和CLAIMINGReport。物品既没有完成交付也无法让其他申请者继续核验。另一个常见问题是永久等待确认发起方选择地点后对方一直不操作记录没有明确失败出口。因此交接状态机需要同时回答两个问题最多允许改期几次一次安排等待确认多久。二、Handoff 状态的职责当前HandoffStatus包含状态含义PROPOSED首次安排已发送等待确认RESCHEDULED改期安排已发送等待再次确认CONFIRMED双方已确认安排可以线下交接COMPLETED双方已确认完成CANCELLED交接主动取消EXPIRED安排超过确认窗口只有PROPOSED和RESCHEDULED处于“等待确认计时”阶段。三、24 小时窗口如何进入数据模型Service 使用明确常量constCONFIRM_WINDOW_MS:number24*60*60*1000;创建安排时expiresAt now CONFIRM_WINDOW_MS。Repository 把它保存为整数时间戳进程重启后仍可重新判断。把截止时间落盘比只在页面启动一个倒计时可靠。页面销毁、应用退到后台或设备重启都不应重置业务期限。四、首次安排为什么已经占用一次 24 小时窗口propose()验证 Claim 为ACCEPTED、地点和时间片合法、没有其他活跃安排后创建PROPOSED记录。此时拾得者侧安排视为已发出keeperConfirmed初始化为true申请者侧仍待确认。当前单机演示页面随后通过“切换到失主视角并确认安排”模拟对方动作。这个角色切换是同设备演示不是真实双账号通知或签名确认。五、改期为什么只允许一次HandoffRecord.rescheduleCount记录已用次数。reschedule()先检查当前状态是否允许改期再判断if(record.rescheduleCount1){returnnewOperationResultHandoffRecord(false,每次交接只允许改期一次);}成功后计数加一。页面也会把按钮文案改为“已使用改期机会”并禁用。Service 和页面双重约束保证普通体验与业务规则一致。六、改期不是只替换一段时间文字一次改期会更新locationId / locationName / locationHourstimeSlot状态为RESCHEDULED申请者安排确认为false拾得者安排确认为true双方完成标记重置为falserescheduleCount加一updatedAt更新expiresAt重新设置为未来 24 小时。如果只改timeSlot而保留旧确认标记新安排可能被系统误认为双方已经同意。七、为什么改期后要重置完成标记理论上改期通常发生在交接完成前但 Service 允许从CONFIRMED进入一次RESCHEDULED。因此必须清除claimantCompleted和keeperCompleted。否则旧安排中的一方完成标记可能污染新安排使第二次确认直接触发结案。状态迁移的副作用要和来源集合一起审查不能只关注目标枚举值。八、shouldExpire() 为什么只检查两个状态当前规则是return(record.statusHandoffStatus.PROPOSED||record.statusHandoffStatus.RESCHEDULED)record.expiresAt0record.expiresAtDate.now();CONFIRMED表示双方已经接受安排不再受“等待确认 24 小时”规则约束。它后续是否需要“实际交接完成期限”是另一条尚未实现的业务规则。不要用一个expiresAt同时表达所有时间语义。上图展示当前过期检测发生在getByClaimId()或确认安排等业务访问路径读取记录、比较时间、保存EXPIRED、联动 Claim再把目标 Report 恢复OPEN。九、当前“自动过期”不是后台定时任务项目没有注册后台任务也没有每分钟扫描数据库。过期判断发生在进入交接页读取记录时确认安排前其他显式调用过期检查的业务路径。因此更准确的描述是“访问时自动检测并落库过期”。如果用户长期不再打开相关页面数据库中的状态可能暂时仍是PROPOSED直到下一次业务访问。十、过期后为什么要联动 Claim 和 ReportHandoff 进入EXPIRED后Service 调用claimService.expire(claimId)Claim 更新为EXPIRED目标 Report 恢复为OPEN其他真实失主可以重新发起申请。如果只把 Handoff 标记为过期Claim 仍为ACCEPTEDReport 仍为CLAIMING防拖延规则就没有真正释放资源。十一、确认动作必须先检查是否过期confirmSchedule()在把记录改成CONFIRMED之前调用shouldExpire()。已经超过截止时间的旧页面不能通过一次迟到点击复活安排。这类检查应靠当前时间和 Repository 权威记录而不是页面上倒计时是否还在运行。十二、时间比较需要哪些工程假设当前使用Date.now()适合单机演示但正式系统要考虑用户修改设备时间不同设备时钟偏差服务端与客户端时区夏令时和日期格式展示离线期间状态如何同步服务端接收请求时是否已过期。联网版应以服务端权威时间决定状态客户端时间主要用于展示。十三、本地测试如何稳定制造过期自动化不会真的等待 24 小时而是创建已接受 Claim发起交接安排从HandoffRepository读取记录把expiresAt改为Date.now() - 1再调用getByClaimId()。随后断言 Handoff 为EXPIRED、Claim 为EXPIRED、Report 为OPEN。这种时间注入让测试快速且可重复。十四、改期测试必须验证“第二次失败”只验证第一次改期成功不能证明“一次限制”真的存在。当前回归测试还会再次调用reschedule()并断言返回失败。同时检查状态为RESCHEDULEDrescheduleCount 1地点和时间已更新第二次改期不覆盖数据页面按钮进入禁用状态。十五、失败补偿仍有改进空间过期联动先保存 Handoff再更新 Claim 和 Report。如果后续写入失败可能出现HandoffEXPIRED但 Claim 或 Report 仍未同步。当前方法缺少跨 Repository 事务和补偿队列。正式版本可以采用服务端事务或记录待补偿事件并持续重试。错误不能只被catch吞掉否则用户会以为物品已经重新开放。十六、后台过期服务应该怎样升级当产品接入服务端后可以增加以权威时间查询即将过期和已过期安排定时任务幂等更新状态发送到期前提醒过期后通知双方事务联动 Claim 与 Report保留过期原因和执行时间防止后台任务与用户确认同时写入监控补偿失败和积压数量。这属于未来架构不是当前客户端已经实现的能力。工程复盘把时间规则写成可执行合约时间类需求最容易因为一句“24 小时后过期”留下边界歧义。可以先定义四个量首次确认时间、最近一次改期时间、当前安排的expiresAt、执行判断时的now。创建安排后过期点由首次确认时间加固定窗口得到允许的一次改期完成后过期点改为改期确认时间加固定窗口查询、确认和完成操作都以同一个expiresAt为准不能各自重新计算一遍。边界比较也必须固定。若规则采用now expiresAt即过期那么恰好到截止毫秒时确认动作必须失败并先执行过期联动如果采用严格大于截止点会多出一个最小时间单位。两种定义都能实现但客户端、服务端、测试和用户文案必须一致。当前实现使用本机时间测试只能证明算法分支正确不能证明用户修改系统时间后仍可靠因此文章把权威时间明确列为正式版能力而没有把本地Date.now()描述成安全时钟。测试数据应避免依赖真实等待。稳定做法是创建一个expiresAt已早于当前时间的记录执行访问时检查断言 Handoff 进入EXPIRED、Claim 进入EXPIRED、目标 Report 恢复OPEN再创建一个未来时间记录断言状态完全不变。对改期限制则连续调用两次改期第一次检查时间、地点和完成标记被正确重置第二次必须失败并确认失败调用没有偷偷修改过期时间。访问时过期还要覆盖多个入口。交接详情页、确认安排、确认完成和消息卡片都可能先接触到旧记录如果只有某一个页面调用expireIfNeeded()用户从其他入口进入就会看到过期安排仍可操作。更可靠的边界是让 Service 在所有会改变交接状态的公开方法前执行同一检查页面只消费返回结果。读取列表是否也触发写入要谨慎决定避免一次普通浏览产生大量隐式更新。并发场景下后台过期任务和用户确认可能在同一秒发生。正式服务端需要用版本号、条件更新或事务保证只有一个迁移获胜确认操作要求当前状态仍可确认且尚未到期过期操作要求当前状态仍属于待处理集合。失败的一方重新读取权威状态并返回可理解的结果不能靠“最后一次写入覆盖前一次”决定物品是否重新开放。提醒任务与过期任务也要分开。提醒失败不应阻止状态在截止时间后过期重复提醒不能延长窗口用户未收到推送也不能把已经过期的安排继续视为有效。消息文案应显示具体截止时间和时区并说明这是确认窗口不承诺后台在手机离线时由客户端准点执行。这样用户看到的是系统真实保证而不是一个可能被进程休眠打断的倒计时动画。可观测性可以只记录非敏感字段Handoff ID、旧状态、新状态、规则版本、计划过期时间、实际执行时间、触发来源和联动结果。它们足以排查“为什么提前过期”“为什么过期后仍被占用”不需要记录认领私密答案、精确个人位置或双方联系方式。补偿任务按操作 ID 幂等重试成功后重新计算列表和消息状态避免页面手工拼出一个与数据库不一致的成功假象。升级旧数据时还要处理缺失的expiresAt。可以先统计旧记录的状态、创建时间和改期次数再为仍活跃且时间证据完整的记录计算截止点证据不足的记录进入人工处理或给出安全、明确的重新安排流程。不能简单用升级时间统一续 24 小时因为这会静默改变原约定也可能让已经拖延很久的物品继续被占用。十七、本文小结“一次改期 24 小时确认窗口”把拖延风险转化为明确状态和截止时间。改期必须重置确认与完成标记过期必须联动 Claim 和 Report第二次改期必须被 Service 拒绝。当前实现采用访问时检查不是后台定时任务本地测试证明时间规则和主要联动但服务端权威时间、并发控制、事务补偿和主动提醒仍待正式版本建设。系列导航第 29 篇 / 共 50 篇。上一篇《固定校内交接点的隐私设计》下一篇《双方确认后的联动结案》。