JD-PRODUCT-DETAIL-PARSE-VERIFICATION-2026-08-21.md 8.8 KB

JD 商品详情与目标 Parse 联调复测记录(2026-08-21)

1. 结论

本次先完成连接层修复,不进入 Listing AI 页面开发。

  1. getProduct 的 HTTP 400 已定位并修复。根因不是 scene 缺失,也不是 URL 路径错误,而是路径参数 productId 没有参与 JD MD5 签名。
  2. 目标 Parse 的 /backend/vO3T8gAdwB/data 是应用挂载前缀,不是可直接 GET 的健康检查资源;直接请求该前缀返回 404 是预期的路由表现。实际 REST 资源必须追加 /classes/<ClassName>。
  3. 已用 ProductDetail 资源完成读取与正式幂等写入。目标路径已可用:/backend/vO3T8gAdwB/data/classes/ProductDetail。
  4. 写入过程中发现并修复了 ProductDetail.itemStatus 的真实 schema 冲突:JD 详情中的状态是 Object,而目标 Parse 字段已是 String,现统一提取为字符串后再写入。
  5. 当前店铺商品总数由接口实时返回。本次 paginationData.totalItems 为 824;不得继续把 614、821 或 824 写死在页面或任务代码中。

2. 安全边界

  • 凭据只从环境变量或本机忽略文件读取,本文不记录 AppSecret、MasterKey、access token 或 refresh token。
  • docs/JD-CONNECTION-CONFIG.local.md 已加入 .gitignore。
  • 诊断脚本只打印状态码、业务错误码、参与签名的字段名和响应字段路径,不打印签名原文、签名值或凭据值。
  • JD 和 Parse 调用仍必须留在 Node/服务端边界内,不能移动到 Angular 运行时。

3. 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 },
);

4. 成功响应的真实字段

以下仅记录本次 200 响应中已观察到的字段路径;字段是否为空仍需按每个商品单独建模。

4.1 商品主信息

  • productInfo.productId
  • productInfo.productName
  • productInfo.productTitle.title
  • productInfo.productTitle.titleBrandName
  • productInfo.brandInfo.brandId
  • productInfo.brandInfo.brandName
  • productInfo.categoryDetail.lastCategoryId
  • productInfo.categoryDetail.thirdCategoryId
  • productInfo.priceInfo.jdPrice
  • productInfo.priceInfo.costPrice
  • productInfo.itemNum
  • productInfo.upcCode
  • productInfo.createdTime
  • productInfo.modifiedTime
  • productInfo.productStatus.yn
  • productInfo.productStatus.productStatus
  • productInfo.productStatus.productStatusNew
  • productInfo.wareStatus

4.2 Listing 内容

  • productInfo.productDetailDesc.desc
  • productInfo.productDetailDesc.mobileDesc
  • productInfo.features[].key
  • productInfo.features[].value
  • productInfo.goodsAttrInfos[].attrId
  • productInfo.goodsAttrInfos[].attrName
  • productInfo.goodsAttrInfos[].values[].attrValue
  • productInfo.goodsAttrInfos[].values[].attrValueAlias
  • material.mainImages[].imageInfoList[].imgUrl
  • material.mainImages[].imageInfoList[].orderSort
  • material.mainImages[].imageInfoList[].primaryFlag
  • material.mainImages[].imageInfoList[].gptFlag

4.3 SKU 与履约

  • skuList[].skuId
  • skuList[].productId
  • skuList[].skuName
  • skuList[].outerId
  • skuList[].upcCode
  • skuList[].logo
  • skuList[].priceInfo.jdPrice
  • skuList[].stockNum
  • skuList[].skuEnableStatus
  • skuList[].skuStatus.onOffShelfStatus
  • skuList[].goodsAttrInfos[]
  • productInfo.length, width, height, weight
  • productInfo.logisticsInfo.delivery
  • productInfo.logisticsInfo.promiseId
  • productInfo.logisticsInfo.transportId
  • productInfo.logisticsInfo.packSpecification
  • productInfo.afterServiceInfo
  • productInfo.to7ReturnFlag

这些字段足以支持标题、主图元数据、详情、规格、SKU 与内容完整度的第一版评分;它们不能证明当前 AI 网关已具备图片视觉理解能力。

5. 目标 Parse 404 诊断与修复

5.1 路由矩阵

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 验证。

5.2 写入时发现的 schema 冲突

修复签名后首次正式写入返回:

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。

5.3 上游不稳定性

对正确 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 当作导入完成度真值。

6. 修复文件

文件 变更
.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 在开发前固化实际实施计划

7. 最终复测结果

dry-run

{
  "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。

本阶段完成判定

  • 本机凭据文档不再出现在 Git 未跟踪列表中;
  • 复现旧签名的 HTTP 400 与 JD 错误码 99904000013;
  • 加入 productId 签名字段后同一商品 HTTP 200;
  • 记录实际响应字段路径;
  • 正确 Parse class URL HTTP 200;
  • 修复 itemStatus schema mismatch;
  • 正式导入 detailAvailable=1;
  • 明确上游间歇性 403 和 count 不可信风险;
  • 全量分页导入与 824 个商品评分尚未执行,必须按实施计划分阶段完成。