docs(common): @Valid 校验失败 toast 去掉字段名前缀,提示更友好 (PR #1916)
hl-common-log 全局 GlobalExceptionHandler 改动,影响 6 个服务所有 @Valid + @RequestBody 接口的 400 校验响应文案。message 字符串从 "字段名: 文案" 变为 "仅文案"(多字段仍以 ;空格 分隔)。无前端改动情况下绝大多数 Toast(message) 直接更友好。提醒 mmg 全局搜 message.startsWith / split / match 等解析逻 辑确认无误。
这个提交包含在:
父节点
6870ffd178
当前提交
b6676fbb5f
@ -0,0 +1,129 @@
|
|||||||
|
# @Valid 校验失败 toast 去掉字段名前缀,提示更友好
|
||||||
|
|
||||||
|
> **服务**: hl-common-log (共享 module,影响全部 6 个后端服务)
|
||||||
|
> **PR**: #1916
|
||||||
|
> **Issue**: 无(从前端 BUG 通知 `09_frontend_notice_admin_service-edit-categorycode-required.md` 衍生的后端体验改善)
|
||||||
|
> **日期**: 2026-05-09
|
||||||
|
> **影响范围**: 全部 admin / mp / 内部接口的 `@Valid + @RequestBody` 触发的 400 校验响应文案
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚠️ 关键变化
|
||||||
|
|
||||||
|
后端全局 `MethodArgumentNotValidException` / `BindException` 处理器返回的 `Result.message` 文案格式变了:
|
||||||
|
|
||||||
|
| | 改前 | 改后 |
|
||||||
|
|---|---|---|
|
||||||
|
| **单字段错误** | `categoryCode: 分类编码不能为空` | `分类编码不能为空` |
|
||||||
|
| **多字段错误** | `name: 姓名不能为空; age: 必须为正数` | `姓名不能为空; 必须为正数` |
|
||||||
|
| **defaultMessage 为 null 时** | 字段名: null | 被过滤掉(filter `Objects::nonNull`) |
|
||||||
|
|
||||||
|
`Result.code` 仍是 `400`,HTTP status 仍是 `200`(项目惯例),只是 `message` 字符串格式不再带 Java 字段名前缀。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 一、背景
|
||||||
|
|
||||||
|
正式环境 admin 后台,用户编辑服务时 toast 显示 `categoryCode: 分类编码不能为空`(同日 changelog `09_frontend_notice_admin_service-edit-categorycode-required.md` 已确认前端误调 POST 创建接口为根因)。即使前端修了误调接口,**`categoryCode:` 这种字段名前缀对最终用户来说是技术噪音 — 用户不该看到 Java 字段名**,所以后端这次顺手做了体验改善。
|
||||||
|
|
||||||
|
`hl-common-log` 是 6 个服务的共享异常处理 module,改一处全部受益。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 二、改动接口清单
|
||||||
|
|
||||||
|
**所有 `@Valid + @RequestBody` 接口都受影响**(数量约 200+,无法一一列举)。常见高频:
|
||||||
|
|
||||||
|
- `POST /admin/*/save` / `POST /admin/*/create`
|
||||||
|
- `PUT /admin/*/update`
|
||||||
|
- `POST /mp/*/submit`
|
||||||
|
- 几乎所有带 `@RequestBody` 的写入接口
|
||||||
|
|
||||||
|
不影响:
|
||||||
|
- `@Validated` query / path 参数(走 `ConstraintViolationException`,本次未改)
|
||||||
|
- 业务异常(`BizException` 等)
|
||||||
|
- 系统异常(NPE / 序列化错误等)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 三、对前端 (mmg) 的影响
|
||||||
|
|
||||||
|
### 1. 必看:有没有 `message` 解析逻辑
|
||||||
|
|
||||||
|
如果 hl-ui / 小程序里有以下代码,**会失效**:
|
||||||
|
|
||||||
|
```js
|
||||||
|
// 类似这样的 message 解析逻辑都需要排查
|
||||||
|
if (response.message.startsWith('categoryCode:')) { ... }
|
||||||
|
const [field, msg] = response.message.split(':')
|
||||||
|
const fieldName = response.message.match(/^(\w+):/)?.[1]
|
||||||
|
```
|
||||||
|
|
||||||
|
请 mmg 全局搜以下关键字检查:
|
||||||
|
- `\.message\.startsWith\(`
|
||||||
|
- `\.message\.split\(':?'\)`
|
||||||
|
- `\.message\.match`
|
||||||
|
- `\.message\.indexOf\(':'\)`
|
||||||
|
|
||||||
|
### 2. 实际更友好
|
||||||
|
|
||||||
|
绝大多数情况下,前端只是 `Toast(response.message)` 直接显示,本次改动让用户看到的提示**直接更友好**,**不需要任何前端改动**。
|
||||||
|
|
||||||
|
### 3. 多字段错误仍可读
|
||||||
|
|
||||||
|
多字段错误时仍以 `;空格` 分隔,例如:
|
||||||
|
|
||||||
|
```
|
||||||
|
姓名不能为空; 手机号格式不正确; 年龄必须为正数
|
||||||
|
```
|
||||||
|
|
||||||
|
足以让用户看清所有问题。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 四、技术细节
|
||||||
|
|
||||||
|
### 改动前
|
||||||
|
|
||||||
|
```java
|
||||||
|
// hl-common-log/.../GlobalExceptionHandler.java
|
||||||
|
String message = e.getBindingResult().getFieldErrors().stream()
|
||||||
|
.map(f -> f.getField() + ": " + f.getDefaultMessage()) // 拼字段名
|
||||||
|
.reduce((a, b) -> a + "; " + b)
|
||||||
|
.orElse("参数校验失败");
|
||||||
|
```
|
||||||
|
|
||||||
|
### 改动后
|
||||||
|
|
||||||
|
```java
|
||||||
|
String message = e.getBindingResult().getFieldErrors().stream()
|
||||||
|
.map(FieldError::getDefaultMessage)
|
||||||
|
.filter(Objects::nonNull) // null 兜底,避免 toast 出 "null"
|
||||||
|
.reduce((a, b) -> a + "; " + b)
|
||||||
|
.orElse("参数校验失败");
|
||||||
|
```
|
||||||
|
|
||||||
|
`BindException` 处理同款改法。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 五、为什么不顺手优化单个 message 文案
|
||||||
|
|
||||||
|
有人会想:`分类编码不能为空` 里"分类编码"对用户也不够友好,改成"请选择分类"更好。**本次不动**,理由:
|
||||||
|
|
||||||
|
- 改全局 1 处,200+ 接口统一受益,B 方案逐字段改 message 工作量翻 100 倍且新加字段会复发
|
||||||
|
- `defaultMessage` 文案优化属于 UX polish,可后续单独治理(并不紧急)
|
||||||
|
- 主目标是**去掉字段名前缀这个技术噪音**,主目标已达成
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 六、未覆盖
|
||||||
|
|
||||||
|
- `ConstraintViolationException`(`@Validated` 修饰 Controller + query/path 参数路径,如 `@RequestParam @Min(1)`)未改 — line 191 `e.getMessage()` 自带 `arg0.fieldName: 消息` 格式。本次主线 toast 不在此路径,风险/收益偏低,留待下次治理。
|
||||||
|
- 业务异常 `BizException`、自定义异常文案不动。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 验收
|
||||||
|
|
||||||
|
无前端改动情况下,任何 `@Valid + @RequestBody` 接口校验失败时,前端 `response.message` 应不再带 `字段名: ` 前缀。
|
||||||
正在加载...
x
在新工单中引用
屏蔽一个用户