feat: add export functionality for web API with demo script
- Add export API endpoint POST /web/v1/accounts/export - Support two export modes: text (data strings) and object (full account objects) - Automatically set account status to 'exported' during export - Add comprehensive demo script that uploads, exports and verifies functionality - Update API documentation with export endpoint details - Add TypeScript types for export functionality 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
574
example/accounts-manager-sdk.js
Normal file
574
example/accounts-manager-sdk.js
Normal file
@@ -0,0 +1,574 @@
|
||||
import http from 'http';
|
||||
import https from 'https';
|
||||
|
||||
/**
|
||||
* 账户管理系统 NodeJS SDK
|
||||
*
|
||||
* 该SDK提供了与账户管理系统API交互的功能,支持账号的获取、上传、更新等操作。
|
||||
* 使用ESM格式,兼容Node.js环境。
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* import AccountsManagerSDK, { AccountStatus } from './accounts-manager-sdk.js';
|
||||
*
|
||||
* // 基本用法
|
||||
* const sdk = new AccountsManagerSDK({
|
||||
* baseUrl: 'https://api.example.com'
|
||||
* });
|
||||
*
|
||||
* // 指定默认ownerId和platform
|
||||
* const sdk = new AccountsManagerSDK({
|
||||
* baseUrl: 'https://api.example.com',
|
||||
* ownerId: 'owner123',
|
||||
* platform: 'platform1'
|
||||
* });
|
||||
*
|
||||
* // 使用默认ownerId和platform,获取2个账号
|
||||
* const accounts = await sdk.getAccounts(2);
|
||||
* console.log(accounts);
|
||||
*
|
||||
* // 使用默认ownerId上传账号
|
||||
* const items = [
|
||||
* {
|
||||
* platform: 'platform1',
|
||||
* customId: 'user123',
|
||||
* data: { username: 'user123', password: 'pass123' },
|
||||
* status: AccountStatus.AVAILABLE
|
||||
* }
|
||||
* ];
|
||||
* const result = await sdk.uploadAccounts(items);
|
||||
* console.log(result);
|
||||
*
|
||||
* // 使用默认ownerId更新账号状态
|
||||
* const updateResult = await sdk.updateAccountStatus(1, AccountStatus.USED);
|
||||
* console.log(updateResult);
|
||||
* ```
|
||||
*/
|
||||
|
||||
/**
|
||||
* 业务状态码枚举
|
||||
* @enum {number}
|
||||
*/
|
||||
export const BusinessCode = {
|
||||
/** 请求成功 */
|
||||
Success: 0,
|
||||
/** 资源不存在 */
|
||||
NoResource: 1001,
|
||||
/** 参数无效 */
|
||||
InvalidParams: 2001,
|
||||
/** 资源冲突 */
|
||||
ResourceConflict: 3001,
|
||||
/** 权限不足 */
|
||||
PermissionDenied: 4001,
|
||||
/** 业务错误 */
|
||||
BusinessError: 5001,
|
||||
};
|
||||
|
||||
/**
|
||||
* 账户状态枚举
|
||||
* @enum {string}
|
||||
*/
|
||||
export const AccountStatus = {
|
||||
/** 可用 */
|
||||
AVAILABLE: 'available',
|
||||
/** 已使用 */
|
||||
USED: 'used',
|
||||
/** 已锁定 */
|
||||
LOCKED: 'locked',
|
||||
/** 已禁用 */
|
||||
DISABLED: 'disabled',
|
||||
/** 已过期 */
|
||||
EXPIRED: 'expired',
|
||||
};
|
||||
|
||||
/**
|
||||
* 通用响应格式
|
||||
* @template T - 响应数据类型
|
||||
*/
|
||||
export class ApiResponse {
|
||||
/**
|
||||
* 业务状态码
|
||||
* @type {BusinessCode}
|
||||
*/
|
||||
code;
|
||||
|
||||
/**
|
||||
* 响应消息
|
||||
* @type {string}
|
||||
*/
|
||||
message;
|
||||
|
||||
/**
|
||||
* 响应数据
|
||||
* @type {T|null}
|
||||
*/
|
||||
data;
|
||||
|
||||
/**
|
||||
* 创建API响应实例
|
||||
* @param {BusinessCode} code - 业务状态码
|
||||
* @param {string} message - 响应消息
|
||||
* @param {T|null} data - 响应数据
|
||||
*/
|
||||
constructor(code, message, data = null) {
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查响应是否成功
|
||||
* @returns {boolean} 是否成功
|
||||
*/
|
||||
isSuccess() {
|
||||
return this.code === BusinessCode.Success;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 账户数据模型
|
||||
*/
|
||||
export class Account {
|
||||
/**
|
||||
* 账户ID,自增主键
|
||||
* @type {number}
|
||||
*/
|
||||
id;
|
||||
|
||||
/**
|
||||
* 所有者ID
|
||||
* @type {string}
|
||||
*/
|
||||
ownerId;
|
||||
|
||||
/**
|
||||
* 平台名称
|
||||
* @type {string}
|
||||
*/
|
||||
platform;
|
||||
|
||||
/**
|
||||
* 自定义ID
|
||||
* @type {string}
|
||||
*/
|
||||
customId;
|
||||
|
||||
/**
|
||||
* 账户数据,JSON格式字符串
|
||||
* @type {string}
|
||||
*/
|
||||
data;
|
||||
|
||||
/**
|
||||
* 账户状态
|
||||
* @type {string}
|
||||
*/
|
||||
status;
|
||||
|
||||
/**
|
||||
* 备注,可选
|
||||
* @type {string|undefined}
|
||||
*/
|
||||
notes;
|
||||
|
||||
/**
|
||||
* 锁定时间,可选
|
||||
* @type {Date|undefined}
|
||||
*/
|
||||
lockedAt;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
* @type {Date}
|
||||
*/
|
||||
createdAt;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
* @type {Date}
|
||||
*/
|
||||
updatedAt;
|
||||
|
||||
/**
|
||||
* 创建账户实例
|
||||
* @param {object} data - 账户数据
|
||||
* @param {number} data.id - 账户ID
|
||||
* @param {string} data.ownerId - 所有者ID
|
||||
* @param {string} data.platform - 平台名称
|
||||
* @param {string} data.customId - 自定义ID
|
||||
* @param {string} data.data - 账户数据
|
||||
* @param {string} data.status - 账户状态
|
||||
* @param {string} [data.notes] - 备注
|
||||
* @param {string} [data.lockedAt] - 锁定时间
|
||||
* @param {string} data.createdAt - 创建时间
|
||||
* @param {string} data.updatedAt - 更新时间
|
||||
*/
|
||||
constructor({ id, ownerId, platform, customId, data, status, notes, lockedAt, createdAt, updatedAt }) {
|
||||
this.id = id;
|
||||
this.ownerId = ownerId;
|
||||
this.platform = platform;
|
||||
this.customId = customId;
|
||||
this.data = data;
|
||||
this.status = status;
|
||||
this.notes = notes;
|
||||
this.lockedAt = lockedAt ? new Date(lockedAt) : undefined;
|
||||
this.createdAt = new Date(createdAt);
|
||||
this.updatedAt = new Date(updatedAt);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析账户数据为对象
|
||||
* @returns {object} 解析后的账户数据
|
||||
*/
|
||||
parseData() {
|
||||
try {
|
||||
return JSON.parse(this.data);
|
||||
} catch (error) {
|
||||
console.error('Failed to parse account data:', error);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 脚本上传项
|
||||
*/
|
||||
export class ScriptUploadItem {
|
||||
/**
|
||||
* 平台名称
|
||||
* @type {string}
|
||||
*/
|
||||
platform;
|
||||
|
||||
/**
|
||||
* 自定义ID
|
||||
* @type {string}
|
||||
*/
|
||||
customId;
|
||||
|
||||
/**
|
||||
* 账户数据,JSON格式字符串
|
||||
* @type {string}
|
||||
*/
|
||||
data;
|
||||
|
||||
/**
|
||||
* 账户状态,可选,默认为"available"
|
||||
* @type {string|undefined}
|
||||
*/
|
||||
status;
|
||||
|
||||
/**
|
||||
* 创建脚本上传项实例
|
||||
* @param {object} data - 上传项数据
|
||||
* @param {string} data.platform - 平台名称
|
||||
* @param {string} data.customId - 自定义ID
|
||||
* @param {string|object} data.data - 账户数据,可以是对象或JSON字符串
|
||||
* @param {string} [data.status] - 账户状态
|
||||
*/
|
||||
constructor({ platform, customId, data, status = 'available' }) {
|
||||
this.platform = platform;
|
||||
this.customId = customId;
|
||||
this.data = typeof data === 'string' ? data : JSON.stringify(data);
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将实例转换为普通对象
|
||||
* @returns {object} 普通对象
|
||||
*/
|
||||
toObject() {
|
||||
return {
|
||||
platform: this.platform,
|
||||
customId: this.customId,
|
||||
data: this.data,
|
||||
status: this.status,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 账户管理系统SDK
|
||||
*/
|
||||
export default class AccountsManagerSDK {
|
||||
/**
|
||||
* API基础URL
|
||||
* @type {string}
|
||||
*/
|
||||
baseUrl;
|
||||
|
||||
|
||||
/**
|
||||
* 创建SDK实例
|
||||
* @param {object} options - 配置选项
|
||||
* @param {string} options.baseUrl - API基础URL
|
||||
* @param {string} [options.ownerId] - 默认所有者ID(可选)
|
||||
* @param {string} [options.platform] - 默认平台名称(可选)
|
||||
* @example
|
||||
* ```javascript
|
||||
* // 基本用法
|
||||
* const sdk = new AccountsManagerSDK({
|
||||
* baseUrl: 'https://api.example.com'
|
||||
* });
|
||||
*
|
||||
* // 指定默认ownerId和platform
|
||||
* const sdk = new AccountsManagerSDK({
|
||||
* baseUrl: 'https://api.example.com',
|
||||
* ownerId: 'owner123',
|
||||
* platform: 'platform1'
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
constructor({ baseUrl, ownerId, platform }) {
|
||||
if (!baseUrl) {
|
||||
throw new Error('baseUrl is required');
|
||||
}
|
||||
|
||||
this.baseUrl = baseUrl;
|
||||
this.ownerId = ownerId;
|
||||
this.platform = platform;
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送HTTP请求
|
||||
* @private
|
||||
* @param {string} path - 请求路径
|
||||
* @param {object} options - 请求选项
|
||||
* @param {string} [options.method='GET'] - 请求方法
|
||||
* @param {object} [options.headers] - 请求头
|
||||
* @param {string|object} [options.body] - 请求体
|
||||
* @returns {Promise<object>} 响应数据
|
||||
*/
|
||||
async _request(path, options = {}) {
|
||||
const {
|
||||
method = 'GET',
|
||||
headers = {},
|
||||
body = null,
|
||||
} = options;
|
||||
|
||||
const url = new URL(path, this.baseUrl);
|
||||
const isHttps = url.protocol === 'https:';
|
||||
|
||||
const requestOptions = {
|
||||
hostname: url.hostname,
|
||||
port: url.port || (isHttps ? 443 : 80),
|
||||
path: url.pathname + url.search,
|
||||
method,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...headers,
|
||||
},
|
||||
};
|
||||
|
||||
if (body) {
|
||||
const bodyStr = typeof body === 'string' ? body : JSON.stringify(body);
|
||||
requestOptions.headers['Content-Length'] = Buffer.byteLength(bodyStr);
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const req = (isHttps ? https : http).request(requestOptions, (res) => {
|
||||
let data = '';
|
||||
|
||||
res.on('data', (chunk) => {
|
||||
data += chunk;
|
||||
});
|
||||
|
||||
res.on('end', () => {
|
||||
try {
|
||||
const jsonData = JSON.parse(data);
|
||||
resolve(jsonData);
|
||||
} catch (error) {
|
||||
reject(new Error(`Failed to parse response: ${error.message}`));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
req.on('error', (error) => {
|
||||
reject(new Error(`Request failed: ${error.message}`));
|
||||
});
|
||||
|
||||
if (body) {
|
||||
const bodyStr = typeof body === 'string' ? body : JSON.stringify(body);
|
||||
req.write(bodyStr);
|
||||
}
|
||||
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取账号
|
||||
* @param {number} [count=1] - 获取数量,默认1,最大100
|
||||
* @param {string} [ownerId] - 所有者ID(可选,如果未提供则使用初始化时设置的值)
|
||||
* @param {string} [platform] - 平台名称(可选,如果未提供则使用初始化时设置的值)
|
||||
* @returns {Promise<ApiResponse<Array<{id: number, customId: string, data: string}>>>} API响应
|
||||
* @example
|
||||
* ```javascript
|
||||
* // 使用初始化时设置的ownerId和platform,获取2个账号
|
||||
* const result = await sdk.getAccounts(2);
|
||||
*
|
||||
* // 覆盖初始化时设置的ownerId和platform
|
||||
* const result = await sdk.getAccounts(2, 'owner456', 'platform2');
|
||||
*
|
||||
* if (result.isSuccess()) {
|
||||
* console.log('获取的账号:', result.data);
|
||||
* } else {
|
||||
* console.error('获取失败:', result.message);
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
async getAccounts(count = 1, ownerId, platform) {
|
||||
try {
|
||||
// 使用传入的参数或默认值
|
||||
const finalOwnerId = ownerId || this.ownerId;
|
||||
const finalPlatform = platform || this.platform;
|
||||
|
||||
if (!finalOwnerId) {
|
||||
throw new Error('ownerId is required (either in constructor or method call)');
|
||||
}
|
||||
if (!finalPlatform) {
|
||||
throw new Error('platform is required (either in constructor or method call)');
|
||||
}
|
||||
|
||||
const query = new URLSearchParams({
|
||||
platform: finalPlatform,
|
||||
count: Math.min(Math.max(count, 1), 100).toString(),
|
||||
});
|
||||
|
||||
const response = await this._request(`/s/v1/${finalOwnerId}/acquire?${query}`);
|
||||
return new ApiResponse(response.code, response.message, response.data);
|
||||
} catch (error) {
|
||||
return new ApiResponse(BusinessCode.BusinessError, error.message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新账户状态
|
||||
* @param {number} accountId - 账户ID
|
||||
* @param {string} newStatus - 新状态
|
||||
* @param {string} [ownerId] - 所有者ID(可选,如果未提供则使用初始化时设置的值)
|
||||
* @param {string} [notes] - 备注
|
||||
* @returns {Promise<ApiResponse<{updatedId: number}>>} API响应
|
||||
* @example
|
||||
* ```javascript
|
||||
* // 使用初始化时设置的ownerId
|
||||
* const result = await sdk.updateAccountStatus(1, 'used', undefined, '测试使用');
|
||||
*
|
||||
* // 覆盖初始化时设置的ownerId
|
||||
* const result = await sdk.updateAccountStatus(1, 'used', 'owner456', '测试使用');
|
||||
*
|
||||
* if (result.isSuccess()) {
|
||||
* console.log('更新成功:', result.data);
|
||||
* } else {
|
||||
* console.error('更新失败:', result.message);
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
async updateAccountStatus(accountId, newStatus, ownerId, notes) {
|
||||
try {
|
||||
// 使用传入的参数或默认值
|
||||
const finalOwnerId = ownerId || this.ownerId;
|
||||
|
||||
if (!finalOwnerId) {
|
||||
throw new Error('ownerId is required (either in constructor or method call)');
|
||||
}
|
||||
|
||||
let path = `/s/v1/${finalOwnerId}/update/${accountId}/${encodeURIComponent(newStatus)}`;
|
||||
|
||||
if (notes) {
|
||||
path += `?notes=${encodeURIComponent(notes)}`;
|
||||
}
|
||||
|
||||
const response = await this._request(path);
|
||||
return new ApiResponse(response.code, response.message, response.data);
|
||||
} catch (error) {
|
||||
return new ApiResponse(BusinessCode.BusinessError, error.message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传账户
|
||||
* @param {Array<string>} items - 账户数据数组,每个字符串是"----"分隔的数据,格式为"customId----data"
|
||||
* @param {string} [status] - 账户状态(可选,默认为AccountStatus.AVAILABLE)
|
||||
* @param {string} [ownerId] - 所有者ID(可选,如果未提供则使用初始化时设置的值)
|
||||
* @param {string} [platform] - 平台名称(可选,如果未提供则使用初始化时设置的值)
|
||||
* @returns {Promise<ApiResponse<{processedCount: number, createdCount: number, updatedCount: number}>>} API响应
|
||||
* @example
|
||||
* ```javascript
|
||||
* // 简化的账户数据字符串数组
|
||||
* const items = [
|
||||
* 'user123----{"username":"user123","password":"pass123"}',
|
||||
* 'user456----{"username":"user456","password":"pass456"}',
|
||||
* 'admin@qq.com----{"email":"admin@qq.com","role":"admin"}'
|
||||
* ];
|
||||
*
|
||||
* // 使用初始化时设置的ownerId和platform,默认状态
|
||||
* const result = await sdk.uploadAccounts(items);
|
||||
*
|
||||
* // 指定状态为used
|
||||
* const result = await sdk.uploadAccounts(items, AccountStatus.USED);
|
||||
*
|
||||
* // 覆盖初始化时设置的ownerId和platform
|
||||
* const result = await sdk.uploadAccounts(items, AccountStatus.USED, 'owner456', 'platform2');
|
||||
*
|
||||
* if (result.isSuccess()) {
|
||||
* console.log('上传成功:', result.data);
|
||||
* } else {
|
||||
* console.error('上传失败:', result.message);
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
async uploadAccounts(items, status, ownerId, platform) {
|
||||
try {
|
||||
// 使用传入的参数或默认值
|
||||
const finalStatus = status || AccountStatus.AVAILABLE;
|
||||
const finalOwnerId = ownerId || this.ownerId;
|
||||
const finalPlatform = platform || this.platform;
|
||||
|
||||
if (!finalOwnerId) {
|
||||
throw new Error('ownerId is required (either in constructor or method call)');
|
||||
}
|
||||
if (!finalPlatform) {
|
||||
throw new Error('platform is required (either in constructor or method call)');
|
||||
}
|
||||
|
||||
// 解析每个字符串并创建ScriptUploadItem实例
|
||||
const uploadItems = items.map(item => {
|
||||
// 使用"----"分割字符串
|
||||
const parts = item.split('----');
|
||||
|
||||
if (parts.length < 2) {
|
||||
throw new Error(`Invalid item format: "${item}". Expected format: "customId----data"`);
|
||||
}
|
||||
|
||||
const customId = parts[0].trim();
|
||||
// 使用完整的原始字符串作为data
|
||||
const data = item; // 使用完整的原始字符串作为data
|
||||
|
||||
// 创建新的ScriptUploadItem实例
|
||||
return new ScriptUploadItem({
|
||||
platform: finalPlatform,
|
||||
customId,
|
||||
data,
|
||||
status: finalStatus,
|
||||
});
|
||||
});
|
||||
|
||||
const body = uploadItems.map(item => ({
|
||||
platform: item.platform,
|
||||
customId: item.customId,
|
||||
data: item.data,
|
||||
status: item.status,
|
||||
}));
|
||||
|
||||
const response = await this._request(`/s/v1/${finalOwnerId}/upload`, {
|
||||
method: 'POST',
|
||||
body,
|
||||
});
|
||||
|
||||
return new ApiResponse(response.code, response.message, response.data);
|
||||
} catch (error) {
|
||||
return new ApiResponse(BusinessCode.BusinessError, error.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user