feat(huya): 协作台账店铺字典·新建强校验·同买家多单 + uid 放开(迁移 0057/0058)

- 店铺字典(迁移 0057):大按钮点选 + 「新店」自定义自动登记;重命名可级联、
  停用/备注;列表行内改、详情、筛选条共用同一个 picker
- 新建强校验:订单编号/店铺/CK/礼包名/主页截图必填;贴 CK 即探针预览,
  读不到绑定角色禁止提交;礼包用工作台配置的活动 SID 现拉(浏览器缓存 + 强制刷新)
- 图片:绑定图停用、「绑定斗鱼号」下线(绑定关系改由探针读 bind_game_*),
  主页截图改为新建必填(弹窗内先传 OSS 再随表单落库)
- 同买家多单软识别:按「绑定账号 + 绑定角色」整批一次查询,列表「同买家」列 +
  详情分组,点单号直接切单
- uid 放开唯一性(迁移 0058):同一份 CK 可服务多单,原 409 冲突改为提示 + 审计
- 列表体验:序号列、搜索扩到 11 个字段、CK 状态列截断、新建弹窗放大并去滚动条
- 验证:pytest 932 passed / 6 skipped;接口冒烟 56 项全绿;tsc+eslint+build 通过
This commit is contained in:
yml2213
2026-10-16 21:29:08 +08:00
parent df8c63b829
commit aac264b639
17 changed files with 3237 additions and 155 deletions
+198 -9
View File
@@ -78,7 +78,7 @@ CK 自带完整生命周期(导入 → 验证 → 使用/失效 → 重贴/重
| `id` | int PK | |
| `order_no` | varchar(64) **unique** | 订单编号(Excel B 列),唯一 |
| `shop` | varchar(64) index | 店铺(A 列) |
| `owner_id` | int FK `users.id` index | 负责人(C 列),可空 |
| `owner_id` | int FK `users.id` index | 负责人(C 列);**不填默认 = 创建人**(新建时不用选,要转单在列表里改) |
| `uid` | varchar(32) **unique, nullable** | 虎牙 uid(从 CK 解析;空 = 还没贴 CK) |
| `yyuid` | varchar(32) | 虎牙 yyuid |
| `hy_username` | varchar(128) | CK 里的 `udb_passport`(登录名) |
@@ -112,20 +112,58 @@ CK 自带完整生命周期(导入 → 验证 → 使用/失效 → 重贴/重
| `version` | int | **乐观锁版本号**,每次写入 +1(见 §7.2 为什么不用 `updated_at`) |
| `created_at` / `updated_at` | datetime | `updated_at` 只用于显示与排序 |
索引:`order_no`(unique)、`uid`(unique)、`shop`、`owner_id`、`status`、`ck_status`、`updated_at`。
索引:`order_no`(unique)、`uid`(**普通索引**,2026-10-16 由 unique 降级,见 §3.2)、`shop`、`owner_id`、`status`、`ck_status`、`updated_at`。
### 3.2 两个 key 设计上的选择
- **`uid` 唯一但可空**:MySQL 的唯一索引允许多个 NULL,
所以"先建单、后贴 CK"和"一个 CK 只能挂一单"同时成立(1 单 = 1 CK 已确认)。
贴 CK 时如果 `uid` 已被别的行占用 → 报冲突,提示"这个虎牙号已在第 N 行(订单 xxx)"。
- **`order_no` 唯一**:Excel 里订单号是天然主键,重复导入要能识别。
- **`order_no` 唯一**:Excel 里订单号是天然主键,重复导入要能识别(重复 → 409 `order_no_conflict`,
导入路径则按"更新"处理)。
- **`uid` 可空、且【不再唯一】(2026-10-16 用户拍板改的口径)**:
| 口径 | 时间 | 行为 |
| --- | --- | --- |
| 一个虎牙号只能挂一单 | 2026-10-16 上午 | 贴 CK / 建单时若 `uid` 已被占用 → 409 `uid_conflict`;DB 上是 unique 索引 |
| **一个虎牙号可以挂多单** | 2026-10-16 晚(当前) | **不拦**。真机上"同一个买家买两件"常常继续用同一份 CK,拦住就变成第二单建不了 |
落地:迁移 **`20261016_0058`** 把 `ix_huya_external_orders_uid` 从 unique 改成普通索引
(降级时会先检查有没有重复 uid,有就报错而不是静默丢数据);服务层
`uid_used_orders()` / `uids_in_use()` 只做**提示与审计**:
- 新建 / 贴 CK 的审计 detail 里带 `uid_reused_in: [其它单号]`;
- `POST /ck/preview` 返回 `uid_used_by`,新建弹窗的 Alert 里会写「这份 CK 的虎牙号已经挂在:
xxx —— 同一个号服务多单是允许的,照常提交」。
- `uid` 仍可空,「先建单、后贴 CK」不受影响(多个 NULL 不冲突)。
- 同一个买家的多单怎么认:见 **§5.6**(按绑定账号 + 绑定角色软识别)。
### 3.3 不建的表
- 不建"操作历史表":直接用现有 `audit_logs`(§7.3),避免再造一套事件表。
- 不建"图片表":图片本体在 OSS,元数据在 `file_assets`(已落地),本表只存 key。
### 3.4 表 `huya_collab_shops`(店铺字典,2026-10-16 加)
原来的"店铺"是手打自由文本,同一个店铺会出现多种写法(错别字、多空格),把筛选打散。
改成一个可维护的字典:**新建协作单时点一下选中**(点选按钮组,不做下拉——就这么几个店铺)。
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `id` | int PK | |
| `name` | varchar(64) **unique** | 店铺名(订单里存的就是它) |
| `sort_order` | int | 越小越靠前(下拉顺序) |
| `is_active` | bool index | 停用 = 不再出现在新建下拉;**历史订单不受影响** |
| `remark` | varchar(255) | 备注 |
| `created_by` / `updated_by` | int FK `users.id` | 谁加/谁改 |
| `created_at` / `updated_at` | datetime | |
**三个已定的取舍**:
1. **订单里存的是店铺名字符串**(`huya_external_orders.shop`),不是外键 —— 历史 Excel
就是文本,改成外键要动所有读写与导入。代价:改名默认**级联**更新已有订单
(`update_shop(..., rename_orders=True)`,前端弹窗里可取消勾选);
2. **支持自定义输入**:下拉里输一个字典里没有的名字,选中即用,后端
`ensure_shops_registered()` 会把它登记进字典(新建/编辑/粘贴导入共用同一条路);
3. **字典从空开始**:不回填历史订单里出现过的店铺名(用户明确"不用看之前的")。
---
## 4. 两套生命周期(本设计的核心)
@@ -235,6 +273,57 @@ CK 失效了业务上可能仍然有效。列表上只做**显式提示**(红
- `void` 作废:保留行、留 `void_reason`,列表默认过滤掉(可切换显示)。
- 物理删除:`huya:collab:manage` 才能做,且要**级联清理图片**(§6.4),并记审计。
### 5.5 新建协作单的必填项与礼包(2026-10-16 用户拍板)
人工新建表单(`POST /api/huya/external-orders`,`require_verified=true` 是默认值):
| 字段 | 必填 | 说明 |
| --- | --- | --- |
| 订单编号 | ✅ | 台账内唯一 |
| 店铺 | ✅ | **大按钮点选**;没有就点「新店」自定义,自动进字典 |
| 外部 CK | ✅ | 贴进来即调 `POST /ck/preview` 预览 |
| 礼包名 | ✅ | 从**礼包列表**里点选(= 用户购买的物品);列表平时用浏览器缓存,要更新点「强制刷新礼包」 |
| 主页截图 | ✅ | 至少 1 张,第一张作封面;绑定图**不要了**(绑了谁由探针读,见 §11.5) |
| 负责人 | — | 默认 = 当前登录账号,不用选 |
| 绑定斗鱼号 / 备注 | — | 备注可选;**绑定斗鱼号已从界面下线**(老数据在详情里只读展示) |
**"CK 有效" = 探针通 + 读到绑定角色**:预览与提交都按这条判;读不到角色一律拒绝
(`ck_not_verified`)。所有校验都在落库**之前**做完,失败不留半行 —— 前端只是即时反馈,
服务端才是最终关口(AGENTS §2)。
- **礼包来源**:`webActUI.getActPrizeList`(活动可兑换商品,只读、不落库;拉失败只提示
`gifts_message`,不影响 CK 的有效性结论)。**SID 必须用工作台配置里的那个**
(`huya_config.sid`,走 `resolve_gifts_sid(db)`)—— 见 §11.4 的真机定位:
SID 就是活动 ID、会随活动轮换,写死默认值会出现「接口 200 但礼包列表空白」;
- **记住最近选的礼包**:前端 localStorage(`huya.collab.lastGift`),同一个礼包连开几单不用重复选;
- **礼包列表缓存**:前端 localStorage(`huya.collab.gifts`,存 `{at, gifts}`)—— 一个活动期间礼包清单基本不变,
贴 CK 时**不再每次都去虎牙拉**(`with_gifts=false`),要用最新库存点「强制刷新礼包」;
缓存时间摆在礼包名下方,活动换了(SID 变了)就点一次强制刷新;
- **探针缓存**:同一份 CK 在 `PROBE_CACHE_TTL_SECONDS`(90 秒)内复用结论 ——
"预览一次 + 提交校验一次"只打一次虎牙,别为了校验白白翻倍请求;
- 粘贴导入**不受**这些必填项约束(Excel 里本来就可能没有 CK),它走 `require_verified=false`。
### 5.6 同一买家的多单(软识别,2026-10-16 用户提的)
一个买家买两件 → 台账里就是两行。要不要把它们认成"同一个买家",判据如下:
| 口径 | 说明 |
| --- | --- |
| **买家指纹 = 探针读到的「绑定账号 + 绑定角色」** | 采用。同一个游戏角色就是同一个买家:`buyer_key_of()`,两边都有值才参与归组 |
| uid(虎牙号) | **不参与归组**:一单一份外部 CK,同一个买家两单往往用**两份不同的 CK**(uid 不同),拿 uid 比永远比不中;反过来说,同一个买家第二单继续用同一份 CK 也是允许的(uid 已放开唯一性,见 §3.2)。uid 只在明细里展示 |
| 平台·区服(`bind_game_channel`) | 不做判据(同一角色往往跨端显示不同),只在明细里展示 |
落地点(**只读派生:不落库、不加表字段、不改订单状态**;`uid` 唯一性是单独一条迁移改的,见 §3.2):
- 服务层 `buyer_clusters(db, orders)`:整批**一次** `(bind_game_account, bind_game_role) IN (...)`
查出同买家的全部行(不按行发 N 条 SQL),只有真 ≥2 单才产出结果;
- `serialize_order(..., buyer_cluster=...)` → 出参 `buyer_cluster: {key, method, total, others[]}`
(`others` 按创建时间升序,带 order_no / shop / gift_name / uid / status);
列表接口整页算一次,单行接口(`_serialize_one`)单独算一次;
- 前端:列表多一列「同买家」(显示「还有 N 单」,点开是浮层,列出其它单号 + 状态 + 礼包,
**点单号直接切到那一单的详情**);详情抽屉里也有一段「同一个买家的其它单」。
- 这是**软识别**:不写库、不改任何状态,识别错了也不会污染数据(`method` 字段在界面上写明口径)。
---
## 6. 图片(复用已落地的上传能力)
@@ -243,8 +332,8 @@ CK 失效了业务上可能仍然有效。列表上只做**显式提示**(红
| 槽位 | 对应 Excel 列 | 字段 | 张数 |
| --- | --- | --- | --- |
| 绑定图 | E | `bind_images` | 多张(第一张作封面) |
| 主页账号图 | F | `home_images` | 多张(第一张作封面) |
| 主页截图 | F | `home_images` | 多张(第一张作封面);**新建必填至少 1 张** |
| ~~绑定图~~ | ~~E~~ | `bind_images` | **已停用**:绑定关系由探针自动读,不需要人工截图。列还在(老数据不丢),详情里只读展示 |
存的是 OSS `object_key` 数组;前端用 `/api/files/object?key=...` 直接 `<img>` 预览。
**显示时不在列表里下原图**——用 `thumbnail_url`(缩略图变体),点开才看原图。
@@ -332,8 +421,13 @@ CK 失效了业务上可能仍然有效。列表上只做**显式提示**(红
| POST | `/api/huya/external-orders/{id}/ck` | `huya:collab:edit` | 贴/换/重建 CK |
| POST | `/api/huya/external-orders/{id}/ck/status` | `huya:collab:edit` | 人工停用/恢复(只能 `disabled`/`unverified`) |
| POST | `/api/huya/external-orders/{id}/verify` | `huya:collab:edit` | 验证这一行的 CK(只读探针,§5.2) |
| POST | `/api/huya/external-orders/ck/preview` | `huya:collab:edit` | 粘贴 CK 后的即时预览(探针 + 拉活动礼包,**只读不写库**) |
| POST | `/api/huya/external-orders/ck/verify` | `huya:collab:edit` | 批量验证 CK(探针,body `{ids: [...]}`,最多 50 条) |
| POST | `/api/huya/external-orders/import` | `huya:collab:edit` | 粘贴批量导入 |
| GET | `/api/huya/collab-shops` | `huya:collab:view` | 店铺字典(含停用的,管理弹窗用) |
| POST | `/api/huya/collab-shops` | `huya:collab:edit` | 新增店铺(重名 409) |
| PUT | `/api/huya/collab-shops/{id}` | `huya:collab:edit` | 改名(可级联)/ 排序 / 停用启用 / 备注 |
| DELETE | `/api/huya/collab-shops/{id}` | `huya:collab:manage` | 从字典删除(**只给运营**;不动订单) |
| DELETE | `/api/huya/external-orders/{id}` | `huya:collab:manage` | 删除(级联清图) |
| GET | `/api/huya/external-orders/owners` | `huya:collab:view` | 可选负责人列表(给筛选/下拉用) |
@@ -361,9 +455,17 @@ CK 失效了业务上可能仍然有效。列表上只做**显式提示**(红
- **图片列**:缩略图 + 张数,点击开大图(`Image.PreviewGroup`);
- **状态**:Tag 展示,行内"下一步"按钮推进(带二次确认);
- **CK 状态**:Tag + 一键"验证";
- 筛选:店铺 / 负责人 / 业务状态 / CK 状态 / 关键词(订单号、斗鱼号、uid);
- 筛选:店铺 / 负责人 / 业务状态 / CK 状态 / 关键词(订单号、绑定账号、角色、uid);
- 页头统计条:`待处理 N / 已绑定 N / 已充值 N / 已上脚本 N / 已完成 N / CK失效 N`。
> 交互细节(2026-10-16 用户拍板):**店铺是点选按钮组**(列表里点单元格弹出同一组按钮,
> 筛选条里也是),不是下拉;**新建时不用选负责人**——默认就是当前登录账号
> (要转单在列表里点一下改)。
> 新建弹窗(同日拍板):**订单编号 / 店铺 / CK / 礼包名必填**;贴 CK 后自动验证并显示
> 「绑定账号 + 绑定角色 + 平台区服」,**读不到角色就禁止提交**;礼包下拉来自这次 CK 现拉的
> 活动礼包(点选,不是手打),并记住上次选的那个(见 §5.5)。
### 9.2 详情抽屉
左侧:图片(两个槽位,可上传/删除/换序);
@@ -404,6 +506,10 @@ CK 失效了业务上可能仍然有效。列表上只做**显式提示**(红
| — | `uid` 可空 | 可空(唯一索引允许多 NULL)→ 支持"先建单、后贴 CK" |
| — | **绑定对象的展示口径**(2026-10-16 用户拍板) | 展示**探针读到的游戏账号 + 绑定角色**(角色最重要);人工的"绑定斗鱼号"不再作列表主列,只留详情抽屉(Excel 导入列) |
| — | 人工能否改 CK 状态 | 只能"停用"/"恢复(未验证)";`valid`/`invalid` 归探针 |
| — | 店铺怎么填(2026-10-16 用户拍板) | **点选按钮组**(不是下拉);允许"新店"自定义输入,后端自动登记进字典 |
| — | 负责人默认值(同日拍板) | **默认 = 当前登录账号**,新建时不用选;要转单在列表/详情里改 |
| — | 新建必填项(同日拍板) | **订单编号 / 店铺 / CK / 礼包名**必填;CK 必须探针通且读到绑定角色 |
| — | 礼包来源(同日拍板) | 用这份 CK 现拉活动礼包(`getActPrizeList`),点选 + 记住上次选择 |
> 决定 7 的后果:**本模块不 import 任何 `huya_tasks` / runner 代码**。
> 以后要做「一键送进工作台」,那是一个**显式的**新动作(会创建 `HuyaAccount` 并写
@@ -450,6 +556,8 @@ CK 失效了业务上可能仍然有效。列表上只做**显式提示**(红
| **S5** | CK 探针验证(只读 `check_user_bind_game_account`,真机判据已定) | ✅ 2026-10-16 |
| **S6** | 前端页面(列表 + 内联编辑 + 图片 + 详情抽屉 + 导入弹窗) | ✅ 2026-10-16 |
| **S7** | 权限点接入(`huya:collab:view/edit/manage`) | ✅ 2026-10-16 |
| **S8** | 店铺字典(表 `huya_collab_shops` + 管理弹窗 + 新建/编辑/筛选改成点选) | ✅ 2026-10-16 |
| **S9** | 新建弹窗强校验(CK 预览 + 必填 + 礼包点选与记忆) | ✅ 2026-10-16 |
测试:`tests/test_huya_collab_orders.py`(68 例,其中探针 21 例,用假客户端离线跑)+
`tests/test_cleanup_orphan_file_assets.py`(6 例,孤儿图清理脚本)。
@@ -513,6 +621,86 @@ CK 失效了业务上可能仍然有效。列表上只做**显式提示**(红
7. 迁移 `20261016_0056` 只加列(`bind_game_*` + `bind_snapshot_at`),老列一动不动;
应用启动时会自动 `upgrade head`,本地不用手动跑。
### 11.4 2026-10-16 页面复核后补的点(用户点着看发现的)
1. **礼包列表空白 = SID 过期,不是解析问题**(真机定位,最值钱的一条):
| 活动 SID | 接口结论 | 礼包数 |
| --- | --- | --- |
| `2203`(9 月的活动,曾是 `HUYA_DEFAULT_SID`) | `status=200` + `msg=请求成功` | **0** |
| `2277`(库里 `huya_config.sid`,今天的工作台配置) | `status=200` + `请求成功` | **30**(限量返场 / 精英专享) |
同一份真 CK、同一个 UA,只是 SID 不同 → 一个空一个满。**UA 不是原因**
(`web&1.0&huya` 与我们的 `webh5&...` 结果一样),解析器也不是原因
(`ActPrizeItem` 的 tag 映射与 huya-go 实测一致,`to_dict()["goods"]` 正常)。
处理:新增 `resolve_gifts_sid(db)` —— **优先读工作台配置 `huya_config.sid`**,
读不到(没有配置行 / 不是数字)才回落到 `GIFTS_SID`;接口层
`POST /external-orders/ck/preview` 注入 `db` 后把它传给 `preview_ck(sid=...)`。
配置行不存在时**不建行**(预览是只读动作)。空列表的结论也改成人话:
「活动 SID=xxx 没返回商品:接口通了但列表是空的,多半是活动换 ID 了 —— 去工作台
「虎牙配置」把活动 SID 改成当前的再试」。
→ **换活动时只改工作台配置,不用改代码**。
2. **店铺必填的地方不再给「不填」按钮**:`HuyaCollabShopPicker` 新增 `allowClear`
(默认 `true`,筛选条保持「全部」),新建弹窗 / 详情抽屉 / 列表行内改都传 `false` ——
点出一个后端会拒的空值纯属自找麻烦。
3. **礼包名改 `AutoComplete`**:列表有货时就是点选,拉不到列表时还能手输 ——
否则「礼包名必填 + 拉取失败」会把新建彻底卡死。选项标签带上
`[类目] 名称 · 积分 · 剩余`,同名的礼包能分清。
4. **记住最近礼包改成「选中即记」**(原来只在创建成功后记):中途取消 / 提交失败也不丢,
符合「记住最近选择的礼包」的原意。
5. **预览请求加了序号防串**:连贴两份 CK 时,先发的那份迟到返回不再覆盖新结论
(之前可能出现「显示上一份 CK 的有效」这种错觉);清空输入也会让在途请求作废。
真正的把关仍在服务端:提交时 `require_verified=true` 会拿表单里的 CK 重新探针。
6. **记忆里的礼包不在本次列表里**:清空并提示「上次选的「xx」不在这次拉到的礼包列表里,
请重新点选」(拉取失败时**不**清,避免把人卡死)。
### 11.5 2026-10-16 第二轮页面复核(用户点着看又提的)
1. **弹窗放大了**:新建协作单 `width` 520→**920**、表单 `size` 回到 `middle`、CK 输入框 3→5 行、
正文给 `maxHeight: 72vh` 滚动区(内容比一屏高时按钮不会被顶出去);粘贴导入弹窗 760→900。
2. **绑定点选改成"一个店铺一个独立按钮、中间留白"**:`HuyaCollabShopPicker` 加 `size`
(`small`/`middle`/`large`),新建弹窗用 `large`;原来 antd `Radio.Group optionType="button"`
把按钮连成一整排分段控件,小且不好点 —— 现在每个店铺一个 `Button`(选中是 primary,
未选是描边),按钮之间留 8–12px 空白;列表单元格与筛选条仍是 `small`。
「+ 新店」跟在最后一个店铺后面(同一行):店铺这栏在新建弹窗里**独占一行**,
否则半栏宽度放不下「4 个店铺 + 新店」,`Space wrap` 会把新店挤到第二行去。
3. **「绑定斗鱼号」从界面下线**(用户原话"绑定斗鱼号不需要了"):绑了谁由探针写 `bind_game_*`,
人工那一列已无意义。新建弹窗不再有这个字段(提交也不再带);详情抽屉去掉可编辑项,
**老数据非空时只读展示**;`bind_douyu_account` 列与粘贴导入的 Excel 列序**都不动**
(导进来的老 Excel 不会解析错位)。
4. **礼包列表改为浏览器缓存 + 一个强制刷新**:见 §5.5 的「礼包列表缓存」。
贴 CK 时带 `with_gifts=false`(本地没缓存时才自动拉一次),Alert 里会写明"用本地缓存(N 个 · 时间)";
按钮从「重新拉取礼包」变成「强制刷新礼包(N)」。
细节:礼包 state 的**初值直接读 localStorage**(不是等 `useEffect`)—— 否则"刚打开页面就贴 CK"
那一瞬缓存还没进 state,会白拉一次;CK 输入框的 placeholder / tooltip 也改成
「贴进来即自动验证 + 读绑定角色(礼包有缓存就不再拉)」,别再让人以为每次都会拉礼包。
5. **主页截图必填、绑定图下线**:`create_order` 新增 `home_images` 入参,严格模式下
「主页截图」进必填清单(`create_field_required`),key 走 `normalize_images`(防路径穿越);
新建弹窗内嵌 `HuyaCollabHomeImageUploader`(先传 `/api/files/upload` 拿 object_key、
可预览/设为封面/移除,提交时随表单一起落库)。
≥1 张的兜底在前端 `Form.Item` validator + 服务端双重把关。
### 11.6 2026-10-16 第三轮页面复核(弹窗/列表体验)
1. **新建弹窗尽量不出滚动条**:
- CK 输入框由固定 5 行改成 `autoSize={{ minRows: 3, maxRows: 8 }}`(短 CK 就 3 行,不再白占高度);
- 「主页截图」和「备注」并排一行(14/10),备注 3 行 → 2 行;
- 表单项间距压到 14px(`src/index.css` 里 `.huya-collab-create .ant-form-item`),
正文 maxHeight 72vh → 78vh 只当兜底。
2. **列表加「序号」列**:跨页连续(`(page-1)*pageSize + index + 1`),固定在左侧 ——
群里沟通时可以直接说"第 12 行"。
3. **搜索扩到 11 个字段**:原来只搜订单号 / 绑定斗鱼号 / 绑定账号 / 角色 / uid / 昵称 /
hy_username / 礼包 / 备注,现在补上 **店铺** 与 **平台·区服**(`bind_game_channel`),
输入框加宽到 340px 并在 placeholder 里逐项写清能搜什么。
(其实一个输入框就能打穿这些列,之前是列没写全 + 提示太短,容易被当成"没有搜索"。)
4. **放开 `uid` 唯一性 + 同买家识别**:见 §3.2 与 §5.6;迁移 `20261016_0058`。
5. **「CK 状态」列的探针结论必须截断**(用户反馈:长文案顶到「最后改动」列上去了):
那一格是「标签 + 探针结论」,而结论里塞了账号 / 角色 / 平台·区服,长度不可控。
改成单行省略(`display:block` + `maxWidth` + `text-overflow:ellipsis` + `nowrap`),
列宽 150 → 170,全文放 Tooltip(附验证时间)。
顺带把礼包 / 备注那格的可编辑文本加上 `wordBreak: break-word`,防止一长串无空格字符串顶穿列。
---
## 12. 风险
@@ -521,6 +709,7 @@ CK 失效了业务上可能仍然有效。列表上只做**显式提示**(红
| --- | --- |
| 与 `huya_accounts` 出现"两份 CK 谁是准的" | §2.3 已写死:本表 CK 只在协作语境有效,不自动同步;需要跑任务时人工导入 |
| 探针接口被风控 | 只做人工触发、串行、带间隔;不做定时轮询 |
| 活动换 ID(SID 过期)→ 礼包列表空白 | 礼包 SID 一律读 `huya_config.sid`(工作台「虎牙配置」可改,见 §11.4);空列表时结论直接提示去改 SID |
| 自由文本的"绑定斗鱼号"没法校验 | 先按文本存(Excel 里就是文本);能拿到结构化 uid 时补 `bind_douyu_uid` |
| 图片越攒越多 | §6.4 孤儿清理脚本 + `file_assets` 台账已有 `size_bytes` 可统计 |
| 分支 | 按 AGENTS §1,新功能应从 `main` 开 `feat/*`;开工前需确认当前分支与库迁移版本对齐 |