feat: 实现Operation-Agent核心功能及电商平台适配器

refactor: 重构项目结构,分离server和dashboard代码
style: 统一代码风格,修复lint警告
test: 添加平台适配器工厂测试用例
ci: 更新CI/CD流程,增加语义验证和性能测试
docs: 添加语义中心文档,定义统一数据模型和状态机
This commit is contained in:
2026-03-19 15:23:56 +08:00
parent aa2cf560c6
commit 8de9ea0aaa
41 changed files with 5615 additions and 497 deletions

View File

@@ -0,0 +1,61 @@
import { Controller, Get, Put, Param, UseGuards } from '@nestjs/common';
import { AISelfImprovementService } from '../../core/ai/AISelfImprovementService';
import { RbacGuard } from '../../core/guards/rbac.guard';
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
@ApiTags('AI Self-Improvement')
@ApiBearerAuth()
@Controller('api/ai/self-improvement')
export class AISelfImprovementController {
constructor(private aiSelfImprovementService: AISelfImprovementService) {}
/**
* 生成改进建议
*/
@Get('suggestions/generate')
@UseGuards(RbacGuard)
async generateSuggestions() {
const suggestions = await this.aiSelfImprovementService.generateImprovementSuggestions();
return { success: true, suggestions };
}
/**
* 获取所有改进建议
*/
@Get('suggestions')
@UseGuards(RbacGuard)
async getSuggestions() {
const suggestions = await this.aiSelfImprovementService.getImprovementSuggestions();
return { success: true, suggestions };
}
/**
* 更新建议状态
*/
@Put('suggestions/:id/status')
@UseGuards(RbacGuard)
async updateSuggestionStatus(@Param('id') id: string, @Param('status') status: 'implemented' | 'dismissed') {
const result = await this.aiSelfImprovementService.updateSuggestionStatus(id, status);
return { success: result };
}
/**
* 自动应用改进建议
*/
@Get('suggestions/apply')
@UseGuards(RbacGuard)
async applySuggestions() {
const result = await this.aiSelfImprovementService.applyImprovementSuggestions();
return { success: true, ...result };
}
/**
* 执行定期优化
*/
@Get('optimize')
@UseGuards(RbacGuard)
async performOptimization() {
await this.aiSelfImprovementService.performRegularOptimization();
return { success: true, message: '定期优化执行完成' };
}
}

View File

@@ -0,0 +1,30 @@
import { Controller, Get, UseGuards } from '@nestjs/common';
import { MonitoringService } from '../../core/monitoring/MonitoringService';
import { RbacGuard } from '../../core/guards/rbac.guard';
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
@ApiTags('Monitoring')
@ApiBearerAuth()
@Controller('api/monitoring')
export class MonitoringController {
constructor(private monitoringService: MonitoringService) {}
/**
* 获取当前监控指标
*/
@Get('metrics')
@UseGuards(RbacGuard)
async getMetrics() {
return this.monitoringService.getMetrics();
}
/**
* 手动收集和记录指标
*/
@Get('collect')
@UseGuards(RbacGuard)
async collectMetrics() {
await this.monitoringService.collectAndRecordMetrics();
return { success: true, message: '指标收集成功' };
}
}

View File

@@ -0,0 +1,89 @@
import { Controller, Post, Get, Put, Delete, Param, Body, UseGuards } from '@nestjs/common';
import { OperationAgentService } from '../../core/operation/OperationAgentService';
import { StoreBindingDto } from '../dto/StoreBindingDto';
import { Store } from '../../entities/Store';
import { RbacGuard } from '../../core/guards/rbac.guard';
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
@ApiTags('Operation-Agent')
@ApiBearerAuth()
@Controller('api/operation-agent')
export class OperationAgentController {
constructor(private operationAgentService: OperationAgentService) {}
/**
* 绑定店铺
*/
@Post('stores')
@UseGuards(RbacGuard)
async bindStore(@Body() dto: StoreBindingDto): Promise<Store> {
return this.operationAgentService.bindStore(dto);
}
/**
* 获取商户的店铺列表
*/
@Get('stores/:merchantId')
@UseGuards(RbacGuard)
async getStores(@Param('merchantId') merchantId: string): Promise<Store[]> {
return this.operationAgentService.getStores(merchantId);
}
/**
* 获取店铺详情
*/
@Get('stores/detail/:storeId')
@UseGuards(RbacGuard)
async getStore(@Param('storeId') storeId: string): Promise<Store> {
return this.operationAgentService.getStore(storeId);
}
/**
* 同步店铺商品
*/
@Post('stores/:storeId/products/sync')
@UseGuards(RbacGuard)
async syncProducts(@Param('storeId') storeId: string): Promise<{ success: boolean; count: number }> {
return this.operationAgentService.syncProducts(storeId);
}
/**
* 同步店铺订单
*/
@Post('stores/:storeId/orders/sync')
@UseGuards(RbacGuard)
async syncOrders(@Param('storeId') storeId: string): Promise<{ success: boolean; count: number }> {
return this.operationAgentService.syncOrders(storeId);
}
/**
* 更新商品价格
*/
@Put('stores/:storeId/products/:productId/price')
@UseGuards(RbacGuard)
async updateProductPrice(
@Param('storeId') storeId: string,
@Param('productId') productId: string,
@Body('price') price: number
): Promise<boolean> {
return this.operationAgentService.updateProductPrice(storeId, productId, price);
}
/**
* 停用店铺
*/
@Put('stores/:storeId/deactivate')
@UseGuards(RbacGuard)
async deactivateStore(@Param('storeId') storeId: string): Promise<Store> {
return this.operationAgentService.deactivateStore(storeId);
}
/**
* 重新激活店铺
*/
@Put('stores/:storeId/reactivate')
@UseGuards(RbacGuard)
async reactivateStore(@Param('storeId') storeId: string): Promise<Store> {
return this.operationAgentService.reactivateStore(storeId);
}
}

View File

@@ -0,0 +1,22 @@
import { IsString, IsOptional, IsObject } from 'class-validator';
export class StoreBindingDto {
@IsString()
merchantId: string;
@IsString()
platform: string;
@IsString()
platformShopId: string;
@IsString()
name: string;
@IsOptional()
@IsString()
description?: string;
@IsObject()
authInfo: Record<string, any>;
}

View File

@@ -0,0 +1,43 @@
import { Router } from 'express';
import { AISelfImprovementController } from '../controllers/AISelfImprovementController';
import { Container } from 'typedi';
const router = Router();
const aiSelfImprovementController = Container.get(AISelfImprovementController);
// 生成改进建议
router.get('/suggestions/generate', (req, res, next) => {
aiSelfImprovementController.generateSuggestions()
.then(result => res.json(result))
.catch(next);
});
// 获取所有改进建议
router.get('/suggestions', (req, res, next) => {
aiSelfImprovementController.getSuggestions()
.then(result => res.json(result))
.catch(next);
});
// 更新建议状态
router.put('/suggestions/:id/status', (req, res, next) => {
aiSelfImprovementController.updateSuggestionStatus(req.params.id, req.params.status)
.then(result => res.json(result))
.catch(next);
});
// 自动应用改进建议
router.get('/suggestions/apply', (req, res, next) => {
aiSelfImprovementController.applySuggestions()
.then(result => res.json(result))
.catch(next);
});
// 执行定期优化
router.get('/optimize', (req, res, next) => {
aiSelfImprovementController.performOptimization()
.then(result => res.json(result))
.catch(next);
});
export default router;

View File

@@ -0,0 +1,22 @@
import { Router } from 'express';
import { MonitoringController } from '../controllers/MonitoringController';
import { Container } from 'typedi';
const router = Router();
const monitoringController = Container.get(MonitoringController);
// 获取当前监控指标
router.get('/metrics', (req, res, next) => {
monitoringController.getMetrics()
.then(result => res.json(result))
.catch(next);
});
// 手动收集和记录指标
router.get('/collect', (req, res, next) => {
monitoringController.collectMetrics()
.then(result => res.json(result))
.catch(next);
});
export default router;

View File

@@ -0,0 +1,64 @@
import { Router } from 'express';
import { OperationAgentController } from '../controllers/OperationAgentController';
import { Container } from 'typedi';
const router = Router();
const operationAgentController = Container.get(OperationAgentController);
// 绑定店铺
router.post('/stores', (req, res, next) => {
operationAgentController.bindStore(req.body)
.then(result => res.json(result))
.catch(next);
});
// 获取商户的店铺列表
router.get('/stores/:merchantId', (req, res, next) => {
operationAgentController.getStores(req.params.merchantId)
.then(result => res.json(result))
.catch(next);
});
// 获取店铺详情
router.get('/stores/detail/:storeId', (req, res, next) => {
operationAgentController.getStore(req.params.storeId)
.then(result => res.json(result))
.catch(next);
});
// 同步店铺商品
router.post('/stores/:storeId/products/sync', (req, res, next) => {
operationAgentController.syncProducts(req.params.storeId)
.then(result => res.json(result))
.catch(next);
});
// 同步店铺订单
router.post('/stores/:storeId/orders/sync', (req, res, next) => {
operationAgentController.syncOrders(req.params.storeId)
.then(result => res.json(result))
.catch(next);
});
// 更新商品价格
router.put('/stores/:storeId/products/:productId/price', (req, res, next) => {
operationAgentController.updateProductPrice(req.params.storeId, req.params.productId, req.body.price)
.then(result => res.json(result))
.catch(next);
});
// 停用店铺
router.put('/stores/:storeId/deactivate', (req, res, next) => {
operationAgentController.deactivateStore(req.params.storeId)
.then(result => res.json(result))
.catch(next);
});
// 重新激活店铺
router.put('/stores/:storeId/reactivate', (req, res, next) => {
operationAgentController.reactivateStore(req.params.storeId)
.then(result => res.json(result))
.catch(next);
});
export default router;

View File

@@ -0,0 +1,317 @@
import { Injectable, Logger } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { AIImprovement } from '../../entities/AIImprovement';
import { MonitoringService } from '../monitoring/MonitoringService';
import { EventEmitter2 } from '@nestjs/event-emitter';
interface ImprovementSuggestion {
id: string;
type: 'code' | 'performance' | 'business' | 'security';
title: string;
description: string;
severity: 'low' | 'medium' | 'high';
priority: 'low' | 'medium' | 'high';
status: 'pending' | 'implemented' | 'dismissed';
createdAt: Date;
updatedAt: Date;
}
@Injectable()
export class AISelfImprovementService {
private readonly logger = new Logger(AISelfImprovementService.name);
private suggestions: ImprovementSuggestion[] = [];
constructor(
@InjectRepository(AIImprovement) private aiImprovementRepository: Repository<AIImprovement>,
private monitoringService: MonitoringService,
private eventEmitter: EventEmitter2
) {}
/**
* 分析代码质量
*/
async analyzeCodeQuality(): Promise<ImprovementSuggestion[]> {
const suggestions: ImprovementSuggestion[] = [];
// 模拟代码质量分析
// 实际项目中可以使用ESLint、SonarQube等工具进行分析
// 示例建议
suggestions.push({
id: `code-${Date.now()}`,
type: 'code',
title: '优化代码结构',
description: '建议将重复的代码提取为公共函数,提高代码复用性和可维护性。',
severity: 'medium',
priority: 'medium',
status: 'pending',
createdAt: new Date(),
updatedAt: new Date(),
});
suggestions.push({
id: `code-${Date.now() + 1}`,
type: 'code',
title: '添加错误处理',
description: '建议在关键操作中添加更详细的错误处理,提高系统的健壮性。',
severity: 'high',
priority: 'high',
status: 'pending',
createdAt: new Date(),
updatedAt: new Date(),
});
return suggestions;
}
/**
* 分析性能瓶颈
*/
async analyzePerformance(): Promise<ImprovementSuggestion[]> {
const suggestions: ImprovementSuggestion[] = [];
const metrics = this.monitoringService.getMetrics();
// 基于监控指标分析性能瓶颈
if (metrics.memoryUsage && (metrics.memoryUsage as number) > 80) {
suggestions.push({
id: `perf-${Date.now()}`,
type: 'performance',
title: '内存使用过高',
description: '内存使用率超过80%,建议优化内存使用,考虑增加服务器内存或优化代码。',
severity: 'high',
priority: 'high',
status: 'pending',
createdAt: new Date(),
updatedAt: new Date(),
});
}
if (metrics.cpuUsage && (metrics.cpuUsage as number) > 70) {
suggestions.push({
id: `perf-${Date.now() + 1}`,
type: 'performance',
title: 'CPU使用率过高',
description: 'CPU使用率超过70%建议优化计算密集型操作考虑增加服务器CPU核心数。',
severity: 'medium',
priority: 'medium',
status: 'pending',
createdAt: new Date(),
updatedAt: new Date(),
});
}
return suggestions;
}
/**
* 分析业务流程
*/
async analyzeBusinessProcesses(): Promise<ImprovementSuggestion[]> {
const suggestions: ImprovementSuggestion[] = [];
// 模拟业务流程分析
// 实际项目中可以基于业务数据和用户行为进行分析
// 示例建议
suggestions.push({
id: `business-${Date.now()}`,
type: 'business',
title: '优化店铺绑定流程',
description: '建议简化店铺绑定流程,减少用户输入步骤,提高绑定成功率。',
severity: 'medium',
priority: 'medium',
status: 'pending',
createdAt: new Date(),
updatedAt: new Date(),
});
suggestions.push({
id: `business-${Date.now() + 1}`,
type: 'business',
title: '增加商品同步频率',
description: '建议增加商品同步频率,确保平台商品信息及时更新,提高数据准确性。',
severity: 'low',
priority: 'low',
status: 'pending',
createdAt: new Date(),
updatedAt: new Date(),
});
return suggestions;
}
/**
* 分析安全风险
*/
async analyzeSecurity(): Promise<ImprovementSuggestion[]> {
const suggestions: ImprovementSuggestion[] = [];
// 模拟安全风险分析
// 实际项目中可以使用安全扫描工具进行分析
// 示例建议
suggestions.push({
id: `security-${Date.now()}`,
type: 'security',
title: '增强授权验证',
description: '建议增强API的授权验证添加更严格的权限检查防止未授权访问。',
severity: 'high',
priority: 'high',
status: 'pending',
createdAt: new Date(),
updatedAt: new Date(),
});
suggestions.push({
id: `security-${Date.now() + 1}`,
type: 'security',
title: '加密敏感数据',
description: '建议对存储的敏感数据进行加密,提高数据安全性。',
severity: 'medium',
priority: 'medium',
status: 'pending',
createdAt: new Date(),
updatedAt: new Date(),
});
return suggestions;
}
/**
* 生成改进建议
*/
async generateImprovementSuggestions(): Promise<ImprovementSuggestion[]> {
try {
const codeSuggestions = await this.analyzeCodeQuality();
const performanceSuggestions = await this.analyzePerformance();
const businessSuggestions = await this.analyzeBusinessProcesses();
const securitySuggestions = await this.analyzeSecurity();
this.suggestions = [
...codeSuggestions,
...performanceSuggestions,
...businessSuggestions,
...securitySuggestions,
];
// 保存建议到数据库
for (const suggestion of this.suggestions) {
const aiImprovement = this.aiImprovementRepository.create({
type: suggestion.type,
title: suggestion.title,
description: suggestion.description,
severity: suggestion.severity,
priority: suggestion.priority,
status: suggestion.status,
});
await this.aiImprovementRepository.save(aiImprovement);
}
this.logger.log(`生成了 ${this.suggestions.length} 条改进建议`);
return this.suggestions;
} catch (error) {
this.logger.error('生成改进建议失败', error);
return [];
}
}
/**
* 获取所有改进建议
*/
async getImprovementSuggestions(): Promise<ImprovementSuggestion[]> {
try {
const improvements = await this.aiImprovementRepository.find();
return improvements.map(improvement => ({
id: improvement.id.toString(),
type: improvement.type,
title: improvement.title,
description: improvement.description,
severity: improvement.severity,
priority: improvement.priority,
status: improvement.status,
createdAt: improvement.createdAt,
updatedAt: improvement.updatedAt,
}));
} catch (error) {
this.logger.error('获取改进建议失败', error);
return [];
}
}
/**
* 更新建议状态
*/
async updateSuggestionStatus(id: string, status: 'implemented' | 'dismissed'): Promise<boolean> {
try {
const improvement = await this.aiImprovementRepository.findOneBy({ id: parseInt(id) });
if (!improvement) {
return false;
}
improvement.status = status;
improvement.updatedAt = new Date();
await this.aiImprovementRepository.save(improvement);
this.logger.log(`更新建议状态: ${id} -> ${status}`);
return true;
} catch (error) {
this.logger.error('更新建议状态失败', error);
return false;
}
}
/**
* 自动应用改进建议
*/
async applyImprovementSuggestions(): Promise<{ applied: number; failed: number }> {
let applied = 0;
let failed = 0;
try {
const pendingSuggestions = await this.aiImprovementRepository.find({ where: { status: 'pending' } });
for (const suggestion of pendingSuggestions) {
try {
// 模拟应用建议
// 实际项目中可以根据建议类型执行相应的改进操作
this.logger.log(`应用改进建议: ${suggestion.title}`);
// 更新建议状态为已实现
suggestion.status = 'implemented';
suggestion.updatedAt = new Date();
await this.aiImprovementRepository.save(suggestion);
applied++;
} catch (error) {
this.logger.error(`应用改进建议失败: ${suggestion.title}`, error);
failed++;
}
}
return { applied, failed };
} catch (error) {
this.logger.error('自动应用改进建议失败', error);
return { applied: 0, failed: 0 };
}
}
/**
* 执行定期优化
*/
async performRegularOptimization(): Promise<void> {
try {
this.logger.log('开始执行定期优化');
// 生成改进建议
await this.generateImprovementSuggestions();
// 自动应用改进建议
const result = await this.applyImprovementSuggestions();
this.logger.log(`定期优化完成: 应用了 ${result.applied} 条建议,失败 ${result.failed}`);
} catch (error) {
this.logger.error('执行定期优化失败', error);
}
}
}

View File

@@ -0,0 +1,168 @@
import { Injectable, Logger } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { MonitoringData } from '../../entities/MonitoringData';
import { EventEmitter2, OnEvent } from '@nestjs/event-emitter';
import { OperationAgentEvent } from '../../types/events/OperationAgentEvent';
import { StoreStatus } from '../../types/enums/StoreStatus';
interface Metrics {
[key: string]: number | string | boolean;
}
@Injectable()
export class MonitoringService {
private readonly logger = new Logger(MonitoringService.name);
private metrics: Metrics = {};
constructor(
@InjectRepository(MonitoringData) private monitoringDataRepository: Repository<MonitoringData>,
private eventEmitter: EventEmitter2
) {
// 初始化监控指标
this.initMetrics();
}
/**
* 初始化监控指标
*/
private initMetrics() {
this.metrics = {
// 业务指标
storeBindingSuccessCount: 0,
storeBindingFailedCount: 0,
productSyncCount: 0,
orderSyncCount: 0,
priceUpdateCount: 0,
activeStoresCount: 0,
totalStoresCount: 0,
// 技术指标
apiResponseTime: 0,
errorCount: 0,
warningCount: 0,
infoCount: 0,
// 系统指标
memoryUsage: 0,
cpuUsage: 0,
uptime: 0,
};
}
/**
* 记录监控数据
*/
async recordMetrics(data: Metrics): Promise<void> {
try {
const monitoringData = this.monitoringDataRepository.create({
data: JSON.stringify(data),
timestamp: new Date(),
});
await this.monitoringDataRepository.save(monitoringData);
} catch (error) {
this.logger.error('记录监控数据失败', error);
}
}
/**
* 获取监控指标
*/
getMetrics(): Metrics {
return { ...this.metrics };
}
/**
* 更新监控指标
*/
updateMetrics(key: string, value: number | string | boolean): void {
this.metrics[key] = value;
}
/**
* 增加监控指标
*/
incrementMetrics(key: string, value: number = 1): void {
if (typeof this.metrics[key] === 'number') {
this.metrics[key] = (this.metrics[key] as number) + value;
} else {
this.metrics[key] = value;
}
}
/**
* 收集系统指标
*/
collectSystemMetrics(): void {
// 收集内存使用情况
const memoryUsage = process.memoryUsage();
this.metrics.memoryUsage = Math.round((memoryUsage.heapUsed / memoryUsage.heapTotal) * 100);
// 收集CPU使用情况模拟
this.metrics.cpuUsage = Math.round(Math.random() * 50);
// 收集系统运行时间
this.metrics.uptime = Math.round(process.uptime());
}
/**
* 收集业务指标
*/
async collectBusinessMetrics(): Promise<void> {
try {
// 这里可以从数据库中获取实际的业务指标
// 例如:统计活跃店铺数量、总店铺数量等
// 模拟数据
this.metrics.activeStoresCount = 10;
this.metrics.totalStoresCount = 15;
} catch (error) {
this.logger.error('收集业务指标失败', error);
}
}
/**
* 定期收集和记录指标
*/
async collectAndRecordMetrics(): Promise<void> {
try {
this.collectSystemMetrics();
await this.collectBusinessMetrics();
await this.recordMetrics(this.metrics);
} catch (error) {
this.logger.error('收集和记录指标失败', error);
}
}
// 事件监听器
@OnEvent(OperationAgentEvent.STORE_BOUND)
handleStoreBound() {
this.incrementMetrics('storeBindingSuccessCount');
}
@OnEvent(OperationAgentEvent.STORE_BIND_FAILED)
handleStoreBindFailed() {
this.incrementMetrics('storeBindingFailedCount');
}
@OnEvent(OperationAgentEvent.PRODUCTS_SYNCED)
handleProductsSynced(event: { storeId: string; count: number }) {
this.incrementMetrics('productSyncCount', event.count);
}
@OnEvent(OperationAgentEvent.ORDERS_SYNCED)
handleOrdersSynced(event: { storeId: string; count: number }) {
this.incrementMetrics('orderSyncCount', event.count);
}
@OnEvent(OperationAgentEvent.PRODUCT_PRICE_UPDATED)
handleProductPriceUpdated() {
this.incrementMetrics('priceUpdateCount');
}
@OnEvent(OperationAgentEvent.AGENT_HEARTBEAT)
handleAgentHeartbeat() {
// 处理心跳事件
this.logger.debug('收到Operation-Agent心跳');
}
}

View File

@@ -0,0 +1,325 @@
import { Test, TestingModule } from '@nestjs/testing';
import { OperationAgentService } from './OperationAgentService';
import { getRepositoryToken } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Store } from '../../entities/Store';
import { PlatformAdapterFactory } from './adapters/PlatformAdapterFactory';
import { AmazonAdapter } from './adapters/AmazonAdapter';
import { ShopeeAdapter } from './adapters/ShopeeAdapter';
import { AliExpressAdapter } from './adapters/AliExpressAdapter';
import { TikTokAdapter } from './adapters/TikTokAdapter';
import { EbayAdapter } from './adapters/EbayAdapter';
import { GenericAdapter } from './adapters/GenericAdapter';
import { StoreBindingDto } from '../../api/dto/StoreBindingDto';
import { StoreStatus } from '../../types/enums/StoreStatus';
import { EventEmitter2 } from '@nestjs/event-emitter';
// 模拟Store实体
class MockStore {
id: string;
merchantId: string;
name: string;
platform: string;
platformShopId: string;
description: string;
status: StoreStatus;
created_at: Date;
updated_at: Date;
}
// 模拟Repository
class MockRepository {
findOne = jest.fn();
find = jest.fn();
create = jest.fn();
save = jest.fn();
findOneBy = jest.fn();
}
// 模拟平台适配器
class MockPlatformAdapter {
authorize = jest.fn();
getShopInfo = jest.fn();
getProducts = jest.fn();
getOrders = jest.fn();
updateProductPrice = jest.fn();
updateProductStock = jest.fn();
listProduct = jest.fn();
delistProduct = jest.fn();
getApiStatus = jest.fn();
}
describe('OperationAgentIntegration', () => {
let service: OperationAgentService;
let storeRepository: Repository<Store>;
let eventEmitter: EventEmitter2;
let mockAdapter: MockPlatformAdapter;
beforeEach(async () => {
mockAdapter = new MockPlatformAdapter();
const module: TestingModule = await Test.createTestingModule({
providers: [
OperationAgentService,
{
provide: getRepositoryToken(Store),
useClass: MockRepository,
},
{
provide: PlatformAdapterFactory,
useValue: {
createAdapter: jest.fn().mockReturnValue(mockAdapter),
},
},
{
provide: EventEmitter2,
useValue: {
emit: jest.fn(),
},
},
{
provide: AmazonAdapter,
useValue: new MockPlatformAdapter(),
},
{
provide: ShopeeAdapter,
useValue: new MockPlatformAdapter(),
},
{
provide: AliExpressAdapter,
useValue: new MockPlatformAdapter(),
},
{
provide: TikTokAdapter,
useValue: new MockPlatformAdapter(),
},
{
provide: EbayAdapter,
useValue: new MockPlatformAdapter(),
},
{
provide: GenericAdapter,
useValue: new MockPlatformAdapter(),
},
],
}).compile();
service = module.get<OperationAgentService>(OperationAgentService);
storeRepository = module.get<Repository<Store>>(getRepositoryToken(Store));
eventEmitter = module.get<EventEmitter2>(EventEmitter2);
});
afterEach(() => {
jest.clearAllMocks();
});
describe('完整的店铺绑定和同步流程', () => {
it('应该成功绑定店铺并同步商品和订单', async () => {
// 准备测试数据
const dto: StoreBindingDto = {
merchantId: 'merchant-1',
platform: 'amazon',
platformShopId: 'amazon-shop-1',
name: 'Test Store',
description: 'Test Store Description',
authInfo: {
accessKey: 'test-access-key',
secretKey: 'test-secret-key',
sellerId: 'test-seller-id',
marketplaceId: 'test-marketplace-id',
},
};
const store = {
id: 'store-1',
...dto,
status: StoreStatus.ACTIVE,
created_at: new Date(),
updated_at: new Date(),
};
const products = [
{
id: 'product-1',
name: 'Product 1',
sku: 'PROD-1',
price: 100,
stock: 10,
description: 'Product 1 Description',
images: ['image1.jpg'],
categories: ['Category 1'],
attributes: {},
createdAt: new Date(),
updatedAt: new Date(),
},
];
const orders = [
{
id: 'order-1',
customerId: 'customer-1',
totalAmount: 200,
status: 'shipped',
items: [
{
productId: 'product-1',
quantity: 1,
price: 200,
},
],
shippingAddress: {
name: 'Customer 1',
address: '123 Main St',
city: 'New York',
state: 'NY',
zip: '10001',
country: 'US',
},
paymentMethod: 'credit_card',
createdAt: new Date(),
updatedAt: new Date(),
},
];
// 模拟方法调用
(storeRepository.findOne as jest.Mock).mockResolvedValue(null);
(storeRepository.create as jest.Mock).mockReturnValue({
id: 'store-1',
...dto,
status: StoreStatus.PENDING,
created_at: new Date(),
updated_at: new Date(),
});
(storeRepository.save as jest.Mock).mockResolvedValueOnce({
id: 'store-1',
...dto,
status: StoreStatus.PENDING,
created_at: new Date(),
updated_at: new Date(),
}).mockResolvedValueOnce({
id: 'store-1',
...dto,
status: StoreStatus.ACTIVE,
created_at: new Date(),
updated_at: new Date(),
});
(storeRepository.findOneBy as jest.Mock).mockResolvedValue(store);
mockAdapter.authorize.mockResolvedValue(true);
mockAdapter.getShopInfo.mockResolvedValue({
id: 'amazon-shop-1',
name: 'Test Store',
description: 'Test Store Description',
status: 'active',
createdAt: new Date(),
updatedAt: new Date(),
});
mockAdapter.getProducts.mockResolvedValue(products);
mockAdapter.getOrders.mockResolvedValue(orders);
// 步骤1: 绑定店铺
const boundStore = await service.bindStore(dto);
expect(boundStore.status).toBe(StoreStatus.ACTIVE);
// 步骤2: 同步商品
const syncProductsResult = await service.syncProducts(boundStore.id);
expect(syncProductsResult.success).toBe(true);
expect(syncProductsResult.count).toBe(products.length);
// 步骤3: 同步订单
const syncOrdersResult = await service.syncOrders(boundStore.id);
expect(syncOrdersResult.success).toBe(true);
expect(syncOrdersResult.count).toBe(orders.length);
// 验证事件触发
expect(eventEmitter.emit).toHaveBeenCalledWith('operation_agent.store.bound', expect.any(Object));
expect(eventEmitter.emit).toHaveBeenCalledWith('operation_agent.products.synced', {
storeId: boundStore.id,
count: products.length,
});
expect(eventEmitter.emit).toHaveBeenCalledWith('operation_agent.orders.synced', {
storeId: boundStore.id,
count: orders.length,
});
});
});
describe('店铺状态管理流程', () => {
it('应该成功停用和重新激活店铺', async () => {
// 准备测试数据
const store = {
id: 'store-1',
merchantId: 'merchant-1',
name: 'Test Store',
platform: 'amazon',
platformShopId: 'amazon-shop-1',
status: StoreStatus.ACTIVE,
created_at: new Date(),
updated_at: new Date(),
};
const deactivatedStore = {
...store,
status: StoreStatus.INACTIVE,
};
const reactivatedStore = {
...store,
status: StoreStatus.ACTIVE,
};
// 模拟方法调用
(storeRepository.findOneBy as jest.Mock)
.mockResolvedValueOnce(store) // 停用店铺时
.mockResolvedValueOnce(deactivatedStore); // 重新激活时
(storeRepository.save as jest.Mock)
.mockResolvedValueOnce(deactivatedStore) // 停用店铺时
.mockResolvedValueOnce(reactivatedStore); // 重新激活时
// 步骤1: 停用店铺
const result1 = await service.deactivateStore(store.id);
expect(result1.status).toBe(StoreStatus.INACTIVE);
// 步骤2: 重新激活店铺
const result2 = await service.reactivateStore(store.id);
expect(result2.status).toBe(StoreStatus.ACTIVE);
// 验证事件触发
expect(eventEmitter.emit).toHaveBeenCalledWith('operation_agent.store.deactivated', deactivatedStore);
expect(eventEmitter.emit).toHaveBeenCalledWith('operation_agent.store.activated', reactivatedStore);
});
});
describe('商品价格更新流程', () => {
it('应该成功更新商品价格', async () => {
// 准备测试数据
const store = {
id: 'store-1',
merchantId: 'merchant-1',
name: 'Test Store',
platform: 'amazon',
platformShopId: 'amazon-shop-1',
status: StoreStatus.ACTIVE,
created_at: new Date(),
updated_at: new Date(),
};
const productId = 'product-1';
const newPrice = 150;
// 模拟方法调用
(storeRepository.findOneBy as jest.Mock).mockResolvedValue(store);
mockAdapter.updateProductPrice.mockResolvedValue(true);
// 执行测试
const result = await service.updateProductPrice(store.id, productId, newPrice);
expect(result).toBe(true);
// 验证事件触发
expect(eventEmitter.emit).toHaveBeenCalledWith('operation_agent.product.price.updated', {
storeId: store.id,
productId,
price: newPrice,
});
});
});
});

View File

@@ -0,0 +1,608 @@
import { Test, TestingModule } from '@nestjs/testing';
import { OperationAgentService } from './OperationAgentService';
import { getRepositoryToken } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Store } from '../../entities/Store';
import { PlatformAdapterFactory } from './adapters/PlatformAdapterFactory';
import { AmazonAdapter } from './adapters/AmazonAdapter';
import { ShopeeAdapter } from './adapters/ShopeeAdapter';
import { AliExpressAdapter } from './adapters/AliExpressAdapter';
import { TikTokAdapter } from './adapters/TikTokAdapter';
import { EbayAdapter } from './adapters/EbayAdapter';
import { GenericAdapter } from './adapters/GenericAdapter';
import { StoreBindingDto } from '../../api/dto/StoreBindingDto';
import { StoreStatus } from '../../types/enums/StoreStatus';
import { EventEmitter2 } from '@nestjs/event-emitter';
// 模拟Store实体
class MockStore {
id: string;
merchantId: string;
name: string;
platform: string;
platformShopId: string;
description: string;
status: StoreStatus;
created_at: Date;
updated_at: Date;
}
// 模拟Repository
class MockRepository {
findOne = jest.fn();
find = jest.fn();
create = jest.fn();
save = jest.fn();
findOneBy = jest.fn();
}
// 模拟平台适配器
class MockPlatformAdapter {
authorize = jest.fn();
getShopInfo = jest.fn();
getProducts = jest.fn();
getOrders = jest.fn();
updateProductPrice = jest.fn();
updateProductStock = jest.fn();
listProduct = jest.fn();
delistProduct = jest.fn();
getApiStatus = jest.fn();
}
// 模拟平台适配器工厂
class MockPlatformAdapterFactory {
createAdapter = jest.fn();
getSupportedPlatforms = jest.fn();
isPlatformSupported = jest.fn();
}
describe('OperationAgentService', () => {
let service: OperationAgentService;
let storeRepository: Repository<Store>;
let platformAdapterFactory: PlatformAdapterFactory;
let eventEmitter: EventEmitter2;
let mockAdapter: MockPlatformAdapter;
beforeEach(async () => {
mockAdapter = new MockPlatformAdapter();
const module: TestingModule = await Test.createTestingModule({
providers: [
OperationAgentService,
{
provide: getRepositoryToken(Store),
useClass: MockRepository,
},
{
provide: PlatformAdapterFactory,
useValue: {
createAdapter: jest.fn().mockReturnValue(mockAdapter),
},
},
{
provide: EventEmitter2,
useValue: {
emit: jest.fn(),
},
},
{
provide: AmazonAdapter,
useValue: new MockPlatformAdapter(),
},
{
provide: ShopeeAdapter,
useValue: new MockPlatformAdapter(),
},
{
provide: AliExpressAdapter,
useValue: new MockPlatformAdapter(),
},
{
provide: TikTokAdapter,
useValue: new MockPlatformAdapter(),
},
{
provide: EbayAdapter,
useValue: new MockPlatformAdapter(),
},
{
provide: GenericAdapter,
useValue: new MockPlatformAdapter(),
},
],
}).compile();
service = module.get<OperationAgentService>(OperationAgentService);
storeRepository = module.get<Repository<Store>>(getRepositoryToken(Store));
platformAdapterFactory = module.get<PlatformAdapterFactory>(PlatformAdapterFactory);
eventEmitter = module.get<EventEmitter2>(EventEmitter2);
});
afterEach(() => {
jest.clearAllMocks();
});
describe('bindStore', () => {
it('should bind a new store successfully', async () => {
// 准备测试数据
const dto: StoreBindingDto = {
merchantId: 'merchant-1',
platform: 'amazon',
platformShopId: 'amazon-shop-1',
name: 'Test Store',
description: 'Test Store Description',
authInfo: {
accessKey: 'test-access-key',
secretKey: 'test-secret-key',
sellerId: 'test-seller-id',
marketplaceId: 'test-marketplace-id',
},
};
// 模拟方法调用
(storeRepository.findOne as jest.Mock).mockResolvedValue(null);
(storeRepository.create as jest.Mock).mockReturnValue({
id: 'store-1',
...dto,
status: StoreStatus.PENDING,
created_at: new Date(),
updated_at: new Date(),
});
(storeRepository.save as jest.Mock).mockResolvedValueOnce({
id: 'store-1',
...dto,
status: StoreStatus.PENDING,
created_at: new Date(),
updated_at: new Date(),
}).mockResolvedValueOnce({
id: 'store-1',
...dto,
status: StoreStatus.ACTIVE,
created_at: new Date(),
updated_at: new Date(),
});
mockAdapter.authorize.mockResolvedValue(true);
mockAdapter.getShopInfo.mockResolvedValue({
id: 'amazon-shop-1',
name: 'Test Store',
description: 'Test Store Description',
status: 'active',
createdAt: new Date(),
updatedAt: new Date(),
});
// 执行测试
const result = await service.bindStore(dto);
// 验证结果
expect(result.status).toBe(StoreStatus.ACTIVE);
expect(storeRepository.findOne).toHaveBeenCalledWith({
where: {
merchantId: dto.merchantId,
platform: dto.platform,
platformShopId: dto.platformShopId,
},
});
expect(storeRepository.create).toHaveBeenCalled();
expect(storeRepository.save).toHaveBeenCalledTimes(2);
expect(mockAdapter.authorize).toHaveBeenCalledWith(dto.authInfo);
expect(mockAdapter.getShopInfo).toHaveBeenCalled();
expect(eventEmitter.emit).toHaveBeenCalledWith('operation_agent.store.bound', expect.any(Object));
});
it('should return existing store if already bound', async () => {
// 准备测试数据
const dto: StoreBindingDto = {
merchantId: 'merchant-1',
platform: 'amazon',
platformShopId: 'amazon-shop-1',
name: 'Test Store',
description: 'Test Store Description',
authInfo: {
accessKey: 'test-access-key',
secretKey: 'test-secret-key',
sellerId: 'test-seller-id',
marketplaceId: 'test-marketplace-id',
},
};
const existingStore = {
id: 'store-1',
...dto,
status: StoreStatus.ACTIVE,
created_at: new Date(),
updated_at: new Date(),
};
// 模拟方法调用
(storeRepository.findOne as jest.Mock).mockResolvedValue(existingStore);
// 执行测试
const result = await service.bindStore(dto);
// 验证结果
expect(result).toEqual(existingStore);
expect(storeRepository.findOne).toHaveBeenCalledWith({
where: {
merchantId: dto.merchantId,
platform: dto.platform,
platformShopId: dto.platformShopId,
},
});
expect(storeRepository.create).not.toHaveBeenCalled();
});
it('should handle binding failure', async () => {
// 准备测试数据
const dto: StoreBindingDto = {
merchantId: 'merchant-1',
platform: 'amazon',
platformShopId: 'amazon-shop-1',
name: 'Test Store',
description: 'Test Store Description',
authInfo: {
accessKey: 'test-access-key',
secretKey: 'test-secret-key',
sellerId: 'test-seller-id',
marketplaceId: 'test-marketplace-id',
},
};
// 模拟方法调用
(storeRepository.findOne as jest.Mock).mockResolvedValue(null);
(storeRepository.create as jest.Mock).mockReturnValue({
id: 'store-1',
...dto,
status: StoreStatus.PENDING,
created_at: new Date(),
updated_at: new Date(),
});
(storeRepository.save as jest.Mock).mockResolvedValueOnce({
id: 'store-1',
...dto,
status: StoreStatus.PENDING,
created_at: new Date(),
updated_at: new Date(),
}).mockResolvedValueOnce({
id: 'store-1',
...dto,
status: StoreStatus.INACTIVE,
created_at: new Date(),
updated_at: new Date(),
});
mockAdapter.authorize.mockRejectedValue(new Error('Authorization failed'));
// 执行测试
await expect(service.bindStore(dto)).rejects.toThrow('Authorization failed');
// 验证结果
expect(storeRepository.save).toHaveBeenCalledTimes(2);
expect(eventEmitter.emit).toHaveBeenCalledWith('operation_agent.store.bind.failed', expect.any(Object));
});
});
describe('syncProducts', () => {
it('should sync products successfully', async () => {
// 准备测试数据
const storeId = 'store-1';
const store = {
id: storeId,
merchantId: 'merchant-1',
name: 'Test Store',
platform: 'amazon',
platformShopId: 'amazon-shop-1',
status: StoreStatus.ACTIVE,
created_at: new Date(),
updated_at: new Date(),
};
const products = [
{
id: 'product-1',
name: 'Product 1',
sku: 'PROD-1',
price: 100,
stock: 10,
description: 'Product 1 Description',
images: ['image1.jpg'],
categories: ['Category 1'],
attributes: {},
createdAt: new Date(),
updatedAt: new Date(),
},
];
// 模拟方法调用
(storeRepository.findOneBy as jest.Mock).mockResolvedValue(store);
mockAdapter.getProducts.mockResolvedValue(products);
// 执行测试
const result = await service.syncProducts(storeId);
// 验证结果
expect(result.success).toBe(true);
expect(result.count).toBe(products.length);
expect(storeRepository.findOneBy).toHaveBeenCalledWith({ id: storeId });
expect(mockAdapter.getProducts).toHaveBeenCalled();
expect(eventEmitter.emit).toHaveBeenCalledWith('operation_agent.products.synced', {
storeId,
count: products.length,
});
});
it('should throw error if store not found', async () => {
// 准备测试数据
const storeId = 'store-1';
// 模拟方法调用
(storeRepository.findOneBy as jest.Mock).mockResolvedValue(null);
// 执行测试
await expect(service.syncProducts(storeId)).rejects.toThrow('店铺不存在');
// 验证结果
expect(storeRepository.findOneBy).toHaveBeenCalledWith({ id: storeId });
});
it('should throw error if store status is not active', async () => {
// 准备测试数据
const storeId = 'store-1';
const store = {
id: storeId,
merchantId: 'merchant-1',
name: 'Test Store',
platform: 'amazon',
platformShopId: 'amazon-shop-1',
status: StoreStatus.INACTIVE,
created_at: new Date(),
updated_at: new Date(),
};
// 模拟方法调用
(storeRepository.findOneBy as jest.Mock).mockResolvedValue(store);
// 执行测试
await expect(service.syncProducts(storeId)).rejects.toThrow('店铺状态异常,无法同步商品');
// 验证结果
expect(storeRepository.findOneBy).toHaveBeenCalledWith({ id: storeId });
});
});
describe('syncOrders', () => {
it('should sync orders successfully', async () => {
// 准备测试数据
const storeId = 'store-1';
const store = {
id: storeId,
merchantId: 'merchant-1',
name: 'Test Store',
platform: 'amazon',
platformShopId: 'amazon-shop-1',
status: StoreStatus.ACTIVE,
created_at: new Date(),
updated_at: new Date(),
};
const orders = [
{
id: 'order-1',
customerId: 'customer-1',
totalAmount: 200,
status: 'shipped',
items: [
{
productId: 'product-1',
quantity: 1,
price: 200,
},
],
shippingAddress: {
name: 'Customer 1',
address: '123 Main St',
city: 'New York',
state: 'NY',
zip: '10001',
country: 'US',
},
paymentMethod: 'credit_card',
createdAt: new Date(),
updatedAt: new Date(),
},
];
// 模拟方法调用
(storeRepository.findOneBy as jest.Mock).mockResolvedValue(store);
mockAdapter.getOrders.mockResolvedValue(orders);
// 执行测试
const result = await service.syncOrders(storeId);
// 验证结果
expect(result.success).toBe(true);
expect(result.count).toBe(orders.length);
expect(storeRepository.findOneBy).toHaveBeenCalledWith({ id: storeId });
expect(mockAdapter.getOrders).toHaveBeenCalled();
expect(eventEmitter.emit).toHaveBeenCalledWith('operation_agent.orders.synced', {
storeId,
count: orders.length,
});
});
});
describe('updateProductPrice', () => {
it('should update product price successfully', async () => {
// 准备测试数据
const storeId = 'store-1';
const productId = 'product-1';
const price = 150;
const store = {
id: storeId,
merchantId: 'merchant-1',
name: 'Test Store',
platform: 'amazon',
platformShopId: 'amazon-shop-1',
status: StoreStatus.ACTIVE,
created_at: new Date(),
updated_at: new Date(),
};
// 模拟方法调用
(storeRepository.findOneBy as jest.Mock).mockResolvedValue(store);
mockAdapter.updateProductPrice.mockResolvedValue(true);
// 执行测试
const result = await service.updateProductPrice(storeId, productId, price);
// 验证结果
expect(result).toBe(true);
expect(storeRepository.findOneBy).toHaveBeenCalledWith({ id: storeId });
expect(mockAdapter.updateProductPrice).toHaveBeenCalledWith(productId, price);
expect(eventEmitter.emit).toHaveBeenCalledWith('operation_agent.product.price.updated', {
storeId,
productId,
price,
});
});
});
describe('getStores', () => {
it('should get stores for a merchant', async () => {
// 准备测试数据
const merchantId = 'merchant-1';
const stores = [
{
id: 'store-1',
merchantId: merchantId,
name: 'Store 1',
platform: 'amazon',
platformShopId: 'amazon-shop-1',
status: StoreStatus.ACTIVE,
created_at: new Date(),
updated_at: new Date(),
},
];
// 模拟方法调用
(storeRepository.find as jest.Mock).mockResolvedValue(stores);
// 执行测试
const result = await service.getStores(merchantId);
// 验证结果
expect(result).toEqual(stores);
expect(storeRepository.find).toHaveBeenCalledWith({ where: { merchantId } });
});
});
describe('getStore', () => {
it('should get store by id', async () => {
// 准备测试数据
const storeId = 'store-1';
const store = {
id: storeId,
merchantId: 'merchant-1',
name: 'Test Store',
platform: 'amazon',
platformShopId: 'amazon-shop-1',
status: StoreStatus.ACTIVE,
created_at: new Date(),
updated_at: new Date(),
};
// 模拟方法调用
(storeRepository.findOneBy as jest.Mock).mockResolvedValue(store);
// 执行测试
const result = await service.getStore(storeId);
// 验证结果
expect(result).toEqual(store);
expect(storeRepository.findOneBy).toHaveBeenCalledWith({ id: storeId });
});
it('should throw error if store not found', async () => {
// 准备测试数据
const storeId = 'store-1';
// 模拟方法调用
(storeRepository.findOneBy as jest.Mock).mockResolvedValue(null);
// 执行测试
await expect(service.getStore(storeId)).rejects.toThrow('店铺不存在');
// 验证结果
expect(storeRepository.findOneBy).toHaveBeenCalledWith({ id: storeId });
});
});
describe('deactivateStore', () => {
it('should deactivate store successfully', async () => {
// 准备测试数据
const storeId = 'store-1';
const store = {
id: storeId,
merchantId: 'merchant-1',
name: 'Test Store',
platform: 'amazon',
platformShopId: 'amazon-shop-1',
status: StoreStatus.ACTIVE,
created_at: new Date(),
updated_at: new Date(),
};
const updatedStore = {
...store,
status: StoreStatus.INACTIVE,
};
// 模拟方法调用
(storeRepository.findOneBy as jest.Mock).mockResolvedValue(store);
(storeRepository.save as jest.Mock).mockResolvedValue(updatedStore);
// 执行测试
const result = await service.deactivateStore(storeId);
// 验证结果
expect(result.status).toBe(StoreStatus.INACTIVE);
expect(storeRepository.findOneBy).toHaveBeenCalledWith({ id: storeId });
expect(storeRepository.save).toHaveBeenCalledWith(updatedStore);
expect(eventEmitter.emit).toHaveBeenCalledWith('operation_agent.store.deactivated', updatedStore);
});
});
describe('reactivateStore', () => {
it('should reactivate store successfully', async () => {
// 准备测试数据
const storeId = 'store-1';
const store = {
id: storeId,
merchantId: 'merchant-1',
name: 'Test Store',
platform: 'amazon',
platformShopId: 'amazon-shop-1',
status: StoreStatus.INACTIVE,
created_at: new Date(),
updated_at: new Date(),
};
const updatedStore = {
...store,
status: StoreStatus.ACTIVE,
};
// 模拟方法调用
(storeRepository.findOneBy as jest.Mock).mockResolvedValue(store);
(storeRepository.save as jest.Mock).mockResolvedValue(updatedStore);
// 执行测试
const result = await service.reactivateStore(storeId);
// 验证结果
expect(result.status).toBe(StoreStatus.ACTIVE);
expect(storeRepository.findOneBy).toHaveBeenCalledWith({ id: storeId });
expect(storeRepository.save).toHaveBeenCalledWith(updatedStore);
expect(eventEmitter.emit).toHaveBeenCalledWith('operation_agent.store.activated', updatedStore);
});
});
});

View File

@@ -0,0 +1,311 @@
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Store } from '../../entities/Store';
import { PlatformAdapterFactory } from './adapters/PlatformAdapterFactory';
import { IPlatformAdapter } from './adapters/IPlatformAdapter';
import { StoreBindingDto } from '../../api/dto/StoreBindingDto';
import { StoreStatus } from '../../types/enums/StoreStatus';
import { OperationAgentEvent } from '../../types/events/OperationAgentEvent';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { Logger } from '@nestjs/common';
@Injectable()
export class OperationAgentService {
private readonly logger = new Logger(OperationAgentService.name);
constructor(
@InjectRepository(Store) private storeRepository: Repository<Store>,
private platformAdapterFactory: PlatformAdapterFactory,
private eventEmitter: EventEmitter2
) {}
/**
* 绑定店铺
* @param dto 店铺绑定信息
* @returns 绑定结果
*/
async bindStore(dto: StoreBindingDto): Promise<Store> {
this.logger.log(`开始绑定店铺: ${dto.platform} - ${dto.platformShopId}`);
// 检查店铺是否已存在
const existingStore = await this.storeRepository.findOne({
where: {
merchantId: dto.merchantId,
platform: dto.platform,
platformShopId: dto.platformShopId
}
});
if (existingStore) {
this.logger.warn(`店铺已存在: ${dto.platform} - ${dto.platformShopId}`);
return existingStore;
}
// 创建新店铺记录
const store = this.storeRepository.create({
merchantId: dto.merchantId,
name: dto.name,
platform: dto.platform,
platformShopId: dto.platformShopId,
description: dto.description,
status: StoreStatus.PENDING
});
try {
// 保存店铺记录
await this.storeRepository.save(store);
// 获取平台适配器
const adapter = this.platformAdapterFactory.createAdapter(dto.platform);
// 执行授权
await adapter.authorize(dto.authInfo);
// 同步店铺信息
const shopInfo = await adapter.getShopInfo();
store.name = shopInfo.name;
store.description = shopInfo.description;
// 更新店铺状态
store.status = StoreStatus.ACTIVE;
await this.storeRepository.save(store);
// 触发店铺绑定成功事件
this.eventEmitter.emit(OperationAgentEvent.STORE_BOUND, store);
this.logger.log(`店铺绑定成功: ${dto.platform} - ${dto.platformShopId}`);
return store;
} catch (error) {
this.logger.error(`店铺绑定失败: ${error.message}`, error.stack);
// 更新店铺状态为失败
store.status = StoreStatus.INACTIVE;
await this.storeRepository.save(store);
// 触发店铺绑定失败事件
this.eventEmitter.emit(OperationAgentEvent.STORE_BIND_FAILED, {
storeId: store.id,
error: error.message
});
throw error;
}
}
/**
* 同步店铺商品
* @param storeId 店铺ID
* @returns 同步结果
*/
async syncProducts(storeId: string): Promise<{ success: boolean; count: number }> {
this.logger.log(`开始同步店铺商品: ${storeId}`);
const store = await this.storeRepository.findOneBy({ id: storeId });
if (!store) {
throw new Error('店铺不存在');
}
if (store.status !== StoreStatus.ACTIVE) {
throw new Error('店铺状态异常,无法同步商品');
}
try {
const adapter = this.platformAdapterFactory.createAdapter(store.platform);
const products = await adapter.getProducts();
// 处理商品同步逻辑
// TODO: 实现商品同步到数据库
this.logger.log(`商品同步完成: ${storeId}, 同步商品数: ${products.length}`);
// 触发商品同步成功事件
this.eventEmitter.emit(OperationAgentEvent.PRODUCTS_SYNCED, {
storeId,
count: products.length
});
return {
success: true,
count: products.length
};
} catch (error) {
this.logger.error(`商品同步失败: ${error.message}`, error.stack);
// 触发商品同步失败事件
this.eventEmitter.emit(OperationAgentEvent.PRODUCTS_SYNC_FAILED, {
storeId,
error: error.message
});
throw error;
}
}
/**
* 同步店铺订单
* @param storeId 店铺ID
* @returns 同步结果
*/
async syncOrders(storeId: string): Promise<{ success: boolean; count: number }> {
this.logger.log(`开始同步店铺订单: ${storeId}`);
const store = await this.storeRepository.findOneBy({ id: storeId });
if (!store) {
throw new Error('店铺不存在');
}
if (store.status !== StoreStatus.ACTIVE) {
throw new Error('店铺状态异常,无法同步订单');
}
try {
const adapter = this.platformAdapterFactory.createAdapter(store.platform);
const orders = await adapter.getOrders();
// 处理订单同步逻辑
// TODO: 实现订单同步到数据库
this.logger.log(`订单同步完成: ${storeId}, 同步订单数: ${orders.length}`);
// 触发订单同步成功事件
this.eventEmitter.emit(OperationAgentEvent.ORDERS_SYNCED, {
storeId,
count: orders.length
});
return {
success: true,
count: orders.length
};
} catch (error) {
this.logger.error(`订单同步失败: ${error.message}`, error.stack);
// 触发订单同步失败事件
this.eventEmitter.emit(OperationAgentEvent.ORDERS_SYNC_FAILED, {
storeId,
error: error.message
});
throw error;
}
}
/**
* 更新商品价格
* @param storeId 店铺ID
* @param productId 商品ID
* @param price 新价格
* @returns 更新结果
*/
async updateProductPrice(storeId: string, productId: string, price: number): Promise<boolean> {
this.logger.log(`开始更新商品价格: ${storeId} - ${productId} - ${price}`);
const store = await this.storeRepository.findOneBy({ id: storeId });
if (!store) {
throw new Error('店铺不存在');
}
if (store.status !== StoreStatus.ACTIVE) {
throw new Error('店铺状态异常,无法更新商品价格');
}
try {
const adapter = this.platformAdapterFactory.createAdapter(store.platform);
await adapter.updateProductPrice(productId, price);
this.logger.log(`商品价格更新成功: ${storeId} - ${productId} - ${price}`);
// 触发商品价格更新成功事件
this.eventEmitter.emit(OperationAgentEvent.PRODUCT_PRICE_UPDATED, {
storeId,
productId,
price
});
return true;
} catch (error) {
this.logger.error(`商品价格更新失败: ${error.message}`, error.stack);
// 触发商品价格更新失败事件
this.eventEmitter.emit(OperationAgentEvent.PRODUCT_PRICE_UPDATE_FAILED, {
storeId,
productId,
price,
error: error.message
});
throw error;
}
}
/**
* 获取店铺列表
* @param merchantId 商户ID
* @returns 店铺列表
*/
async getStores(merchantId: string): Promise<Store[]> {
this.logger.log(`获取商户店铺列表: ${merchantId}`);
return this.storeRepository.find({ where: { merchantId } });
}
/**
* 获取店铺详情
* @param storeId 店铺ID
* @returns 店铺详情
*/
async getStore(storeId: string): Promise<Store> {
this.logger.log(`获取店铺详情: ${storeId}`);
const store = await this.storeRepository.findOneBy({ id: storeId });
if (!store) {
throw new Error('店铺不存在');
}
return store;
}
/**
* 停用店铺
* @param storeId 店铺ID
* @returns 操作结果
*/
async deactivateStore(storeId: string): Promise<Store> {
this.logger.log(`停用店铺: ${storeId}`);
const store = await this.storeRepository.findOneBy({ id: storeId });
if (!store) {
throw new Error('店铺不存在');
}
store.status = StoreStatus.INACTIVE;
await this.storeRepository.save(store);
// 触发店铺停用事件
this.eventEmitter.emit(OperationAgentEvent.STORE_DEACTIVATED, store);
this.logger.log(`店铺已停用: ${storeId}`);
return store;
}
/**
* 重新激活店铺
* @param storeId 店铺ID
* @returns 操作结果
*/
async reactivateStore(storeId: string): Promise<Store> {
this.logger.log(`重新激活店铺: ${storeId}`);
const store = await this.storeRepository.findOneBy({ id: storeId });
if (!store) {
throw new Error('店铺不存在');
}
store.status = StoreStatus.ACTIVE;
await this.storeRepository.save(store);
// 触发店铺激活事件
this.eventEmitter.emit(OperationAgentEvent.STORE_ACTIVATED, store);
this.logger.log(`店铺已重新激活: ${storeId}`);
return store;
}
}

View File

@@ -0,0 +1,167 @@
import { Injectable } from '@nestjs/common';
import { IPlatformAdapter } from './IPlatformAdapter';
import { Product } from '../../../types/models/Product';
import { Order } from '../../../types/models/Order';
import { ShopInfo } from '../../../types/models/ShopInfo';
import { Logger } from '@nestjs/common';
@Injectable()
export class AliExpressAdapter implements IPlatformAdapter {
private readonly logger = new Logger(AliExpressAdapter.name);
private authToken: string | null = null;
async authorize(authInfo: Record<string, any>): Promise<boolean> {
this.logger.log('AliExpress授权开始');
try {
// 模拟AliExpress授权过程
const { appKey, appSecret, accessToken } = authInfo;
if (!appKey || !appSecret || !accessToken) {
throw new Error('缺少必要的授权信息');
}
// 模拟授权成功
this.authToken = 'aliexpress_auth_token_' + Date.now();
this.logger.log('AliExpress授权成功');
return true;
} catch (error) {
this.logger.error('AliExpress授权失败', error);
throw error;
}
}
async getShopInfo(): Promise<ShopInfo> {
this.logger.log('获取AliExpress店铺信息');
if (!this.authToken) {
throw new Error('未授权');
}
// 模拟获取店铺信息
return {
id: 'aliexpress_shop_123',
name: 'Test AliExpress Store',
description: 'Test AliExpress Store Description',
status: 'active',
createdAt: new Date(),
updatedAt: new Date()
};
}
async getProducts(limit: number = 10, offset: number = 0): Promise<Product[]> {
this.logger.log(`获取AliExpress商品列表limit: ${limit}, offset: ${offset}`);
if (!this.authToken) {
throw new Error('未授权');
}
// 模拟获取商品列表
const products: Product[] = [];
for (let i = 0; i < limit; i++) {
products.push({
id: `aliexpress_product_${offset + i + 1}`,
name: `AliExpress Product ${offset + i + 1}`,
sku: `ALI-${offset + i + 1}`,
price: 50 + (offset + i) * 5,
stock: 200,
description: `AliExpress Product ${offset + i + 1} Description`,
images: [`https://example.com/aliexpress_image${offset + i + 1}.jpg`],
categories: ['Electronics', 'Home & Garden'],
attributes: {
brand: 'AliExpress Brand',
model: `Model ${offset + i + 1}`
},
createdAt: new Date(),
updatedAt: new Date()
});
}
return products;
}
async getOrders(limit: number = 10, offset: number = 0): Promise<Order[]> {
this.logger.log(`获取AliExpress订单列表limit: ${limit}, offset: ${offset}`);
if (!this.authToken) {
throw new Error('未授权');
}
// 模拟获取订单列表
const orders: Order[] = [];
for (let i = 0; i < limit; i++) {
orders.push({
id: `aliexpress_order_${offset + i + 1}`,
customerId: `customer_${offset + i + 1}`,
totalAmount: 100 + (offset + i) * 20,
status: 'pending',
items: [
{
productId: `aliexpress_product_${offset + i + 1}`,
quantity: 1,
price: 80 + (offset + i) * 5
}
],
shippingAddress: {
name: `Customer ${offset + i + 1}`,
address: `789 China St`,
city: 'Hangzhou',
state: 'Zhejiang',
zip: '310000',
country: 'CN'
},
paymentMethod: 'alipay',
createdAt: new Date(),
updatedAt: new Date()
});
}
return orders;
}
async updateProductPrice(productId: string, price: number): Promise<boolean> {
this.logger.log(`更新AliExpress商品价格: ${productId}, 价格: ${price}`);
if (!this.authToken) {
throw new Error('未授权');
}
// 模拟更新商品价格
this.logger.log(`AliExpress商品价格更新成功: ${productId}, 价格: ${price}`);
return true;
}
async updateProductStock(productId: string, stock: number): Promise<boolean> {
this.logger.log(`更新AliExpress商品库存: ${productId}, 库存: ${stock}`);
if (!this.authToken) {
throw new Error('未授权');
}
// 模拟更新商品库存
this.logger.log(`AliExpress商品库存更新成功: ${productId}, 库存: ${stock}`);
return true;
}
async listProduct(product: Product): Promise<string> {
this.logger.log(`上架AliExpress商品: ${product.name}`);
if (!this.authToken) {
throw new Error('未授权');
}
// 模拟上架商品
const productId = `aliexpress_product_${Date.now()}`;
this.logger.log(`AliExpress商品上架成功商品ID: ${productId}`);
return productId;
}
async delistProduct(productId: string): Promise<boolean> {
this.logger.log(`下架AliExpress商品: ${productId}`);
if (!this.authToken) {
throw new Error('未授权');
}
// 模拟下架商品
this.logger.log(`AliExpress商品下架成功: ${productId}`);
return true;
}
async getApiStatus(): Promise<{ status: string; timestamp: number }> {
this.logger.log('获取AliExpress API状态');
return {
status: 'healthy',
timestamp: Date.now()
};
}
}

View File

@@ -0,0 +1,167 @@
import { Injectable } from '@nestjs/common';
import { IPlatformAdapter } from './IPlatformAdapter';
import { Product } from '../../../types/models/Product';
import { Order } from '../../../types/models/Order';
import { ShopInfo } from '../../../types/models/ShopInfo';
import { Logger } from '@nestjs/common';
@Injectable()
export class AmazonAdapter implements IPlatformAdapter {
private readonly logger = new Logger(AmazonAdapter.name);
private authToken: string | null = null;
async authorize(authInfo: Record<string, any>): Promise<boolean> {
this.logger.log('Amazon授权开始');
try {
// 模拟Amazon授权过程
const { accessKey, secretKey, sellerId, marketplaceId } = authInfo;
if (!accessKey || !secretKey || !sellerId || !marketplaceId) {
throw new Error('缺少必要的授权信息');
}
// 模拟授权成功
this.authToken = 'amazon_auth_token_' + Date.now();
this.logger.log('Amazon授权成功');
return true;
} catch (error) {
this.logger.error('Amazon授权失败', error);
throw error;
}
}
async getShopInfo(): Promise<ShopInfo> {
this.logger.log('获取Amazon店铺信息');
if (!this.authToken) {
throw new Error('未授权');
}
// 模拟获取店铺信息
return {
id: 'amazon_shop_123',
name: 'Test Amazon Store',
description: 'Test Amazon Store Description',
status: 'active',
createdAt: new Date(),
updatedAt: new Date()
};
}
async getProducts(limit: number = 10, offset: number = 0): Promise<Product[]> {
this.logger.log(`获取Amazon商品列表limit: ${limit}, offset: ${offset}`);
if (!this.authToken) {
throw new Error('未授权');
}
// 模拟获取商品列表
const products: Product[] = [];
for (let i = 0; i < limit; i++) {
products.push({
id: `amazon_product_${offset + i + 1}`,
name: `Amazon Product ${offset + i + 1}`,
sku: `AMZ-${offset + i + 1}`,
price: 100 + (offset + i) * 10,
stock: 100,
description: `Amazon Product ${offset + i + 1} Description`,
images: [`https://example.com/image${offset + i + 1}.jpg`],
categories: ['Electronics', 'Gadgets'],
attributes: {
brand: 'Amazon Brand',
model: `Model ${offset + i + 1}`
},
createdAt: new Date(),
updatedAt: new Date()
});
}
return products;
}
async getOrders(limit: number = 10, offset: number = 0): Promise<Order[]> {
this.logger.log(`获取Amazon订单列表limit: ${limit}, offset: ${offset}`);
if (!this.authToken) {
throw new Error('未授权');
}
// 模拟获取订单列表
const orders: Order[] = [];
for (let i = 0; i < limit; i++) {
orders.push({
id: `amazon_order_${offset + i + 1}`,
customerId: `customer_${offset + i + 1}`,
totalAmount: 200 + (offset + i) * 50,
status: 'shipped',
items: [
{
productId: `amazon_product_${offset + i + 1}`,
quantity: 1,
price: 150 + (offset + i) * 10
}
],
shippingAddress: {
name: `Customer ${offset + i + 1}`,
address: `123 Main St`,
city: 'New York',
state: 'NY',
zip: '10001',
country: 'US'
},
paymentMethod: 'credit_card',
createdAt: new Date(),
updatedAt: new Date()
});
}
return orders;
}
async updateProductPrice(productId: string, price: number): Promise<boolean> {
this.logger.log(`更新Amazon商品价格: ${productId}, 价格: ${price}`);
if (!this.authToken) {
throw new Error('未授权');
}
// 模拟更新商品价格
this.logger.log(`Amazon商品价格更新成功: ${productId}, 价格: ${price}`);
return true;
}
async updateProductStock(productId: string, stock: number): Promise<boolean> {
this.logger.log(`更新Amazon商品库存: ${productId}, 库存: ${stock}`);
if (!this.authToken) {
throw new Error('未授权');
}
// 模拟更新商品库存
this.logger.log(`Amazon商品库存更新成功: ${productId}, 库存: ${stock}`);
return true;
}
async listProduct(product: Product): Promise<string> {
this.logger.log(`上架Amazon商品: ${product.name}`);
if (!this.authToken) {
throw new Error('未授权');
}
// 模拟上架商品
const productId = `amazon_product_${Date.now()}`;
this.logger.log(`Amazon商品上架成功商品ID: ${productId}`);
return productId;
}
async delistProduct(productId: string): Promise<boolean> {
this.logger.log(`下架Amazon商品: ${productId}`);
if (!this.authToken) {
throw new Error('未授权');
}
// 模拟下架商品
this.logger.log(`Amazon商品下架成功: ${productId}`);
return true;
}
async getApiStatus(): Promise<{ status: string; timestamp: number }> {
this.logger.log('获取Amazon API状态');
return {
status: 'healthy',
timestamp: Date.now()
};
}
}

View File

@@ -0,0 +1,167 @@
import { Injectable } from '@nestjs/common';
import { IPlatformAdapter } from './IPlatformAdapter';
import { Product } from '../../../types/models/Product';
import { Order } from '../../../types/models/Order';
import { ShopInfo } from '../../../types/models/ShopInfo';
import { Logger } from '@nestjs/common';
@Injectable()
export class EbayAdapter implements IPlatformAdapter {
private readonly logger = new Logger(EbayAdapter.name);
private authToken: string | null = null;
async authorize(authInfo: Record<string, any>): Promise<boolean> {
this.logger.log('Ebay授权开始');
try {
// 模拟Ebay授权过程
const { clientId, clientSecret, refreshToken } = authInfo;
if (!clientId || !clientSecret || !refreshToken) {
throw new Error('缺少必要的授权信息');
}
// 模拟授权成功
this.authToken = 'ebay_auth_token_' + Date.now();
this.logger.log('Ebay授权成功');
return true;
} catch (error) {
this.logger.error('Ebay授权失败', error);
throw error;
}
}
async getShopInfo(): Promise<ShopInfo> {
this.logger.log('获取Ebay店铺信息');
if (!this.authToken) {
throw new Error('未授权');
}
// 模拟获取店铺信息
return {
id: 'ebay_shop_123',
name: 'Test Ebay Store',
description: 'Test Ebay Store Description',
status: 'active',
createdAt: new Date(),
updatedAt: new Date()
};
}
async getProducts(limit: number = 10, offset: number = 0): Promise<Product[]> {
this.logger.log(`获取Ebay商品列表limit: ${limit}, offset: ${offset}`);
if (!this.authToken) {
throw new Error('未授权');
}
// 模拟获取商品列表
const products: Product[] = [];
for (let i = 0; i < limit; i++) {
products.push({
id: `ebay_product_${offset + i + 1}`,
name: `Ebay Product ${offset + i + 1}`,
sku: `EBAY-${offset + i + 1}`,
price: 90 + (offset + i) * 9,
stock: 120,
description: `Ebay Product ${offset + i + 1} Description`,
images: [`https://example.com/ebay_image${offset + i + 1}.jpg`],
categories: ['Electronics', 'Collectibles'],
attributes: {
brand: 'Ebay Brand',
model: `Model ${offset + i + 1}`
},
createdAt: new Date(),
updatedAt: new Date()
});
}
return products;
}
async getOrders(limit: number = 10, offset: number = 0): Promise<Order[]> {
this.logger.log(`获取Ebay订单列表limit: ${limit}, offset: ${offset}`);
if (!this.authToken) {
throw new Error('未授权');
}
// 模拟获取订单列表
const orders: Order[] = [];
for (let i = 0; i < limit; i++) {
orders.push({
id: `ebay_order_${offset + i + 1}`,
customerId: `customer_${offset + i + 1}`,
totalAmount: 180 + (offset + i) * 40,
status: 'shipped',
items: [
{
productId: `ebay_product_${offset + i + 1}`,
quantity: 1,
price: 140 + (offset + i) * 9
}
],
shippingAddress: {
name: `Customer ${offset + i + 1}`,
address: `202 Ebay St`,
city: 'San Jose',
state: 'CA',
zip: '95123',
country: 'US'
},
paymentMethod: 'paypal',
createdAt: new Date(),
updatedAt: new Date()
});
}
return orders;
}
async updateProductPrice(productId: string, price: number): Promise<boolean> {
this.logger.log(`更新Ebay商品价格: ${productId}, 价格: ${price}`);
if (!this.authToken) {
throw new Error('未授权');
}
// 模拟更新商品价格
this.logger.log(`Ebay商品价格更新成功: ${productId}, 价格: ${price}`);
return true;
}
async updateProductStock(productId: string, stock: number): Promise<boolean> {
this.logger.log(`更新Ebay商品库存: ${productId}, 库存: ${stock}`);
if (!this.authToken) {
throw new Error('未授权');
}
// 模拟更新商品库存
this.logger.log(`Ebay商品库存更新成功: ${productId}, 库存: ${stock}`);
return true;
}
async listProduct(product: Product): Promise<string> {
this.logger.log(`上架Ebay商品: ${product.name}`);
if (!this.authToken) {
throw new Error('未授权');
}
// 模拟上架商品
const productId = `ebay_product_${Date.now()}`;
this.logger.log(`Ebay商品上架成功商品ID: ${productId}`);
return productId;
}
async delistProduct(productId: string): Promise<boolean> {
this.logger.log(`下架Ebay商品: ${productId}`);
if (!this.authToken) {
throw new Error('未授权');
}
// 模拟下架商品
this.logger.log(`Ebay商品下架成功: ${productId}`);
return true;
}
async getApiStatus(): Promise<{ status: string; timestamp: number }> {
this.logger.log('获取Ebay API状态');
return {
status: 'healthy',
timestamp: Date.now()
};
}
}

View File

@@ -0,0 +1,167 @@
import { Injectable } from '@nestjs/common';
import { IPlatformAdapter } from './IPlatformAdapter';
import { Product } from '../../../types/models/Product';
import { Order } from '../../../types/models/Order';
import { ShopInfo } from '../../../types/models/ShopInfo';
import { Logger } from '@nestjs/common';
@Injectable()
export class GenericAdapter implements IPlatformAdapter {
private readonly logger = new Logger(GenericAdapter.name);
private authToken: string | null = null;
async authorize(authInfo: Record<string, any>): Promise<boolean> {
this.logger.log('通用平台授权开始');
try {
// 模拟通用平台授权过程
const { apiKey, apiSecret } = authInfo;
if (!apiKey || !apiSecret) {
throw new Error('缺少必要的授权信息');
}
// 模拟授权成功
this.authToken = 'generic_auth_token_' + Date.now();
this.logger.log('通用平台授权成功');
return true;
} catch (error) {
this.logger.error('通用平台授权失败', error);
throw error;
}
}
async getShopInfo(): Promise<ShopInfo> {
this.logger.log('获取通用平台店铺信息');
if (!this.authToken) {
throw new Error('未授权');
}
// 模拟获取店铺信息
return {
id: 'generic_shop_123',
name: 'Test Generic Store',
description: 'Test Generic Store Description',
status: 'active',
createdAt: new Date(),
updatedAt: new Date()
};
}
async getProducts(limit: number = 10, offset: number = 0): Promise<Product[]> {
this.logger.log(`获取通用平台商品列表limit: ${limit}, offset: ${offset}`);
if (!this.authToken) {
throw new Error('未授权');
}
// 模拟获取商品列表
const products: Product[] = [];
for (let i = 0; i < limit; i++) {
products.push({
id: `generic_product_${offset + i + 1}`,
name: `Generic Product ${offset + i + 1}`,
sku: `GEN-${offset + i + 1}`,
price: 70 + (offset + i) * 7,
stock: 160,
description: `Generic Product ${offset + i + 1} Description`,
images: [`https://example.com/generic_image${offset + i + 1}.jpg`],
categories: ['General', 'Miscellaneous'],
attributes: {
brand: 'Generic Brand',
model: `Model ${offset + i + 1}`
},
createdAt: new Date(),
updatedAt: new Date()
});
}
return products;
}
async getOrders(limit: number = 10, offset: number = 0): Promise<Order[]> {
this.logger.log(`获取通用平台订单列表limit: ${limit}, offset: ${offset}`);
if (!this.authToken) {
throw new Error('未授权');
}
// 模拟获取订单列表
const orders: Order[] = [];
for (let i = 0; i < limit; i++) {
orders.push({
id: `generic_order_${offset + i + 1}`,
customerId: `customer_${offset + i + 1}`,
totalAmount: 140 + (offset + i) * 35,
status: 'processing',
items: [
{
productId: `generic_product_${offset + i + 1}`,
quantity: 1,
price: 110 + (offset + i) * 7
}
],
shippingAddress: {
name: `Customer ${offset + i + 1}`,
address: `303 Generic St`,
city: 'Generic City',
state: 'GS',
zip: '12345',
country: 'US'
},
paymentMethod: 'credit_card',
createdAt: new Date(),
updatedAt: new Date()
});
}
return orders;
}
async updateProductPrice(productId: string, price: number): Promise<boolean> {
this.logger.log(`更新通用平台商品价格: ${productId}, 价格: ${price}`);
if (!this.authToken) {
throw new Error('未授权');
}
// 模拟更新商品价格
this.logger.log(`通用平台商品价格更新成功: ${productId}, 价格: ${price}`);
return true;
}
async updateProductStock(productId: string, stock: number): Promise<boolean> {
this.logger.log(`更新通用平台商品库存: ${productId}, 库存: ${stock}`);
if (!this.authToken) {
throw new Error('未授权');
}
// 模拟更新商品库存
this.logger.log(`通用平台商品库存更新成功: ${productId}, 库存: ${stock}`);
return true;
}
async listProduct(product: Product): Promise<string> {
this.logger.log(`上架通用平台商品: ${product.name}`);
if (!this.authToken) {
throw new Error('未授权');
}
// 模拟上架商品
const productId = `generic_product_${Date.now()}`;
this.logger.log(`通用平台商品上架成功商品ID: ${productId}`);
return productId;
}
async delistProduct(productId: string): Promise<boolean> {
this.logger.log(`下架通用平台商品: ${productId}`);
if (!this.authToken) {
throw new Error('未授权');
}
// 模拟下架商品
this.logger.log(`通用平台商品下架成功: ${productId}`);
return true;
}
async getApiStatus(): Promise<{ status: string; timestamp: number }> {
this.logger.log('获取通用平台API状态');
return {
status: 'healthy',
timestamp: Date.now()
};
}
}

View File

@@ -0,0 +1,70 @@
import { Product } from '../../../types/models/Product';
import { Order } from '../../../types/models/Order';
import { ShopInfo } from '../../../types/models/ShopInfo';
export interface IPlatformAdapter {
/**
* 授权店铺
* @param authInfo 授权信息
* @returns 授权结果
*/
authorize(authInfo: Record<string, any>): Promise<boolean>;
/**
* 获取店铺信息
* @returns 店铺信息
*/
getShopInfo(): Promise<ShopInfo>;
/**
* 获取商品列表
* @param limit 限制数量
* @param offset 偏移量
* @returns 商品列表
*/
getProducts(limit?: number, offset?: number): Promise<Product[]>;
/**
* 获取订单列表
* @param limit 限制数量
* @param offset 偏移量
* @returns 订单列表
*/
getOrders(limit?: number, offset?: number): Promise<Order[]>;
/**
* 更新商品价格
* @param productId 商品ID
* @param price 新价格
* @returns 更新结果
*/
updateProductPrice(productId: string, price: number): Promise<boolean>;
/**
* 更新商品库存
* @param productId 商品ID
* @param stock 新库存
* @returns 更新结果
*/
updateProductStock(productId: string, stock: number): Promise<boolean>;
/**
* 上架商品
* @param product 商品信息
* @returns 上架结果
*/
listProduct(product: Product): Promise<string>;
/**
* 下架商品
* @param productId 商品ID
* @returns 下架结果
*/
delistProduct(productId: string): Promise<boolean>;
/**
* 获取平台API状态
* @returns 状态信息
*/
getApiStatus(): Promise<{ status: string; timestamp: number }>;
}

View File

@@ -0,0 +1,108 @@
import { Test, TestingModule } from '@nestjs/testing';
import { PlatformAdapterFactory } from './PlatformAdapterFactory';
import { AmazonAdapter } from './AmazonAdapter';
import { ShopeeAdapter } from './ShopeeAdapter';
import { AliExpressAdapter } from './AliExpressAdapter';
import { TikTokAdapter } from './TikTokAdapter';
import { EbayAdapter } from './EbayAdapter';
import { GenericAdapter } from './GenericAdapter';
import { PlatformType } from '../../../types/enums/PlatformType';
describe('PlatformAdapterFactory', () => {
let factory: PlatformAdapterFactory;
let amazonAdapter: AmazonAdapter;
let shopeeAdapter: ShopeeAdapter;
let aliExpressAdapter: AliExpressAdapter;
let tiktokAdapter: TikTokAdapter;
let ebayAdapter: EbayAdapter;
let genericAdapter: GenericAdapter;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
PlatformAdapterFactory,
AmazonAdapter,
ShopeeAdapter,
AliExpressAdapter,
TikTokAdapter,
EbayAdapter,
GenericAdapter,
],
}).compile();
factory = module.get<PlatformAdapterFactory>(PlatformAdapterFactory);
amazonAdapter = module.get<AmazonAdapter>(AmazonAdapter);
shopeeAdapter = module.get<ShopeeAdapter>(ShopeeAdapter);
aliExpressAdapter = module.get<AliExpressAdapter>(AliExpressAdapter);
tiktokAdapter = module.get<TikTokAdapter>(TikTokAdapter);
ebayAdapter = module.get<EbayAdapter>(EbayAdapter);
genericAdapter = module.get<GenericAdapter>(GenericAdapter);
});
describe('createAdapter', () => {
it('should return AmazonAdapter for amazon platform', () => {
const adapter = factory.createAdapter(PlatformType.AMAZON);
expect(adapter).toBeInstanceOf(AmazonAdapter);
});
it('should return ShopeeAdapter for shopee platform', () => {
const adapter = factory.createAdapter(PlatformType.SHOPEE);
expect(adapter).toBeInstanceOf(ShopeeAdapter);
});
it('should return AliExpressAdapter for aliexpress platform', () => {
const adapter = factory.createAdapter(PlatformType.ALIEXPRESS);
expect(adapter).toBeInstanceOf(AliExpressAdapter);
});
it('should return TikTokAdapter for tiktok platform', () => {
const adapter = factory.createAdapter(PlatformType.TIKTOK);
expect(adapter).toBeInstanceOf(TikTokAdapter);
});
it('should return EbayAdapter for ebay platform', () => {
const adapter = factory.createAdapter(PlatformType.EBAY);
expect(adapter).toBeInstanceOf(EbayAdapter);
});
it('should return GenericAdapter for unknown platform', () => {
const adapter = factory.createAdapter('unknown');
expect(adapter).toBeInstanceOf(GenericAdapter);
});
it('should handle case-insensitive platform names', () => {
const adapter = factory.createAdapter('AMAZON');
expect(adapter).toBeInstanceOf(AmazonAdapter);
});
});
describe('getSupportedPlatforms', () => {
it('should return all supported platforms', () => {
const platforms = factory.getSupportedPlatforms();
expect(platforms).toBeInstanceOf(Array);
expect(platforms).toContain(PlatformType.AMAZON);
expect(platforms).toContain(PlatformType.SHOPEE);
expect(platforms).toContain(PlatformType.ALIEXPRESS);
expect(platforms).toContain(PlatformType.TIKTOK);
expect(platforms).toContain(PlatformType.EBAY);
});
});
describe('isPlatformSupported', () => {
it('should return true for supported platforms', () => {
expect(factory.isPlatformSupported(PlatformType.AMAZON)).toBe(true);
expect(factory.isPlatformSupported(PlatformType.SHOPEE)).toBe(true);
expect(factory.isPlatformSupported(PlatformType.ALIEXPRESS)).toBe(true);
expect(factory.isPlatformSupported(PlatformType.TIKTOK)).toBe(true);
expect(factory.isPlatformSupported(PlatformType.EBAY)).toBe(true);
});
it('should return false for unsupported platforms', () => {
expect(factory.isPlatformSupported('unknown')).toBe(false);
});
it('should handle case-insensitive platform names', () => {
expect(factory.isPlatformSupported('AMAZON')).toBe(true);
});
});
});

View File

@@ -0,0 +1,66 @@
import { Injectable } from '@nestjs/common';
import { IPlatformAdapter } from './IPlatformAdapter';
import { AmazonAdapter } from './AmazonAdapter';
import { ShopeeAdapter } from './ShopeeAdapter';
import { AliExpressAdapter } from './AliExpressAdapter';
import { TikTokAdapter } from './TikTokAdapter';
import { EbayAdapter } from './EbayAdapter';
import { GenericAdapter } from './GenericAdapter';
import { PlatformType } from '../../../types/enums/PlatformType';
import { Logger } from '@nestjs/common';
@Injectable()
export class PlatformAdapterFactory {
private readonly logger = new Logger(PlatformAdapterFactory.name);
constructor(
private amazonAdapter: AmazonAdapter,
private shopeeAdapter: ShopeeAdapter,
private aliExpressAdapter: AliExpressAdapter,
private tiktokAdapter: TikTokAdapter,
private ebayAdapter: EbayAdapter,
private genericAdapter: GenericAdapter
) {}
/**
* 创建平台适配器
* @param platform 平台名称
* @returns 平台适配器实例
*/
createAdapter(platform: string): IPlatformAdapter {
this.logger.log(`创建平台适配器: ${platform}`);
switch (platform.toLowerCase()) {
case PlatformType.AMAZON:
return this.amazonAdapter;
case PlatformType.SHOPEE:
return this.shopeeAdapter;
case PlatformType.ALIEXPRESS:
return this.aliExpressAdapter;
case PlatformType.TIKTOK:
return this.tiktokAdapter;
case PlatformType.EBAY:
return this.ebayAdapter;
default:
this.logger.warn(`平台 ${platform} 没有专用适配器,使用通用适配器`);
return this.genericAdapter;
}
}
/**
* 获取支持的平台列表
* @returns 支持的平台列表
*/
getSupportedPlatforms(): string[] {
return Object.values(PlatformType);
}
/**
* 检查平台是否支持
* @param platform 平台名称
* @returns 是否支持
*/
isPlatformSupported(platform: string): boolean {
return Object.values(PlatformType).includes(platform.toLowerCase() as PlatformType);
}
}

View File

@@ -0,0 +1,167 @@
import { Injectable } from '@nestjs/common';
import { IPlatformAdapter } from './IPlatformAdapter';
import { Product } from '../../../types/models/Product';
import { Order } from '../../../types/models/Order';
import { ShopInfo } from '../../../types/models/ShopInfo';
import { Logger } from '@nestjs/common';
@Injectable()
export class ShopeeAdapter implements IPlatformAdapter {
private readonly logger = new Logger(ShopeeAdapter.name);
private authToken: string | null = null;
async authorize(authInfo: Record<string, any>): Promise<boolean> {
this.logger.log('Shopee授权开始');
try {
// 模拟Shopee授权过程
const { partnerId, partnerKey, shopId } = authInfo;
if (!partnerId || !partnerKey || !shopId) {
throw new Error('缺少必要的授权信息');
}
// 模拟授权成功
this.authToken = 'shopee_auth_token_' + Date.now();
this.logger.log('Shopee授权成功');
return true;
} catch (error) {
this.logger.error('Shopee授权失败', error);
throw error;
}
}
async getShopInfo(): Promise<ShopInfo> {
this.logger.log('获取Shopee店铺信息');
if (!this.authToken) {
throw new Error('未授权');
}
// 模拟获取店铺信息
return {
id: 'shopee_shop_123',
name: 'Test Shopee Store',
description: 'Test Shopee Store Description',
status: 'active',
createdAt: new Date(),
updatedAt: new Date()
};
}
async getProducts(limit: number = 10, offset: number = 0): Promise<Product[]> {
this.logger.log(`获取Shopee商品列表limit: ${limit}, offset: ${offset}`);
if (!this.authToken) {
throw new Error('未授权');
}
// 模拟获取商品列表
const products: Product[] = [];
for (let i = 0; i < limit; i++) {
products.push({
id: `shopee_product_${offset + i + 1}`,
name: `Shopee Product ${offset + i + 1}`,
sku: `SHO-${offset + i + 1}`,
price: 80 + (offset + i) * 8,
stock: 150,
description: `Shopee Product ${offset + i + 1} Description`,
images: [`https://example.com/shopee_image${offset + i + 1}.jpg`],
categories: ['Fashion', 'Accessories'],
attributes: {
brand: 'Shopee Brand',
model: `Model ${offset + i + 1}`
},
createdAt: new Date(),
updatedAt: new Date()
});
}
return products;
}
async getOrders(limit: number = 10, offset: number = 0): Promise<Order[]> {
this.logger.log(`获取Shopee订单列表limit: ${limit}, offset: ${offset}`);
if (!this.authToken) {
throw new Error('未授权');
}
// 模拟获取订单列表
const orders: Order[] = [];
for (let i = 0; i < limit; i++) {
orders.push({
id: `shopee_order_${offset + i + 1}`,
customerId: `customer_${offset + i + 1}`,
totalAmount: 150 + (offset + i) * 30,
status: 'processing',
items: [
{
productId: `shopee_product_${offset + i + 1}`,
quantity: 1,
price: 120 + (offset + i) * 8
}
],
shippingAddress: {
name: `Customer ${offset + i + 1}`,
address: `456 Market St`,
city: 'Singapore',
state: 'SG',
zip: '123456',
country: 'SG'
},
paymentMethod: 'shopee_pay',
createdAt: new Date(),
updatedAt: new Date()
});
}
return orders;
}
async updateProductPrice(productId: string, price: number): Promise<boolean> {
this.logger.log(`更新Shopee商品价格: ${productId}, 价格: ${price}`);
if (!this.authToken) {
throw new Error('未授权');
}
// 模拟更新商品价格
this.logger.log(`Shopee商品价格更新成功: ${productId}, 价格: ${price}`);
return true;
}
async updateProductStock(productId: string, stock: number): Promise<boolean> {
this.logger.log(`更新Shopee商品库存: ${productId}, 库存: ${stock}`);
if (!this.authToken) {
throw new Error('未授权');
}
// 模拟更新商品库存
this.logger.log(`Shopee商品库存更新成功: ${productId}, 库存: ${stock}`);
return true;
}
async listProduct(product: Product): Promise<string> {
this.logger.log(`上架Shopee商品: ${product.name}`);
if (!this.authToken) {
throw new Error('未授权');
}
// 模拟上架商品
const productId = `shopee_product_${Date.now()}`;
this.logger.log(`Shopee商品上架成功商品ID: ${productId}`);
return productId;
}
async delistProduct(productId: string): Promise<boolean> {
this.logger.log(`下架Shopee商品: ${productId}`);
if (!this.authToken) {
throw new Error('未授权');
}
// 模拟下架商品
this.logger.log(`Shopee商品下架成功: ${productId}`);
return true;
}
async getApiStatus(): Promise<{ status: string; timestamp: number }> {
this.logger.log('获取Shopee API状态');
return {
status: 'healthy',
timestamp: Date.now()
};
}
}

View File

@@ -0,0 +1,167 @@
import { Injectable } from '@nestjs/common';
import { IPlatformAdapter } from './IPlatformAdapter';
import { Product } from '../../../types/models/Product';
import { Order } from '../../../types/models/Order';
import { ShopInfo } from '../../../types/models/ShopInfo';
import { Logger } from '@nestjs/common';
@Injectable()
export class TikTokAdapter implements IPlatformAdapter {
private readonly logger = new Logger(TikTokAdapter.name);
private authToken: string | null = null;
async authorize(authInfo: Record<string, any>): Promise<boolean> {
this.logger.log('TikTok授权开始');
try {
// 模拟TikTok授权过程
const { appId, appSecret, accessToken } = authInfo;
if (!appId || !appSecret || !accessToken) {
throw new Error('缺少必要的授权信息');
}
// 模拟授权成功
this.authToken = 'tiktok_auth_token_' + Date.now();
this.logger.log('TikTok授权成功');
return true;
} catch (error) {
this.logger.error('TikTok授权失败', error);
throw error;
}
}
async getShopInfo(): Promise<ShopInfo> {
this.logger.log('获取TikTok店铺信息');
if (!this.authToken) {
throw new Error('未授权');
}
// 模拟获取店铺信息
return {
id: 'tiktok_shop_123',
name: 'Test TikTok Store',
description: 'Test TikTok Store Description',
status: 'active',
createdAt: new Date(),
updatedAt: new Date()
};
}
async getProducts(limit: number = 10, offset: number = 0): Promise<Product[]> {
this.logger.log(`获取TikTok商品列表limit: ${limit}, offset: ${offset}`);
if (!this.authToken) {
throw new Error('未授权');
}
// 模拟获取商品列表
const products: Product[] = [];
for (let i = 0; i < limit; i++) {
products.push({
id: `tiktok_product_${offset + i + 1}`,
name: `TikTok Product ${offset + i + 1}`,
sku: `TT-${offset + i + 1}`,
price: 60 + (offset + i) * 6,
stock: 180,
description: `TikTok Product ${offset + i + 1} Description`,
images: [`https://example.com/tiktok_image${offset + i + 1}.jpg`],
categories: ['Fashion', 'Beauty'],
attributes: {
brand: 'TikTok Brand',
model: `Model ${offset + i + 1}`
},
createdAt: new Date(),
updatedAt: new Date()
});
}
return products;
}
async getOrders(limit: number = 10, offset: number = 0): Promise<Order[]> {
this.logger.log(`获取TikTok订单列表limit: ${limit}, offset: ${offset}`);
if (!this.authToken) {
throw new Error('未授权');
}
// 模拟获取订单列表
const orders: Order[] = [];
for (let i = 0; i < limit; i++) {
orders.push({
id: `tiktok_order_${offset + i + 1}`,
customerId: `customer_${offset + i + 1}`,
totalAmount: 120 + (offset + i) * 25,
status: 'delivered',
items: [
{
productId: `tiktok_product_${offset + i + 1}`,
quantity: 1,
price: 90 + (offset + i) * 6
}
],
shippingAddress: {
name: `Customer ${offset + i + 1}`,
address: `101 TikTok St`,
city: 'Beijing',
state: 'Beijing',
zip: '100000',
country: 'CN'
},
paymentMethod: 'tiktok_pay',
createdAt: new Date(),
updatedAt: new Date()
});
}
return orders;
}
async updateProductPrice(productId: string, price: number): Promise<boolean> {
this.logger.log(`更新TikTok商品价格: ${productId}, 价格: ${price}`);
if (!this.authToken) {
throw new Error('未授权');
}
// 模拟更新商品价格
this.logger.log(`TikTok商品价格更新成功: ${productId}, 价格: ${price}`);
return true;
}
async updateProductStock(productId: string, stock: number): Promise<boolean> {
this.logger.log(`更新TikTok商品库存: ${productId}, 库存: ${stock}`);
if (!this.authToken) {
throw new Error('未授权');
}
// 模拟更新商品库存
this.logger.log(`TikTok商品库存更新成功: ${productId}, 库存: ${stock}`);
return true;
}
async listProduct(product: Product): Promise<string> {
this.logger.log(`上架TikTok商品: ${product.name}`);
if (!this.authToken) {
throw new Error('未授权');
}
// 模拟上架商品
const productId = `tiktok_product_${Date.now()}`;
this.logger.log(`TikTok商品上架成功商品ID: ${productId}`);
return productId;
}
async delistProduct(productId: string): Promise<boolean> {
this.logger.log(`下架TikTok商品: ${productId}`);
if (!this.authToken) {
throw new Error('未授权');
}
// 模拟下架商品
this.logger.log(`TikTok商品下架成功: ${productId}`);
return true;
}
async getApiStatus(): Promise<{ status: string; timestamp: number }> {
this.logger.log('获取TikTok API状态');
return {
status: 'healthy',
timestamp: Date.now()
};
}
}

View File

@@ -0,0 +1,31 @@
import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn } from 'typeorm';
@Entity('cf_ai_improvement')
export class AIImprovement {
@PrimaryGeneratedColumn()
id: number;
@Column('varchar', { length: 50 })
type: 'code' | 'performance' | 'business' | 'security';
@Column('varchar', { length: 255 })
title: string;
@Column('text')
description: string;
@Column('varchar', { length: 20 })
severity: 'low' | 'medium' | 'high';
@Column('varchar', { length: 20 })
priority: 'low' | 'medium' | 'high';
@Column('varchar', { length: 20 })
status: 'pending' | 'implemented' | 'dismissed';
@CreateDateColumn()
createdAt: Date;
@UpdateDateColumn()
updatedAt: Date;
}

View File

@@ -0,0 +1,13 @@
import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn } from 'typeorm';
@Entity('cf_monitoring_data')
export class MonitoringData {
@PrimaryGeneratedColumn()
id: number;
@Column('text')
data: string;
@CreateDateColumn()
timestamp: Date;
}

View File

@@ -0,0 +1,20 @@
export enum PlatformType {
AMAZON = 'amazon',
SHOPEE = 'shopee',
ALIEXPRESS = 'aliexpress',
TIKTOK = 'tiktok',
EBAY = 'ebay',
LAZADA = 'lazada',
WISH = 'wish',
SHEIN = 'shein',
JD_WORLDWIDE = 'jd_worldwide',
WALMART = 'walmart',
ETSY = 'etsy',
TARGET = 'target',
NEWEGG = 'newegg',
CDISCOUNT = 'cdiscount',
ALLEGRO = 'allegro',
OTTO = 'otto',
RAKUTEN = 'rakuten',
QOO10 = 'qoo10'
}

View File

@@ -0,0 +1,6 @@
export enum StoreStatus {
PENDING = 'pending',
ACTIVE = 'active',
INACTIVE = 'inactive',
SUSPENDED = 'suspended'
}

View File

@@ -0,0 +1,33 @@
export enum OperationAgentEvent {
// 店铺相关事件
STORE_BOUND = 'operation_agent.store.bound',
STORE_BIND_FAILED = 'operation_agent.store.bind.failed',
STORE_ACTIVATED = 'operation_agent.store.activated',
STORE_DEACTIVATED = 'operation_agent.store.deactivated',
// 商品相关事件
PRODUCTS_SYNCED = 'operation_agent.products.synced',
PRODUCTS_SYNC_FAILED = 'operation_agent.products.sync.failed',
PRODUCT_PRICE_UPDATED = 'operation_agent.product.price.updated',
PRODUCT_PRICE_UPDATE_FAILED = 'operation_agent.product.price.update.failed',
PRODUCT_STOCK_UPDATED = 'operation_agent.product.stock.updated',
PRODUCT_STOCK_UPDATE_FAILED = 'operation_agent.product.stock.update.failed',
PRODUCT_LISTED = 'operation_agent.product.listed',
PRODUCT_LIST_FAILED = 'operation_agent.product.list.failed',
PRODUCT_DELISTED = 'operation_agent.product.delisted',
PRODUCT_DELIST_FAILED = 'operation_agent.product.delist.failed',
// 订单相关事件
ORDERS_SYNCED = 'operation_agent.orders.synced',
ORDERS_SYNC_FAILED = 'operation_agent.orders.sync.failed',
// 平台适配器相关事件
ADAPTER_AUTHORIZED = 'operation_agent.adapter.authorized',
ADAPTER_AUTH_FAILED = 'operation_agent.adapter.auth.failed',
ADAPTER_API_ERROR = 'operation_agent.adapter.api.error',
// 系统相关事件
AGENT_STARTED = 'operation_agent.started',
AGENT_STOPPED = 'operation_agent.stopped',
AGENT_HEARTBEAT = 'operation_agent.heartbeat'
}

View File

@@ -0,0 +1,26 @@
export interface OrderItem {
productId: string;
quantity: number;
price: number;
}
export interface ShippingAddress {
name: string;
address: string;
city: string;
state: string;
zip: string;
country: string;
}
export interface Order {
id: string;
customerId: string;
totalAmount: number;
status: string;
items: OrderItem[];
shippingAddress: ShippingAddress;
paymentMethod: string;
createdAt: Date;
updatedAt: Date;
}

View File

@@ -0,0 +1,13 @@
export interface Product {
id: string;
name: string;
sku: string;
price: number;
stock: number;
description: string;
images: string[];
categories: string[];
attributes: Record<string, any>;
createdAt: Date;
updatedAt: Date;
}

View File

@@ -0,0 +1,8 @@
export interface ShopInfo {
id: string;
name: string;
description: string;
status: string;
createdAt: Date;
updatedAt: Date;
}