fix(error-message): 删除用户可见错误文案规范HTML文档
- 移除了完整的用户可见错误文案规范HTML文件
This commit is contained in:
589
用户可见错误文案规范.html
589
用户可见错误文案规范.html
@@ -1,589 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
|
||||
<title>用户可见错误文案规范 · cn-rdms</title>
|
||||
<style>
|
||||
:root {
|
||||
--fg: #1f2328;
|
||||
--fg-muted: #57606a;
|
||||
--bg: #ffffff;
|
||||
--bg-soft: #f6f8fa;
|
||||
--border: #d0d7de;
|
||||
--border-soft: #e7ecf2;
|
||||
--accent: #0969da;
|
||||
--accent-soft: #ddf4ff;
|
||||
--purple: #8250df;
|
||||
--purple-soft: #fbefff;
|
||||
--warn: #9a6700;
|
||||
--warn-soft: #fff8c5;
|
||||
--danger: #cf222e;
|
||||
--danger-soft: #ffebe9;
|
||||
--ok: #1a7f37;
|
||||
--ok-soft: #dafbe1;
|
||||
--code-bg: #f6f8fa;
|
||||
}
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background: var(--bg);
|
||||
color: var(--fg);
|
||||
font-family:
|
||||
-apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Microsoft YaHei', 'Hiragino Sans GB',
|
||||
'Source Han Sans CN', 'Noto Sans CJK SC', Helvetica, Arial, sans-serif;
|
||||
font-size: 15px;
|
||||
line-height: 1.75;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
body {
|
||||
display: grid;
|
||||
grid-template-columns: 240px 1fr;
|
||||
min-height: 100vh;
|
||||
}
|
||||
aside.sidebar {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
align-self: start;
|
||||
height: 100vh;
|
||||
overflow-y: auto;
|
||||
background: var(--bg-soft);
|
||||
border-right: 1px solid var(--border);
|
||||
padding: 24px 18px;
|
||||
}
|
||||
aside.sidebar .sidebar-title {
|
||||
font-size: 13px;
|
||||
color: var(--fg-muted);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
margin: 0 0 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
aside.sidebar nav ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
aside.sidebar nav li {
|
||||
margin: 2px 0;
|
||||
}
|
||||
aside.sidebar nav a {
|
||||
color: var(--fg);
|
||||
text-decoration: none;
|
||||
font-size: 13px;
|
||||
display: block;
|
||||
padding: 4px 10px;
|
||||
border-radius: 4px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
aside.sidebar nav a:hover {
|
||||
background: var(--accent-soft);
|
||||
color: var(--accent);
|
||||
}
|
||||
.wrap {
|
||||
max-width: 920px;
|
||||
margin: 0 auto;
|
||||
padding: 48px 32px 96px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
header.doc-header {
|
||||
border-bottom: 1px solid var(--border);
|
||||
padding-bottom: 16px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
header.doc-header h1 {
|
||||
margin: 0 0 8px;
|
||||
font-size: 28px;
|
||||
}
|
||||
header.doc-header .meta {
|
||||
color: var(--fg-muted);
|
||||
font-size: 13px;
|
||||
margin: 2px 0;
|
||||
}
|
||||
header.doc-header .lead {
|
||||
font-size: 16px;
|
||||
color: var(--fg);
|
||||
margin: 12px 0 0;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin: 44px 0 12px;
|
||||
padding: 10px 16px;
|
||||
background: var(--accent-soft);
|
||||
border-left: 5px solid var(--accent);
|
||||
font-size: 21px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
h3 {
|
||||
margin: 26px 0 8px;
|
||||
font-size: 17px;
|
||||
}
|
||||
p {
|
||||
margin: 8px 0;
|
||||
}
|
||||
ul,
|
||||
ol {
|
||||
margin: 8px 0;
|
||||
padding-left: 24px;
|
||||
}
|
||||
ul li,
|
||||
ol li {
|
||||
margin: 4px 0;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: 'JetBrains Mono', Menlo, Consolas, 'Courier New', monospace;
|
||||
font-size: 13px;
|
||||
background: var(--code-bg);
|
||||
padding: 1px 6px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--border-soft);
|
||||
word-break: break-all;
|
||||
}
|
||||
pre {
|
||||
font-family: 'JetBrains Mono', Menlo, Consolas, 'Courier New', monospace;
|
||||
font-size: 13px;
|
||||
background: var(--code-bg);
|
||||
border: 1px solid var(--border-soft);
|
||||
border-radius: 6px;
|
||||
padding: 14px 16px;
|
||||
overflow-x: auto;
|
||||
line-height: 1.55;
|
||||
}
|
||||
pre code {
|
||||
background: transparent;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
margin: 12px 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
th,
|
||||
td {
|
||||
border: 1px solid var(--border);
|
||||
padding: 8px 12px;
|
||||
text-align: left;
|
||||
vertical-align: top;
|
||||
}
|
||||
th {
|
||||
background: var(--bg-soft);
|
||||
font-weight: 600;
|
||||
}
|
||||
tr:nth-child(2n) td {
|
||||
background: #fafbfc;
|
||||
}
|
||||
|
||||
.callout {
|
||||
border-left: 4px solid var(--accent);
|
||||
background: var(--accent-soft);
|
||||
padding: 10px 14px;
|
||||
border-radius: 4px;
|
||||
margin: 14px 0;
|
||||
}
|
||||
.callout.warn {
|
||||
border-left-color: var(--warn);
|
||||
background: var(--warn-soft);
|
||||
}
|
||||
.callout.danger {
|
||||
border-left-color: var(--danger);
|
||||
background: var(--danger-soft);
|
||||
}
|
||||
.callout.ok {
|
||||
border-left-color: var(--ok);
|
||||
background: var(--ok-soft);
|
||||
}
|
||||
.callout .title {
|
||||
font-weight: 600;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.bad {
|
||||
color: var(--danger);
|
||||
font-weight: 600;
|
||||
}
|
||||
.good {
|
||||
color: var(--ok);
|
||||
font-weight: 600;
|
||||
}
|
||||
footer.doc-footer {
|
||||
margin-top: 56px;
|
||||
padding-top: 16px;
|
||||
border-top: 1px solid var(--border);
|
||||
color: var(--fg-muted);
|
||||
font-size: 13px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<aside class="sidebar">
|
||||
<div class="sidebar-title">目录</div>
|
||||
<nav>
|
||||
<ul>
|
||||
<li><a href="#理念">1 · 核心理念</a></li>
|
||||
<li><a href="#实现">2 · 技术实现</a></li>
|
||||
<li><a href="#决策">3 · 关键设计决策</a></li>
|
||||
<li><a href="#清单">4 · 新功能落地清单</a></li>
|
||||
<li><a href="#缺口">5 · 信息泄漏类红线</a></li>
|
||||
<li><a href="#关联">6 · 关联文档</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</aside>
|
||||
<div class="wrap">
|
||||
<header class="doc-header">
|
||||
<h1>用户可见错误文案规范</h1>
|
||||
<p class="meta">来源:技术负债 TD-012 · 落地日期 2026-06-03 · 文档 2026-06-04</p>
|
||||
<p class="meta">
|
||||
适用范围:整仓所有"状态机动作 / 状态校验失败"的业务异常,凡
|
||||
<code>message</code>
|
||||
会被前端直接展示者
|
||||
</p>
|
||||
<p class="lead">
|
||||
给前端
|
||||
<code>toast</code>
|
||||
的
|
||||
<code>message</code>
|
||||
只放用户能看懂的中文;动作 / 状态的内部 code、堆栈等技术细节不进
|
||||
<code>message</code>
|
||||
,由访问日志承载。本规范是必须遵守的跨模块约定,新功能照做。
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<h2 id="理念">1 · 核心理念</h2>
|
||||
<p><strong>message 面向用户、诊断面向开发,两者分离。</strong></p>
|
||||
<p>
|
||||
前端往往直接把后端业务异常的
|
||||
<code>message</code>
|
||||
弹给最终用户。如果
|
||||
<code>message</code>
|
||||
里夹着
|
||||
<code>complete</code>
|
||||
/
|
||||
<code>status</code>
|
||||
/
|
||||
<code>action</code>
|
||||
这类内部术语,用户看不懂,会误以为系统异常或数据没保存。
|
||||
</p>
|
||||
<div class="callout danger">
|
||||
<div class="title">反例(TD-012 的原始案例)</div>
|
||||
用户点"完成任务",第一次请求已把任务置为已完成;前端重复发了第二次
|
||||
<code>complete</code>
|
||||
动作,后端返回
|
||||
<span class="bad">"当前任务状态不支持动作【complete】"</span>
|
||||
。用户合理的预期是看到
|
||||
<span class="good">"任务已完成,请勿重复提交"</span>
|
||||
这类人话。
|
||||
</div>
|
||||
<p>
|
||||
因此约定:
|
||||
<strong>
|
||||
用户看友好中文(
|
||||
<code>message</code>
|
||||
),开发排查看访问日志(
|
||||
<code>infra_api_access_log</code>
|
||||
里有原始 code、入参、堆栈)。
|
||||
</strong>
|
||||
两条信息流互不污染。
|
||||
</p>
|
||||
|
||||
<h2 id="实现">2 · 技术实现</h2>
|
||||
<p>
|
||||
整套方案
|
||||
<strong>零新表、framework 零改动</strong>
|
||||
,纯在
|
||||
<code>rdms-project</code>
|
||||
域内:一个解析器组件 + 错误码文案模板改造 + service 接入。
|
||||
</p>
|
||||
|
||||
<h3>
|
||||
2.1 文案解析器
|
||||
<code>StatusActionTextResolver</code>
|
||||
</h3>
|
||||
<p>
|
||||
位置:
|
||||
<code>rdms-project-boot · service/status/StatusActionTextResolver.java</code>
|
||||
(
|
||||
<code>@Component</code>
|
||||
)。把动作 / 状态的 code 翻成中文展示名,供错误文案使用。
|
||||
</p>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>方法</th>
|
||||
<th>作用</th>
|
||||
<th>查不到 / 空入参</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>actionName(objectType, actionCode)</code></td>
|
||||
<td>动作中文名</td>
|
||||
<td>
|
||||
回退原
|
||||
<code>actionCode</code>
|
||||
,不抛错
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>statusName(objectType, statusCode)</code></td>
|
||||
<td>状态中文名</td>
|
||||
<td>
|
||||
回退原
|
||||
<code>statusCode</code>
|
||||
,不抛错
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="callout purple">
|
||||
<div class="title">权威源:DB 状态机表,不在代码里硬编码映射</div>
|
||||
动作名取自
|
||||
<code>rdms_object_status_transition.action_name</code>
|
||||
,状态名取自
|
||||
<code>rdms_object_status_model.status_name</code>
|
||||
。运维在状态机表里配新动作 / 新状态,文案自动生效,
|
||||
<strong>不用改代码发版</strong>
|
||||
。
|
||||
</div>
|
||||
|
||||
<h3>2.2 错误码文案用「{}」占位中文名</h3>
|
||||
<p>错误码定义时,文案就留中文名占位,由 service 在抛错前用 resolver 填入。例如:</p>
|
||||
<pre><code>// 个人事项 —— 正面样板
|
||||
ErrorCode PERSONAL_ITEM_STATUS_ACTION_NOT_ALLOWED =
|
||||
new ErrorCode(1_008_008_004, "当前个人事项为「{}」状态,不支持「{}」操作");</code></pre>
|
||||
<p>
|
||||
抛出前:
|
||||
<code>
|
||||
exception(PERSONAL_ITEM_STATUS_ACTION_NOT_ALLOWED, resolver.statusName(...), resolver.actionName(...))
|
||||
</code>
|
||||
—— 占位填的是中文名,不是裸 code。
|
||||
</p>
|
||||
|
||||
<h3>2.3 已接入面(7 个 service)</h3>
|
||||
<p>状态机校验失败抛错时,先经 resolver 翻译再返回的 service:</p>
|
||||
<ul>
|
||||
<li>
|
||||
<code>ProductRequirementServiceImpl</code>
|
||||
(产品需求)
|
||||
</li>
|
||||
<li>
|
||||
<code>PersonalItemServiceImpl</code>
|
||||
(个人事项)
|
||||
</li>
|
||||
<li>
|
||||
<code>ProjectTaskServiceImpl</code>
|
||||
(任务)
|
||||
</li>
|
||||
<li>
|
||||
<code>ProductServiceImpl</code>
|
||||
(产品)
|
||||
</li>
|
||||
<li>
|
||||
<code>ProjectExecutionServiceImpl</code>
|
||||
(执行)
|
||||
</li>
|
||||
<li>
|
||||
<code>ProjectServiceImpl</code>
|
||||
(项目)
|
||||
</li>
|
||||
<li>
|
||||
<code>ProjectRequirementServiceImpl</code>
|
||||
(项目需求)
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h2 id="决策">3 · 关键设计决策</h2>
|
||||
<ol>
|
||||
<li>
|
||||
<strong>不硬编码映射,跟 DB 状态机走。</strong>
|
||||
中文名是状态机表里运维可配的数据,不复刻成 Java 常量 / Enum;加新动作、新状态不需要改代码(呼应仓库"字典 /
|
||||
状态值不复刻成 Java 常量"的纪律)。
|
||||
</li>
|
||||
<li>
|
||||
<strong>解析器只翻译、绝不抛错。</strong>
|
||||
空入参或查不到一律回退原 code。它是文案美化层,不能反过来变成新的故障点——翻译失败也要让原始业务异常正常返回。
|
||||
</li>
|
||||
<li>
|
||||
<strong>轻量、可回滚。</strong>
|
||||
纯加一个 Component + 改错误码文案模板 + service 接入,不新表、不动 framework、复用现有错误码体系,因此
|
||||
<strong>无演示库补丁、前端零改动</strong>
|
||||
。
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
<h2 id="清单">4 · 新功能落地清单</h2>
|
||||
<div class="callout ok">
|
||||
<div class="title">凡新增"状态机动作 / 状态校验"且 message 会被前端展示,照此四步</div>
|
||||
</div>
|
||||
<ol>
|
||||
<li>
|
||||
service 注入
|
||||
<code>StatusActionTextResolver</code>
|
||||
。
|
||||
</li>
|
||||
<li>
|
||||
错误码文案写成「{}」占位中文名(参考
|
||||
<code>PERSONAL_ITEM_STATUS_ACTION_NOT_ALLOWED</code>
|
||||
),
|
||||
<span class="bad">不要把 code 直接嵌进文案</span>
|
||||
。
|
||||
</li>
|
||||
<li>
|
||||
抛错前用
|
||||
<code>actionName / statusName</code>
|
||||
把 code 翻成中文名再填占位。
|
||||
</li>
|
||||
<li>
|
||||
新对象类型在
|
||||
<code>rdms_object_status_model</code>
|
||||
/
|
||||
<code>rdms_object_status_transition</code>
|
||||
配好
|
||||
<code>status_name</code>
|
||||
/
|
||||
<code>action_name</code>
|
||||
,resolver 即自动生效。
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
<h2 id="缺口">5 · 信息泄漏类红线</h2>
|
||||
<p>
|
||||
除"状态机动作 / 状态翻中文"外,凡
|
||||
<code>message</code>
|
||||
会被前端直接展示,
|
||||
<strong>以下技术 token 一律不得出现在 message 里</strong>
|
||||
,只能进日志(
|
||||
<code>log.warn</code>
|
||||
/
|
||||
<code>infra_api_access_log</code>
|
||||
):
|
||||
</p>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>禁止外泄</th>
|
||||
<th>反例</th>
|
||||
<th>正确做法</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>数据库表名 / 列名</td>
|
||||
<td>
|
||||
"未在
|
||||
<code>system_role</code>
|
||||
找到"
|
||||
</td>
|
||||
<td>"…未配置,请联系管理员"</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>权限码 / 内部标记</td>
|
||||
<td>
|
||||
"操作权限【
|
||||
<code>project:project:update</code>
|
||||
】"、"【
|
||||
<code>member</code>
|
||||
】"
|
||||
</td>
|
||||
<td>"您没有此项操作权限,请联系管理员"</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>动作 / 状态 code</td>
|
||||
<td>
|
||||
"不支持动作【
|
||||
<code>complete</code>
|
||||
】"
|
||||
</td>
|
||||
<td>resolver 翻中文名(见 §2)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>类名 / 字段名 / 堆栈</td>
|
||||
<td>
|
||||
"
|
||||
<code>NullPointerException at ...</code>
|
||||
"
|
||||
</td>
|
||||
<td>友好提示,异常进日志</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="callout ok">
|
||||
<div class="title">2026-06-04 · B 类存量整改已落地</div>
|
||||
<ul>
|
||||
<li>
|
||||
<strong>加班申请</strong>
|
||||
:
|
||||
<code>OvertimeApplicationServiceImpl</code>
|
||||
已注入 resolver,文案对齐其它域「当前加班申请为「{}」状态,不支持「{}」操作」。
|
||||
</li>
|
||||
<li>
|
||||
<strong>操作权限不足</strong>
|
||||
:
|
||||
<code>Project/ProductObjectPermissionService</code>
|
||||
已去占位、权限码改
|
||||
<code>log.warn</code>
|
||||
;错误码文案改"您没有该项目/产品的此项操作权限,请联系管理员"。
|
||||
</li>
|
||||
<li>
|
||||
<strong>表名外泄</strong>
|
||||
:
|
||||
<code>PRODUCT/PROJECT_INTERNAL_ROLE_NOT_CONFIGURED</code>
|
||||
两条已去掉
|
||||
<code>system_role</code>
|
||||
。
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="callout ok">
|
||||
<div class="title">2026-06-04 · 加班申请 service 层单测已补</div>
|
||||
新增
|
||||
<code>OvertimeApplicationServiceImplTest</code>
|
||||
(2 用例,
|
||||
<code>mvn test</code>
|
||||
通过):验证状态机「动作不允许 / 缺原因」抛错时,
|
||||
<code>message</code>
|
||||
填的是
|
||||
<code>StatusActionTextResolver</code>
|
||||
翻出的中文名、不外泄英文动作 / 状态 code。属 Mockito 单测(mock resolver),覆盖的是 service 层「填中文名而非裸
|
||||
code」这条契约;resolver 自身的 DB 翻译由
|
||||
<code>StatusActionTextResolverTest</code>
|
||||
覆盖。真实 DB 状态机表是否配齐
|
||||
<code>status_name</code>
|
||||
/
|
||||
<code>action_name</code>
|
||||
的端到端校验仍依赖运行时,不在单测范围。
|
||||
</div>
|
||||
|
||||
<h2 id="关联">6 · 关联文档</h2>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="./对象状态能力落地规范.md">对象状态能力落地规范.md</a>
|
||||
—— 状态机模型与流转设计,本规范的中文名权威源即来自这两张表。
|
||||
</li>
|
||||
<li>
|
||||
<a href="../debt/技术负债台账.html">技术负债台账 · TD-012</a>
|
||||
—— 本规范的需求来源条目。
|
||||
</li>
|
||||
<li>
|
||||
技术诊断承载:
|
||||
<code>infra_api_access_log</code>
|
||||
(访问日志,留原始 code / 入参 / 堆栈)。
|
||||
</li>
|
||||
<li>
|
||||
<code>CLAUDE.md · 接口语义</code>
|
||||
章节留有指向本文档的红线指针。
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<footer class="doc-footer">cn-rdms · 跨模块约定 · 用户可见错误文案规范</footer>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user