ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

【寻迹校园 HarmonyOS NEXT 实战 27】认领状态机实战:拒绝、取消、过期后为什么必须恢复物品为 OPEN

【寻迹校园 HarmonyOS NEXT 实战 27】认领状态机实战:拒绝、取消、过期后为什么必须恢复物品为 OPEN 【寻迹校园 HarmonyOS NEXT 实战 27】认领状态机实战拒绝、取消、过期后为什么必须恢复物品为 OPEN这是“寻迹校园 HarmonyOS NEXT 实战”系列第 27 篇。本文结合ClaimStatus、ClaimService.transition()、ReportService.markOpen()与本地回归测试说明 Claim 状态和 Report 状态为什么必须联动以及拒绝、取消、过期、完成四条终止路径分别应该产生什么副作用。上图为原创生成的双状态机插画不是应用截图。认领申请从PENDING出发而物品记录在OPEN / CLAIMING / RESOLVED之间变化两者不能各自独立更新。一、最隐蔽的状态机 Bug 是“申请结束了物品还卡着”用户提交认领申请后目标拾得记录会从OPEN变为CLAIMING。这样可以阻止同一件物品同时进入多个待处理流程。如果申请后来被拒绝、取消或过期只更新 Claim 状态而忘记恢复 Report物品会永久停在“认领中”。其他真实失主既不能提交新申请也很难理解为什么页面一直不可操作。所以终止认领不是单表更新而是一组业务不变量。二、先把 Claim 状态语义定义清楚项目中的ClaimStatus包含状态含义是否终态PENDING等待拾得者核验否ACCEPTED已同意等待安全交接否REJECTED审核拒绝是CANCELLED申请或交接被取消是EXPIRED交接确认窗口过期是COMPLETED双方完成交接是终态并不都对应同一个 Report 结果。前三种失败终态需要重新开放物品COMPLETED才应该把关联报告结案。三、Report 状态承担的是资源可用性认领流程关心“这次申请走到了哪里”报告流程关心“这条物品记录是否还能被发现和认领”。当前核心状态可以概括为OPEN可进入候选与认领CLAIMING已有申请正在处理RESOLVED交接完成记录结案HIDDEN治理隐藏WITHDRAWN发布者撤回DRAFT尚未发布。因此 Claim 和 Report 是两个不同聚合。联动不等于把两套状态合并成一个枚举。四、提交成功后为什么先标记 CLAIMINGClaimService.submit()完成输入校验、目标类型检查、重复申请检查后先插入认领记录再调用awaitreportService.markClaiming(reportId);这能让首页和详情页立即反映“正在处理认领”。但当前实现是两次顺序写入不是一个数据库事务。如果 Claim 插入成功而 Report 更新失败可能出现申请存在但物品仍为OPEN。文章必须把它记录为补偿风险不能把try/catch描述为原子事务。五、transition() 如何限制状态来源同意和拒绝都通过transition()约束if(current.statusnext){returnnewOperationResultClaimRecord(true,successMessage,current);}if(current.status!expected){returnnewOperationResultClaimRecord(false,当前认领状态已变化请刷新后重试);}这里同时处理两个问题重复提交同一个目标状态时保持幂等成功从错误来源状态跳转时拒绝覆盖。例如REJECTED → ACCEPTED不会因为旧页面仍显示“同意”而被静默写入。六、拒绝路径PENDING → REJECTED再恢复 OPEN拒绝只允许从PENDING发生。状态更新成功后Service 调用reportService.markOpen(reportId)。如果恢复失败返回值会明确提示申请已拒绝但物品状态恢复失败请重新进入后检查。这是比统一提示“操作失败”更有价值的部分失败语义。用户知道申请决策已经生效也知道物品可用性需要复核。七、取消路径为什么允许 PENDING 和 ACCEPTED申请可能在审核前撤销也可能在同意后因为无法完成交接而取消。当前cancel()接受这两种来源PENDING → CANCELLEDACCEPTED → CANCELLED成功后同样恢复目标 Report 为OPEN。如果 Claim 已经COMPLETED、REJECTED或EXPIRED则不能再次取消。八、过期路径由交接状态机触发ClaimService.expire()不直接计算时间。时间窗口属于HandoffService当交接安排超过确认期限时后者把 Handoff 标记为EXPIRED再调用 Claim 的过期动作。Claim 进入EXPIRED后目标 Report 恢复OPEN。这使“时间规则”和“资源重新开放”分别由对应 Service 负责。上图展示REJECTED / CANCELLED / EXPIRED都汇入OPEN而COMPLETED汇入RESOLVED。相同的“终态”分类不代表相同的业务副作用。九、完成路径为什么不能恢复 OPEN完成交接时ClaimService.complete()要求当前 Claim 必须为ACCEPTED。随后Claim 更新为COMPLETED目标拾得报告更新为RESOLVED如果存在关联丢失报告也更新为RESOLVED。这条路径代表物品已经安全交付再恢复OPEN会制造重复认领和重复候选。十、为什么状态恢复应放在 Service如果页面在拒绝成功后直接修改列表中的report.status其他页面、进程重启和 Repository 数据都可能仍保留旧值。Service 负责跨 Repository 的业务协调页面只触发动作、显示结果并通过dataRevision重新读取权威数据。这种分层还能让 Node 自动化直接调用生产 Service 验证规则而不需要模拟 ArkUI 点击。十一、本地回归测试如何防止“永久 CLAIMING”当前测试真实执行以下断言提交后目标报告为CLAIMING拒绝后 Claim 为REJECTED拒绝后目标报告恢复OPEN接受后取消Claim 为CANCELLED取消后目标报告恢复OPEN交接过期后 Claim 为EXPIRED过期后目标报告恢复OPEN双方完成后 Claim 和关联报告为完成态。powershell-ExecutionPolicy Bypass-File.\scripts\test-local-state-machines.ps1这组测试比只断言按钮文案更接近业务契约。十二、幂等不等于所有操作都可以重复transition()对“当前状态已经等于目标状态”返回成功因此重复接受不会再次改变数据。但取消、过期、完成各自有独立前置条件。一个操作是否应该幂等需要按业务语义定义不能简单把所有重复请求都返回成功。正式 API 还可以使用幂等键、请求 ID 和版本号区分“重复提交”与“状态已被其他人修改”。十三、当前跨实体更新的失败窗口现有 Service 按顺序写入 Claim 和 Report。任何一步失败都可能留下部分状态已完成写入后续失败可能结果Claim 已拒绝Report 恢复失败REJECTED CLAIMINGClaim 已取消Report 恢复失败CANCELLED CLAIMINGClaim 已完成Report 结案失败COMPLETED 非 RESOLVEDHandoff 已过期Claim 过期失败两套状态不一致当前通过明确错误信息和重新进入检查降低风险但这不是完整补偿事务。十四、正式联网版需要事务和并发控制多用户系统至少应补充服务端事务包裹 Claim、Handoff、Report 联动乐观锁或版本号阻止旧状态覆盖对同一 Report 建立唯一活跃申请约束失败后有可重试的补偿任务状态变更记录操作者、时间和来源查询接口检测并报告不一致状态管理端提供受控修复能力。单机状态机测试通过不能证明两个用户同时操作时仍然正确。十五、状态机改动前要做影响分析如果新增“申诉中”“等待补充材料”或“人工复核”状态需要同步检查哪些状态允许新申请哪些状态进入候选列表消息页显示什么文案交接页面能否打开取消、拒绝和过期的来源集合历史数据库值如何迁移自动化是否覆盖所有终态副作用部分失败如何反馈和补偿。只给枚举增加一个值会把未定义行为扩散到多个页面和 Service。十六、用户可见文案必须描述真实结果“已拒绝本次认领物品已重新开放认领”只有在两个动作都成功时才能展示。如果只完成前半部分页面应显示部分失败而不是继续使用成功文案。状态机可靠性不仅是数据库问题也包括用户是否被准确告知当前状态。工程复盘先写不变量表再修改迁移代码Claim 与 Report 联动时最有价值的文档不是一张只有箭头的状态图而是一组任何实现都必须满足的不变量。对当前流程而言至少包括存在活跃认领时目标 Report 不能仍为OPEN认领被拒绝、取消或过期后如果没有其他活跃认领目标 Report 必须重新可认领交接完成后 Claim 与关联 Report 必须同时表达结束不能一边显示COMPLETED另一边继续出现在可认领列表。不变量可以直接转成测试矩阵。每一行写清 Claim 初始状态、Report 初始状态、操作、预期 Claim、预期 Report 和允许的重复调用结果。这样新增一个状态时工程师会立刻看到它需要回答哪些问题而不是只给枚举增加名称。例如未来增加“争议处理中”就必须明确它是否占用物品、能否取消、是否允许新申请以及结束争议后恢复到哪个状态。跨 Repository 顺序写入还需要失败矩阵。第一步失败时通常没有副作用可以直接返回第一步成功、第二步失败时则必须暴露“部分完成”并进入重试或补偿路径。当前单机实现尚未提供事务因此最安全的做法是让错误信息保留可诊断上下文同时保证重复执行不会把终态倒退。正式服务端可以使用同一事务也可以用操作记录加幂等键完成补偿但不能用一个宽泛的try/catch把部分成功描述成完全失败。上线前的数据迁移同样要检查不变量。旧版本里如果已经存在REJECTED CLAIMING、CANCELLED CLAIMING等不一致组合单纯发布新代码不会自动修复历史记录。迁移脚本应先只读统计异常组合再按可解释规则修复并保留数量、范围和失败项无法确定的记录进入人工复核不能批量猜测真实归属。页面验收则要关注权威刷新操作成功后从 Service 重新读取 canonical data列表、详情和消息页都由同一状态计算展示。不要在三个页面分别手工把角标减一、把卡片移除、把按钮改色否则下一次冷启动很容易暴露彼此矛盾的缓存。十七、本文小结认领申请和物品报告是两套职责不同、必须联动的状态机。REJECTED / CANCELLED / EXPIRED代表本次申请结束但物品仍可能等待真正失主所以目标 Report 必须恢复OPEN只有双方完成交接后才进入RESOLVED。“寻迹校园”已经通过生产 Service 和本地回退 Repository 验证主要迁移及副作用。跨实体事务、并发版本锁、服务端补偿和真实多用户权限仍未实现不能从单机测试中外推。系列导航第 27 篇 / 共 50 篇。上一篇《认领审核多层门禁》下一篇《固定校内交接点的隐私设计》。
返回列表