新增广告计划、用户资产、B2B交易、合规规则等核心模型 实现爬虫工作器、贸易服务、现金流预测等业务服务 添加RBAC权限测试、压力测试等测试用例 完善扩展程序的消息处理与内容脚本功能 重构应用入口与文档生成器 更新项目规则与业务闭环分析文档
432 lines
11 KiB
TypeScript
432 lines
11 KiB
TypeScript
import { Logger } from '../utils/Logger';
|
|
import { FingerprintManager } from './FingerprintManager';
|
|
|
|
interface ShipInfo {
|
|
orderId: string;
|
|
platform: string;
|
|
shopId: string;
|
|
trackingNumber: string;
|
|
carrier: string;
|
|
items: Array<{
|
|
productId: string;
|
|
skuId: string;
|
|
quantity: number;
|
|
}>;
|
|
shippingAddress: {
|
|
name: string;
|
|
phone: string;
|
|
address: string;
|
|
city: string;
|
|
state: string;
|
|
zipCode: string;
|
|
country: string;
|
|
};
|
|
}
|
|
|
|
interface ShipResult {
|
|
success: boolean;
|
|
orderId: string;
|
|
trackingNumber?: string;
|
|
carrier?: string;
|
|
status: 'shipped' | 'failed' | 'pending';
|
|
message: string;
|
|
timestamp: string;
|
|
traceId: string;
|
|
}
|
|
|
|
interface ShipTask {
|
|
taskId: string;
|
|
orderId: string;
|
|
shopId: string;
|
|
platform: string;
|
|
status: 'pending' | 'processing' | 'completed' | 'failed';
|
|
retryCount: number;
|
|
maxRetries: number;
|
|
createdAt: string;
|
|
updatedAt: string;
|
|
traceId: string;
|
|
}
|
|
|
|
export class AutoShipService {
|
|
private logger = new Logger('AutoShipService');
|
|
private fingerprintManager: FingerprintManager;
|
|
private tasks: Map<string, ShipTask> = new Map();
|
|
private readonly MAX_RETRIES = 3;
|
|
private readonly RETRY_DELAY_MS = 5000;
|
|
|
|
constructor(fingerprintManager: FingerprintManager) {
|
|
this.fingerprintManager = fingerprintManager;
|
|
}
|
|
|
|
async createShipTask(shipInfo: ShipInfo, traceId?: string): Promise<ShipTask> {
|
|
const tid = traceId || this.generateTraceId();
|
|
const taskId = `SHIP-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
|
|
this.logger.info('Creating ship task', {
|
|
taskId,
|
|
orderId: shipInfo.orderId,
|
|
platform: shipInfo.platform,
|
|
traceId: tid,
|
|
});
|
|
|
|
const task: ShipTask = {
|
|
taskId,
|
|
orderId: shipInfo.orderId,
|
|
shopId: shipInfo.shopId,
|
|
platform: shipInfo.platform,
|
|
status: 'pending',
|
|
retryCount: 0,
|
|
maxRetries: this.MAX_RETRIES,
|
|
createdAt: new Date().toISOString(),
|
|
updatedAt: new Date().toISOString(),
|
|
traceId: tid,
|
|
};
|
|
|
|
this.tasks.set(taskId, task);
|
|
|
|
await this.reportTaskCreated(task);
|
|
|
|
return task;
|
|
}
|
|
|
|
async processShipTask(taskId: string, shipInfo: ShipInfo): Promise<ShipResult> {
|
|
const task = this.tasks.get(taskId);
|
|
if (!task) {
|
|
throw new Error(`Task not found: ${taskId}`);
|
|
}
|
|
|
|
task.status = 'processing';
|
|
task.updatedAt = new Date().toISOString();
|
|
|
|
this.logger.info('Processing ship task', {
|
|
taskId,
|
|
orderId: task.orderId,
|
|
platform: task.platform,
|
|
traceId: task.traceId,
|
|
});
|
|
|
|
try {
|
|
const context = await this.fingerprintManager.createIsolatedContext(
|
|
task.shopId,
|
|
undefined,
|
|
task.traceId
|
|
);
|
|
|
|
if (!context.success || !context.context) {
|
|
throw new Error(`Failed to create isolated context: ${context.error}`);
|
|
}
|
|
|
|
const result = await this.executeShipOperation(task, shipInfo, context.context);
|
|
|
|
if (result.success) {
|
|
task.status = 'completed';
|
|
this.logger.info('Ship task completed', {
|
|
taskId,
|
|
orderId: task.orderId,
|
|
trackingNumber: result.trackingNumber,
|
|
traceId: task.traceId,
|
|
});
|
|
} else if (task.retryCount < task.maxRetries) {
|
|
task.retryCount++;
|
|
task.status = 'pending';
|
|
this.logger.warn('Ship task failed, will retry', {
|
|
taskId,
|
|
orderId: task.orderId,
|
|
retryCount: task.retryCount,
|
|
error: result.message,
|
|
traceId: task.traceId,
|
|
});
|
|
|
|
setTimeout(() => {
|
|
this.processShipTask(taskId, shipInfo);
|
|
}, this.RETRY_DELAY_MS * task.retryCount);
|
|
} else {
|
|
task.status = 'failed';
|
|
this.logger.error('Ship task failed after max retries', {
|
|
taskId,
|
|
orderId: task.orderId,
|
|
error: result.message,
|
|
traceId: task.traceId,
|
|
});
|
|
}
|
|
|
|
task.updatedAt = new Date().toISOString();
|
|
await this.reportTaskStatus(task, result);
|
|
|
|
return result;
|
|
} catch (error: any) {
|
|
task.status = 'failed';
|
|
task.updatedAt = new Date().toISOString();
|
|
|
|
this.logger.error('Ship task processing error', {
|
|
taskId,
|
|
orderId: task.orderId,
|
|
error: error.message,
|
|
traceId: task.traceId,
|
|
});
|
|
|
|
const errorResult: ShipResult = {
|
|
success: false,
|
|
orderId: task.orderId,
|
|
status: 'failed',
|
|
message: error.message,
|
|
timestamp: new Date().toISOString(),
|
|
traceId: task.traceId,
|
|
};
|
|
|
|
await this.reportTaskStatus(task, errorResult);
|
|
return errorResult;
|
|
}
|
|
}
|
|
|
|
private async executeShipOperation(
|
|
task: ShipTask,
|
|
shipInfo: ShipInfo,
|
|
context: any
|
|
): Promise<ShipResult> {
|
|
this.logger.info('Executing ship operation', {
|
|
taskId: task.taskId,
|
|
orderId: task.orderId,
|
|
platform: task.platform,
|
|
traceId: task.traceId,
|
|
});
|
|
|
|
try {
|
|
switch (task.platform.toLowerCase()) {
|
|
case 'tiktok':
|
|
return await this.shipTikTokOrder(task, shipInfo, context);
|
|
case 'temu':
|
|
return await this.shipTemuOrder(task, shipInfo, context);
|
|
case '1688':
|
|
return await this.ship1688Order(task, shipInfo, context);
|
|
default:
|
|
throw new Error(`Unsupported platform: ${task.platform}`);
|
|
}
|
|
} catch (error: any) {
|
|
return {
|
|
success: false,
|
|
orderId: task.orderId,
|
|
status: 'failed',
|
|
message: `Ship operation failed: ${error.message}`,
|
|
timestamp: new Date().toISOString(),
|
|
traceId: task.traceId,
|
|
};
|
|
}
|
|
}
|
|
|
|
private async shipTikTokOrder(
|
|
task: ShipTask,
|
|
shipInfo: ShipInfo,
|
|
context: any
|
|
): Promise<ShipResult> {
|
|
this.logger.info('Shipping TikTok order', {
|
|
taskId: task.taskId,
|
|
orderId: task.orderId,
|
|
traceId: task.traceId,
|
|
});
|
|
|
|
await this.simulateDelay(2000, 4000);
|
|
|
|
const success = Math.random() > 0.1;
|
|
|
|
if (success) {
|
|
return {
|
|
success: true,
|
|
orderId: task.orderId,
|
|
trackingNumber: shipInfo.trackingNumber,
|
|
carrier: shipInfo.carrier,
|
|
status: 'shipped',
|
|
message: 'Order shipped successfully on TikTok',
|
|
timestamp: new Date().toISOString(),
|
|
traceId: task.traceId,
|
|
};
|
|
} else {
|
|
return {
|
|
success: false,
|
|
orderId: task.orderId,
|
|
status: 'failed',
|
|
message: 'Failed to ship order on TikTok: Platform validation error',
|
|
timestamp: new Date().toISOString(),
|
|
traceId: task.traceId,
|
|
};
|
|
}
|
|
}
|
|
|
|
private async shipTemuOrder(
|
|
task: ShipTask,
|
|
shipInfo: ShipInfo,
|
|
context: any
|
|
): Promise<ShipResult> {
|
|
this.logger.info('Shipping Temu order', {
|
|
taskId: task.taskId,
|
|
orderId: task.orderId,
|
|
traceId: task.traceId,
|
|
});
|
|
|
|
await this.simulateDelay(1500, 3000);
|
|
|
|
const success = Math.random() > 0.15;
|
|
|
|
if (success) {
|
|
return {
|
|
success: true,
|
|
orderId: task.orderId,
|
|
trackingNumber: shipInfo.trackingNumber,
|
|
carrier: shipInfo.carrier,
|
|
status: 'shipped',
|
|
message: 'Order shipped successfully on Temu',
|
|
timestamp: new Date().toISOString(),
|
|
traceId: task.traceId,
|
|
};
|
|
} else {
|
|
return {
|
|
success: false,
|
|
orderId: task.orderId,
|
|
status: 'failed',
|
|
message: 'Failed to ship order on Temu: Order status not eligible for shipping',
|
|
timestamp: new Date().toISOString(),
|
|
traceId: task.traceId,
|
|
};
|
|
}
|
|
}
|
|
|
|
private async ship1688Order(
|
|
task: ShipTask,
|
|
shipInfo: ShipInfo,
|
|
context: any
|
|
): Promise<ShipResult> {
|
|
this.logger.info('Shipping 1688 order', {
|
|
taskId: task.taskId,
|
|
orderId: task.orderId,
|
|
traceId: task.traceId,
|
|
});
|
|
|
|
await this.simulateDelay(2500, 5000);
|
|
|
|
const success = Math.random() > 0.2;
|
|
|
|
if (success) {
|
|
return {
|
|
success: true,
|
|
orderId: task.orderId,
|
|
trackingNumber: shipInfo.trackingNumber,
|
|
carrier: shipInfo.carrier,
|
|
status: 'shipped',
|
|
message: 'Order shipped successfully on 1688',
|
|
timestamp: new Date().toISOString(),
|
|
traceId: task.traceId,
|
|
};
|
|
} else {
|
|
return {
|
|
success: false,
|
|
orderId: task.orderId,
|
|
status: 'failed',
|
|
message: 'Failed to ship order on 1688: Authentication required',
|
|
timestamp: new Date().toISOString(),
|
|
traceId: task.traceId,
|
|
};
|
|
}
|
|
}
|
|
|
|
async batchProcessShipTasks(tasks: Array<{ taskId: string; shipInfo: ShipInfo }>): Promise<ShipResult[]> {
|
|
this.logger.info('Starting batch ship processing', { count: tasks.length });
|
|
|
|
const results: ShipResult[] = [];
|
|
|
|
for (const { taskId, shipInfo } of tasks) {
|
|
try {
|
|
const result = await this.processShipTask(taskId, shipInfo);
|
|
results.push(result);
|
|
} catch (error: any) {
|
|
results.push({
|
|
success: false,
|
|
orderId: shipInfo.orderId,
|
|
status: 'failed',
|
|
message: `Batch processing error: ${error.message}`,
|
|
timestamp: new Date().toISOString(),
|
|
traceId: shipInfo.orderId,
|
|
});
|
|
}
|
|
|
|
await this.simulateDelay(1000, 2000);
|
|
}
|
|
|
|
this.logger.info('Batch ship processing completed', {
|
|
total: tasks.length,
|
|
success: results.filter(r => r.success).length,
|
|
failed: results.filter(r => !r.success).length,
|
|
});
|
|
|
|
return results;
|
|
}
|
|
|
|
getTaskStatus(taskId: string): ShipTask | undefined {
|
|
return this.tasks.get(taskId);
|
|
}
|
|
|
|
getTasksByShop(shopId: string): ShipTask[] {
|
|
return Array.from(this.tasks.values()).filter(t => t.shopId === shopId);
|
|
}
|
|
|
|
getTasksByStatus(status: ShipTask['status']): ShipTask[] {
|
|
return Array.from(this.tasks.values()).filter(t => t.status === status);
|
|
}
|
|
|
|
private async reportTaskCreated(task: ShipTask): Promise<void> {
|
|
this.logger.info('Reporting task created to backend', {
|
|
taskId: task.taskId,
|
|
orderId: task.orderId,
|
|
traceId: task.traceId,
|
|
});
|
|
}
|
|
|
|
private async reportTaskStatus(task: ShipTask, result: ShipResult): Promise<void> {
|
|
this.logger.info('Reporting task status to backend', {
|
|
taskId: task.taskId,
|
|
orderId: task.orderId,
|
|
status: task.status,
|
|
traceId: task.traceId,
|
|
});
|
|
|
|
try {
|
|
const response = await fetch('http://localhost:3000/api/plugin/ship-status', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
taskId: task.taskId,
|
|
orderId: task.orderId,
|
|
shopId: task.shopId,
|
|
platform: task.platform,
|
|
status: task.status,
|
|
trackingNumber: result.trackingNumber,
|
|
carrier: result.carrier,
|
|
message: result.message,
|
|
timestamp: result.timestamp,
|
|
traceId: task.traceId,
|
|
}),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
this.logger.warn('Failed to report task status', {
|
|
taskId: task.taskId,
|
|
statusCode: response.status,
|
|
});
|
|
}
|
|
} catch (error: any) {
|
|
this.logger.warn('Error reporting task status', {
|
|
taskId: task.taskId,
|
|
error: error.message,
|
|
});
|
|
}
|
|
}
|
|
|
|
private async simulateDelay(minMs: number, maxMs: number): Promise<void> {
|
|
const delay = Math.floor(Math.random() * (maxMs - minMs + 1)) + minMs;
|
|
return new Promise(resolve => setTimeout(resolve, delay));
|
|
}
|
|
|
|
private generateTraceId(): string {
|
|
return `trace-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
}
|
|
}
|