UNPKG

@liuliang520500/pdd-sdk

Version:

拼多多开放平台SDK,支持多多进宝API

191 lines (164 loc) 5.85 kB
/** * 拼多多开放平台API基础类 * 处理公共参数、签名和请求 */ const crypto = require('crypto'); const axios = require('axios'); class BaseApi { /** * 构造函数 * @param {Object} config 配置参数 * @param {String} config.clientId 应用ID * @param {String} config.clientSecret 应用密钥 * @param {String} config.accessToken 访问令牌 */ constructor(config) { this.clientId = config.clientId; this.clientSecret = config.clientSecret; this.accessToken = config.accessToken; this.timestamp = null; this.baseUrl = 'https://gw-api.pinduoduo.com/api/router'; this.dataType = 'JSON'; this.version = 'V1'; this.debug = config.debug || false; } /** * 获取公共参数 * @returns {Object} 公共参数对象 */ getCommonParams() { this.timestamp = this.timestamp || Math.floor(Date.now() / 1000).toString(); const params = { client_id: this.clientId, timestamp: this.timestamp, data_type: this.dataType, version: this.version }; // 如果有访问令牌,添加到参数中 if (this.accessToken) { params.access_token = this.accessToken; } return params; } /** * 生成签名 * @param {Object} params 所有请求参数 * @returns {String} 签名 */ generateSign(params) { // 按键名升序排序 const keys = Object.keys(params).sort(); // 拼接参数字符串 let stringToSign = this.clientSecret; for (let i = 0; i < keys.length; i++) { const key = keys[i]; // 签名不包含sign字段本身 if (key !== 'sign' && params[key] !== undefined && params[key] !== null) { stringToSign += key + params[key]; } } stringToSign += this.clientSecret; // 调试模式下输出签名过程 if (this.debug) { console.log('======= 签名调试信息 ======='); console.log('排序后的键名:', keys.join(', ')); console.log('拼接后的字符串:', stringToSign); } // 计算MD5并转为大写 const sign = crypto.createHash('md5').update(stringToSign).digest('hex').toUpperCase(); if (this.debug) { console.log('生成的签名:', sign); console.log('==========================='); } return sign; } /** * 执行API请求 * @param {String} type API接口名称 * @param {Object} params 业务参数 * @param {Boolean} [convertParams=true] 是否转换参数命名风格 * @returns {Promise} Promise对象 */ async execute(type, params = {}, convertParams = true) { // 重置timestamp,确保每次请求使用新的时间戳 this.timestamp = Math.floor(Date.now() / 1000).toString(); // 将参数转换为snake_case格式 const processedParams = convertParams ? this.convertCamelToSnake(params) : params; // 构建请求参数 const requestParams = { type, ...this.getCommonParams(), ...processedParams }; // 生成签名 requestParams.sign = this.generateSign(requestParams); // 调试模式下输出完整请求信息 if (this.debug) { console.log('======= 请求信息 ======='); console.log('请求URL:', this.baseUrl); console.log('请求参数:', JSON.stringify(requestParams, null, 2)); console.log('========================'); } try { // 发送POST请求 const response = await axios.post(this.baseUrl, new URLSearchParams(requestParams), { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }); // 调试模式下输出响应信息 if (this.debug && response.data) { console.log('======= 响应信息 ======='); console.log('状态码:', response.status); console.log('响应数据:', JSON.stringify(response.data, null, 2)); console.log('========================'); } return response.data; } catch (error) { if (error.response) { throw new Error(`API请求失败: ${error.response.status} ${JSON.stringify(error.response.data)}`); } else if (error.request) { throw new Error(`未收到响应: ${error.message}`); } else { throw new Error(`请求错误: ${error.message}`); } } } /** * 将驼峰命名转换为下划线命名 * @param {Object} obj 需要转换的对象 * @returns {Object} 转换后的对象 */ convertCamelToSnake(obj) { if (typeof obj !== 'object' || obj === null) { return obj; } // 如果是数组,则递归处理每个元素 if (Array.isArray(obj)) { return obj.map(item => this.convertCamelToSnake(item)); } // 处理对象 const result = {}; for (const key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { // 驼峰转下划线 const snakeKey = key.replace(/([A-Z])/g, '_$1').toLowerCase(); const value = obj[key]; // 对于数组和对象值,需要序列化为JSON字符串 let processedValue = value; if (typeof value === 'object' && value !== null) { if (!Array.isArray(value) || typeof value[0] === 'object') { processedValue = JSON.stringify(value); } else { // 如果是简单类型的数组,按照拼多多API的要求处理 processedValue = JSON.stringify(value); } } // 存储转换后的键和值 result[snakeKey === key ? key : snakeKey] = processedValue; } } return result; } } module.exports = BaseApi;