理由:现有依赖方向是 service → repository。SpecKey 要在 repository/mysql_db.go 的迁移回填里调用,repository 再导入 service
就是 Go import cycle,直接编译不过。repository、service、#90 共同调用中立包。
GOTOOLCHAIN=go1.23.0 下 go vet / gofmt -l . / go test ./... 全过
怎么验证
cd D:\chengma\cmautobuy\admin$env:GOTOOLCHAIN="go1.23.0"govet./...;gofmt-l.;gotest./...-count=1Remove-ItemEnv:GOTOOLCHAIN
端到端(用数据库备份跑,不要在正式库上试):
迁移后: SELECT COUNT(*) FROM syb_orders WHERE (product_spec IS NOT NULL AND TRIM(product_spec)<>'') AND (spec_key IS NULL OR spec_key='') → 0 SELECT COUNT(*) FROM syb_orders WHERE TRIM(COALESCE(product_spec,''))='' AND spec_key IS NOT NULL → 0 SELECT COUNT(*) FROM syb_orders o WHERE NOT EXISTS (SELECT 1 FROM shopee_products p WHERE p.goods_id=o.shopee_goods_id) → 0 SELECT source, COUNT(*) FROM shopee_products GROUP BY source → report ≈5195,syb ≈2936
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
基本信息
[必须]实施中发现实质性问题先停下来更新工单并重新审核,不要照着已知缺陷落地,也不要自行改设计(CLAUDE.md §5/§7.5)
#38(
ParseSpec)、#46(syb 同步)要解决什么
用线上真实数据量出来的结论
当前状态机的前两关是「未找到蝦皮商品」「待确认蝦皮规格」——
97% 的真实订单���死在第一关。蝦皮那份 Excel 是「最佳表現商品」报表,
只含有销售成绩的商品,与每天实际来的订单几乎不相交。
不止状态机:
AssociateSybPdd/CreateSybPddCollectTask/SaveSybMapping/validatePurchaseRequest都有if context.Order.ShopeeSKUID == "" { return "请先确认蝦皮规格" }——连填 PDD 链接、发采集任务都被这道闸挡着。
根因
把蝦皮 Excel 当成了主干。但顺运宝明细自带采购需要的一切:
中间绕道
shopee_sku_id只为给映射一个键,而这个键 98% 不存在。做什么 / 不做什么
做:
spec_mappings,键 = (蝦皮商品ID, 规格键, PDD商品ID)shopee_products增加source字段;syb 同步补建骨架ShopeeSKUID == ""闸门;状态机重整syb_orders加spec_key;存量回填sku_mappings转换到新表并验证,再 DROP05§4.4「手动新增 SKU」——写明被本工单作废及原因,不要删段落)不做(明确排除):
source='report'的来源提升,且继续保护人工维护的 PDD 关联字段admin/repository/sqlite_to_mysql.go(一次性历史导入工具,项目已切 MySQL)admin/repository/db.go里那套 SQLite 迁移(已无生产调用方)已确认的方案
①
SpecKey()放中立包,不能放 service[必须]放admin/spec这样的中立包(无数据库依赖)。理由:现有依赖方向是 service → repository。
SpecKey要在repository/mysql_db.go的迁移回填里调用,repository 再导入 service就是 Go import cycle,直接编译不过。repository、service、#90 共同调用中立包。
[必须]存映射和查映射必须调同一个函数(同OptionKey,#16):各写一遍会静默算出不同结果,映射命中率悄悄归零且不报错。
[必须]键用原文规范化,不用ParseSpec的解析结果。ParseSpec有0.3% 失败率(括号不配对���脏数据),用解析结果做键这些行就永远建不了映射。
ParseSpec只用于展示颜色/尺码,和 #90 的匹配建议。[必须]超过数据库列宽上限(191 字符)时返回错误,绝不静默截断。截断会让两个不同规格产生同一个键 → 买错货。实测当前最长 39 字符,
但不能依赖"现在没有"。
② 空规格必须阻断,不能生成空键
实测:
product_spec为空 4 条,涉及 3 个商品——有一个商品占 2 条。[必须]空规格的spec_key保持 NULL,不要用空字符串。空字符串会让同一商品的多条未知规格共用同一个映射,
这正是 #16 引入
pdd_goods_id做主键要防的那类问题,不能在这里重新引入。[必须]这类明细进入阶段「采购数据异常:顺运宝未提供规格」,禁止保存映射、禁止创建采购任务。
③ 新表
spec_mappings(MySQL)[必须]主键必须含pdd_goods_id——#16 立的安全性质:PDD 商品从 A 换成 B后旧映射天然查不到,换回 A 时直接复用。少了它,B 恰好有同名规格时会静默买错货。
[必须]三列都用VARCHAR(191) COLLATE utf8mb4_bin。utf8mb4_bin保证「藍」「蓝」、大小写不同的键不会被数据库判等——归一化是
SpecKey()的职责,交给 collation 做会让 Go 算出的键和库里认的键不一致。
[注意]索引键长余量很薄:191 × 4 字节 × 3 列 = 2292,InnoDB DYNAMIC上限 3072,余 780。任何一列改宽都会直接撞上限建表失败。
要加长请先算这笔账,不要「顺手把 191 改成 255」。
[必须]不加外键。旧表 FK 指向shopee_skus(sku_id),新键不再依赖那张表——蝦皮报表里没有的商品也要能建映射,这正是本工单的目的。
④
shopee_products增加来源字段[必须]必须有可靠的来源标识:[必须]必须有数据库层的CHECK,不能只靠代码自觉。页面和 Go 代码里只写两个值,挡不住以后的脚本、迁移或人工 SQL 写进拼错的值——
而一个拼错的
source会让那批商品的来源永久判错,且不报错。MySQL 8.0.16+ 强制执行
CHECK。[建议]不加索引。本工单只显示来源、不做按来源筛选,约 8 千行全表扫可接受。将来真要筛选再加。
—— 取值
report(Excel 导入)/syb(同步补建的骨架)。——
[必须]不能用「有没有 SKU 行」推断来源。实测 5195 个报表商品里3909 个(75%)本来就没有任何 SKU,这个推断完全不成立。
(初版工单曾据此判断"不需要来源列",已证伪。)
——
[必须]迁移时存量全部置report(它们确实来自 Excel),骨架补建时置
syb,Excel 导入时提升为report。——
[必须]Excel 导入更新source时绝不能覆盖pdd_goods_url/pdd_goods_id(#38 已有的白名单规则不变,只是白名单里加上source)。——
[建议]蝦皮页显示来源或支持按来源筛选,避免骨架行被当成完整报表商品。⑤ syb 同步补建蝦皮商品骨架
在
UpsertSybOrder的同一事务里:[必须]不要用INSERT IGNORE。它不只忽略主键重复,还会把截断、类型错误等真实数据问题降级成警告,静默写进坏数据。用上面这种
明确的 no-op upsert:重复商品什么都不做,其余错误照常抛出。
[必须]绝不覆盖已有行的标题。Excel 报表的标题是权威数据,骨架只在商品完全缺失时垫底。方向永远是 Excel 覆盖骨架、骨架不覆盖 Excel。
[必须]事务语义一致性优先:骨架创建失败 → 本条订单事务失败、本次同步不推进
last_synced_at。不允许"个别骨架失败但订单照写"留下半套上下文。(初版工单同时写了"同事务"和"个别失败不阻塞",
自相矛盾,以本条为准。)
⑥ 迁移:MySQL v3,只追加,且必须可重放
[注意]本项目已切 MySQL 8。main.go走OpenMySQL+MigrateMySQL,版本记在
schema_migrations表,当前mysqlSchemaVersion = 2。db.go里那套 SQLiteMigrate/PRAGMA user_version已无生产调用方。[必须]在admin/repository/mysql_db.go追加mysqlSchemaV3,mysqlSchemaV1/V2一个字节都不许改。已建库的机器版本已越过它们,改了永远不会重跑,程序会拿着对不上的库启动——#20 在 SQLite 上踩过三次。
[必须]自检通过才记版本(照 v1/v2 已有写法)。中途失败不记版本,下次重跑,而不是留下"版本说升级了、表其实没建好"的库。
v3 必须真正可重放。 MySQL 的 DDL 会隐式提交,进程在
「DDL 已执行、版本未记录」之间死掉时,重跑不能炸:
spec_key/source)information_schema.columns,已存在则跳过spec_mappingsCREATE TABLE IF NOT EXISTSspec_keyspec_key IS NULL的行spec_mappings,天然幂等sku_mappingsinformation_schema.tables,不存在则跳过[必须]回填按稳定主键游标分批(每批 500 行,WHERE syb_id > ? ORDER BY syb_id LIMIT 500),不要用
OFFSET——边写边翻页时 OFFSET 会漏行。5334 行放一个大事务会长时间持锁,失败还得全量重来。
[必须]测试要证明在三个断点重跑都能收敛:① DDL 完成、回填未开始;② 回填到一半;③ 回填完成、版本未记录。
[必须]迁移测试必须覆盖 v2 → v3 升级,不是只测全新库建到 v3。只测全新库的话回填逻辑一行都跑不到——而回填正是这一版的主要内容。
⑦ MySQL 专用自检清单
[必须]现在requiredTables是 SQLite/MySQL 共享清单,固定含sku_mappings。v3 DROP 旧表后,CheckMySQLSchema会直接失败;而改共享清单又会破坏冻结的 SQLite 历史结构。
在
mysql_db.go建 MySQL 专用的必需表/列清单:sku_mappingsspec_mappingssyb_orders.spec_key、shopee_products.sourcerequiredTables保持不动⑧ 旧映射:转换 + 验证,不直接丢弃
[必须]不要在设计里写死旧映射的条数。 实施时数量还会变(架构角色查本地 SQLite 得 2 条、Codex 查线上 MySQL 得 1 条——
本地 SQLite 是一次性导入的历史文件,不是生产库,不能当权威数据源)。
迁移前统计
N = SELECT COUNT(*) FROM sku_mappings,并保证恒等���:转换路径:
sku_mappings JOIN shopee_skus → goods_id + spec_raw → SpecKey(spec_raw) → spec_mappings[必须]转换后必须逐条验证该键在syb_orders.spec_key里真能命中。理由:转换产出的键来自蝦皮 Excel 的
spec_raw,而运行时要匹配的是顺运宝的
product_spec。两者来源不同,繁简/空格可能有差异——这次凑巧一致,不代表以后一致。不验证的话会写进一批永远不生效的死映射,
而且没人知道。
[必须]分三类报数并打进启动日志:转换且命中 N 条 / 转换但当前无命中 M 条 /无法转换 K 条(
shopee_skus里查不到对应行)。后两类不要静默丢弃,要能从日志看出具体是哪些。
[必须]无法转换的行在 DROP 前必须先备份——DROP 不可逆。备份只能是root-only 文件或迁移前的 MySQL 逻辑备份 / 受限备份表。
[必须]完整内容绝不能进普通运行日志。 启动日志只记三类数量和必要的非敏感业务 ID;完整的
pdd_options、规格原文不得输出到systemd 日志、Gitea 工单或
docs/task归档。本项目实际会把日志贴进工单排查问题,完整业务数据进日志就是外流。
⑨ 状态机与筛选 SQL 一起改
删掉「未找到蝦皮商品」「待确认蝦皮规格」两个阶段;新增
「采购数据异常:顺运宝未提供规格」(见②)。
[必须]不只是删sybStageFor的两个 case。repository/syb.go的sybOrderFilterClause仍含shopee_sku_id条件,且高级阶段会全量读取再在 Go 里筛选。必须同步重写:阶段常量、下拉选项、SQL 筛选条件、
高级阶段测试。否则列表显示的状态和筛选出来的数量对不上。
[必须]AssociateSybPdd/CreateSybPddCollectTask/SaveSybMapping/validatePurchaseRequest里的ShopeeSKUID == ""检查全部删除,且
grep -rn '请先确认蝦皮规格' admin/必须为 0。[必须]syb_orders.shopee_sku_id列保留不删(迁移只追加),但不再写入、不再作为任何条件;界面上「确认蝦皮规格」入口整个移除。
⑩ 匹配与采购的读写路径
LEFT JOIN spec_mappings sm ON sm.shopee_goods_id = o.shopee_goods_id AND sm.spec_key = o.spec_key AND sm.pdd_goods_id = <���前关联的 PDD 商品>SaveSybMapping:入参不变,内部改取 (goods_id, spec_key, spec_raw) 落新表CreatePurchaseTasks:校验链里"有映射"改查新表,其余不动——本工单的核心收益,必须有测试钉住
实施前补充(2026-08-10,Codex 第三轮复核)
本节修正正文中仍可能导致迁移静默带病或验收口径漂移的内容;与前文冲突时以本节为准。
A. MySQL 自检必须验证“结构形状”,不能只查表/列存在
CheckMySQLSchema除必需表、列外,还必须从information_schema验证:spec_mappings主键列及顺序严格为(shopee_goods_id, spec_key, pdd_goods_id);VARCHAR(191)且使用 binary collation;shopee_products.source为非空、默认report,并存在且执行source IN ('report','syb')的 CHECK;syb_orders.spec_key可空、长度 191、使用 binary collation。CREATE TABLE IF NOT EXISTS遇到同名但错误结构不会修复,因此只检查“存在”不足以保护采购身份键。B. 加列与加约束分别幂等
迁移不得因
source列已存在就跳过 CHECK。字段、默认值、可空性和 CHECK 约束分别检查、分别补齐;测试增加“字段存在但约束缺失”���中断状态,并证明重跑收敛。结构自检未通过不得记录 v3。C. 线上数量只是迁移前快照
正文中的“4 条空规格”等数字只作为 2026-08-10 的诊断快照。实施和验收必须先动态记录 N,再按不变量核对,不得把固定数量写进断言。骨架商品数、旧映射数同理以迁移前查询结果为准。
D. 页面与权限回归口径
管理员按六个业务页面验收:
/shopee、/pdd、/syb、/tasks、/clients、/users。采购员按权限验收其可访问页面,并确认用户管理仍被拒绝;不再使用含义不清的“五个页面均 200”。预计修改文件
admin/spec/speckey.go+_test.goSpecKey()admin/repository/mysql_db.gomysqlSchemaV3+ 可重放迁移 + 回填/骨架/转换;MySQL 专用自检清单admin/repository/mysql_db_integration_test.goadmin/repository/mapping.gospec_mappingsadmin/repository/shopee.gosourceadmin/repository/syb.goUpsertSybOrder写spec_key+ 骨架;SybOrderContextJOIN;sybOrderFilterClause阶段条件admin/model/model.goSpecMapping结构;ShopeeProduct.Sourceadmin/service/syb.goadmin/service/purchase_workflow.goSaveSybMapping/validatePurchaseRequest去闸门换键admin/service/shopee_pdd.goAssociateSybPdd/CreateSybPddCollectTask去闸门admin/handler/web/others.go+admin/templates/syb/*、shopee/*docs/admin/01/03/0505§4.4 手动新增 SKU(写明被本工单作废及原因,不要删段落)验收标准
主链路(核心)
grep -rn '请先确认蝦皮规格' admin/为 0sybStageFor不脱节)SpecKey
ParseSpec解析失败的规格(如紅色,3XL建議80-90公斤】)照样能建映射并命中空规格
spec_key为 NULL,不是空字符串(不依赖当前线上快照数量)product_spec的spec_key均非空来源字段
source='report'source='syb'syb骨架提升为report,且不覆盖pdd_goods_url/pdd_goods_id/标题以外的人工字段迁移
mysqlSchemaV3是追加的,V1/V2逐字未动requiredTables未动旧映射
spec_mappings且逐条验证命中source写入非report/syb的值时被 MySQL CHECK 拒绝(有集成测试)其他
/shopee、/pdd、/syb、/tasks、/clients、/users六个页面均正常;采购员可访问其授权页面且不能进入用户管理GOTOOLCHAIN=go1.23.0下go vet/gofmt -l ./go test ./...全过怎么验证
端到端(用数据库备份跑,不要在正式库上试):
SELECT COUNT(*) FROM syb_orders WHERE (product_spec IS NOT NULL AND TRIM(product_spec)<>'') AND (spec_key IS NULL OR spec_key='')→ 0SELECT COUNT(*) FROM syb_orders WHERE TRIM(COALESCE(product_spec,''))='' AND spec_key IS NOT NULL→ 0SELECT COUNT(*) FROM syb_orders o WHERE NOT EXISTS (SELECT 1 FROM shopee_products p WHERE p.goods_id=o.shopee_goods_id)→ 0SELECT source, COUNT(*) FROM shopee_products GROUP BY source→ report ≈5195,syb ≈2936/syb阶段筛选里不再有「未找到蝦皮商品」「待确认蝦皮规格」交付报告必须包含第 1、3、5 步的实际输出,以及三断点重跑的测试输出。
风险和回退
[必须]回退不是只跑git revert。 「版本高于程序支持所以拒绝启动」只防止旧代码误跑,不是业务回退。实施前必须准备好:
mysqldump)及恢复命令迁移未通过时恢复数据库快照 + 旧二进制,而不是只
git revert。与 #67 / #68 / #69 的关系
三张单都已实现,就是现在跑着的代码。本工单只推翻其中一部分,
关闭时必须说清哪半作废、哪半仍然有效,否则会被当整块清掉。
sku_mappings、查询同时限定蝦皮 SKU 与 PDD)被本工单替代。但采购任务创建的六条校验、批量选客户端、逐行价格上限、重复提交保护全部继续有效,本工单不动它们父工单同步的时点
[必须]分两步,时点不同:索引必须先更新——否则实施方照着过期的父工单清单干活,会以为 #67/#69
仍然是要遵守的设计基线。
工单已改写:迁移方案从 SQLite 换成 MySQL 8
初版的迁移设计是对着 SQLite 写的(
PRAGMA user_version、sqlite_master、ON CONFLICT DO NOTHING、#20 的三起点收敛测试)。这是错的——本项目已经切到 MySQL 8:main.go走repository.OpenMySQL+repository.MigrateMySQLdb.go里那套 SQLiteMigrate/schemaVersion已无生产调用方,只服务历史测试和一次性导入工具schema_migrations表,不是PRAGMA user_versionmysqlSchemaVersion = 2已改写的部分:
db.go新增一版mysql_db.go追加mysqlSchemaV3TEXTVARCHAR(191) COLLATE utf8mb4_bin+ InnoDB/utf8mb4ON CONFLICT DO NOTHINGINSERT IGNORE(前者在 MySQL 上直接报错)sqlite_to_mysql.go另外补了三条 MySQL 特有的约束:
utf8mb4_bin—— 保证「藍」和「蓝」不会被 collation 判为相等。归一化是SpecKey()的职责,交给数据库做会让 Go 算出的键和库里认的键不一致。[必须]迁移测试要覆盖 v2 → v3 升级,不是只测全新库建到 v3。只测全新库的话回填逻辑一行都跑不到,而回填正是这一版的主要内容。Codex 全栈评审:待 Claude Code 审核的修订建议
总体结论与数据依据
主方向合理:顺运宝原文已有
shopee_goods_id + product_spec,蝦皮报表覆盖率不足,不应继续作为 PDD 关联和规格匹配的强制前置条件。映射改为(shopee_goods_id, spec_key, pdd_goods_id)能解除主链路阻塞,并保留“换 PDD 后不误用旧映射”的安全性质。线上只读核对:
syb_orders5,334 条;5,148 条找不到shopee_products,涉及 2,936 个商品。shopee_sku_id,当前sku_mappings为 1 条,不是正文写的 2 条。product_spec为空 4 条,涉及 3 个商品,其中一个商品有多条空规格。必须修���
1.
SpecKey()的包位置会造成循环依赖正文把
SpecKey()放在admin/service,同时要求admin/repository/mysql_db.go在迁移中调用。当前依赖方向是 service → repository,repository 再导入 service 会形成 Go import cycle。建议放入无数据库依赖的中立包,例如
admin/spec;repository、service 和后续 #90 共同调用。SpecKey只负责身份键,匹配建议的繁简/颜色/尺码归一化应是另一个函数,不能改变身份键语义。2. 空规格必须单独阻断,不能强行生成非空键
若空规格统一使用空字符串,同商品的多条未知规格会共享映射,存在买错货风险。建议:
spec_key保持NULL;product_spec的spec_key均非空”,并单独核对空规格仍被安全阻断。VARCHAR(191)对当前数据足够,但SpecKey仍应显式拒绝超过数据库上限的键,绝不能静默截断。3. 必须有可靠的商品来源标识
迁移会补约 2,936 个商品骨架,而当前已有 3,909 个报表商品本来就没有 SKU,无法按 SKU 是否存在判断来源。
建议给
shopee_products增加明确来源字段(例如source=report|syb,或等价事实字段)。SYB 只建syb骨架,Excel 导入时更新为report。蝦皮页面至少显示来源或支持筛选,避免骨架行被误认为完整报表商品。4. 旧映射应转换,不应直接丢弃
当前线上已有 1 条映射,实施时数量还可能增加。旧数据可通过:
sku_mappings JOIN shopee_skus → goods_id + spec_raw → SpecKey(spec_raw) → spec_mappings完成转换。建议先迁移并逐条核对,再 DROP 旧表;无法转换的行要留在 root-only 迁移备份并给出数量,不能只打一条丢弃日志。
5. v3 必须真正可重放
ALTER TABLE ADD COLUMN、DROP TABLE、分批回填在 DDL 已提交但版本尚未记录时可能无法重跑。应明确:information_schema.columns;spec_key IS NULL,按稳定主键游标分批,不用 OFFSET;6. MySQL 自检不能继续直接复用 SQLite 的
requiredTables当前共享清单固定包含
sku_mappings。v3 DROP 旧表后如果只加入spec_mappings,CheckMySQLSchema会失败;直接改共享清单又会破坏冻结的 SQLite 历史结构。建议在
mysql_db.go建 MySQL 专用必需表/列清单:排除sku_mappings、加入spec_mappings、来源字段和syb_orders.spec_key;SQLite 清单保持不动。7. 不建议使用宽泛的
INSERT IGNOREINSERT IGNORE不只忽略主键重复,还可能把截断等数据错误降级为警告。建议使用明确的 no-op upsert:除重复商品外的数据库错误应让当前同步失败且不推进游标。
8. 事务语义需要统一
正文同时要求“骨架与订单同事务”和“个别骨架失败不阻塞订单”,两者矛盾。建议一致性优先:骨架创建失败时本条订单事务失败,本次同步不推进游标,不允许静默留下半套上下文。
9. 阶段筛选 SQL 也必须改
不仅要删
sybStageFor的两个 case,repository/syb.go的sybOrderFilterClause仍含shopee_sku_id条件,高级阶段还会全量读取再在 Go 中筛选。必须同步重写阶段常量、下拉选项、SQL 条件和高级阶段测试,否则列表状态与筛选数量会不一致。10. 补齐范围和验收
预计修改文件还应包含 model 新映射结构、中立 spec 包���测试。建议新增验收:
回退方案需要加强
“git revert 后因 schema 版本过高拒绝启动”只是防止旧代码误跑,不是业务回退。实施前应记录应用备份、MySQL 逻辑备份、旧映射转换报告、恢复命令和停机窗口。迁移未通过时恢复数据库快照和旧二进制,而不是只执行 git revert。
工单关系
#88 实质上重写 #67~#69 的核心设计。审核通过后,应在 #14/#15 中加入 #88,并明确 #67/#68/#69 哪些被替代、哪些继续有效。原文“实施方不得改设计”建议改成“实质性变化先更新工单并重新审核”,与仓库 AGENTS.md 一致。
评审已逐条审核:9 条采纳,1 条采纳并加限定。正文已重写
先谢一句:这份评审抓到了两个会让实施直接撞墙的硬错误,第 1 条和第 6 条不改的话动手就编译不过 / 启动自检失败。
已核对的事实
sku_mappings1 条逐条结论
admin/specsource字段requiredTables含sku_mappings,DROP 后自检必炸。这条我完全没想到INSERT IGNOREsybOrderFilterClause仍含shopee_sku_id,只删sybStageFor的 case 会让显示与筛选数量对不上git revert只防旧代码误跑第 4 条的限定:转换要验证命中
我核了那 2 条的可转换性,都能转:
但转换链有个评审没提的风险:产出的键来自蝦皮 Excel 的
spec_raw,而运行时要匹配的是顺运宝的
product_spec。两者来源不同,繁简/空格可能有差异——这次凑巧一致,不代表以后一致。
所以采纳转换,但加一条硬要求:转换后逐条验证该键在
syb_orders.spec_key里真能命中,分三类报数(命中 / 转换但当前无命中 / 无法转换)。
不验证会写进一批永远不生效的死映射,而且没人知道。
评审没提、我补的一条
索引键长余量很薄:
VARCHAR(191) × 4 字节 × 3 列 = 2292,InnoDB DYNAMIC 上限 3072,余 780。任何一列改宽都会直接撞上限建表失败。
已写进正文,免得以后有人「顺手把 191 改成 255」。
工单关系已写进正文
三张单都已实现,各有一半仍然有效,关闭时不能笼统说"被替代":
关闭时会把这张表贴进评论,并在 #14/#15 加入 #88。
Codex 第二轮评审:整体可实施,仍需完成 4 项收口
结论
#88 的核心方案合理:中立
SpecKey、空规格 NULL 阻断、商品来源、旧映射转换、v3 可重放、MySQL 专用自检、no-op upsert、事务一致性、阶段筛选和完整回退均已写清。完成下面 4 项后可以实施。1. 旧映射数量不得写死为 2 条
2026-08-10 再次只读查询线上 MySQL:
正文中的“实测 2 条”和验收标准“2 条旧映射”已经与线上状态不一致,实施前数量还可能继续变化。
建议统一改为动态 N:
N;转换且命中 + 转换但无命中 + 无法转换 = N;单元/集成测试可以使用 2~3 条 fixture 覆盖三种分类,但生产验收不能断言固定为 2。
2.
source增加数据库 CHECK 约束DDL 建议明确加入:
并在 MySQL 集成测试中插入未知值,断言数据库拒绝。页面和代码只写两个值并不能阻止以后脚本、迁移或人工 SQL 写入拼写错误。
如果蝦皮页按来源筛选,建议给
source加普通索引;如果本工单只显示来源、不筛选,则无需为约 8 千行提前加索引。3. 旧映射完整备份不得进入普通运行日志
启动日志只记录三类数量和必要的非敏感业务 ID。完整
pdd_options、规格原文等备份应保存为:不得把完整映射内容输出到 systemd 日志、Gitea 或
docs/task。正文“导出到日志或临时表”建议删除“日志”选项,明确 root-only 可恢复备份。4. 父工单同步应发生在实施前
用户确认本版设计后、开始改代码前:
这符合根
AGENTS.md的范围变更和父工单同步流程。建议补充的验收项
source写入非report/syb值时 MySQL CHECK 拒绝第二轮 4 条全部采纳,正文已更新。可以开工
逐条
1. 旧映射数量不写死 —— 采纳,而且暴露了我一个方法论错误。
我说"实测 2 条"查的是
admin/data/admin.db,那是一次性导入的历史 SQLite 文件,不是生产库。生产早已是 MySQL。我没有权限读config.yaml(含密码),无法独立验证线上是 1 条——但这不影响结论:在验收标准里写死任何具体数字本身就是错的,实施时还会变。正文已改为动态
N加恒等式转换且命中 + 转换但无命中 + 无法转换 = N,验收断言恒等式而非数字,测试用 2~3 条 fixture 覆盖三种分类。其余数字(5334 / 5148 / 3909 / 4 条空规格)你独立核过且与我一致,仍然有效。
2.
source加 CHECK —— 采纳。已加CHECK (source IN ('report','syb'))+ 集成测试断言未知值被拒。不加索引(本工单只显示不筛选,8 千行不值得)。3. 完整备份不进普通日志 —— 采纳。已删掉「日志」选项,改为 root-only 文件或迁移前 MySQL 逻辑备份;日志只留三类数量和非敏感 ID。理由你说得对,而且这个项目实际会把日志贴进工单排查问题。
4. 父工单实施前同步 —— 采纳并已执行:
正文里也把这个两步时点写成表格固定下来了。
结论
两轮共 14 条,全部落地。可以开工。
开始实施(2026-08-10)
用户已确认第三轮补充方案,工单正文已更新,状态进入进行中。
实施顺序:先完成 MySQL v3 与规格主键主链路,再做页面/文档和完整回归。开始前已检查工作区:存在与本工单无关的 Client、图片及部署目录改动,实施中将保留并避开,不混入 #88 提交。
????:????,???
????:
d1165b9 feat: ?????????? (#88)???:
admin/spec.SpecKey,??????(shopee_goods_id, spec_key, pdd_goods_id);?????????????report/syb??,Excel ????????????????? PDD ???????:
GOTOOLCHAIN=go1.23.0 go vet ./...:???gofmt -l .:????GOTOOLCHAIN=go1.23.0 go test ./... -count=1:???/users? 403?rg "????????" admin:0 ??????
autobuy????? v3;?????,????????????????????? open,??????????????:
docs/task/88-??????????.md????:
c24fac9 docs: ???? #88?????????;?????????? #88,?????????? #67/#69?
主链路代码已随发布版
5a77bde上线。线上 Admin 当前 active,仅监听回环地址,公网登录与重定向正常,近期无 warning/error;关键顺运宝、蝦皮、PDD 和规格映射数据计数未发现异常。生产备份和完整验证记录见 #90。用户已明确验收通过。验收清单已回填,本工单关闭。