@liuliang520500/pdd-sdk
Version:
拼多多开放平台SDK,支持多多进宝API
191 lines (164 loc) • 5.85 kB
JavaScript
/**
* 拼多多开放平台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;