fix(douyin-mashida): 出网响应按大整数安全解析(product_id 被压精度)
真实联调发现:抖店大量 id 是 19 位数字,超过 Number.MAX_SAFE_INTEGER,
入站推送我们用 express.text() 收原文保住了精度,但**出网响应是 JSON.parse 出来的**,
19 位数字在解析瞬间就被压成相邻 double:
平台真值 product_id = 3847255376114352567
我们存到的 = 3847255376114352600
后果:拿这个 product_id 去调 product.detail 报「商品不存在」(实测),
任何按商品ID做的映射/查询都会错。
修复:抖音出网响应改用共享的 parseJsonObject(..., { preserveLargeIntegers: true })。
utils/json.ts 里其实早有 wrapLargeIntegerLiterals(≥16 位/超 MAX_SAFE_INTEGER 才包裹,
只处理值位置的整数字面量,不误伤字符串内数字与短数字),但全仓没有调用方 —— 现在接上。
实测:同一笔订单 product_id 已精确为 …567,product.detail 返回 code=10000 并拿到商品图。
补了 json 解析的大整数单测(含「字符串里的数字、短数字不受影响」的反例)。
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { asJsonObject, isPlainObject, type JsonObject } from '../../../types/json.js'
|
||||
import { asJsonObject, type JsonObject } from '../../../types/json.js'
|
||||
|
||||
import { createHttpError } from '../../../utils/http.js'
|
||||
import { parseJsonObject } from '../../../utils/json.js'
|
||||
import { createRequestId, logExternalHttpPacket } from '../../../utils/logger.js'
|
||||
import {
|
||||
assertDouyinMashidaConfig,
|
||||
@@ -94,7 +95,7 @@ export async function douyinMashidaGatewayRequest(input: {
|
||||
clearTimeout(timeout)
|
||||
}
|
||||
|
||||
const parsed = parseJsonObject(responseText)
|
||||
const parsed = parseGatewayResponseJson(responseText)
|
||||
const code = String(parsed?.code ?? '').trim()
|
||||
const message = String(parsed?.message ?? '').trim()
|
||||
|
||||
@@ -136,18 +137,25 @@ export async function douyinMashidaGatewayRequest(input: {
|
||||
return { code, message, data: parsed.data, raw: parsed }
|
||||
}
|
||||
|
||||
function parseJsonObject(text: string): JsonObject | null {
|
||||
/**
|
||||
* 解析网关响应。
|
||||
*
|
||||
* ⚠️ **必须开启 `preserveLargeIntegers`**:抖店大量 id 是 19 位数字,超过
|
||||
* `Number.MAX_SAFE_INTEGER`,直接 `JSON.parse` 会把它压成相邻的 double ——
|
||||
* 实测 `product_id` 真值 `3847255376114352567` 被存成 `3847255376114352600`,
|
||||
* 之后拿它去调 `product.detail` 就会报「商品不存在」。
|
||||
* 开启后这类字面量会先被转成字符串,全程精确。
|
||||
*
|
||||
* 返回 null 表示「不是可用的 JSON 对象」(空响应/解析失败),由调用方报错。
|
||||
*/
|
||||
function parseGatewayResponseJson(text: string): JsonObject | null {
|
||||
const normalized = String(text || '').trim()
|
||||
if (!normalized) {
|
||||
return null
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed: unknown = JSON.parse(normalized)
|
||||
return isPlainObject(parsed) ? parsed : null
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
const parsed = parseJsonObject(normalized, { preserveLargeIntegers: true })
|
||||
return Object.keys(parsed).length > 0 ? parsed : null
|
||||
}
|
||||
|
||||
function isAbortError(error: unknown): boolean {
|
||||
|
||||
Reference in New Issue
Block a user