docs(plans): J1 回执(全局 ID 字符串化 + 契约统一),标为已处理·待用户验收
This commit is contained in:
1 parent
f35db2cd3b
commit
0cab094194
2 files changed
+205
-1
No files matched your search
@@ -242,3 +242,119 @@ DELETE 1 # 公告 I1 复测公告:印尼开服
|
||||
1. 雪花 id 的全局方案:admin-api 是否统一 `Long → String`(涉及所有既有页面的 `id` 类型),
|
||||
app-api 是否要动(影响 Flutter 客户端解析)——属于平台级改动,等裁定。
|
||||
2. `/app/orders/verify` 的真发币链路需要 Google Play 凭据(B1-1 已列清单)。
|
||||
|
||||
---
|
||||
|
||||
## 2026-10-16 J1:全局 ID 字符串化(Long → String)+ 契约统一
|
||||
|
||||
计划文件:`docs/plans/2026-10-16-J1.md`(先写计划再动手)。用户裁定「方案 (a) 全局修改,统一契约」。
|
||||
|
||||
### 动作 1:后端全局序列化(fruitshorts-framework)
|
||||
|
||||
新增 `JacksonLongToStringConfig`:`SimpleModule` 把 `Long.class` / `Long.TYPE` → `ToStringSerializer`,
|
||||
经 `Jackson2ObjectMapperBuilderCustomizer` 挂进 Spring Boot 的 `ObjectMapper`。
|
||||
两个入口都是 `scanBasePackages = "com.fruitshorts"`,一处生效两端一致,**没改 yml**。
|
||||
|
||||
一个刻意的例外:`PageResult.total` 是条数不是 id,用 mixin 显式保持数字(前端分页组件要数字)。
|
||||
为此给 mixin 加了个 `PlainLongSerializer`(`NumberSerializer` 没有无参构造,`using=` 装不上)。
|
||||
|
||||
I1 里给商品/公告 VO 逐个加的 `@JsonSerialize` 与 `Result<String>` 新建返回**已回退成自然写法** ——
|
||||
全局生效后它们只是冗余特例(现在是"所有模块一个口径")。
|
||||
|
||||
### 动作 2:契约 §0 总口径
|
||||
|
||||
`docs/api-phase1.md` §0 新增「**ID 字段一律以字符串下发**」+ 示例 + 边界(`total` 与 `Integer` 保持数字、
|
||||
id 回传按字符串传),§2.11/§2.12 的逐模块表述改成引用 §0。
|
||||
|
||||
### 动作 3:契约测试的 standalone mapper 也挂上模块(否则假绿)
|
||||
|
||||
`ControllerContractSupport`(app-api / admin-api 两处)自带手搓 `JsonMapper`,不含全局模块 ——
|
||||
不改它的话,测试断言数字 id 也会过,而生产早就变成字符串了。挂上模块后:
|
||||
|
||||
```
|
||||
$ mvn test
|
||||
[INFO] Tests run: 33, Failures: 0, Errors: 0, Skipped: 0 (fruitshorts-module-commerce)
|
||||
[INFO] Tests run: 21, Failures: 0, Errors: 0, Skipped: 0 (fruitshorts-admin-api)
|
||||
[INFO] Tests run: 49, Failures: 0, Errors: 0, Skipped: 0 (fruitshorts-app-api)
|
||||
[INFO] Tests run: 12, Failures: 0, Errors: 0, Skipped: 0 (fruitshorts-transcode-worker)
|
||||
[INFO] BUILD SUCCESS # 共 115 条(存量 110 + 新增 5)
|
||||
```
|
||||
|
||||
新增 `LongToStringSerializationTest` 5 条:普通字段 / 嵌套对象 / List / Map 值 / 分页(total 数字 + list id 字符串)
|
||||
/ 请求体 `"id":"…"` 反序列化成 Long / 19 位路径变量绑定成 Long。
|
||||
另外把 C 端 4 条 id 断言从数字改成 `value("…") + isString()`(原来"数字也能过"的宽松比较不算证据)。
|
||||
|
||||
### 动作 4:Web 与客户端适配
|
||||
|
||||
见回执的逐文件清单。要点:Web 审计出 4 处把 id 当数字用的地方(代看 id 过 `Number`、
|
||||
两处 `Number(route.…)` 与 id 比较、菜单顶级哨兵 0 与 "0" 不一致);Flutter 侧 id 本来就是
|
||||
`(json['id'] ?? '').toString()`,会崩的是被全局规则扫到的 **Long 计数**,只改了 `expiresIn` 与 `viewCount`。
|
||||
|
||||
```
|
||||
$ pnpm ts:check → 0 错误
|
||||
$ pnpm exec eslint … ./src → 0 error
|
||||
$ pnpm build:pro → Build successful
|
||||
$ dart analyze lib test → No issues found!
|
||||
$ flutter test → All tests passed! (+167)
|
||||
```
|
||||
|
||||
### 动作 5:端到端复现"修复前后"(真实服务)
|
||||
|
||||
```
|
||||
① 复现 PM 那个场景:C 端注册 → admin 列表/详情
|
||||
$ curl -X POST :8081/app/auth/register -d '{"targetType":"EMAIL","target":"j1.selftest@example.com",…}'
|
||||
→ "expiresIn": "7200" # long 字段,已是字符串
|
||||
$ curl -H "Authorization: …" :8080/admin/app-users/page?pageSize=50
|
||||
total = 2 int # 分页 total 仍是数字(例外生效)
|
||||
命中用户: uid=U79DG5IOM id='2111015748376858626' id类型=str
|
||||
$ curl -H "Authorization: …" :8080/admin/app-users/2111015748376858626
|
||||
code= 0 message= ok # 修复前这里就是 200002
|
||||
data.id= '2111015748376858626' uid= U79DG5IOM
|
||||
```
|
||||
|
||||
```
|
||||
② JS 侧逐位一致(真实响应喂 node)
|
||||
$ node -e '…'
|
||||
响应原文里的 id(JSON 字符串): "2111015748376858626"
|
||||
typeof 解析后 : string
|
||||
String(parsed.id) : 2111015748376858626
|
||||
库里 app_user.id : 2111015748376858626
|
||||
逐位相等? : true
|
||||
---- 反面对照:同一串数字当裸数字解析 ----
|
||||
裸数字 -> String(parsed.id) : 2111015748376858600
|
||||
逐位相等? : false
|
||||
```
|
||||
|
||||
```
|
||||
③ 全局范围:换两个模块(商品 / 创作者)走"新建 → 用响应 id 查详情 → 改状态"
|
||||
新建商品响应 : {"code":0,"data":"2111015841427492865"}
|
||||
商品详情 : code=0 id='2111015841427492865'
|
||||
商品上架 : {"code":0}
|
||||
新建创作者响应 : {"code":0,"data":"2111015841771425794"}
|
||||
创作者详情 : code=0 id='2111015841771425794' name=J1 探针工作室
|
||||
```
|
||||
|
||||
```
|
||||
④ C 端字段类型(匿名可读的链路)
|
||||
/app/home/feed : drama card id='5'(str) viewCount='3100000'(str)
|
||||
/app/dramas/5 : id='5'(str) viewCount='3100000'(str) totalEpisodes=72(int) episode id='7'(str)
|
||||
/app/episodes/7/play : episodeId='7'(str) dramaId='5'(str) durationSec=228(int) unlockCoin=0(int)
|
||||
```
|
||||
|
||||
解读:**Long → 字符串**(id、viewCount、expiresIn、coinAmount),**Integer/int 与分页 total 仍是数字**,
|
||||
与 §0 的规则和例外完全对上。
|
||||
|
||||
### 动作 6:自测数据清理(含一次口径纠正)
|
||||
|
||||
```
|
||||
DELETE 1 # 自测用户 app_user(含 credential 2 / session 2 / device 2 / consent 3 / wallet 1)
|
||||
DELETE 1 # 商品 j1_probe_coin
|
||||
DELETE 1 # 创作者 J1PROBE
|
||||
全库扫描 uid / 邮箱 / 雪花 id → 无任何残留(NOTICE: 扫描完成)
|
||||
现存:creator 只剩 PM 那条 id=25(CR20261015001)、app_user 只剩 id=28(UHR-DLTH8)、
|
||||
product 3 行种子、ops_notice 0 行 —— 都不是本轮数据。
|
||||
```
|
||||
|
||||
⚠️ 口径纠正:我第一版清理脚本用 `psql -t -A -c "delete … returning 1" | wc -l` 数"行数",
|
||||
**数到的其实是 psql 的命令标签**(实测删 0 行时输出 `DELETE 0`,也是 1 行),所以那轮打印的
|
||||
"删除 1 行"是假数字。真正的证据是后面的**全库残留扫描**(零命中)与现存数据核对,已按此结论写回执。
|
||||
Reference in new issue
Block a user