12451
8025cd831c
fix(db+users): users.name 改 NOT NULL DEFAULT '',并修掉迁移非幂等的两个解析 bug
## 1. users.name 不再允许 NULL(用户要求)
**数据库层加闸**:`name VARCHAR(50) COMMENT '姓名'`
→ `name VARCHAR(50) NOT NULL DEFAULT '' COMMENT '姓名(选填,为空串表示未填写;⛔ 不允许 NULL)'`
同步三处(AGENTS.md 硬约束):
- `SCHEMA_SQL` 建表语句;
- `LEGACY_COLUMN_TARGETS` 加 `("users", "name", "VARCHAR(50) NOT NULL DEFAULT ''")`
(老库靠它对齐,`CREATE TABLE IF NOT EXISTS` 不改已存在的表);
- Rust `UserRow.name` / `load_user_for_update` / `persist_user_update` /
`log_user_update` 全部 `Option<String>` → `String`;
创建用户时 `input.name.unwrap_or_default().trim()`,**不再可能插 NULL**。
前端 `User.name` 类型同步改为 `string`(空串 = 未填写,界面 `v || '-'` 照常)。
### 实测(库层)
```
users.name: varchar(50) nullable=NO default='' OK
插空串姓名 → name='' OK
插 NULL → 1048 Column 'name' cannot be null OK(被拒)
```
## 2. 顺带修掉两个**迁移非幂等**的解析 bug(都是这次上线才暴露的)
`ensure_column_type` 的定位是"只有与目标不一致才 ALTER",但两个归一化缺失让
`users.name` **每次重启都重发一遍 ALTER**(白搭 DDL,大表上还可能触发重建):
| # | 症状 | 根因 | 修法 |
|---|---|---|---|
| 1 | 日志 `default '' -> ''''` 永远不相等 | `target_default_value` 原样返回带引号的 `''`,而 `information_schema.COLUMN_DEFAULT` 返回**去引用后**的空串 | `.trim_matches('\'')` |
| 2 | 日志 `type varchar -> VARCHAR(50)` 永远不相等 | `target_data_type` 返回 `VARCHAR(50)`,而 `information_schema.DATA_TYPE` 只报**裸类型名** `varchar`(长度在 `COLUMN_TYPE`) | 去掉 `(...)` 后缀 |
`users.name` 是对齐清单里**第一条带括号长度**的类型,之前的条目都是单词型
(`BIGINT`/`DATETIME`/`TINYINT`),所以这两个 bug 一直没暴露。
### 幂等已验证
```
测试环境:连续重启两次,ALTER users.name 次数 = 0,迁移完成日志 6 条,ERROR 0
生产环境:重启一次, ALTER users.name 次数 = 0,ERROR 0
```
## 3. 守门
- `resolve_new_name` 三态单测(未传保持 / 传空清空 / 传非空去空白)—— 已自证能失败;
- `target_default_value` 去引号单测 —— 已自证能失败;
- `target_data_type` 去括号单测 —— 已自证能失败;
- SQL 快照守卫**有意更新**(新增 `users.name` 两条片段)—— 已自证能失败。
⚠️ 守卫报警是**对的**:我确实改了 SQL,所以显式更新快照,而不是放宽守卫。
## 4. 数据修复(本次 bug 的实际损害)
"表格内分配组织"只传 `organization_id`,命中姓名三态 bug,把生产
**李四 / 王五** 的姓名抹成 NULL。已按用户此前截图还原(`李四`/`王五`),
组织分配结果保留(org=2)。
## 验收
- `cargo check --all-targets` 两服务:**0 error / 0 warning**
- `cargo test --bin pms-api`:**214 passed**
- 已发布测试 + 生产;生产重启后 0 ERROR、迁移幂等、域名 401/200
2026-09-21 10:42:22 +08:00
..
2026-09-21 10:42:22 +08:00
2026-09-19 23:41:09 +08:00
2026-09-19 23:41:09 +08:00
2026-09-19 23:41:09 +08:00