cangku/backend/src/services/productCodePublicApiClient.js

125 lines
3.7 KiB
JavaScript

'use strict';
const axios = require('axios');
const DEFAULT_BASE_URL = 'https://yyd.jiansuntuzhi.cn';
const DEFAULT_TIMEOUT_MS = 30000;
class ProductCodePublicApiError extends Error {
constructor(message, options = {}) {
super(message);
this.name = 'ProductCodePublicApiError';
this.status = options.status || 502;
this.remoteStatus = options.remoteStatus || null;
this.code = options.code || null;
this.indeterminate = options.indeterminate === true;
this.responseData = options.responseData || null;
}
}
function getProductCodePublicApiConfig() {
const apiKey = String(
process.env.PRODUCT_CODE_PUBLIC_API_KEY
|| process.env.PRODUCT_CODE_API_KEY
|| ''
).trim();
const baseUrl = String(
process.env.PRODUCT_CODE_PUBLIC_API_BASE_URL || DEFAULT_BASE_URL
).trim().replace(/\/+$/, '');
const timeoutValue = Number(process.env.PRODUCT_CODE_PUBLIC_API_TIMEOUT_MS);
const timeoutMs = Number.isFinite(timeoutValue) && timeoutValue >= 5000
? Math.min(timeoutValue, 120000)
: DEFAULT_TIMEOUT_MS;
return {
apiKey,
baseUrl,
timeoutMs,
configured: Boolean(apiKey && baseUrl),
};
}
function buildClient() {
const config = getProductCodePublicApiConfig();
if (!config.configured) {
throw new ProductCodePublicApiError('公司商品编码接口尚未配置', {
status: 503,
code: 'PRODUCT_CODE_API_NOT_CONFIGURED',
});
}
return {
config,
client: axios.create({
baseURL: config.baseUrl,
timeout: config.timeoutMs,
headers: {
'Content-Type': 'application/json',
'X-API-Key': config.apiKey,
},
}),
};
}
function normalizeRemoteError(error, operation) {
if (error instanceof ProductCodePublicApiError) return error;
const remoteStatus = Number(error?.response?.status) || null;
const responseData = error?.response?.data || null;
const remoteMessage = String(
responseData?.error || responseData?.message || error?.message || '未知错误'
).trim();
const isNetworkUncertain = !error?.response;
const indeterminate = isNetworkUncertain || remoteStatus === 409 || remoteStatus >= 500;
const status = remoteStatus === 400
? 400
: (remoteStatus === 401 || remoteStatus === 403 ? 503 : 502);
return new ProductCodePublicApiError(`${operation}失败:${remoteMessage}`, {
status,
remoteStatus,
code: error?.code || responseData?.code || 'PRODUCT_CODE_API_ERROR',
indeterminate,
responseData,
});
}
async function getProductCodeMeta() {
const { client } = buildClient();
try {
const response = await client.get('/api/public/product-code-board/meta');
if (!response.data?.success || !response.data?.data) {
throw new ProductCodePublicApiError('公司商品编码接口返回了无效的元数据', {
status: 502,
code: 'PRODUCT_CODE_META_INVALID',
});
}
return response.data.data;
} catch (error) {
throw normalizeRemoteError(error, '获取公司商品编码元数据');
}
}
async function createProductCodes(payload) {
const { client } = buildClient();
try {
const response = await client.post('/api/public/product-code-board/products', payload);
if (!response.data?.success || !response.data?.data) {
throw new ProductCodePublicApiError('公司商品编码接口返回了无效的编码结果', {
status: 502,
code: 'PRODUCT_CODE_RESULT_INVALID',
indeterminate: true,
responseData: response.data,
});
}
return response.data.data;
} catch (error) {
throw normalizeRemoteError(error, '申请公司SKU编码');
}
}
module.exports = {
ProductCodePublicApiError,
createProductCodes,
getProductCodeMeta,
getProductCodePublicApiConfig,
};