feat: 实现Operation-Agent核心功能及电商平台适配器
refactor: 重构项目结构,分离server和dashboard代码 style: 统一代码风格,修复lint警告 test: 添加平台适配器工厂测试用例 ci: 更新CI/CD流程,增加语义验证和性能测试 docs: 添加语义中心文档,定义统一数据模型和状态机
This commit is contained in:
61
server/src/api/controllers/AISelfImprovementController.ts
Normal file
61
server/src/api/controllers/AISelfImprovementController.ts
Normal 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: '定期优化执行完成' };
|
||||
}
|
||||
}
|
||||
30
server/src/api/controllers/MonitoringController.ts
Normal file
30
server/src/api/controllers/MonitoringController.ts
Normal 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: '指标收集成功' };
|
||||
}
|
||||
}
|
||||
89
server/src/api/controllers/OperationAgentController.ts
Normal file
89
server/src/api/controllers/OperationAgentController.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
22
server/src/api/dto/StoreBindingDto.ts
Normal file
22
server/src/api/dto/StoreBindingDto.ts
Normal 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>;
|
||||
}
|
||||
43
server/src/api/routes/ai-self-improvement.ts
Normal file
43
server/src/api/routes/ai-self-improvement.ts
Normal 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;
|
||||
22
server/src/api/routes/monitoring.ts
Normal file
22
server/src/api/routes/monitoring.ts
Normal 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;
|
||||
64
server/src/api/routes/operation-agent.ts
Normal file
64
server/src/api/routes/operation-agent.ts
Normal 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;
|
||||
317
server/src/core/ai/AISelfImprovementService.ts
Normal file
317
server/src/core/ai/AISelfImprovementService.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
168
server/src/core/monitoring/MonitoringService.ts
Normal file
168
server/src/core/monitoring/MonitoringService.ts
Normal 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心跳');
|
||||
}
|
||||
}
|
||||
325
server/src/core/operation/OperationAgentIntegration.test.ts
Normal file
325
server/src/core/operation/OperationAgentIntegration.test.ts
Normal 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,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
608
server/src/core/operation/OperationAgentService.test.ts
Normal file
608
server/src/core/operation/OperationAgentService.test.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
});
|
||||
311
server/src/core/operation/OperationAgentService.ts
Normal file
311
server/src/core/operation/OperationAgentService.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
167
server/src/core/operation/adapters/AliExpressAdapter.ts
Normal file
167
server/src/core/operation/adapters/AliExpressAdapter.ts
Normal 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()
|
||||
};
|
||||
}
|
||||
}
|
||||
167
server/src/core/operation/adapters/AmazonAdapter.ts
Normal file
167
server/src/core/operation/adapters/AmazonAdapter.ts
Normal 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()
|
||||
};
|
||||
}
|
||||
}
|
||||
167
server/src/core/operation/adapters/EbayAdapter.ts
Normal file
167
server/src/core/operation/adapters/EbayAdapter.ts
Normal 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()
|
||||
};
|
||||
}
|
||||
}
|
||||
167
server/src/core/operation/adapters/GenericAdapter.ts
Normal file
167
server/src/core/operation/adapters/GenericAdapter.ts
Normal 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()
|
||||
};
|
||||
}
|
||||
}
|
||||
70
server/src/core/operation/adapters/IPlatformAdapter.ts
Normal file
70
server/src/core/operation/adapters/IPlatformAdapter.ts
Normal 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 }>;
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
66
server/src/core/operation/adapters/PlatformAdapterFactory.ts
Normal file
66
server/src/core/operation/adapters/PlatformAdapterFactory.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
167
server/src/core/operation/adapters/ShopeeAdapter.ts
Normal file
167
server/src/core/operation/adapters/ShopeeAdapter.ts
Normal 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()
|
||||
};
|
||||
}
|
||||
}
|
||||
167
server/src/core/operation/adapters/TikTokAdapter.ts
Normal file
167
server/src/core/operation/adapters/TikTokAdapter.ts
Normal 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()
|
||||
};
|
||||
}
|
||||
}
|
||||
31
server/src/entities/AIImprovement.ts
Normal file
31
server/src/entities/AIImprovement.ts
Normal 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;
|
||||
}
|
||||
13
server/src/entities/MonitoringData.ts
Normal file
13
server/src/entities/MonitoringData.ts
Normal 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;
|
||||
}
|
||||
20
server/src/types/enums/PlatformType.ts
Normal file
20
server/src/types/enums/PlatformType.ts
Normal 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'
|
||||
}
|
||||
6
server/src/types/enums/StoreStatus.ts
Normal file
6
server/src/types/enums/StoreStatus.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export enum StoreStatus {
|
||||
PENDING = 'pending',
|
||||
ACTIVE = 'active',
|
||||
INACTIVE = 'inactive',
|
||||
SUSPENDED = 'suspended'
|
||||
}
|
||||
33
server/src/types/events/OperationAgentEvent.ts
Normal file
33
server/src/types/events/OperationAgentEvent.ts
Normal 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'
|
||||
}
|
||||
26
server/src/types/models/Order.ts
Normal file
26
server/src/types/models/Order.ts
Normal 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;
|
||||
}
|
||||
13
server/src/types/models/Product.ts
Normal file
13
server/src/types/models/Product.ts
Normal 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;
|
||||
}
|
||||
8
server/src/types/models/ShopInfo.ts
Normal file
8
server/src/types/models/ShopInfo.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
export interface ShopInfo {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
status: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
Reference in New Issue
Block a user