在电商数据服务开发中,可靠的 API SDK 是连接应用与平台的重要桥梁。本文将详细介绍如何使用 Node.js 和 TypeScript 开发一个健壮的淘宝商品 API SDK,实现类型安全、错误处理、请求签名等核心功能,高效对接淘宝平台。
一、淘宝平台准备工作
在开始开发 SDK 之前,需要完成淘宝平台的相关准备工作:
- 注册开发者账号:访问注册账号并完成实名认证
- 创建应用:获取 Api Key 和 Api Secret调用api唯一凭证
- 申请 API 权限:为应用申请商品相关 API 的调用权限,如taobao.item.get、taobao.items.search等
- 了解 API 文档:熟悉淘宝 API 的请求格式、参数要求、返回结果及错误码
二、SDK 核心设计思路
一个健壮的淘宝商品 API SDK 应具备以下特性:
- 类型安全:使用 TypeScript 定义请求参数和返回结果的类型
- 签名机制:实现淘宝 API 要求的签名算法
- 错误处理:统一的错误处理机制,包含网络错误和 API 错误
- 请求控制:支持超时设置、重试机制和请求节流
- 可扩展性:易于添加新的 API 方法和扩展功能
- 日志记录:记录关键操作日志,便于调试和问题排查
三、项目初始化与依赖安装
首先创建项目并安装必要的依赖:
bash
mkdir taobao-api-sdk && cd taobao-api-sdk
npm init -y
npm install axios crypto-js qs
npm install -D typescript @types/node @types/crypto-js @types/qs ts-node
npx tsc --init
修改tsconfig.json配置,确保以下选项正确设置:
json
{
"compilerOptions": {
"target": "ES2020",
"module": "CommonJS",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "**/*.test.ts"]
}
四、核心代码实现
- 类型定义
首先定义核心类型,确保类型安全:
css
// src/types/index.ts
/**
* 淘宝API公共响应结构
*/
export interface TaobaoResponse<T = any> {
error_response?: {
code: number;
msg: string;
sub_code?: string;
sub_msg?: string;
};
[key: string]: T | undefined;
}
/**
* 商品基本信息
*/
export interface ProductBase {
num_iid: number; // 商品ID
title: string; // 商品标题
pic_url: string; // 商品主图
price: string; // 商品价格
orginal_price: string; // 商品原价
sales: number; // 销量
seller_id: number; // 卖家ID
shop_title: string; // 店铺名称
}
/**
* 商品详情信息
*/
export interface ProductDetail extends ProductBase {
desc: string; // 商品描述
item_imgs: { url: string }[]; // 商品图片
props_name: string; // 商品属性名称
props: { name: string; value: string }[]; // 商品属性
skus: {
sku_id: number;
price: string;
properties: string;
properties_name: string;
stock: number;
}[]; // 商品规格
stock: number; // 库存
post_fee: string; // 运费
}
/**
* 商品搜索结果
*/
export interface ProductSearchResult {
items: {
item: ProductBase[];
};
total_results: number;
page_no: number;
page_size: number;
}
/**
* SDK配置选项
*/
export interface TaobaoSDKOptions {
appKey: string;
appSecret: string;
timeout?: number; // 请求超时时间,默认5000ms
retry?: number; // 重试次数,默认1次
endpoint?: string; // API端点,默认https://eco.taobao.com/router/rest
logger?: (message: string) => void; // 日志回调函数
}
/**
* API请求参数
*/
export interface ApiParams {
[key: string]: string | number | boolean | undefined;
}
- 错误处理
实现自定义错误类,统一处理各类错误:
typescript
// src/errors.ts
export enum TaobaoErrorType {
NETWORK_ERROR = 'NETWORK_ERROR',
API_ERROR = 'API_ERROR',
PARAM_ERROR = 'PARAM_ERROR',
AUTH_ERROR = 'AUTH_ERROR'
}
export class TaobaoError extends Error {
type: TaobaoErrorType;
code?: number | string;
details?: any;
constructor(
message: string,
type: TaobaoErrorType,
code?: number | string,
details?: any
) {
super(message);
this.name = 'TaobaoError';
this.type = type;
this.code = code;
this.details = details;
}
toString(): string {
return `[${this.name}] ${this.type}: ${this.message} (code: ${this.code})`;
}
}
- 签名工具
实现淘宝 API 要求的签名算法:
typescript
// src/utils/sign.ts
import crypto from 'crypto-js';
import qs from 'qs';
import { ApiParams } from '../types';
/**
* 生成淘宝API签名
* 签名规则:https://open.taobao.com/doc.htm?docId=101617&docType=1&source=search
*/
export function generateSign(
params: ApiParams,
appSecret: string
): string {
// 1. 去除空值参数
const filteredParams = Object.entries(params).reduce(
(obj, [key, value]) => {
if (value !== undefined && value !== null && value !== '') {
obj[key] = value;
}
return obj;
},
{} as Record<string, string | number | boolean>
);
// 2. 按键名ASCII排序
const sortedParams = Object.keys(filteredParams).sort().reduce(
(obj, key) => {
obj[key] = filteredParams[key];
return obj;
},
{} as Record<string, string | number | boolean>
);
// 3. 拼接为key=value&key=value形式
const paramString = qs.stringify(sortedParams, { encode: false });
// 4. 拼接appSecret,进行HMAC-SHA1加密
const signString = appSecret + paramString + appSecret;
const sign = crypto.HmacSHA1(signString, appSecret).toString().toUpperCase();
return sign;
}
- 核心 SDK 类
实现 SDK 的核心功能,包括请求处理、签名生成等:
typescript
// src/index.ts
import axios, { AxiosError, AxiosRequestConfig } from 'axios';
import { generateSign } from './utils/sign';
import {
TaobaoSDKOptions,
ApiParams,
TaobaoResponse,
ProductDetail,
ProductSearchResult
} from './types';
import { TaobaoError, TaobaoErrorType } from './errors';
export class TaobaoSDK {
private appKey: string;
private appSecret: string;
private endpoint: string;
private timeout: number;
private retry: number;
private logger?: (message: string) => void;
constructor(options: TaobaoSDKOptions) {
if (!options.appKey || !options.appSecret) {
throw new TaobaoError(
'appKey和appSecret不能为空',
TaobaoErrorType.PARAM_ERROR
);
}
this.appKey = options.appKey;
this.appSecret = options.appSecret;
this.endpoint = options.endpoint || 'https://eco.taobao.com/router/rest';
this.timeout = options.timeout || 5000;
this.retry = options.retry !== undefined ? options.retry : 1;
this.logger = options.logger;
this.log('Taobao SDK 初始化成功');
}
/**
* 日志记录
*/
private log(message: string): void {
if (this.logger) {
this.logger(`[TaobaoSDK] ${message}`);
}
}
/**
* 生成公共参数
*/
private getCommonParams(method: string): ApiParams {
return {
app_key: this.appKey,
method: method,
format: 'json',
v: '2.0',
sign_method: 'hmac',
timestamp: new Date().toISOString().replace(/T/, ' ').replace(/..+/, ''),
partner_id: 'top-sdk-nodejs',
simplify: true
};
}
/**
* 发送API请求
*/
private async request<T>(
method: string,
params: ApiParams = {},
retryCount = 0
): Promise<T> {
try {
// 合并公共参数和接口参数
const requestParams = {
...this.getCommonParams(method),
...params
};
// 生成签名
const sign = generateSign(requestParams, this.appSecret);
// 构建最终请求参数
const finalParams = {
...requestParams,
sign
};
this.log(`调用API: ${method}, 参数: ${JSON.stringify(finalParams)}`);
// 发送请求
const axiosConfig: AxiosRequestConfig = {
url: this.endpoint,
method: 'get',
params: finalParams,
timeout: this.timeout
};
const response = await axios(axiosConfig);
const data: TaobaoResponse<T> = response.data;
// 处理API错误
if (data.error_response) {
const error = data.error_response;
this.log(`API错误: ${error.code} - ${error.msg}`);
// 特定错误码不重试
const noRetryCodes = [400, 401, 403, 404, 501];
if (
noRetryCodes.includes(error.code) ||
retryCount >= this.retry
) {
throw new TaobaoError(
error.msg || error.sub_msg || 'API请求失败',
error.code === 401 || error.code === 403
? TaobaoErrorType.AUTH_ERROR
: TaobaoErrorType.API_ERROR,
error.code,
error
);
}
// 重试
this.log(`API请求失败,将进行第${retryCount + 1}次重试`);
return this.request<T>(method, params, retryCount + 1);
}
// 提取业务数据
const resultKey = Object.keys(data).find(
(key) => key !== 'error_response'
);
if (!resultKey) {
throw new TaobaoError('API返回格式异常', TaobaoErrorType.API_ERROR);
}
return data[resultKey] as T;
} catch (error) {
// 处理网络错误
if (error instanceof AxiosError) {
if (retryCount >= this.retry) {
throw new TaobaoError(
`网络请求失败: ${error.message}`,
TaobaoErrorType.NETWORK_ERROR,
error.code
);
}
// 网络错误重试
this.log(`网络请求失败,将进行第${retryCount + 1}次重试: ${error.message}`);
return this.request<T>(method, params, retryCount + 1);
}
// 抛出其他错误
if (error instanceof TaobaoError) {
throw error;
}
throw new TaobaoError(
`未知错误: ${(error as Error).message}`,
TaobaoErrorType.NETWORK_ERROR
);
}
}
/**
* 获取商品详情
* @param numIid 商品ID
*/
async getItemDetail(numIid: number): Promise<ProductDetail> {
if (!numIid || typeof numIid !== 'number') {
throw new TaobaoError(
'商品ID必须为有效的数字',
TaobaoErrorType.PARAM_ERROR
);
}
return this.request<ProductDetail>('taobao.item.get', {
num_iid: numIid,
fields: 'num_iid,title,pic_url,price,orginal_price,sales,seller_id,shop_title,desc,item_imgs,props_name,props,skus,stock,post_fee'
});
}
/**
* 搜索商品
* @param keyword 搜索关键词
* @param page 页码,默认1
* @param pageSize 每页数量,默认40
*/
async searchItems(
keyword: string,
page = 1,
pageSize = 40
): Promise<ProductSearchResult> {
if (!keyword || typeof keyword !== 'string' || keyword.trim() === '') {
throw new TaobaoError(
'搜索关键词不能为空',
TaobaoErrorType.PARAM_ERROR
);
}
if (page < 1) page = 1;
if (pageSize < 1 || pageSize > 100) pageSize = 40;
return this.request<ProductSearchResult>('taobao.items.search', {
q: keyword,
page_no: page,
page_size: pageSize,
fields: 'num_iid,title,pic_url,price,orginal_price,sales,seller_id,shop_title'
});
}
/**
* 扩展方法:调用其他API
* @param method API方法名
* @param params API参数
*/
async invokeApi<T>(method: string, params: ApiParams = {}): Promise<T> {
if (!method || typeof method !== 'string' || method.trim() === '') {
throw new TaobaoError(
'API方法名不能为空',
TaobaoErrorType.PARAM_ERROR
);
}
return this.request<T>(method, params);
}
}
export { TaobaoError, TaobaoErrorType } from './errors';
export * from './types';
五、使用示例
下面是 SDK 的使用示例,展示如何初始化 SDK 并调用相关方法:
javascript
// example.ts
import { TaobaoSDK, TaobaoError } from './src';
// 初始化SDK
const taobaoSDK = new TaobaoSDK({
appKey: 'your_app_key',
appSecret: 'your_app_secret',
timeout: 10000,
retry: 2,
logger: (message) => {
console.log(message);
}
});
// 搜索商品示例
async function searchProducts() {
try {
const result = await taobaoSDK.searchItems('手机', 1, 20);
console.log(`搜索到${result.total_results}个商品`);
console.log('商品列表:', result.items.item.map(item => ({
id: item.num_iid,
title: item.title,
price: item.price,
sales: item.sales
})));
} catch (error) {
if (error instanceof TaobaoError) {
console.error(`搜索商品失败: ${error.message}, 类型: ${error.type}, 代码: ${error.code}`);
} else {
console.error('搜索商品发生未知错误:', error);
}
}
}
// 获取商品详情示例
async function getProductDetail(numIid: number) {
try {
const detail = await taobaoSDK.getItemDetail(numIid);
console.log('商品详情:', {
id: detail.num_iid,
title: detail.title,
price: detail.price,
stock: detail.stock,
shop: detail.shop_title,
properties: detail.props.map(prop => `${prop.name}: ${prop.value}`).join('; ')
});
} catch (error) {
if (error instanceof TaobaoError) {
console.error(`获取商品详情失败: ${error.message}, 类型: ${error.type}, 代码: ${error.code}`);
} else {
console.error('获取商品详情发生未知错误:', error);
}
}
}
// 调用示例
async function runExamples() {
await searchProducts();
// 假设搜索结果中第一个商品的ID为123456
await getProductDetail(123456);
// 调用其他API示例
try {
const categories = await taobaoSDK.invokeApi('taobao.itemcats.get', {
cid: 0,
fields: 'cid,name,is_parent'
});
console.log('商品分类:', categories);
} catch (error) {
console.error('获取商品分类失败:', error);
}
}
runExamples();
六、高级特性与优化
- 请求节流
为避免超过 API 调用频率限制,可以实现请求节流功能:
ini
// src/utils/throttle.ts
export function throttle<T extends (...args: any[]) => Promise<any>>(
func: T,
limit: number
): T {
let lastCall = 0;
let pendingPromise: Promise<any> | null = null;
return (async (...args: Parameters<T>): Promise<ReturnType<T>> => {
const now = Date.now();
const elapsed = now - lastCall;
if (elapsed >= limit) {
lastCall = now;
return func(...args);
}
// 等待上一个请求完成
if (pendingPromise) {
await pendingPromise;
}
// 再次检查时间
const now2 = Date.now();
if (now2 - lastCall >= limit) {
lastCall = now2;
return func(...args);
}
// 延迟执行
return new Promise((resolve) => {
setTimeout(async () => {
lastCall = Date.now();
pendingPromise = func(...args);
const result = await pendingPromise;
pendingPromise = null;
resolve(result);
}, limit - (now2 - lastCall));
});
}) as T;
}
在 SDK 中使用节流:
kotlin
// 在构造函数中添加
import { throttle } from './utils/throttle';
// ...
this.request = throttle(this.request.bind(this), 1000); // 限制每秒最多1次请求
- 缓存机制
对于不常变化的数据,可以添加缓存功能:
kotlin
// src/utils/cache.ts
export class Cache {
private data: Map<string, { value: any; expiry: number }>;
constructor() {
this.data = new Map();
// 定期清理过期缓存
setInterval(() => this.cleanup(), 60000);
}
get<T>(key: string): T | null {
const item = this.data.get(key);
if (!item) return null;
if (item.expiry < Date.now()) {
this.data.delete(key);
return null;
}
return item.value as T;
}
set<T>(key: string, value: T, ttl: number = 300000): void {
this.data.set(key, {
value,
expiry: Date.now() + ttl
});
}
delete(key: string): void {
this.data.delete(key);
}
clear(): void {
this.data.clear();
}
private cleanup(): void {
const now = Date.now();
for (const [key, item] of this.data) {
if (item.expiry < now) {
this.data.delete(key);
}
}
}
}
七、总结
本文介绍了如何使用 Node.js 和 TypeScript 开发一个健壮的淘宝商品 API SDK。该 SDK 具有以下特点:
- 类型安全:使用 TypeScript 定义所有请求和响应类型,提供良好的开发体验
- 健壮可靠:实现了完善的错误处理和重试机制,保证 API 调用的稳定性
- 易于使用:封装了常用的商品 API,提供简洁的接口
- 可扩展性:设计灵活,易于添加新的 API 方法和功能扩展
通过这个 SDK,开发者可以轻松地与淘宝平台进行集成,快速实现商品数据的获取和处理。在实际使用中,还可以根据具体需求进一步扩展 SDK 的功能,如添加更复杂的缓存策略、请求监控等。
最后,建议在使用 SDK 时遵守淘宝开放平台的使用规范,合理控制 API 调用频率,确保数据采集的合法性和稳定性。