范围 = 本线(插件投放与分库线)产物 + 记忆类,共 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/规则与载体/、归档/、接续包_*。
12 KiB
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),⛔ 不猜、⛔ 不拿空值冒充。
五 未闭合项(逐条:现象 / 归属 / 下一步)
local_id列在真机库仍未就位 —— 现象:A5 切档前置不满足;归属 部署面;下一步 = B 案最后一步(出包 → 上池 → 2 条ADD COLUMN→ 重跑迁移回填local_id)。- 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)。 - D16 映射新鲜度缺口 —— 现象:
hot_accounts/account_videos的写切平台后本地不再新增行 ⇒ 映射缺席;归属 插件侧(已知边界,已在每处具名降级);下一步 =local_id就位后把映射改为「优先平台、缺则降级本地」。 account_videos列表页在有账号级过滤时仍走本地 SQL —— 现象:index.js/mcn/api/videos的平台档分支已由第 32 棒落地(buildVideosPlatform()),本棒只补aweme_id换轨;归属 插件侧;下一步 =shadow档真机比对(S7)。imports.js有一处未使用的findVideoByFolder导入 —— 现象:仅db.js:264调用(同步版,属 S10 收口);归属 插件侧;下一步 = S10 随db.js一并退场(⛔ 本批不动)。- 自动化记忆目录缺失 —— 现象:
.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 只读),未在实例内留下任何残留。
七 待拍板(不影响本棒结论)
- ㈠ 47 实例重启窗口 —— 要用你定:什么时候可以重启线上实例。为什么需要你定:重启会让正在用平台的用户短暂断线(分钟级)。A 案:约一个低峰时段(优点:影响最小;缺点:要等)。B 案:现在重启(优点:立刻能验完 A2/A5;缺点:在线用户会被打断)。我的倾向:A 案(可推翻)。
- ㈡ 106 重启窗口 —— 同上,可合并到同一次窗口。
- ㈢
json列是否改声明 —— 不可逆;本批不需要(不属第三批)。 - ㈣ 插件包源码是否纳入版本管理 —— 现状:6 件改动无版本留痕(第 34 棒已第 4 次累积)。