增加feifei 日志
This commit is contained in:
@@ -2,10 +2,7 @@ import crypto from 'node:crypto'
|
||||
import test from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
|
||||
import {
|
||||
isKuaishouFeifeiSuccessResponse,
|
||||
signKuaishouFeifeiPayload,
|
||||
} from './http-client.js'
|
||||
import { isKuaishouFeifeiSuccessResponse, signKuaishouFeifeiPayload } from './http-client.js'
|
||||
|
||||
test('signKuaishouFeifeiPayload signs app key, timestamp and raw body with HMAC SHA256', () => {
|
||||
const input = {
|
||||
@@ -26,6 +23,8 @@ test('signKuaishouFeifeiPayload signs app key, timestamp and raw body with HMAC
|
||||
test('isKuaishouFeifeiSuccessResponse accepts documented and actual success codes', () => {
|
||||
assert.equal(isKuaishouFeifeiSuccessResponse({ code: 0, message: 'success' }), true)
|
||||
assert.equal(isKuaishouFeifeiSuccessResponse({ code: 10000, message: 'success' }), true)
|
||||
assert.equal(isKuaishouFeifeiSuccessResponse({ code: 10000, message: '下单成功' }), true)
|
||||
assert.equal(isKuaishouFeifeiSuccessResponse({ code: 10000, message: '查询成功' }), true)
|
||||
assert.equal(isKuaishouFeifeiSuccessResponse({ code: 10000, message: '商品不存在' }), false)
|
||||
assert.equal(isKuaishouFeifeiSuccessResponse({ code: 1, message: 'success' }), false)
|
||||
})
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import crypto from 'node:crypto'
|
||||
|
||||
import { createHttpError } from '../../../utils/http.js'
|
||||
import { createRequestId, logExternalHttpPacket } from '../../../utils/logger.js'
|
||||
import { assertKuaishouFeifeiConfig } from './config.js'
|
||||
|
||||
type JsonObject = Record<string, any>
|
||||
@@ -9,6 +10,9 @@ export async function kuaishouFeifeiRequest(pathname: string, payload: JsonObjec
|
||||
const config = assertKuaishouFeifeiConfig()
|
||||
const body = JSON.stringify(payload)
|
||||
const timestamp = String(Math.floor(Date.now() / 1000))
|
||||
const packetId = createRequestId('ff')
|
||||
const startedAt = Date.now()
|
||||
let upstreamPacketLogged = false
|
||||
const sign = signKuaishouFeifeiPayload({
|
||||
appKey: config.appKey,
|
||||
appSecret: config.appSecret,
|
||||
@@ -17,23 +21,62 @@ export async function kuaishouFeifeiRequest(pathname: string, payload: JsonObjec
|
||||
})
|
||||
const controller = new AbortController()
|
||||
const timeout = setTimeout(() => controller.abort(), config.timeoutMs)
|
||||
const url = `${config.baseUrl}${pathname}`
|
||||
const requestHeaders = {
|
||||
'content-type': 'application/json',
|
||||
'x-app-key': config.appKey,
|
||||
'x-timestamp': timestamp,
|
||||
'x-sign': sign,
|
||||
}
|
||||
|
||||
logExternalHttpPacket('[kuaishou-feifei/http]', '发送请求', {
|
||||
packetId,
|
||||
method: 'POST',
|
||||
pathname,
|
||||
url,
|
||||
timeoutMs: config.timeoutMs,
|
||||
request: {
|
||||
headers: requestHeaders,
|
||||
body: payload,
|
||||
rawBody: body,
|
||||
},
|
||||
})
|
||||
|
||||
try {
|
||||
const response = await fetch(`${config.baseUrl}${pathname}`, {
|
||||
const response = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'content-type': 'application/json',
|
||||
'x-app-key': config.appKey,
|
||||
'x-timestamp': timestamp,
|
||||
'x-sign': sign,
|
||||
},
|
||||
headers: requestHeaders,
|
||||
body,
|
||||
signal: controller.signal,
|
||||
})
|
||||
const text = await response.text()
|
||||
const json = parseJsonObject(text)
|
||||
const durationMs = Date.now() - startedAt
|
||||
const responsePacket = {
|
||||
packetId,
|
||||
method: 'POST',
|
||||
pathname,
|
||||
url,
|
||||
status: response.status,
|
||||
durationMs,
|
||||
request: {
|
||||
headers: requestHeaders,
|
||||
body: payload,
|
||||
},
|
||||
response: {
|
||||
ok: response.ok,
|
||||
headers: normalizeFetchHeaders(response.headers),
|
||||
summary: summarizeKuaishouFeifeiResponse(json),
|
||||
body: json,
|
||||
rawText: text,
|
||||
},
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
upstreamPacketLogged = true
|
||||
logExternalHttpPacket('[kuaishou-feifei/http]', 'HTTP 响应失败', responsePacket, {
|
||||
level: 'warn',
|
||||
})
|
||||
throw createHttpError(`kuaishou-feifei 请求失败: HTTP ${response.status}`, {
|
||||
statusCode: 502,
|
||||
errorCode: 'kuaishou_feifei_http_failed',
|
||||
@@ -45,6 +88,10 @@ export async function kuaishouFeifeiRequest(pathname: string, payload: JsonObjec
|
||||
}
|
||||
|
||||
if (!isKuaishouFeifeiSuccessResponse(json)) {
|
||||
upstreamPacketLogged = true
|
||||
logExternalHttpPacket('[kuaishou-feifei/http]', '业务响应失败', responsePacket, {
|
||||
level: 'warn',
|
||||
})
|
||||
throw createHttpError(String(json.message || 'kuaishou-feifei 业务失败'), {
|
||||
statusCode: 502,
|
||||
errorCode: 'kuaishou_feifei_business_failed',
|
||||
@@ -52,15 +99,60 @@ export async function kuaishouFeifeiRequest(pathname: string, payload: JsonObjec
|
||||
})
|
||||
}
|
||||
|
||||
upstreamPacketLogged = true
|
||||
logExternalHttpPacket('[kuaishou-feifei/http]', '请求完成', responsePacket)
|
||||
|
||||
return json
|
||||
} catch (error) {
|
||||
if ((error as Error)?.name === 'AbortError') {
|
||||
logExternalHttpPacket(
|
||||
'[kuaishou-feifei/http]',
|
||||
'请求超时',
|
||||
{
|
||||
packetId,
|
||||
method: 'POST',
|
||||
pathname,
|
||||
url,
|
||||
timeoutMs: config.timeoutMs,
|
||||
durationMs: Date.now() - startedAt,
|
||||
request: {
|
||||
headers: requestHeaders,
|
||||
body: payload,
|
||||
},
|
||||
},
|
||||
{
|
||||
level: 'warn',
|
||||
},
|
||||
)
|
||||
throw createHttpError('kuaishou-feifei 请求超时', {
|
||||
statusCode: 504,
|
||||
errorCode: 'kuaishou_feifei_timeout',
|
||||
})
|
||||
}
|
||||
|
||||
if (!upstreamPacketLogged) {
|
||||
logExternalHttpPacket(
|
||||
'[kuaishou-feifei/http]',
|
||||
'请求异常',
|
||||
{
|
||||
packetId,
|
||||
method: 'POST',
|
||||
pathname,
|
||||
url,
|
||||
timeoutMs: config.timeoutMs,
|
||||
durationMs: Date.now() - startedAt,
|
||||
request: {
|
||||
headers: requestHeaders,
|
||||
body: payload,
|
||||
},
|
||||
error,
|
||||
},
|
||||
{
|
||||
level: 'error',
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
throw error
|
||||
} finally {
|
||||
clearTimeout(timeout)
|
||||
@@ -87,9 +179,11 @@ export function signKuaishouFeifeiPayload({
|
||||
|
||||
export function isKuaishouFeifeiSuccessResponse(json: JsonObject) {
|
||||
const code = Number(json.code ?? 0)
|
||||
const message = String(json.message || json.msg || '').trim().toLowerCase()
|
||||
const message = String(json.message || json.msg || '')
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
|
||||
return code === 0 || (code === 10000 && message === 'success')
|
||||
return code === 0 || (code === 10000 && isKuaishouFeifeiSuccessMessage(message))
|
||||
}
|
||||
|
||||
function parseJsonObject(text: string): JsonObject {
|
||||
@@ -100,3 +194,37 @@ function parseJsonObject(text: string): JsonObject {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
||||
function isKuaishouFeifeiSuccessMessage(message: string) {
|
||||
return message === 'success' || message === 'ok' || message.endsWith('成功')
|
||||
}
|
||||
|
||||
function normalizeFetchHeaders(headers: Headers) {
|
||||
const result: Record<string, string> = {}
|
||||
headers.forEach((value, key) => {
|
||||
result[key] = value
|
||||
})
|
||||
return result
|
||||
}
|
||||
|
||||
function summarizeKuaishouFeifeiResponse(json: JsonObject) {
|
||||
const data = json.data && typeof json.data === 'object' ? (json.data as JsonObject) : {}
|
||||
const order = data.order && typeof data.order === 'object' ? (data.order as JsonObject) : null
|
||||
const h5 = order?.h5 && typeof order.h5 === 'object' ? (order.h5 as JsonObject) : {}
|
||||
const list = Array.isArray(data.list) ? data.list : []
|
||||
|
||||
return {
|
||||
code: json.code,
|
||||
message: String(json.message || json.msg || '').trim(),
|
||||
hasOrder: Boolean(order),
|
||||
orderNo: String(order?.order_no || '').trim(),
|
||||
platformOrderNo: String(order?.platform_order_no || '').trim(),
|
||||
productCode: String(order?.product_code || '').trim(),
|
||||
rechargeStatus: Number(order?.recharge_status ?? order?.status ?? 0) || 0,
|
||||
rechargeStatusLabel: String(order?.recharge_status_label || order?.status_label || '').trim(),
|
||||
hasH5EntryUrl: Boolean(String(h5.entry_url || '').trim()),
|
||||
hasH5RechargeUrl: Boolean(String(h5.recharge_url || '').trim()),
|
||||
listCount: list.length,
|
||||
total: Number(data.total || list.length || 0) || 0,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user