从用户需要采取的动作设计错误
接口失败以后,只显示操作失败会让用户不知道应该重试、修改输入还是重新登录。另一方面,把服务器异常原文直接搬到界面,可能既难理解又暴露内部信息。设计错误提示时,先明确当前操作的结果是否已知,以及用户下一步可以做什么。文章读取失败与保存结果不明,需要不同提示;权限不足与服务暂时不可用,也不能采用同一恢复按钮。本文围绕后台列表、编辑和登录的接口交互说明错误契约,不改变项目已经采用的响应封装。
可以为每种错误保留稳定类型、状态、适合用户的说明和适用恢复动作,服务端另存脱敏诊断信息。HTTP 状态表达基本类别,业务错误类型表达更具体原因,关联标识帮助维护者找到对应日志。它们不应混成一段随版本变化的异常字符串。用户无需知道数据库表名或部署路径,维护者也不能只收到一句没有上下文的失败。把这两种信息分别送到对应位置,可以同时降低界面负担和保留排查能力。
状态码与业务结果需要一起解释
未认证通常需要恢复会话,权限不足需要说明操作范围,输入校验需要指向可以修改的字段,版本冲突则需要核对当前内容。限流可能要求等待,系统暂时失败才适合某些重试。不能把所有非成功响应都当作网络异常,也不能把状态成功的响应直接当作业务成功:若项目采用统一封装,还需要检查业务字段。状态与业务类型的对应规则应由接口契约定义,客户端按明确规则解释,不依赖模糊的错误消息关键词。
RFC 9457 提供一种表达 HTTP API 问题详情的格式,可以作为设计参考,但已有项目不必为了改进提示立刻更换全部响应结构。重要的是稳定字段与适用范围一致,并避免在不同接口中采用互相矛盾的含义。对旧接口,可以先增加统一的客户端解析层,把已知封装转换成内部错误对象。迁移过程中保留兼容分支与必要测试,不能让某个接口失败时因为字段位置不同再次抛出解析异常,最后只留下空白提示。
用户说明来自受控的安全信息
错误消息应由服务端明确允许公开的字段或客户端受控映射产生。不要把任意异常堆栈、数据库错误和外部服务返回的 HTML 直接展示给用户。界面仍需使用安全的文本渲染方式,避免把消息当作可执行标记。对于已知业务错误,可以给出具体且不泄漏敏感状态的说明;对于未知错误,采用清楚的通用恢复信息,并保留关联标识。通用不等于含糊,它仍应说明哪个操作没有完成,以及当前能够尝试什么。
未知账号和错误口令通常不宜暴露不同的身份信息,登录提示应遵循认证接口的约定。权限错误也不需要列出内部权限表或其他账号名称。用户可见内容可以保持简洁,诊断记录则说明接口、错误类型和经过脱敏的必要上下文。不要为了便于排查保存完整口令、会话头和私人正文。关联信息应能帮助维护者定位同一次事件,而不是成为一份长期收集全部请求内容的日志副本。
恢复动作必须适合当前操作
读取列表失败时,可以在当前条件下重新获取;输入校验失败时,应该保留输入并指出需要修改的字段;权限不足时,反复请求一般不会改变结果。会话过期可以引导重新验证身份,但要考虑未保存内容。版本冲突应让用户比较服务器当前版本与本地修改,而不是无条件覆盖。每种恢复动作都需要理解页面拥有的状态,因此客户端可以统一解析错误,最终交互仍由具体页面决定。
保存请求超时尤其需要谨慎。客户端没有取得响应,不说明服务端一定没有提交;直接显示保存失败并自动重试,可能产生重复操作。接口应提供结果确认或幂等约定,界面说明状态待确认并按约定查询。删除、发布和其他写入也有相同边界。本文的重试标记只描述适用条件,不表示所有失败都允许立即重放。恢复按钮应对应当前资源与版本,不能在用户已经切页以后继续执行旧任务。
保留表单状态并把错误放在合适位置
表单级错误可以说明整个提交结果,字段级错误可以说明具体输入问题。失败以后保留用户已经输入的正文和选择,避免让他们重新完成全部工作。焦点可以移动到错误摘要或相关字段,提示应能被辅助技术识别。提交按钮的加载状态需要在成功与失败以后正确结束,但不能因此把页面的未保存状态清空。一个弹出提示瞬间消失,不适合承担仍需要用户处理的校验问题;持久提示更有利于恢复操作。
后台共享错误区域适合展示当前页面范围内的请求问题,但不要用它遮住所有操作细节。某个图片上传失败,应与对应文件和恢复方式联系起来,而不只是显示后台失败。正常导航取消的请求应退出展示,旧任务晚到的异常也不能覆盖新页面。错误状态与任务身份需要保持一致。这样页面既能共享视觉与解析规则,又不会丢掉业务操作需要的具体上下文,也能减少成功操作被其他残留错误误报的情况。
自动重试需要控制范围与次数
读取操作可以根据接口约定采用有限重试,但应考虑等待时间、请求成本与服务器状态。收到限流信息时,按照适用的等待条件处理,避免每个组件同时立即重试。系统暂时失败也不意味着无限循环可以解决;达到约定次数以后,应让用户看到结果并选择下一步。自动重试与用户点击重试需要协调,防止同时启动多个任务。对写入,必须先满足结果确认或幂等条件,再讨论重试策略。
恢复界面可以说明需要等待、重新登录、修改输入或联系维护者,而不是对所有情况显示重新加载。若用户点击重试,旧错误应退出当前任务范围,新请求结束以后再更新状态。不可重试的错误不要提供会重复同一条件的按钮。等待提示应避免假造准确恢复时间;接口只提供一个等待窗口时,就按这个窗口说明。对于未知故障,可以提供脱敏关联标识,便于维护者调查,但不让用户承担阅读系统堆栈的任务。
诊断信息与公开响应保持对应
服务端可以为请求事件生成关联标识,并在错误响应与日志中使用它。记录状态、稳定类型、操作名称和必要资源身份,限制敏感内容与日志保留。客户端上报同样应有范围,不能把用户编辑正文全部复制进去。诊断信息的目标是帮助还原触发路径,而不是尽可能多地保存数据。公开响应中的安全说明与内部日志中的技术原因可以不同,但应能对应到同一次事件,避免维护者根据另一条时间相近的错误作出判断。
代理错误与应用错误还要区分来源。上游不可达时,客户端可能收到代理生成的页面,不能假定它符合应用 JSON 契约。解析层应先检查可用的结构,失败时进入安全降级,而不是再次报 JSON 解析错误。部署和网络问题可以使用通用恢复说明,并由服务端或入口日志定位。错误提示改进不能掩盖真实系统故障,因此未知结构需要被观察与记录,但不应该把原始内容直接泄露到公开界面。
用独立场景验证提示与恢复
验证可以依次覆盖有效会话、会话过期、权限不足、字段校验、版本冲突、限流和系统故障。每项检查状态解析、用户说明、按钮行为与输入是否保留。对保存超时,还要检查服务端已提交和未提交两种结果,证明界面按契约确认,而不是重复写入。测试使用隔离账号与数据,避免锁住真实管理员或修改真实文章。记录具体动作和最终状态即可,不需要编造满意度或错误减少比例。
还应测试未知响应、代理 HTML 和主动取消,确保解析层不会自己制造新的白屏。键盘与辅助技术能够找到持久错误提示,窄屏下也应看到完整恢复动作。没有执行的场景保留为待检查项,不能以一条普通失败提示显示正常就代表全部契约正确。自动化测试可以关注状态与行为,视觉检查关注可读性,两者共同说明用户是否能够继续操作。验证范围应绑定当前接口与页面版本,升级以后重复必要场景。
让错误契约成为接口的一部分
接口设计和评审可以同时查看成功结构与失败结构,明确哪些类型稳定、哪些消息可公开、哪些动作可重试。客户端共用解析层,页面共用基础提示组件,但保留业务恢复差异。新增接口时补充相应映射与场景,旧接口迁移时保留兼容测试。这样,错误处理就不再是每个 catch 分支临时拼一句话,而是与状态和结果确认一起维护的交互契约。
本文适用于后台 API 与页面提示的组织方式,不替代接口自身的权限、幂等和输入校验。好的提示不能让一个不可靠的写入自动可靠,也不能把系统不可用变成用户的责任。完成验收的标准是原因能够理解、输入按约定保留、恢复动作确实适用,诊断信息也能定位而不泄漏。把这些条件落到具体场景以后,用户遇到错误时才有可执行的下一步,而不是只能重复同一动作等待偶然成功。
实现片段
type Hint = { message: string; action: string; retryable: boolean }
const hints: Record<number, Hint> = {
400: { message: '提交内容需要调整。', action: '检查标出的字段', retryable: false },
401: { message: '会话已失效。', action: '保留修改后重新登录', retryable: false },
403: { message: '当前账号不能执行此操作。', action: '核对账号权限', retryable: false },
409: { message: '内容版本已变化。', action: '读取当前版本并比较修改', retryable: false },
429: { message: '请求过于频繁。', action: '按响应等待条件再尝试', retryable: true },
503: { message: '服务暂时不可用。', action: '稍后重试读取操作', retryable: true }
}
export function userHint(status: number): Hint {
return hints[status] ?? {
message: '请求未完成。', action: '保留当前内容并核对操作结果', retryable: false
}
}
// 写入能否重试,还需检查结果确认与幂等契约,不由状态码单独决定。
评论 · 0