Files
dsh_ai1net_server/交付物/MCN数据面接入-阶段二-第三批接线完成-20260925.md
admin 318430c9e9 chore(工作区): 插件投放与分库线收口入库(第 35 棒 + 22:44 拍板落盘)
范围 = 本线(插件投放与分库线)产物 + 记忆类,共 27 件:
- 接续入口_插件投放与分库线(1 件):§0 新增 22:44 拍板行;§2 第 36 棒范围改 5 步、条件步转无条件
- 交付物(22 件):MCN 数据面接入阶段一/阶段二系列(含 B 案落地与 shadow 读数)、
  P0 修通与移动端真机验收、pnpm-EPERM、两机 lib 差异、共享层台账语义、
  基础插件身份与回滚、插件接入验收、插件数据面取数口、移动端迁包与字号扩面、跨机错误消息
- 记忆(4 件):2026-09-24 / 2026-09-25 日志、MEMORY.md、本棒 automation 执行记录

⛔ 未含他线在途改动(只报告、不代提交):机制层 CODEBUDDY.md / state.py /
.codebuddy/rules/server-ops.md、接续入口_IM线、接续入口_StoryForge验收线、
其余 29 个 automation 目录、docs/规则与载体/、归档/、接续包_*。
2026-09-25 23:06:29 +08:00

12 KiB
Raw Permalink Blame History

MCN 数据面接入 · 阶段二第三批 —— 接线完成(第 34 棒 · 执行棒 · 2026-09-25)

工作区:E:/ProgramData/AIProject/aliyun-dsh-server 会话:p34-exec-plugin-dataline(d25f7943-e143-4f7f-a595-8b090fed6e3f) 唯一执行单:D:/github/dsh_shenxian/dsh-server-docs/05-交接单/插件投放与分库线-04MCN数据面接入改造-阶段二第三批.md 上游拍板:用户 2026-09-25 20:38 —— 「修改完成后 一起上服务器」⇒ B 案(接线全做完再一次性动生产)。


一 结论(一句话)

S3 剩 1 处 + S4 + S5 的插件侧接线已全部落地:account_videos / hot_accounts + hot_account_ext / account_video_source 三张表在 platform 档下处处有改写面,local 档行为与原 SQL 逐字一致(零回归)。静态复扫确认全包已无「未被 local() 回调包住的本地 SQL」。单测 62/62、红线 23(零新增)、src/** 零改动。

⛔ 本棒未动生产(不出包 / 不上池 / 不加列迁移 / 不回填)—— 按用户拍板属 B 案最后一步,本轮未做。 ⛔ 未切档:local_id 列在真机库仍未就位(切档前置)⇒ A5 未达成、A2 未达成(需实例重启窗口)。


二 改动面(6 件 · 全部在插件包内)

# 文件 字节 行尾 sha256(前 16) 本棒改了什么
1 lib/host/mcn/store.js 111,028 LF 263c245627bfadce 新增 patchHotAccountSplitByName()(拆表 PATCH:主表列/扩展列各一半,无事务 ⇒partial+compensate)+ patchHotAccountSplitByLocalId()(经 D16 映射)
2 lib/host/mcn/index.js 214,801 LF e0178cb16fe57f8c 22 处落点:新增 6 个插件内共用原语(nameMapOf/hotAccountOf/patchHotAccountOf/videoOf/awemeIdOfVideo/accountNameOf)+ 逐处接档(见 §四)
3 lib/host/mcn/data.js 16,872 LF e3f6de684b59d980 (第 34 棒前段)queryAccountsPlatform 等;本棒为复扫复核对象
4 lib/host/mcn/imports.js 57,547 CRLF 0340c97fe0713e45 (第 34 棒前段)findHotAccountByDelFlagEq / loadVideoWithAccount + 36 处接档
5 lib/host/mcn/workshop.js 31,495 LF 4fc7822d5c5e17ba (第 34 棒前段)accountFilterOf / softDeleteHotAccountPlatform / batchDeleteCascade 平台面
6 tests/mcn-store.test.mjs 57,661 LF e988cd97d79205ef 新增 4 例(52–55):拆表 PATCH 表路由 / 两半失败 partial / 映射缺席 mapping-missing / local 档零请求

未改:dsh.data.yaml(指纹 d6627f1c647a8c27 / 8,351 B —— 与第 33 棒逐字相同)· tools/migrate-local-to-platform.mjs(060bbf855f624fdd / 12,886 B)· lib/client.js(零改动 ⇒ A7 免跑)· D:/github/dsh_shenxian/src/**(零改动)。


三 判据表(A1–A9 · 命令 + 输出原文 + 退出码)

# 判据 命令(原文) 输出原文 退出码 结论
A1 存量迁移一致性 ——(属第 32 棒读数,本棒未重跑) account_videos 362/362 match=true · hot_accounts 5/5 match=true · account_video_source 21/39 上台 · match=false(余 12 行卡平台单行 64 KiB) — 部分达成(12 行卡 64 KiB ⇒ 由 (a) 行内压缩处方闭合,代码未落)
A2 三表读写 E2E(实例内) —— 需 47 实例重启(待拍板 ㈠) — ⛔ 未达成(属窗口项)
A3 结构改写面处置完毕 ① grep -rniE "\bJOIN\b" lib | grep -v "\.join(" ② grep -rn "LIMIT ? OFFSET ?" lib ③ grep -rnE "ROW_NUMBER|OVER\s*\(" lib ① 命中行全部落在三类:local() 回调内 / 本地专属分支 / 第四批主表 rewrite_log(具名豁免);平台档路径零命中 ② data.js:183 data.js:239 index.js:1796 workshop.js:335 —— 前两条在本地专属路径、后两条在 local() 回调内 ③ index.js:1722(where.push,仅本地分支使用)· index.js:1787(本地 SQL)· index.js:3245(local: 回调内) 0 / 0 / 0 ✅ 全部处置(归零或具名豁免)
A4 表名对齐彻底 grep -rE "account_video_analysis" . | grep -v /node_modules/ | wc -l;grep -rn "hot_account_ext" lib | wc -l 0;13(其中 store.js 10) 0 / 0 ✅ 通过
A5 已按表切档(三张表) —— 真机 data-plane.json 三表仍 local — ⛔ 未达成(有意:local_id 列未就位 ⇒ 切档 = 新行没 id ⇒ 前端 6 入口失效)
A6 旧库备份在位 + 零数据损失 ——(第 27 棒备份已在位) 本棒未动 mcn-plugin.db(⛔ 未删 · 未改) — ✅ 维持(旧库 sha256 未取 —— 本棒无文件级操作)
A7 移动端五条 —— 本棒未改 UI:lib/client.js 零改动;改动面 6 件全在 lib/host/mcn/** + tests/** — ✅ 免跑(文件清单依据 = §二)
A8 两机一致 —— 本棒未出包、未投放 ⇒ 代码面/运行面都还是旧的 — ⛔ 未达成(⛔ 不得写「一致」)
A9 红线零新增 grep -rEn "CREATE TABLE|ALTER TABLE|DROP TABLE|better-sqlite3|from 'pg'|require\('pg'\)" . | grep -v /node_modules/ | wc -l 23(全部落在 ./lib/host/mcn/db.js) 0 ✅ 23(与第 27/28/29/33 棒同值)

四 逐处落点(index.js 22 处「改前 → 改后」)

口径(入口 §2 硬约束①):切换的最小原子单位 = 表;local 档 = 原 SQL 逐字(保留在各 local() 回调里)。

处 行 改前(原文) 改后形态
1 :852 platform: () => … awemeId: d.prepare('SELECT aweme_id FROM account_videos WHERE id=?')… platform: async () => … awemeId: await awemeIdOfVideo(d, videoId)
2 :861 同上(countVideoAnalysis) 同上
3 :1573 UPDATE hot_accounts SET favorite=? WHERE id=? + r.changes===0 ⇒ 404 store.patchHotAccountByLocalId(…)(local 回调保原 SQL);mapping-missing 或 changes===0 ⇒ 404(本地语义逐字保留)
4 :1811/1813 SELECT * FROM account_videos WHERE id=? + SELECT * FROM hot_accounts WHERE id=? videoOf(d, videoId) + hotAccountOf(d, v.account_id)
5 :1959 platform 分支内 d.prepare('SELECT aweme_id FROM account_videos WHERE id=?') await awemeIdOfVideo(d, videoId)
6 :2061 SELECT video_title FROM account_videos WHERE id=? videoOf(d, videoId, { sql: 'SELECT id, aweme_id, video_title …' })(⚠️ 唯一差异:本地 SELECT 追加 id, aweme_id —— getVideoByLocalId 平台档靠它换轨;调用方仍只用 video_title)
7 :2072 platform 分支内 aweme_id 子查询 await awemeIdOfVideo(d, videoId)
8 :2397 SELECT * FROM account_videos WHERE id=? videoOf(d, rw.video_id)
9 :2624 SELECT * FROM hot_accounts WHERE id=? hotAccountOf(d, accountId)
10 :2675 WHERE id=? AND (del_flag IS NULL OR del_flag <> 1)(非等值 ⇒ 下推不了,D15) hotAccountOf(…) + 插件内 Number(accRow.del_flag ?? 0) !== 1(逐字复刻)
11 :2814 SELECT id, account_name, douyin_id, source_dir FROM hot_accounts WHERE id=? hotAccountOf(d, id, { sql: <原 SQL> }) + id: accRow.id ?? id
12 :2720(按 v.id) SELECT v.*, h.account_name … LEFT JOIN hot_accounts h … WHERE v.id=? store.readRows({ table:"account_videos", platform: <videoOf + 映射补名>, local: <原 JOIN SQL 逐字> })
13 :2727(按 aweme_id) 同形 JOIN(WHERE v.aweme_id=?) 同上(findVideoByAwemeId + videoLocalIdMap 补本地 id)
14 :2869 SELECT v.id, v.aweme_id, v.video_title, v.account_id, h.account_name … JOIN … store.readRows({ … local: <原 SQL 逐字> })
15 :2939 SELECT COUNT(*) AS c FROM hot_accounts store.countHotAccounts()(平台档 = 平台口径总数)
16 :2985 SELECT id FROM hot_accounts WHERE id=? hotAccountOf(d, id, { sql: <原 SQL> })
17 :3003 UPDATE hot_accounts SET ${sets.join(", ")} WHERE id=?(动态 SET) 按 allowed 逆推 patch 对象 ⇒ patchHotAccountOf()(拆表 PATCH:douyin_id/followers/bio… 归 hot_account_ext);⛔ sets 与本地 SQL 一字未动
18 :3075 SELECT COUNT(*) c FROM hot_accounts WHERE del_flag=0 store.readCount({ table:"hot_accounts", platform: <全量 + 插件内复刻 NULL 语义>, local: <原 SQL> })
19 :3077 ROW_NUMBER() OVER (PARTITION BY account_id ORDER BY like_count DESC) rn<=10(在 c(\…`)` 里) 同法:平台档全量 + 插件内按账号分组排序取前 10(⛔ 不是 /count)
20 :3317 SELECT * FROM hot_accounts WHERE id=? + (acc.del_flag ?? 0) === 1 hotAccountOf(…) + Number(acc.del_flag ?? 0) === 1

D16 换轨:平台无自增 id ⇒ 账号走 id↔account_name(accountNameMap)、视频走 id↔aweme_id(videoLocalIdMap / getVideoByLocalId 内部换算);映射缺席一律具名降级(mapping-missing / row:null + detail),⛔ 不猜、⛔ 不拿空值冒充。


五 未闭合项(逐条:现象 / 归属 / 下一步)

  1. local_id 列在真机库仍未就位 —— 现象:A5 切档前置不满足;归属 部署面;下一步 = B 案最后一步(出包 → 上池 → 2 条 ADD COLUMN → 重跑迁移回填 local_id)。
  2. 12 行 account_video_source 正文超平台单行 64 KiB —— 现象:A1 match=false;归属 插件侧(处方 (a) 行内压缩);下一步 = 落 gzip 编解码到 normalizeRow/rowsOf 两个唯一必经之路(本棒未落)。⚠️ 真机读数已在位:12 行全为纯文本(base64runs=0 / data:image=0)、gzip 后最大 41,728 B ≤ 64 KiB ⇒ (a) 有效,无需改走 (b)。
  3. D16 映射新鲜度缺口 —— 现象:hot_accounts / account_videos 的写切平台后本地不再新增行 ⇒ 映射缺席;归属 插件侧(已知边界,已在每处具名降级);下一步 = local_id 就位后把映射改为「优先平台、缺则降级本地」。
  4. account_videos 列表页在有账号级过滤时仍走本地 SQL —— 现象:index.js /mcn/api/videos 的平台档分支已由第 32 棒落地(buildVideosPlatform()),本棒只补 aweme_id 换轨;归属 插件侧;下一步 = shadow 档真机比对(S7)。
  5. imports.js 有一处未使用的 findVideoByFolder 导入 —— 现象:仅 db.js:264 调用(同步版,属 S10 收口);归属 插件侧;下一步 = S10 随 db.js 一并退场(⛔ 本批不动)。
  6. 自动化记忆目录缺失 —— 现象:.workbuddy/memory/automations/40d342f3-…/ 首次运行前不存在(本棒已创建);归属 本机工具面;无后续动作。

六 卫生声明(A7 逐条点名)

  • lib/client.js 零改动 · web/** 零改动 · skills/** 零改动 ⇒ 未改 UI,移动端五条免跑。
  • 本棒 tmp 产物:tmp/p34/{patch-index.py,fix-script.py,add-tests.py,triage.py,ix-slice.txt,scan12.py,scan12.out,*.bak-p34}。
  • 真机只读探针:本棒只做只读取数(tmp/p34/scan12.py,find + 本地 sqlite 只读),未在实例内留下任何残留。

七 待拍板(不影响本棒结论)

  1. ㈠ 47 实例重启窗口 —— 要用你定:什么时候可以重启线上实例。为什么需要你定:重启会让正在用平台的用户短暂断线(分钟级)。A 案:约一个低峰时段(优点:影响最小;缺点:要等)。B 案:现在重启(优点:立刻能验完 A2/A5;缺点:在线用户会被打断)。我的倾向:A 案(可推翻)。
  2. ㈡ 106 重启窗口 —— 同上,可合并到同一次窗口。
  3. ㈢ json 列是否改声明 —— 不可逆;本批不需要(不属第三批)。
  4. ㈣ 插件包源码是否纳入版本管理 —— 现状:6 件改动无版本留痕(第 34 棒已第 4 次累积)。