20775cc
https://mobile.yangkeduo.com/goods2.html?ps=14mchKPjYR
PDD App「复制链接」实际给出的是 https://mobile.yangkeduo.com/goods2.html?ps=14mchKPjYR:域名在白名单内,但 query 中没有 goods_id,商品身份藏在 ps= 短码里。两端都恰好落进空档:
goods_id
ps=
Agent 端(android/app/src/main/java/cn/ilapage/goauto/agent/automation/PddShareLinkResolution.kt)
android/app/src/main/java/cn/ilapage/goauto/agent/automation/PddShareLinkResolution.kt
PddShareLinkExtractor.select()
goodsId(uri)
host == "p.pinduoduo.com"
SHORT_HOST
mobile.yangkeduo.com
shortLinks
NOT_FOUND
PddShareLinkExpander.expand()
if (!parsed.host.equals(SHORT_HOST, ignoreCase = true)) return null
Server 端(server/app/goauto/task/current_page.go:279)
server/app/goauto/task/current_page.go:279
if !strings.EqualFold(parsed.Hostname(), "p.pinduoduo.com") { return 无法识别商品链接 }
结论:#108 实现的展开逻辑本身没有被执行到,它只对 p.pinduoduo.com 生效,而 PDD 实际复制出来的是 yangkeduo 的 ps= 短码链接。
p.pinduoduo.com
展开可行性已实证(2026-08-27,移动端 UA,curl 单次请求):
curl
GET https://mobile.yangkeduo.com/goods2.html?ps=14mchKPjYR → HTTP/2 307 location: https://mobile.yangkeduo.com/goods2.html?goods_id=971612375909&page_from=35&...
一跳 307 即得到 goods_id=971612375909,无 JS 跳转、无验证页;现有的 4 跳预算、白名单校验与超时设置均足够。注意实际状态码是 307 而非 302,实现必须按 3xx 通配处理。
goods_id=971612375909
顺带发现的隐患:Agent 端正文正则 bodyGoodsIDPattern 中 goods_id 前的分隔符组是可选的,refer_goods_id=123456 这类会被误匹配;再撞上 goodsIds.singleOrNull() 的唯一性要求,会把本可成功的页面判为失败。服务端 pddBodyGoodsIDPatterns 第一条已强制要求前置分隔符,两端不一致。
bodyGoodsIDPattern
refer_goods_id=123456
goodsIds.singleOrNull()
pddBodyGoodsIDPatterns
refer_goods_id
AMBIGUOUS
host == SHORT_HOST
parseAllowed
statusCode in 300..399
[?&]
&
%26
&
"goods_id":
LINK_RESOLUTION
current_page.go:279
validatePDDShareURL
CheckRedirect
extractPDDGoodsIDFromBody
goods_id=
refer_goods_id=
./gradlew :app:testDebugUnitTest
select()
go test ./app/goauto/task/...
docs/02-architecture-and-code-map.md
docs/03-business-rules-and-glossary.md
docs/08-agent-api-contract.md
待实施(由 Codex 执行)。
开始实施。按工单范围做两端同修:Agent 与 Server 均将“白名单域名且 URL 本身无 goods_id”作为展开触发条件,白名单保持不变;正文提取排除 refer_goods_id 等相近字段。不会改变先取得 goods_id、再请求创建采集任务、设备领取任务后执行的既有时序,也不涉及数据库、共享接口、采购、订单或支付。
首轮针对性测试已通过:Android :app:testDebugUnitTest;Server go test ./app/goauto/task/...。后续继续完整回归、Wiki-first 文档闭环及 PKG110 安装核验。
:app:testDebugUnitTest
#109 已完成实现并提交,现保持工单开放、等待人工验收。
实现:
mobile.yangkeduo.com/goods2.html?ps=...
验证:
cd android && .\gradlew.bat :app:testDebugUnitTest :app:assembleDebug
cd server && go test ./...
python dev_scripts/harness.py check --strict
git diff --check
Android-Agent-API-Contract@b722977b0c8b7db2ce0d3c8c2a1351f42941cb24
Local-Development-and-Verification@6362cbe25f4a0a1826b903ee4342c1a69ee87a42
sync
sync --check
goods2.html?ps=14mchKPjYR
提交:
daf7406ec97262d12a501d85b1245395847a8584
fix: expand all whitelisted PDD share links (#109)
未自动执行:未代替用户在 PDD 真实商品详情页点击“采集”创建正式采集任务,因此真机端到端“分享→展开→识别→采集结果”仍需按验收步骤人工触发;没有创建采购任务、订单或支付。
用户于 2026-08-28 明确确认本工单验收通过。按项目流程记录验收结论并关闭工单;本次仅更新工单状态,无新增长期文档事实,不重复同步 Wiki。
No dependencies set.
The note is not visible to the blocked user.
所属与来源
20775cc)当前页面采集仍然无法解析链接;用户提供了 PDD 实际复制到的链接https://mobile.yangkeduo.com/goods2.html?ps=14mchKPjYR,据此定位到两端的域名判断都把这类链接挡在展开逻辑之外。确认沿用 #108 的分工(展开在 Agent、裁决在服务端),只修两端的判断条件。当前事实与根因(提交
20775cc复核)PDD App「复制链接」实际给出的是
https://mobile.yangkeduo.com/goods2.html?ps=14mchKPjYR:域名在白名单内,但 query 中没有goods_id,商品身份藏在ps=短码里。两端都恰好落进空档:Agent 端(
android/app/src/main/java/cn/ilapage/goauto/agent/automation/PddShareLinkResolution.kt)PddShareLinkExtractor.select()中goodsId(uri)取不到goods_id,该链接不算长链;host == "p.pinduoduo.com"(SHORT_HOST),mobile.yangkeduo.com的链接被排除;shortLinks为空,返回NOT_FOUND,直接失败「无法识别商品链接」,一次展开请求都不会发出;PddShareLinkExpander.expand()循环里还有if (!parsed.host.equals(SHORT_HOST, ignoreCase = true)) return null,同样会立即放弃。Server 端(
server/app/goauto/task/current_page.go:279)if !strings.EqualFold(parsed.Hostname(), "p.pinduoduo.com") { return 无法识别商品链接 },同一个硬编码门槛,同样在发起请求前就拒绝。结论:#108 实现的展开逻辑本身没有被执行到,它只对
p.pinduoduo.com生效,而 PDD 实际复制出来的是 yangkeduo 的ps=短码链接。展开可行性已实证(2026-08-27,移动端 UA,
curl单次请求):一跳 307 即得到
goods_id=971612375909,无 JS 跳转、无验证页;现有的 4 跳预算、白名单校验与超时设置均足够。注意实际状态码是 307 而非 302,实现必须按 3xx 通配处理。顺带发现的隐患:Agent 端正文正则
bodyGoodsIDPattern中goods_id前的分隔符组是可选的,refer_goods_id=123456这类会被误匹配;再撞上goodsIds.singleOrNull()的唯一性要求,会把本可成功的页面判为失败。服务端pddBodyGoodsIDPatterns第一条已强制要求前置分隔符,两端不一致。目标
refer_goods_id误匹配。非目标
p.pinduoduo.com与mobile.yangkeduo.com),本工单只改「是否值得展开」的判断。实施方案
Agent 端
PddShareLinkExtractor.select():短链候选不再按SHORT_HOST过滤,改为「通过白名单校验且 query 中没有goods_id」即视为待展开候选。多个候选且指向不同目标时仍返回AMBIGUOUS,零候选仍返回NOT_FOUND。PddShareLinkExpander.expand():移除循环内host == SHORT_HOST的限制,改为只要parseAllowed通过(白名单域名、https、无 userinfo、端口空或 443)就继续跟随跳转;每一跳仍重新校验,跳转上限 4 跳、超时 5 秒不变。statusCode in 300..399已覆盖 307/308,实施时不得收窄为仅 302)。goods_id前是[?&]、&、%26、&或字符串开头,或形如"goods_id":的 JSON 键,消除refer_goods_id误匹配;保留「正文中不同 goods_id 必须唯一」的要求。LINK_RESOLUTION阶段与既有原因枚举,不新增字段,不记录链接原文与 goods_id。Server 端
current_page.go:279的p.pinduoduo.com判断,改为「已通过validatePDDShareURL白名单校验且 query 无goods_id」即进入 HTTP 兜底解析;CheckRedirect中的白名单校验与 4 跳上限保持不变。extractPDDGoodsIDFromBody的两条正则与 Agent 端对齐(前置分隔符强制、JSON 键形式保留、唯一性要求保留)。安全边界
验收标准
https://mobile.yangkeduo.com/goods2.html?ps=14mchKPjYR这类链接发起展开,一跳 307 后得到goods_id=971612375909并成功识别(真机验证)。goods_id的长链仍然直接上报,不发起任何额外网络请求。p.pinduoduo.com短链路径行为不回退,仍可展开。ps=形态),以 httptest 覆盖 307/302 两种跳转码。goods_id=与refer_goods_id=时,两端都只取goods_id且解析成功。验证方式
./gradlew :app:testDebugUnitTest(含select()新判断与展开逻辑的纯函数测试)。go test ./app/goauto/task/...。风险与回退
ps=、p.pinduoduo.com短链等);本次改为按「无 goods_id」触发展开,对形态变化不敏感,但域名若变化仍需改白名单,属已知边界。文档影响
docs/02-architecture-and-code-map.md、docs/03-business-rules-and-glossary.md、docs/08-agent-api-contract.md中描述了链接解析分工。本次只改判断条件、不改分工,若这些页面写到了「仅p.pinduoduo.com短链」之类的具体表述则需同步更新;否则在工单说明「无长期文档影响」并跳过 Wiki 同步。状态
待实施(由 Codex 执行)。
开始实施。按工单范围做两端同修:Agent 与 Server 均将“白名单域名且 URL 本身无
goods_id”作为展开触发条件,白名单保持不变;正文提取排除refer_goods_id等相近字段。不会改变先取得goods_id、再请求创建采集任务、设备领取任务后执行的既有时序,也不涉及数据库、共享接口、采购、订单或支付。首轮针对性测试已通过:Android
:app:testDebugUnitTest;Servergo test ./app/goauto/task/...。后续继续完整回归、Wiki-first 文档闭环及 PKG110 安装核验。#109 已完成实现并提交,现保持工单开放、等待人工验收。
实现:
goods_id就进入手机侧展开,不再把展开入口限定为p.pinduoduo.com;因此实际形式mobile.yangkeduo.com/goods2.html?ps=...会发起展开。goods_id时请求展开,白名单未扩大。refer_goods_id不会误命中;服务端对应行为增加回归证据。goods_id,再调用识别接口由服务端创建/复用商品并绑定临时采集任务,之后才进入既有采集执行;本工单没有改数据库、共享接口、任务状态机、采购、订单或支付。验证:
cd android && .\gradlew.bat :app:testDebugUnitTest :app:assembleDebug:通过。cd server && go test ./...:通过。python dev_scripts/harness.py check --strict:通过。git diff --check/ staged diff check:通过。Android-Agent-API-Contract@b722977b0c8b7db2ce0d3c8c2a1351f42941cb24、Local-Development-and-Verification@6362cbe25f4a0a1826b903ee4342c1a69ee87a42;执行一次sync和一次sync --check,通过。goods2.html?ps=14mchKPjYR形态、p.pinduoduo.com 302、mobile.yangkeduo.com 307、正文 goods_id、仅 refer_goods_id、白名单外跳转、跳转上限与缺失身份。提交:
daf7406ec97262d12a501d85b1245395847a8584—fix: expand all whitelisted PDD share links (#109)未自动执行:未代替用户在 PDD 真实商品详情页点击“采集”创建正式采集任务,因此真机端到端“分享→展开→识别→采集结果”仍需按验收步骤人工触发;没有创建采购任务、订单或支付。
用户于 2026-08-28 明确确认本工单验收通过。按项目流程记录验收结论并关闭工单;本次仅更新工单状态,无新增长期文档事实,不重复同步 Wiki。