forked from lunanting/cangku
140 lines
4.4 KiB
JavaScript
140 lines
4.4 KiB
JavaScript
'use strict';
|
|
|
|
const axios = require('axios');
|
|
|
|
const DEFAULT_BASE_URL = 'https://yyd.jiansuntuzhi.cn';
|
|
const DEFAULT_TIMEOUT_MS = 30000;
|
|
const MAX_TIMEOUT_MS = 300000;
|
|
|
|
function clientError(message, statusCode = 502, code = 'PRODUCT_CODE_UPSTREAM_ERROR') {
|
|
const error = new Error(message);
|
|
error.statusCode = statusCode;
|
|
error.code = code;
|
|
return error;
|
|
}
|
|
|
|
function normalizeBaseUrl(value) {
|
|
const raw = String(value || DEFAULT_BASE_URL).trim().replace(/\/+$/, '');
|
|
let parsed;
|
|
try {
|
|
parsed = new URL(raw);
|
|
} catch {
|
|
throw clientError('商品编码接口地址配置不正确', 503, 'PRODUCT_CODE_API_CONFIG_INVALID');
|
|
}
|
|
if (!['http:', 'https:'].includes(parsed.protocol)) {
|
|
throw clientError('商品编码接口地址仅支持 HTTP 或 HTTPS', 503, 'PRODUCT_CODE_API_CONFIG_INVALID');
|
|
}
|
|
if (process.env.NODE_ENV === 'production' && parsed.protocol !== 'https:') {
|
|
throw clientError('生产环境商品编码接口必须使用 HTTPS', 503, 'PRODUCT_CODE_API_HTTPS_REQUIRED');
|
|
}
|
|
return raw;
|
|
}
|
|
|
|
function getProductCodeApiConfig() {
|
|
const apiKey = String(process.env.PRODUCT_CODE_API_KEY || '').trim();
|
|
if (!apiKey) {
|
|
throw clientError('商品编码接口尚未配置 API Key', 503, 'PRODUCT_CODE_API_KEY_MISSING');
|
|
}
|
|
const configuredTimeout = Number.parseInt(process.env.PRODUCT_CODE_API_TIMEOUT_MS, 10);
|
|
const timeout = Number.isFinite(configuredTimeout) && configuredTimeout > 0
|
|
? Math.min(configuredTimeout, MAX_TIMEOUT_MS)
|
|
: DEFAULT_TIMEOUT_MS;
|
|
return {
|
|
baseUrl: normalizeBaseUrl(process.env.PRODUCT_CODE_API_BASE_URL),
|
|
apiKey,
|
|
timeout,
|
|
};
|
|
}
|
|
|
|
function normalizeUpstreamError(error, action) {
|
|
if (error?.code === 'ECONNABORTED' || /timeout/i.test(String(error?.message || ''))) {
|
|
return clientError(
|
|
`商品编码接口${action}超时;请使用同一 1688 商品 ID 重试以确认已有编码`,
|
|
504,
|
|
'PRODUCT_CODE_API_TIMEOUT'
|
|
);
|
|
}
|
|
|
|
const upstreamStatus = Number(error?.response?.status) || 0;
|
|
const upstreamMessage = String(
|
|
error?.response?.data?.error
|
|
|| error?.response?.data?.message
|
|
|| ''
|
|
).trim();
|
|
|
|
if (upstreamStatus === 400) {
|
|
return clientError(upstreamMessage || '商品编码参数不正确', 400, 'PRODUCT_CODE_API_INVALID_REQUEST');
|
|
}
|
|
if (upstreamStatus === 401 || upstreamStatus === 403) {
|
|
return clientError('商品编码服务鉴权失败,请联系管理员检查 API Key', 502, 'PRODUCT_CODE_API_AUTH_FAILED');
|
|
}
|
|
if (upstreamStatus === 409) {
|
|
return clientError(
|
|
upstreamMessage || '商品编码服务正在处理相同商品,请稍后使用同一商品 ID 重试',
|
|
409,
|
|
'PRODUCT_CODE_API_CONFLICT'
|
|
);
|
|
}
|
|
if (upstreamStatus === 503) {
|
|
return clientError('商品编码服务暂不可用,请联系管理员检查服务端配置', 502, 'PRODUCT_CODE_API_UNAVAILABLE');
|
|
}
|
|
return clientError(upstreamMessage || `商品编码接口${action}失败`, 502, 'PRODUCT_CODE_UPSTREAM_ERROR');
|
|
}
|
|
|
|
async function requestProductCodeApi({ method, path, data, action, httpClient = axios }) {
|
|
const config = getProductCodeApiConfig();
|
|
try {
|
|
const response = await httpClient({
|
|
method,
|
|
url: `${config.baseUrl}${path}`,
|
|
data,
|
|
timeout: config.timeout,
|
|
headers: {
|
|
Accept: 'application/json',
|
|
'Content-Type': 'application/json',
|
|
'X-API-Key': config.apiKey,
|
|
},
|
|
});
|
|
const payload = response?.data;
|
|
if (!payload?.success || !payload.data) {
|
|
throw clientError(payload?.error || payload?.message || `商品编码接口${action}返回无效数据`);
|
|
}
|
|
return {
|
|
statusCode: Number(response.status) || 200,
|
|
message: payload.message || '',
|
|
data: payload.data,
|
|
};
|
|
} catch (error) {
|
|
if (error?.code?.startsWith('PRODUCT_CODE_')) throw error;
|
|
throw normalizeUpstreamError(error, action);
|
|
}
|
|
}
|
|
|
|
async function getProductCodeMeta(options = {}) {
|
|
return requestProductCodeApi({
|
|
method: 'GET',
|
|
path: '/api/public/product-code-board/meta',
|
|
action: '读取字段枚举',
|
|
httpClient: options.httpClient,
|
|
});
|
|
}
|
|
|
|
async function createProductCodes(payload, options = {}) {
|
|
return requestProductCodeApi({
|
|
method: 'POST',
|
|
path: '/api/public/product-code-board/products',
|
|
data: payload,
|
|
action: '提交',
|
|
httpClient: options.httpClient,
|
|
});
|
|
}
|
|
|
|
module.exports = {
|
|
DEFAULT_BASE_URL,
|
|
DEFAULT_TIMEOUT_MS,
|
|
getProductCodeApiConfig,
|
|
normalizeUpstreamError,
|
|
getProductCodeMeta,
|
|
createProductCodes,
|
|
};
|