#16 把 pdd_products 建好了,但界面完全没有——四个页面还是骨架, 所有写操作返回"尚未实现"。现在要建一条 PDD 商品记录只能手工写 SQL。
pdd_products
PDD 商品在 #16 之后已经是独立实体(有自己的表、自己的采集状态、 可以被多个蝦皮商品共用),独立实体就该有自己的页面。
做完这个页面,Admin 侧的闭环就完整了:建 PDD 商品 → 发起采集 → Client 领走执行 → 提交结果 → 页面显示已采集。整条链路不需要蝦皮和顺运宝的任何数据。
做:
docs/admin/01
docs/admin/05
不做(各自独立工单):
docs/admin/01 §4 现在写着「顶级导航固定四个模块,顺序不变」, 本工单要改成五个。导航顺序:蝦皮数据 / PDD 商品 / 顺运宝数据 / 采购任务 / 客户端列表。
按 docs/admin/05 §2 的既有约定,页面用统一的三段式布局,objectName 用 pddProductPage。
objectName
pddProductPage
[创建] [创建采集任务] 采集状态[全部▾] 商品ID/链接[______] [搜索] [删除]
[必须]
goods_id
skus_json
—
[建议] 规格数用 SQLite 的 json_array_length(skus_json, '$.skus') 直接算, 不要把整个 JSON 读进 Go 再数(本项目 SQLite 3.50,实测 json_each / json_array_length 可用)。
[建议]
json_array_length(skus_json, '$.skus')
json_each
json_array_length
┌─────────────────────────────────────────────┐ │ PDD 商品 737116531267 │ ├─────────────────────────────────────────────┤ │ 商品 ID 737116531267 (只读) │ │ PDD 链接 [___________________](可编辑) │ │ 标题 西装外套三件套 (只读) │ │ 采集状态 已采集 │ │ 失败原因 — │ │ 采集时间 2026-08-07 15:20 │ ├─────────────────────────────────────────────┤ │ 规格(12 个) │ │ 颜色 尺码 价格 有货 │ │ 黑色 M ¥12.56 是 │ │ 白色 M ¥12.56 否 │ ├─────────────────────────────────────────────┤ │ [重新采集] [取消] [保存] │ └─────────────────────────────────────────────┘
[必须] 规格和价格必须显示。这是采集结果的全部价值所在—— 不显示的话操作员没法确认"采得对不对、是不是我要的那个商品"。数据来自 skus_json, 维度顺序按 dimensions 里的 key 顺序(Go map 无序,必须靠它定顺序)。
dimensions
key
[必须] 价格显示成 ¥12.56,底层存的是整数分。price_cent 为 null 时显示 未采到, 不要显示成 ¥0.00。
¥12.56
price_cent
null
未采到
[必须] 只填 PDD 链接,其余全靠采集回填。
[必须] 链接必须能解析出 goods_id,解析不出来直接报错,提示操作员给完整链接。 不接受短链——goods_id 有 UNIQUE 约束,防重全靠它;拿不到就没法查重,会存成多行。
调 repository.EnsurePddProduct(#16 已实现,含新建 / 复用 / 复活三分支)。
repository.EnsurePddProduct
[必须] 不指定客户端(依赖 #17)。谁领到就在领取时标记谁。
collect_status
collecting
MarkCollecting
pdd_goods_url
pdd_goods_id
goods_url
[必须] 按钮文案用「创建采集任务」,不要用「采集」。 它做的是建一个任务, 不是立刻去采——真正的采集要等 Client 来领、去手机上跑,可能几秒也可能几分钟。 点完页面上只有状态从"未采集"变成"采集中",不说清楚操作员会以为没生效。
[必须] 结果在状态条明确提示:已创建 3 个采集任务,等待客户端领取(跳过 1 个采集中的)。
已创建 3 个采集任务,等待客户端领取(跳过 1 个采集中的)
软删除(#16 已实现)。二次确认框要写明:
将删除 3 条记录。删除后重新创建同一链接可恢复,但采集结果会清空。
(复活时清空旧采集结果是 #16 的既定行为——记录被删过一次,旧数据不该再当有效的用。)
共 24 条 · 已采集 18 · 待采集 4 · 采集中 1 · 失败 1
admin/repository/pdd.go
admin/service/pdd.go
admin/handler/web/pdd.go
admin/handler/web/web.go
admin/templates/pdd/list.html
admin/templates/pdd/edit_modal.html
admin/templates/partials/header.html
admin/static/js/app.js
admin/static/css/app.css
docs/admin/01-requirements.md
docs/admin/05-ui-specification.md
列表
创建
弹窗
创建采集任务
assigned_client
status
pending
删除与安全
WHERE deleted_at IS NULL
质量
go vet
gofmt -l .
go test ./...
cd D:\chengma\cmautobuy\admin go vet ./... gofmt -l . go test ./... -count=1 go run .
浏览器打开 http://127.0.0.1:8080/pdd,走一遍: 创建 → 列表出现 → 双击看弹窗 → 勾选建采集任务 → 状态变采集中 → 用 curl 模拟 Client 领取并提交结果 → 刷新页面看到"已采集"和规格数。
不需要真机。
admin/AGENTS.md
回退:改动集中在 admin/,git revert 即可,无表结构变更。
admin/
git revert
eb8d357
4975c68
docs/task/18-admin-pdd-商品页面.md
go vet / gofmt -l . 无输出;go test ./... -count=1 → ok cmautobuy/admin/service 0.945s。本工单新增 32 个用例。
go test ./... -count=1
ok cmautobuy/admin/service 0.945s
补测了工单和实现报告都未覆盖的两项:
① 并发建采集任务 —— 4 个 curl 同时 POST /pdd/collect、同一个 goods_id → tasks 表里正好 1 条,商品置 collecting。 MarkCollecting 在事务里同时充当「有没有人在采」的判断,单线程读代码看不出它扛不扛并发。
/pdd/collect
tasks
② 商品标题 XSS —— 标题来自拼多多,是外部不可信数据。 提交 title = "<script>alert('XSS')</script>正常标题"、规格含 <img src=x onerror=alert(1)> → 列表页原样 <script> 出现 0 次、弹窗原样 onerror 出现 0 次,均为转义形态。
title = "<script>alert('XSS')</script>正常标题"
<img src=x onerror=alert(1)>
<script>
onerror
其余:短链和淘宝链接 400 且不写库、无 CSRF 的 POST 403、端到端建商品→建任务→领取→提交→刷新流转正确、13 规格弹窗 14 行 3808 字节、软删除后重建同链接复活原行(id 不变)、五个页面全部 200。
12 项验收标准全部通过。
docs/admin/05 §4.3 仍写着蝦皮弹窗是「PDD 链接唯一的录入口,也是采集的唯一发起点」,与新增的 PDD 页自相矛盾——而实际情况是反过来的,蝦皮页的 ShopeeCollect 是骨架(501),真能用的只有 PDD 页。
ShopeeCollect
实现报告把它列为「已知待收敛点」,方向对但范围少了:全库共 5 处。架构角色先按 CLAUDE.md §7.3「单处笔误」例外自改 §4.3,发现该例外不成立,剩余 3 处按 §7.2 打回。
CLAUDE.md
同时发现 #16 的遗留:shopee.go 的 TODO 写着「空 → no_link」,而 no_link 已在 #16 从 CHECK 约束删除,照写会直接撞约束。
shopee.go
no_link
返工后 3 处说法已对齐、no_link TODO 已改并加警告。「不要放到表格每一行,N 个 SKU 行会建出 N 个重复任务」这条理由四处全部保留,只把适用范围收窄。行为未动。
artifact_ref
/tasks
purchase
用户实机确认通过。归档状态已更新为「验收通过」(bf81bad)。
bf81bad
相关提交:
归档: docs/task/18-admin-pdd-商品页面.md
工具条的后续调整见 #23。
No dependencies set.
The note is not visible to the blocked user.
基本信息
要解决什么
#16 把
pdd_products建好了,但界面完全没有——四个页面还是骨架,所有写操作返回"尚未实现"。现在要建一条 PDD 商品记录只能手工写 SQL。
PDD 商品在 #16 之后已经是独立实体(有自己的表、自己的采集状态、
可以被多个蝦皮商品共用),独立实体就该有自己的页面。
做完这个页面,Admin 侧的闭环就完整了:建 PDD 商品 → 发起采集 →
Client 领走执行 → 提交结果 → 页面显示已采集。整条链路不需要蝦皮和顺运宝的任何数据。
做什么 / 不做什么
做:
docs/admin/01§4 和docs/admin/05不做(各自独立工单):
怎么做
这是第五个模块
docs/admin/01§4 现在写着「顶级导航固定四个模块,顺序不变」,本工单要改成五个。导航顺序:蝦皮数据 / PDD 商品 / 顺运宝数据 / 采购任务 / 客户端列表。
按
docs/admin/05§2 的既有约定,页面用统一的三段式布局,objectName用pddProductPage。顶部工具条
[必须]这个页面的主要用途是维护(找出失败的重采、找出没采的),只按 ID 搜的话要翻页找,筛选是刚需
中间表格
goods_idskus_json算。[必须]采到 1 个和采到 20 个差别很大——只采到 1 个通常意味着没点开规格面板,是采集有问题,这一列能一眼看出来—[建议]规格数用 SQLite 的json_array_length(skus_json, '$.skus')直接算,不要把整个 JSON 读进 Go 再数(本项目 SQLite 3.50,实测
json_each/json_array_length可用)。双击行打开的弹窗
[必须]规格和价格必须显示。这是采集结果的全部价值所在——不显示的话操作员没法确认"采得对不对、是不是我要的那个商品"。数据来自
skus_json,维度顺序按
dimensions里的key顺序(Go map 无序,必须靠它定顺序)。[必须]价格显示成¥12.56,底层存的是整数分。price_cent为null时显示未采到,不要显示成 ¥0.00。
创建
[必须]只填 PDD 链接,其余全靠采集回填。[必须]链接必须能解析出goods_id,解析不出来直接报错,提示操作员给完整链接。不接受短链——
goods_id有 UNIQUE 约束,防重全靠它;拿不到就没法查重,会存成多行。调
repository.EnsurePddProduct(#16 已实现,含新建 / 复用 / 复活三分支)。创建采集任务
[必须]不指定客户端(依赖 #17)。谁领到就在领取时标记谁。goods_id去重 → 建采集任务collect_status已是collecting的跳过,并在结果里说明跳过了几个MarkCollecting把状态置为collectingpdd_goods_url和pdd_goods_id必须填(Client 契约要求goods_url必填)[必须]按钮文案用「创建采集任务」,不要用「采集」。 它做的是建一个任务,不是立刻去采——真正的采集要等 Client 来领、去手机上跑,可能几秒也可能几分钟。
点完页面上只有状态从"未采集"变成"采集中",不说清楚操作员会以为没生效。
[必须]结果在状态条明确提示:已创建 3 个采集任务,等待客户端领取(跳过 1 个采集中的)。删除
软删除(#16 已实现)。二次确认框要写明:
(复活时清空旧采集结果是 #16 的既定行为——记录被删过一次,旧数据不该再当有效的用。)
底部状态条
共 24 条 · 已采集 18 · 待采集 4 · 采集中 1 · 失败 1预计修改文件
admin/repository/pdd.goadmin/service/pdd.goadmin/handler/web/pdd.goadmin/handler/web/web.goadmin/templates/pdd/list.htmladmin/templates/pdd/edit_modal.htmladmin/templates/partials/header.htmladmin/static/js/app.jsadmin/static/css/app.cssdocs/admin/01-requirements.mddocs/admin/05-ui-specification.md验收标准
列表
objectName为pddProductPage—)创建
goods_id时报错,不写库弹窗
dimensions¥12.56,price_cent为 null 时显示"未采到"而非 ¥0.00创建采集任务
goods_id去重collecting状态的被跳过,结果里说明跳过几个collectingpdd_goods_url有值assigned_client为空,status为pending)删除与安全
WHERE deleted_at IS NULL且只写在 repository 层质量
go vet/gofmt -l ./go test ./...全过怎么验证
浏览器打开 http://127.0.0.1:8080/pdd,走一遍:
创建 → 列表出现 → 双击看弹窗 → 勾选建采集任务 → 状态变采集中 →
用 curl 模拟 Client 领取并提交结果 → 刷新页面看到"已采集"和规格数。
不需要真机。
风险和回退
admin/AGENTS.md前端约束)回退:改动集中在
admin/,git revert即可,无表结构变更。实施完成,待验收
eb8d357feat: PDD 商品页面 (#18)4975c68docs: 归档任务 #18docs/task/18-admin-pdd-商品页面.md验证结果(架构角色亲自复跑,Go 1.23.0)
go vet/gofmt -l .无输出;go test ./... -count=1→ok cmautobuy/admin/service 0.945s。本工单新增 32 个用例。补测了工单和实现报告都未覆盖的两项:
① 并发建采集任务 —— 4 个 curl 同时 POST
/pdd/collect、同一个goods_id→
tasks表里正好 1 条,商品置collecting。MarkCollecting在事务里同时充当「有没有人在采」的判断,单线程读代码看不出它扛不扛并发。② 商品标题 XSS —— 标题来自拼多多,是外部不可信数据。
提交
title = "<script>alert('XSS')</script>正常标题"、规格含<img src=x onerror=alert(1)>→ 列表页原样
<script>出现 0 次、弹窗原样onerror出现 0 次,均为转义形态。其余:短链和淘宝链接 400 且不写库、无 CSRF 的 POST 403、端到端建商品→建任务→领取→提交→刷新流转正确、13 规格弹窗 14 行 3808 字节、软删除后重建同链接复活原行(id 不变)、五个页面全部 200。
12 项验收标准全部通过。
审查打回一次
docs/admin/05§4.3 仍写着蝦皮弹窗是「PDD 链接唯一的录入口,也是采集的唯一发起点」,与新增的 PDD 页自相矛盾——而实际情况是反过来的,蝦皮页的ShopeeCollect是骨架(501),真能用的只有 PDD 页。实现报告把它列为「已知待收敛点」,方向对但范围少了:全库共 5 处。架构角色先按
CLAUDE.md§7.3「单处笔误」例外自改 §4.3,发现该例外不成立,剩余 3 处按 §7.2 打回。同时发现 #16 的遗留:
shopee.go的 TODO 写着「空 →no_link」,而no_link已在 #16 从 CHECK 约束删除,照写会直接撞约束。返工后 3 处说法已对齐、
no_linkTODO 已改并加警告。「不要放到表格每一行,N 个 SKU 行会建出 N 个重复任务」这条理由四处全部保留,只把适用范围收窄。行为未动。未验证到的部分
artifact_ref仍未规范化(#16 遗留)遗留
/tasks只查purchase。已建 #19 处理验收通过,关闭
用户实机确认通过。归档状态已更新为「验收通过」(
bf81bad)。相关提交:
eb8d357feat: PDD 商品页面 (#18)4975c68docs: 归档任务 #18bf81baddocs: 记录 #18 #23 验收通过归档:
docs/task/18-admin-pdd-商品页面.md工具条的后续调整见 #23。
结转到后续工单的遗留
/tasks只查purchase)→ Admin:任务页改造,采集与采购统一展示 (#19)artifact_ref未规范化(#16 遗留),需与 Client 侧一起定格式