本次先完成连接层修复,不进入 Listing AI 页面开发。
getProduct 的 HTTP 400 已定位并修复。根因不是 scene 缺失,也不是 URL 路径错误,而是路径参数 productId 没有参与 JD MD5 签名。/backend/vO3T8gAdwB/data 是应用挂载前缀,不是可直接 GET 的健康检查资源;直接请求该前缀返回 404 是预期的路由表现。实际 REST 资源必须追加 /classes/<ClassName>。ProductDetail 资源完成读取与正式幂等写入。目标路径已可用:/backend/vO3T8gAdwB/data/classes/ProductDetail。ProductDetail.itemStatus 的真实 schema 冲突:JD 详情中的状态是 Object,而目标 Parse 字段已是 String,现统一提取为字符串后再写入。paginationData.totalItems 为 824;不得继续把 614、821 或 824 写死在页面或任务代码中。docs/JD-CONNECTION-CONFIG.local.md 已加入 .gitignore。getProduct 400 复测矩阵测试资源:店铺中的真实商品 10026651532867。
请求路径:
GET https://api-cn.jd.com/rest/sp-product/v0/products/{productId}?scene=pop
| 变体 | 查询参数 | MD5 签名字段 | HTTP | JD 错误码 | 结果 |
|---|---|---|---|---|---|
| 原实现 | scene=pop |
X-JOS-Access-Token, X-JOS-App-Key, X-JOS-Timestamp, scene |
400 | 99904000013 |
请求缺少参数或参数无效,无法解析。 |
| 修复实现 | scene=pop |
上述字段 + productId |
200 | — | success=true,返回商品详情 |
| JSON request + path 签名 | request={"scene":"pop"} |
公共字段 + productId, request |
503 | 10110531002 |
ProductDetailRequest.scene is empty |
| JSON request,无 path 签名 | request={"scene":"pop"} |
公共字段 + request |
400 | 99904000013 |
参数无法解析 |
该接口需要区分“发送位置”和“参与签名”两个概念:
productId 只在 URL path 中出现,但仍必须进入待签名字段集合;scene=pop 作为 query 参数发送,同时进入待签名字段集合;X-JOS-App-Key、X-JOS-Access-Token、X-JOS-Timestamp 进入签名;X-JOS-Sign、X-JOS-Sign-Method、X-JOS-Request-Identity 不进入业务字段串;productId 再重复放入 query。修复后的调用形态:
jdGet(
`/sp-product/v0/products/${productId}`,
{ scene: 'pop' },
accessToken,
{ productId },
);
以下仅记录本次 200 响应中已观察到的字段路径;字段是否为空仍需按每个商品单独建模。
productInfo.productIdproductInfo.productNameproductInfo.productTitle.titleproductInfo.productTitle.titleBrandNameproductInfo.brandInfo.brandIdproductInfo.brandInfo.brandNameproductInfo.categoryDetail.lastCategoryIdproductInfo.categoryDetail.thirdCategoryIdproductInfo.priceInfo.jdPriceproductInfo.priceInfo.costPriceproductInfo.itemNumproductInfo.upcCodeproductInfo.createdTimeproductInfo.modifiedTimeproductInfo.productStatus.ynproductInfo.productStatus.productStatusproductInfo.productStatus.productStatusNewproductInfo.wareStatusproductInfo.productDetailDesc.descproductInfo.productDetailDesc.mobileDescproductInfo.features[].keyproductInfo.features[].valueproductInfo.goodsAttrInfos[].attrIdproductInfo.goodsAttrInfos[].attrNameproductInfo.goodsAttrInfos[].values[].attrValueproductInfo.goodsAttrInfos[].values[].attrValueAliasmaterial.mainImages[].imageInfoList[].imgUrlmaterial.mainImages[].imageInfoList[].orderSortmaterial.mainImages[].imageInfoList[].primaryFlagmaterial.mainImages[].imageInfoList[].gptFlagskuList[].skuIdskuList[].productIdskuList[].skuNameskuList[].outerIdskuList[].upcCodeskuList[].logoskuList[].priceInfo.jdPriceskuList[].stockNumskuList[].skuEnableStatusskuList[].skuStatus.onOffShelfStatusskuList[].goodsAttrInfos[]productInfo.length, width, height, weightproductInfo.logisticsInfo.deliveryproductInfo.logisticsInfo.promiseIdproductInfo.logisticsInfo.transportIdproductInfo.logisticsInfo.packSpecificationproductInfo.afterServiceInfoproductInfo.to7ReturnFlag这些字段足以支持标题、主图元数据、详情、规格、SKU 与内容完整度的第一版评分;它们不能证明当前 AI 网关已具备图片视觉理解能力。
| URL 形态 | 本次结果 | 判断 |
|---|---|---|
/backend/vO3T8gAdwB/data |
404 HTML | 仅是挂载前缀,不是健康资源 |
/backend/vO3T8gAdwB/data/classes/ProductDetail?limit=1 |
200 JSON | 正确的目标 REST class 资源 |
/backend/vO3T8gAdwB/classes/ProductDetail?limit=1 |
404 HTML | 缺少 /data |
/backend/vO3T8gAdwB/data/parse/classes/ProductDetail?limit=1 |
404 HTML | 多拼了一层 /parse |
/parse/classes/ProductDetail?limit=1 |
200 JSON | 全局 Parse 路由也可命中同一应用凭据,但不是本项目约定入口 |
因此,代码中的目标 Parse 配置值保持为:
https://server.fmode.cn/backend/vO3T8gAdwB/data
真正的读取/写入 URL 由客户端拼接为:
{JD_TARGET_PARSE_URL}/classes/ProductDetail
“修复 404”不是把配置改成 /parse,而是停止对挂载前缀做根 GET,并改用真实资源 endpoint 做 readiness 验证。
修复签名后首次正式写入返回:
HTTP 400: schema mismatch for ProductDetail.itemStatus;
expected String but got Object
原因是 getProduct.productInfo.productStatus 为对象,而旧归一化逻辑可能原样写入 itemStatus。现已新增 extractProductStatus(),按以下优先级提取标量:
code -> status -> value -> name -> label -> flattenText
目标 ProductDetail.itemStatus 继续保持 String,避免破坏既有 schema。
对正确 class 资源各连续采样 10 次,/backend/.../data/classes/... 与 /parse/classes/... 都出现间歇性:
{ "code": 119, "error": "unauthorized" }
表现为 HTTP 403,随后重试可恢复为 200。这说明是上游节点/鉴权漂移,而不是 class 路由缺失。当前导入器保留了针对这一精确错误的有限重试;不能把所有 401/403 都无条件重试。
另一个实测差异是:limit=0&count=1 可能返回 count: 0,而普通列表请求能返回真实 results。因此现阶段不能把 Parse count 当作导入完成度真值。
| 文件 | 变更 |
|---|---|
.gitignore |
忽略本机凭据文档 |
tools/jd-import-products.mjs |
productId 参与详情签名;状态对象归一化为 String |
tools/jd-detail-diagnostic.mjs |
新增安全的 JD 签名变体与字段路径诊断;源 Parse 精确 403 有限重试 |
tools/parse-target-diagnostic.mjs |
新增 Parse 路由、schema、class 查询与稳定性诊断 |
docs/JD-FIRST-BATCH-IMPORT.md |
更新真实状态和正确 readiness 方式 |
docs/LISTING-AI-IMPLEMENTATION-PLAN.md |
在开发前固化实际实施计划 |
{
"mode": "dry-run",
"totalProducts": 824,
"products": 1,
"skuMappings": 1,
"stockSnapshots": 1,
"detailAvailable": 1
}
{
"mode": "imported",
"totalProducts": 824,
"products": 1,
"skuMappings": 5,
"stockSnapshots": 5,
"detailAvailable": 1
}
商品/SKU 数量由被抽中的真实商品决定,不能断言每个商品只有一个 SKU。
99904000013;productId 签名字段后同一商品 HTTP 200;itemStatus schema mismatch;detailAvailable=1;count 不可信风险;