feat: 新增多模块功能与服务实现
新增广告计划、用户资产、B2B交易、合规规则等核心模型 实现爬虫工作器、贸易服务、现金流预测等业务服务 添加RBAC权限测试、压力测试等测试用例 完善扩展程序的消息处理与内容脚本功能 重构应用入口与文档生成器 更新项目规则与业务闭环分析文档
This commit is contained in:
431
extension/src/background/AutoShipService.ts
Normal file
431
extension/src/background/AutoShipService.ts
Normal file
@@ -0,0 +1,431 @@
|
||||
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)}`;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user