'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, };