用户可见错误文案规范

来源:技术负债 TD-012 · 落地日期 2026-06-03 · 文档 2026-06-04

适用范围:整仓所有"状态机动作 / 状态校验失败"的业务异常,凡 message 会被前端直接展示者

给前端 toastmessage 只放用户能看懂的中文;动作 / 状态的内部 code、堆栈等技术细节不进 message ,由访问日志承载。本规范是必须遵守的跨模块约定,新功能照做。

1 · 核心理念

message 面向用户、诊断面向开发,两者分离。

前端往往直接把后端业务异常的 message 弹给最终用户。如果 message 里夹着 complete / status / action 这类内部术语,用户看不懂,会误以为系统异常或数据没保存。

反例(TD-012 的原始案例)
用户点"完成任务",第一次请求已把任务置为已完成;前端重复发了第二次 complete 动作,后端返回 "当前任务状态不支持动作【complete】" 。用户合理的预期是看到 "任务已完成,请勿重复提交" 这类人话。

因此约定: 用户看友好中文( message ),开发排查看访问日志( infra_api_access_log 里有原始 code、入参、堆栈)。 两条信息流互不污染。

2 · 技术实现

整套方案 零新表、framework 零改动 ,纯在 rdms-project 域内:一个解析器组件 + 错误码文案模板改造 + service 接入。

2.1 文案解析器 StatusActionTextResolver

位置: rdms-project-boot · service/status/StatusActionTextResolver.java@Component )。把动作 / 状态的 code 翻成中文展示名,供错误文案使用。

方法 作用 查不到 / 空入参
actionName(objectType, actionCode) 动作中文名 回退原 actionCode ,不抛错
statusName(objectType, statusCode) 状态中文名 回退原 statusCode ,不抛错
权威源:DB 状态机表,不在代码里硬编码映射
动作名取自 rdms_object_status_transition.action_name ,状态名取自 rdms_object_status_model.status_name 。运维在状态机表里配新动作 / 新状态,文案自动生效, 不用改代码发版

2.2 错误码文案用「{}」占位中文名

错误码定义时,文案就留中文名占位,由 service 在抛错前用 resolver 填入。例如:

// 个人事项 —— 正面样板
ErrorCode PERSONAL_ITEM_STATUS_ACTION_NOT_ALLOWED =
    new ErrorCode(1_008_008_004, "当前个人事项为「{}」状态,不支持「{}」操作");

抛出前: exception(PERSONAL_ITEM_STATUS_ACTION_NOT_ALLOWED, resolver.statusName(...), resolver.actionName(...)) —— 占位填的是中文名,不是裸 code。

2.3 已接入面(7 个 service)

状态机校验失败抛错时,先经 resolver 翻译再返回的 service:

3 · 关键设计决策

  1. 不硬编码映射,跟 DB 状态机走。 中文名是状态机表里运维可配的数据,不复刻成 Java 常量 / Enum;加新动作、新状态不需要改代码(呼应仓库"字典 / 状态值不复刻成 Java 常量"的纪律)。
  2. 解析器只翻译、绝不抛错。 空入参或查不到一律回退原 code。它是文案美化层,不能反过来变成新的故障点——翻译失败也要让原始业务异常正常返回。
  3. 轻量、可回滚。 纯加一个 Component + 改错误码文案模板 + service 接入,不新表、不动 framework、复用现有错误码体系,因此 无演示库补丁、前端零改动

4 · 新功能落地清单

凡新增"状态机动作 / 状态校验"且 message 会被前端展示,照此四步
  1. service 注入 StatusActionTextResolver
  2. 错误码文案写成「{}」占位中文名(参考 PERSONAL_ITEM_STATUS_ACTION_NOT_ALLOWED ), 不要把 code 直接嵌进文案
  3. 抛错前用 actionName / statusName 把 code 翻成中文名再填占位。
  4. 新对象类型在 rdms_object_status_model / rdms_object_status_transition 配好 status_name / action_name ,resolver 即自动生效。

5 · 信息泄漏类红线

除"状态机动作 / 状态翻中文"外,凡 message 会被前端直接展示, 以下技术 token 一律不得出现在 message 里 ,只能进日志( log.warn / infra_api_access_log ):

禁止外泄 反例 正确做法
数据库表名 / 列名 "未在 system_role 找到" "…未配置,请联系管理员"
权限码 / 内部标记 "操作权限【 project:project:update 】"、"【 member 】" "您没有此项操作权限,请联系管理员"
动作 / 状态 code "不支持动作【 complete 】" resolver 翻中文名(见 §2)
类名 / 字段名 / 堆栈 " NullPointerException at ... " 友好提示,异常进日志
2026-06-04 · B 类存量整改已落地
2026-06-04 · 加班申请 service 层单测已补
新增 OvertimeApplicationServiceImplTest (2 用例, mvn test 通过):验证状态机「动作不允许 / 缺原因」抛错时, message 填的是 StatusActionTextResolver 翻出的中文名、不外泄英文动作 / 状态 code。属 Mockito 单测(mock resolver),覆盖的是 service 层「填中文名而非裸 code」这条契约;resolver 自身的 DB 翻译由 StatusActionTextResolverTest 覆盖。真实 DB 状态机表是否配齐 status_name / action_name 的端到端校验仍依赖运行时,不在单测范围。

6 · 关联文档