Files
accounts-manager/example/accounts-manager-sdk.js
cloud370 3162cbfad0 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>
2025-09-23 11:32:23 +08:00

574 lines
15 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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);
}
}
}