让重复点击只有一个结果:幂等请求的设计
按钮禁用只能改善体验。真正可靠的重试,需要固定输入、服务端回执与可以查询的原操作。
文章目录
网络请求没有返回结果,不代表它没有执行。文章可能已经保存,发布任务可能已经受理,只是回执在途中丢失。此时再点一次按钮,系统应该尽量找回原来的操作,而不是创建一个无法区分的新操作。
这正是幂等设计要面对的日常场景。
先区分动作和尝试
“把文章的第五版发布出去”是一次业务动作;点击按钮、网络重发、页面刷新后恢复,是完成这个动作的不同尝试。
如果每次尝试都分配新的操作标识,服务端就很难知道它们是否属于同一件事。较稳妥的方式是在动作发起前生成标识,把标识与固定输入一起保留下来,在查询与重试时继续使用。
这里的固定输入包括目标修订、发布基础包、所选设置和操作类型。只保留文章 ID,而重试时换成最新草稿,仍然会改变原动作。
同一个标识不能接受两份输入
服务端需要同时保存操作标识、输入摘要与结果回执。处理请求时可以分成三种情况:
| 情况 | 应有结果 |
|---|---|
| 标识尚未出现 | 验证输入并执行,保存业务变化和回执 |
| 标识已存在,输入相同 | 返回原回执 |
| 标识已存在,输入不同 | 返回冲突,不猜测用户想要哪一次 |
业务写入和回执应当处在一致的事务边界中。否则可能出现内容已经创建、回执却不存在的间隙,让重试再次创建内容。
对于耗时任务,回执可以是任务 ID。请求受理并不等于任务完成,界面还需要继续查询实际状态。
超时之后,先查原操作
一个实用的界面不应只剩下“失败,再试一次”。可以明确显示“结果暂未确认”,并保留查询入口。
恢复时先按原标识查询:已经完成就读取结果,仍在执行就继续等待,确认尚未受理才重新发送原输入。取消等待也不应被描述成取消了服务端已经开始的任务。
按钮禁用仍然有用,它能减少误触;但页面关闭、并发请求和网络重试都可能绕过它。关键约束需要在服务端建立。
用丢失回执验证设计
除了连续点击,还应该测试另一种更接近问题本质的情况:让请求在服务端成功处理后,丢弃响应,然后重新打开页面恢复操作。
检查重点包括记录是否重复、结果能否查询、原输入是否保留,以及换一份输入复用旧标识时是否被拒绝。这些观察比单纯检查按钮是否变灰更有说服力。
HTTP 方法本身有幂等语义的区分;业务中的发布和创建通常还需要自己的操作协议,不能仅凭使用了某个方法就省略上述约束。
参考:RFC 9110,第 9.2.2 节:Idempotent Methods。本文的操作标识与回执流程是本站业务设计,不是该规范规定的统一接口格式。