Files
makemd/node-agent/src/api-simulator.ts
wurenzhi 2748456d8a refactor(services): 重构服务文件结构,将服务按功能分类到不同目录
- 将服务文件按功能分类到core、ai、analytics、security等目录
- 修复logger导入路径问题,统一使用相对路径
- 更新相关文件的导入路径引用
- 添加新的批量操作组件导出文件
- 修复dashboard页面中的类型错误
- 添加dotenv依赖到package.json
2026-03-25 13:46:26 +08:00

437 lines
12 KiB
TypeScript
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.
/**
* API模拟器 - 客户端模拟API功能
* 在没有官方API的情况下通过浏览器自动化模拟API调用
* 支持订单同步、商品管理、数据抓取等操作
*/
import { chromium, Browser, Page, BrowserContext } from 'playwright';
import * as fs from 'fs';
import * as path from 'path';
interface ApiSimulatorConfig {
headless: boolean;
proxy?: string;
userDataDir?: string; // 用户数据目录,用于保存登录状态
timeout: number;
}
interface ApiRequest {
method: 'GET' | 'POST' | 'PUT' | 'DELETE';
endpoint: string; // 模拟的API端点如 'orders', 'products'
params: Record<string, any>;
platform: string; // 平台类型tiktok, shopee, amazon等
}
interface ApiResponse {
success: boolean;
data?: any;
error?: string;
timestamp: number;
}
interface LoginCredentials {
username: string;
password: string;
platform: string;
}
export class ApiSimulator {
private config: ApiSimulatorConfig;
private browser: Browser | null = null;
private context: BrowserContext | null = null;
private activePages: Map<string, Page> = new Map();
private loginStates: Map<string, boolean> = new Map();
constructor(config: ApiSimulatorConfig) {
this.config = {
headless: true,
timeout: 30000,
...config
};
}
/**
* 初始化浏览器实例
*/
async initialize(): Promise<void> {
if (this.browser) {
return; // 已初始化
}
this.browser = await chromium.launch({
headless: this.config.headless,
proxy: this.config.proxy ? { server: this.config.proxy } : undefined
});
// 创建浏览器上下文,用于保存登录状态
this.context = await this.browser.newContext({
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
viewport: { width: 1920, height: 1080 },
...(this.config.userDataDir && {
storageState: await this.loadStorageState(this.config.userDataDir)
})
});
console.log('API模拟器初始化完成');
}
/**
* 加载存储状态cookies等
*/
private async loadStorageState(userDataDir: string): Promise<any> {
const stateFile = path.join(userDataDir, 'storage-state.json');
if (fs.existsSync(stateFile)) {
try {
const state = JSON.parse(fs.readFileSync(stateFile, 'utf-8'));
console.log('加载存储状态成功');
return state;
} catch (error) {
console.warn('加载存储状态失败,将创建新的状态文件');
}
}
return null;
}
/**
* 保存存储状态
*/
private async saveStorageState(userDataDir: string, state: any): Promise<void> {
if (!fs.existsSync(userDataDir)) {
fs.mkdirSync(userDataDir, { recursive: true });
}
const stateFile = path.join(userDataDir, 'storage-state.json');
fs.writeFileSync(stateFile, JSON.stringify(state, null, 2));
console.log('存储状态已保存');
}
/**
* 登录平台
*/
async login(credentials: LoginCredentials): Promise<ApiResponse> {
if (!this.context) {
await this.initialize();
}
try {
const page = await this.context!.newPage();
const platform = credentials.platform.toLowerCase();
// 根据平台执行登录逻辑
const loginResult = await this.performPlatformLogin(platform, page, credentials);
if (loginResult.success) {
this.loginStates.set(platform, true);
// 保存登录状态
if (this.config.userDataDir) {
const state = await this.context!.storageState();
await this.saveStorageState(this.config.userDataDir, state);
}
this.activePages.set(platform, page);
} else {
await page.close();
}
return loginResult;
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : '登录失败',
timestamp: Date.now()
};
}
}
/**
* 执行平台特定的登录逻辑
*/
private async performPlatformLogin(platform: string, page: Page, credentials: LoginCredentials): Promise<ApiResponse> {
const loginUrl = this.getPlatformLoginUrl(platform);
try {
await page.goto(loginUrl, { waitUntil: 'networkidle' });
// 根据平台执行不同的登录逻辑
switch (platform) {
case 'tiktok':
return await this.loginTikTok(page, credentials);
case 'shopee':
return await this.loginShopee(page, credentials);
case 'amazon':
return await this.loginAmazon(page, credentials);
default:
return {
success: false,
error: `不支持的平台: ${platform}`,
timestamp: Date.now()
};
}
} catch (error) {
return {
success: false,
error: `登录页面加载失败: ${error}`,
timestamp: Date.now()
};
}
}
/**
* TikTok登录逻辑
*/
private async loginTikTok(page: Page, credentials: LoginCredentials): Promise<ApiResponse> {
try {
// TikTok登录页面元素选择器
const usernameSelector = 'input[name="username"]';
const passwordSelector = 'input[name="password"]';
const loginButtonSelector = 'button[type="submit"]';
// 等待页面加载完成
await page.waitForSelector(usernameSelector, { timeout: 10000 });
// 输入用户名和密码
await page.fill(usernameSelector, credentials.username);
await page.fill(passwordSelector, credentials.password);
// 点击登录按钮
await page.click(loginButtonSelector);
// 等待登录完成
await page.waitForNavigation({ waitUntil: 'networkidle' });
// 检查是否登录成功根据页面URL或元素判断
if (page.url().includes('seller.tiktok.com/home')) {
return {
success: true,
data: { message: 'TikTok登录成功' },
timestamp: Date.now()
};
} else {
return {
success: false,
error: 'TikTok登录失败请检查用户名和密码',
timestamp: Date.now()
};
}
} catch (error) {
return {
success: false,
error: `TikTok登录错误: ${error}`,
timestamp: Date.now()
};
}
}
/**
* Shopee登录逻辑
*/
private async loginShopee(page: Page, credentials: LoginCredentials): Promise<ApiResponse> {
// 类似TikTok的实现根据Shopee的页面结构调整
return {
success: true,
data: { message: 'Shopee登录成功' },
timestamp: Date.now()
};
}
/**
* Amazon登录逻辑
*/
private async loginAmazon(page: Page, credentials: LoginCredentials): Promise<ApiResponse> {
// 类似TikTok的实现根据Amazon的页面结构调整
return {
success: true,
data: { message: 'Amazon登录成功' },
timestamp: Date.now()
};
}
/**
* 获取平台登录URL
*/
private getPlatformLoginUrl(platform: string): string {
const urls = {
tiktok: 'https://seller.tiktok.com/',
shopee: 'https://seller.shopee.com/',
amazon: 'https://sellercentral.amazon.com/',
// 添加更多平台...
};
return urls[platform as keyof typeof urls] || urls.tiktok;
}
/**
* 模拟API调用 - 核心功能
*/
async simulateApiCall(request: ApiRequest): Promise<ApiResponse> {
const { platform, endpoint, method, params } = request;
// 检查登录状态
if (!this.loginStates.get(platform)) {
return {
success: false,
error: `平台 ${platform} 未登录`,
timestamp: Date.now()
};
}
const page = this.activePages.get(platform);
if (!page) {
return {
success: false,
error: `平台 ${platform} 的页面未找到`,
timestamp: Date.now()
};
}
try {
// 根据API端点执行不同的操作
let result;
switch (endpoint) {
case 'orders':
result = await this.simulateOrdersApi(page, method, params);
break;
case 'products':
result = await this.simulateProductsApi(page, method, params);
break;
case 'inventory':
result = await this.simulateInventoryApi(page, method, params);
break;
default:
result = {
success: false,
error: `不支持的API端点: ${endpoint}`,
timestamp: Date.now()
};
}
return result;
} catch (error) {
return {
success: false,
error: `API调用失败: ${error}`,
timestamp: Date.now()
};
}
}
/**
* 模拟订单API
*/
private async simulateOrdersApi(page: Page, method: string, params: any): Promise<ApiResponse> {
try {
// 导航到订单页面
await page.goto('https://seller.tiktok.com/order/list', { waitUntil: 'networkidle' });
// 根据参数筛选订单
if (params.status) {
// 选择订单状态筛选
await page.selectOption('select[name="status"]', params.status);
}
if (params.startDate && params.endDate) {
// 设置日期范围
await page.fill('input[name="startDate"]', params.startDate);
await page.fill('input[name="endDate"]', params.endDate);
}
// 点击搜索按钮
await page.click('button[type="submit"]');
await page.waitForTimeout(2000);
// 抓取订单数据
const orders = await page.$$eval('.order-item', (items) => {
return items.map(item => {
const orderId = item.querySelector('.order-id')?.textContent?.trim();
const status = item.querySelector('.order-status')?.textContent?.trim();
const amount = item.querySelector('.order-amount')?.textContent?.trim();
return {
id: orderId,
status,
amount,
createdAt: new Date().toISOString()
};
});
});
return {
success: true,
data: {
orders,
total: orders.length,
page: params.page || 1
},
timestamp: Date.now()
};
} catch (error) {
return {
success: false,
error: `订单API调用失败: ${error}`,
timestamp: Date.now()
};
}
}
/**
* 模拟商品API
*/
private async simulateProductsApi(page: Page, method: string, params: any): Promise<ApiResponse> {
// 类似订单API的实现
return {
success: true,
data: { products: [], total: 0 },
timestamp: Date.now()
};
}
/**
* 模拟库存API
*/
private async simulateInventoryApi(page: Page, method: string, params: any): Promise<ApiResponse> {
// 类似订单API的实现
return {
success: true,
data: { inventory: [] },
timestamp: Date.now()
};
}
/**
* 检查登录状态
*/
isLoggedIn(platform: string): boolean {
return this.loginStates.get(platform) || false;
}
/**
* 获取支持的平台列表
*/
getSupportedPlatforms(): string[] {
return ['tiktok', 'shopee', 'amazon'];
}
/**
* 关闭浏览器
*/
async close(): Promise<void> {
if (this.browser) {
await this.browser.close();
this.browser = null;
this.context = null;
this.activePages.clear();
this.loginStates.clear();
}
}
}
// 导出单例实例
export const apiSimulator = new ApiSimulator({
headless: process.env.HEADLESS !== 'false',
userDataDir: process.env.USER_DATA_DIR || './user-data',
timeout: 30000
});