feat: 添加MSW模拟服务和数据源集成

refactor: 重构页面组件移除冗余Layout组件

feat: 实现WebSocket和事件总线系统

feat: 添加队列和调度系统

docs: 更新架构文档和服务映射

style: 清理重复接口定义使用数据源

chore: 更新依赖项配置

feat: 添加运行时系统和领域引导

ci: 配置ESLint边界检查规则

build: 添加Redis和WebSocket依赖

test: 添加MSW浏览器环境入口

perf: 优化数据获取逻辑使用统一数据源

fix: 修复类型定义和状态管理问题
This commit is contained in:
2026-03-19 01:39:34 +08:00
parent cd55097dbf
commit 0dac26d781
176 changed files with 47075 additions and 8404 deletions

View File

@@ -0,0 +1,161 @@
import { Router } from 'express';
import { CertificateService } from '../../services/CertificateService';
import { requireTraceContext } from '../../core/guards/trace-context.guard';
import { requirePermission } from '../../core/guards/rbac.guard';
import { logger } from '../../utils/logger';
const router = Router();
const certificateService = new CertificateService();
/**
* [FE-COM001] 证书管理 API
*/
router.get('/certificates', requireTraceContext, requirePermission('certificate:read'), async (req, res) => {
try {
const { tenantId, shopId } = req.traceContext;
const { status, type, page = 1, pageSize = 10 } = req.query;
const result = await certificateService.query({
tenantId,
shopId,
status: status as string,
type: type as string,
page: parseInt(page as string),
pageSize: parseInt(pageSize as string)
});
res.json({
success: true,
data: result.items,
total: result.total,
page: parseInt(page as string),
pageSize: parseInt(pageSize as string)
});
} catch (error) {
logger.error('[CertificateRoutes] GET /certificates error:', error);
res.status(500).json({
success: false,
error: '获取证书列表失败'
});
}
});
router.get('/certificates/:id', requireTraceContext, requirePermission('certificate:read'), async (req, res) => {
try {
const { tenantId, shopId } = req.traceContext;
const { id } = req.params;
const certificate = await certificateService.getById(id, tenantId, shopId);
if (!certificate) {
return res.status(404).json({
success: false,
error: '证书不存在'
});
}
res.json({
success: true,
data: certificate
});
} catch (error) {
logger.error('[CertificateRoutes] GET /certificates/:id error:', error);
res.status(500).json({
success: false,
error: '获取证书详情失败'
});
}
});
router.post('/certificates', requireTraceContext, requirePermission('certificate:create'), async (req, res) => {
try {
const { tenantId, shopId, traceId } = req.traceContext;
const certificateData = req.body;
const id = await certificateService.create({
...certificateData,
tenantId,
shopId,
traceId
});
res.json({
success: true,
data: { id }
});
} catch (error) {
logger.error('[CertificateRoutes] POST /certificates error:', error);
res.status(500).json({
success: false,
error: '创建证书失败'
});
}
});
router.put('/certificates/:id', requireTraceContext, requirePermission('certificate:update'), async (req, res) => {
try {
const { tenantId, shopId, traceId } = req.traceContext;
const { id } = req.params;
const certificateData = req.body;
await certificateService.update(id, {
...certificateData,
tenantId,
shopId,
traceId
});
res.json({
success: true
});
} catch (error) {
logger.error('[CertificateRoutes] PUT /certificates/:id error:', error);
res.status(500).json({
success: false,
error: '更新证书失败'
});
}
});
router.delete('/certificates/:id', requireTraceContext, requirePermission('certificate:delete'), async (req, res) => {
try {
const { tenantId, shopId, traceId } = req.traceContext;
const { id } = req.params;
await certificateService.delete(id, tenantId, shopId, traceId);
res.json({
success: true
});
} catch (error) {
logger.error('[CertificateRoutes] DELETE /certificates/:id error:', error);
res.status(500).json({
success: false,
error: '删除证书失败'
});
}
});
router.put('/certificates/:id/status', requireTraceContext, requirePermission('certificate:approve'), async (req, res) => {
try {
const { tenantId, shopId, traceId } = req.traceContext;
const { id } = req.params;
const { status, approvedBy } = req.body;
await certificateService.updateStatus(id, tenantId, shopId, traceId, status, approvedBy);
res.json({
success: true
});
} catch (error) {
logger.error('[CertificateRoutes] PUT /certificates/:id/status error:', error);
res.status(500).json({
success: false,
error: '更新证书状态失败'
});
}
});
export default router;

View File

@@ -0,0 +1,283 @@
import { Router, Request, Response } from 'express';
import LeaderboardService from '../../services/LeaderboardService';
import MerchantMetricsService from '../../services/MerchantMetricsService';
import { logger } from '../../utils/logger';
import { authenticate, authorize } from '../middleware/auth';
const router = Router();
router.get('/leaderboard/:type', authenticate, async (req: Request, res: Response) => {
try {
const { type } = req.params;
const period = (req.query.period as 'DAILY' | 'WEEKLY' | 'MONTHLY' | 'ALL_TIME') || 'MONTHLY';
const limit = parseInt(req.query.limit as string) || 10;
if (!['REVENUE', 'ROI', 'GROWTH'].includes(type)) {
return res.status(400).json({
success: false,
message: 'Invalid leaderboard type. Must be REVENUE, ROI, or GROWTH'
});
}
const leaderboard = await LeaderboardService.getLeaderboard(
type as 'REVENUE' | 'ROI' | 'GROWTH',
period
);
res.json({
success: true,
data: {
type,
period,
rankings: leaderboard.slice(0, limit),
total: leaderboard.length,
updatedAt: new Date().toISOString()
}
});
} catch (error) {
logger.error('[LeaderboardRoutes] Error fetching leaderboard:', error);
res.status(500).json({
success: false,
message: 'Failed to fetch leaderboard'
});
}
});
router.get('/leaderboard/all', authenticate, async (req: Request, res: Response) => {
try {
const period = (req.query.period as 'DAILY' | 'WEEKLY' | 'MONTHLY' | 'ALL_TIME') || 'MONTHLY';
const [revenue, roi, growth] = await Promise.all([
LeaderboardService.getLeaderboard('REVENUE', period),
LeaderboardService.getLeaderboard('ROI', period),
LeaderboardService.getLeaderboard('GROWTH', period)
]);
res.json({
success: true,
data: {
revenue: { rankings: revenue, total: revenue.length },
roi: { rankings: roi, total: roi.length },
growth: { rankings: growth, total: growth.length },
period,
updatedAt: new Date().toISOString()
}
});
} catch (error) {
logger.error('[LeaderboardRoutes] Error fetching all leaderboards:', error);
res.status(500).json({
success: false,
message: 'Failed to fetch leaderboards'
});
}
});
router.get('/leaderboard/stats', authenticate, async (req: Request, res: Response) => {
try {
const stats = await LeaderboardService.getLeaderboardStats();
res.json({
success: true,
data: stats
});
} catch (error) {
logger.error('[LeaderboardRoutes] Error fetching leaderboard stats:', error);
res.status(500).json({
success: false,
message: 'Failed to fetch leaderboard stats'
});
}
});
router.get('/leaderboard/my-rank', authenticate, async (req: Request, res: Response) => {
try {
const tenantId = req.user?.tenantId;
if (!tenantId) {
return res.status(401).json({
success: false,
message: 'Tenant ID not found'
});
}
const period = (req.query.period as 'DAILY' | 'WEEKLY' | 'MONTHLY' | 'ALL_TIME') || 'MONTHLY';
const [revenueRank, roiRank, growthRank] = await Promise.all([
LeaderboardService.getMerchantRank(tenantId, 'REVENUE', period),
LeaderboardService.getMerchantRank(tenantId, 'ROI', period),
LeaderboardService.getMerchantRank(tenantId, 'GROWTH', period)
]);
res.json({
success: true,
data: {
revenue: revenueRank,
roi: roiRank,
growth: growthRank,
period
}
});
} catch (error) {
logger.error('[LeaderboardRoutes] Error fetching merchant rank:', error);
res.status(500).json({
success: false,
message: 'Failed to fetch merchant rank'
});
}
});
router.get('/metrics/my-metrics', authenticate, async (req: Request, res: Response) => {
try {
const tenantId = req.user?.tenantId;
if (!tenantId) {
return res.status(401).json({
success: false,
message: 'Tenant ID not found'
});
}
const metrics = await MerchantMetricsService.getLatestMetrics(tenantId);
res.json({
success: true,
data: metrics
});
} catch (error) {
logger.error('[LeaderboardRoutes] Error fetching merchant metrics:', error);
res.status(500).json({
success: false,
message: 'Failed to fetch merchant metrics'
});
}
});
router.get('/metrics/history', authenticate, async (req: Request, res: Response) => {
try {
const tenantId = req.user?.tenantId;
if (!tenantId) {
return res.status(401).json({
success: false,
message: 'Tenant ID not found'
});
}
const startDate = req.query.startDate
? new Date(req.query.startDate as string)
: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);
const endDate = req.query.endDate
? new Date(req.query.endDate as string)
: new Date();
const history = await MerchantMetricsService.getMetricsHistory(tenantId, startDate, endDate);
res.json({
success: true,
data: {
history,
startDate: startDate.toISOString(),
endDate: endDate.toISOString()
}
});
} catch (error) {
logger.error('[LeaderboardRoutes] Error fetching metrics history:', error);
res.status(500).json({
success: false,
message: 'Failed to fetch metrics history'
});
}
});
router.post('/metrics/calculate', authenticate, authorize(['ADMIN', 'MANAGER']), async (req: Request, res: Response) => {
try {
const { tenantId, shopId, period = 'MONTHLY' } = req.body;
if (!tenantId || !shopId) {
return res.status(400).json({
success: false,
message: 'tenantId and shopId are required'
});
}
const metrics = await MerchantMetricsService.calculateAndStoreMetrics({
tenantId,
shopId,
period
});
res.json({
success: true,
data: metrics,
message: 'Metrics calculated and stored successfully'
});
} catch (error) {
logger.error('[LeaderboardRoutes] Error calculating metrics:', error);
res.status(500).json({
success: false,
message: 'Failed to calculate metrics'
});
}
});
router.post('/leaderboard/refresh', authenticate, authorize(['ADMIN']), async (req: Request, res: Response) => {
try {
const { type, period } = req.body;
if (type && period) {
await LeaderboardService.refreshLeaderboard(type, period);
res.json({
success: true,
message: `Leaderboard ${type} - ${period} refreshed successfully`
});
} else {
await LeaderboardService.refreshAllLeaderboards();
res.json({
success: true,
message: 'All leaderboards refreshed successfully'
});
}
} catch (error) {
logger.error('[LeaderboardRoutes] Error refreshing leaderboard:', error);
res.status(500).json({
success: false,
message: 'Failed to refresh leaderboard'
});
}
});
router.post('/metrics/verify/:metricsId', authenticate, authorize(['ADMIN', 'FINANCE']), async (req: Request, res: Response) => {
try {
const { metricsId } = req.params;
await MerchantMetricsService.verifyMetrics(metricsId);
res.json({
success: true,
message: 'Metrics verified successfully'
});
} catch (error) {
logger.error('[LeaderboardRoutes] Error verifying metrics:', error);
res.status(500).json({
success: false,
message: 'Failed to verify metrics'
});
}
});
router.get('/metrics/suspicious', authenticate, authorize(['ADMIN', 'FINANCE']), async (req: Request, res: Response) => {
try {
const suspicious = await MerchantMetricsService.flagSuspiciousMetrics();
res.json({
success: true,
data: suspicious,
message: `Found ${suspicious.length} potentially suspicious metrics`
});
} catch (error) {
logger.error('[LeaderboardRoutes] Error fetching suspicious metrics:', error);
res.status(500).json({
success: false,
message: 'Failed to fetch suspicious metrics'
});
}
});
export default router;

View File

@@ -1,47 +1,386 @@
import { Router } from 'express';
import { BizStrategyController } from '../controllers/BizStrategyController';
import { requireTraceContext } from '../../core/guards/trace-context.guard';
import { requirePermission } from '../../core/guards/rbac.guard';
import { Router, Request, Response } from 'express';
import StrategyService from '../../services/StrategyService';
import StrategyRecommendationService from '../../services/StrategyRecommendationService';
import { logger } from '../../utils/logger';
import { authenticate, authorize } from '../middleware/auth';
const router = Router();
/**
* [BIZ_AI_16] 交互式策略建议 (Actionable Insights)
*/
router.get('/advice', requireTraceContext, requirePermission('strategy:read'), BizStrategyController.getAdvice);
router.post('/advice/approve', requireTraceContext, requirePermission('strategy:execute'), BizStrategyController.approveAdvice);
router.get('/advice/:adviceId/explanation', requireTraceContext, requirePermission('strategy:read'), BizStrategyController.getAdviceExplanation);
router.get('/strategies', authenticate, async (req: Request, res: Response) => {
try {
const { category, isActive, isFeatured, limit, offset } = req.query;
/**
* [UX_IAT_01] 自治控制中心:停机与模式切换
*/
router.post('/kill-switch', requireTraceContext, requirePermission('strategy:kill'), BizStrategyController.toggleKillSwitch);
router.post('/mode', requireTraceContext, requirePermission('strategy:write'), BizStrategyController.updateAutonomousMode);
router.get('/status', requireTraceContext, BizStrategyController.getAutonomousStatus);
const result = await StrategyService.getAllStrategies({
category: category as string,
isActive: isActive === 'true' ? true : isActive === 'false' ? false : undefined,
isFeatured: isFeatured === 'true' ? true : isFeatured === 'false' ? false : undefined,
limit: limit ? parseInt(limit as string) : undefined,
offset: offset ? parseInt(offset as string) : undefined
});
/**
* [FE_SB_01] 策略仿真沙盒 (Sandbox Dashboard)
*/
router.post('/sandbox/run', requireTraceContext, requirePermission('strategy:write'), BizStrategyController.runSandboxSimulation);
router.get('/sandbox/results', requireTraceContext, requirePermission('strategy:read'), BizStrategyController.getSandboxResults);
res.json({
success: true,
data: result.strategies,
total: result.total
});
} catch (error) {
logger.error('[StrategyRoutes] Error fetching strategies:', error);
res.status(500).json({
success: false,
message: 'Failed to fetch strategies'
});
}
});
/**
* [BIZ_SC_11] 供应链询盘流 (Semi-Inquiry)
*/
router.post('/inquiry/start', requireTraceContext, BizStrategyController.startInquiry);
router.post('/inquiry/:inquiryId/review', requireTraceContext, BizStrategyController.reviewInquiry);
router.post('/inquiry/:inquiryId/send', requireTraceContext, BizStrategyController.sendInquiry);
router.post('/inquiry/:inquiryId/accept', requireTraceContext, BizStrategyController.acceptAndPurchase);
router.get('/strategies/featured', authenticate, async (req: Request, res: Response) => {
try {
const limit = req.query.limit ? parseInt(req.query.limit as string) : 5;
const strategies = await StrategyService.getFeaturedStrategies(limit);
/**
* [BIZ_TRADE_02] 多仓库存编排 (Inventory Orchestration)
*/
router.get('/inventory/orchestrate', requireTraceContext, BizStrategyController.orchestrateInventory);
router.post('/inventory/transfer', requireTraceContext, BizStrategyController.approveTransfer);
res.json({
success: true,
data: strategies
});
} catch (error) {
logger.error('[StrategyRoutes] Error fetching featured strategies:', error);
res.status(500).json({
success: false,
message: 'Failed to fetch featured strategies'
});
}
});
/**
* [BIZ_AIS_01] 独立站套利回流 (Pixel Feedback)
*/
router.post('/pixel/event', requireTraceContext, BizStrategyController.collectPixelEvent);
router.get('/strategies/trending', authenticate, async (req: Request, res: Response) => {
try {
const limit = req.query.limit ? parseInt(req.query.limit as string) : 5;
const strategies = await StrategyRecommendationService.getTrendingStrategies(limit);
res.json({
success: true,
data: strategies
});
} catch (error) {
logger.error('[StrategyRoutes] Error fetching trending strategies:', error);
res.status(500).json({
success: false,
message: 'Failed to fetch trending strategies'
});
}
});
router.get('/strategies/search', authenticate, async (req: Request, res: Response) => {
try {
const { q } = req.query;
if (!q || typeof q !== 'string') {
return res.status(400).json({
success: false,
message: 'Search query is required'
});
}
const strategies = await StrategyService.searchStrategies(q);
res.json({
success: true,
data: strategies
});
} catch (error) {
logger.error('[StrategyRoutes] Error searching strategies:', error);
res.status(500).json({
success: false,
message: 'Failed to search strategies'
});
}
});
router.get('/strategies/category/:category', authenticate, async (req: Request, res: Response) => {
try {
const { category } = req.params;
const strategies = await StrategyService.getStrategiesByCategory(category);
res.json({
success: true,
data: strategies
});
} catch (error) {
logger.error('[StrategyRoutes] Error fetching strategies by category:', error);
res.status(500).json({
success: false,
message: 'Failed to fetch strategies by category'
});
}
});
router.get('/strategies/:id', authenticate, async (req: Request, res: Response) => {
try {
const { id } = req.params;
const strategy = await StrategyService.getStrategyById(id);
if (!strategy) {
return res.status(404).json({
success: false,
message: 'Strategy not found'
});
}
res.json({
success: true,
data: strategy
});
} catch (error) {
logger.error('[StrategyRoutes] Error fetching strategy:', error);
res.status(500).json({
success: false,
message: 'Failed to fetch strategy'
});
}
});
router.get('/strategies/:id/similar', authenticate, async (req: Request, res: Response) => {
try {
const { id } = req.params;
const strategies = await StrategyRecommendationService.getSimilarStrategies(id);
res.json({
success: true,
data: strategies
});
} catch (error) {
logger.error('[StrategyRoutes] Error fetching similar strategies:', error);
res.status(500).json({
success: false,
message: 'Failed to fetch similar strategies'
});
}
});
router.post('/strategies', authenticate, authorize('strategy:create'), async (req: Request, res: Response) => {
try {
const strategy = await StrategyService.createStrategy({
...req.body,
created_by: (req as any).user?.id || 'system'
});
res.status(201).json({
success: true,
data: strategy,
message: 'Strategy created successfully'
});
} catch (error) {
logger.error('[StrategyRoutes] Error creating strategy:', error);
res.status(500).json({
success: false,
message: 'Failed to create strategy'
});
}
});
router.post('/strategies/:id/activate', authenticate, async (req: Request, res: Response) => {
try {
const { id } = req.params;
const tenantId = (req as any).user?.tenantId;
if (!tenantId) {
return res.status(400).json({
success: false,
message: 'Tenant ID not found'
});
}
const merchantStrategy = await StrategyService.activateStrategy({
tenantId,
strategyId: id,
config: req.body.config
});
res.status(201).json({
success: true,
data: merchantStrategy,
message: 'Strategy activated successfully'
});
} catch (error: any) {
logger.error('[StrategyRoutes] Error activating strategy:', error);
res.status(400).json({
success: false,
message: error.message || 'Failed to activate strategy'
});
}
});
router.get('/my-strategies', authenticate, async (req: Request, res: Response) => {
try {
const tenantId = (req as any).user?.tenantId;
const { status } = req.query;
if (!tenantId) {
return res.status(400).json({
success: false,
message: 'Tenant ID not found'
});
}
const strategies = await StrategyService.getMerchantStrategies(
tenantId,
status as 'ACTIVE' | 'PAUSED' | 'COMPLETED' | 'FAILED'
);
res.json({
success: true,
data: strategies
});
} catch (error) {
logger.error('[StrategyRoutes] Error fetching merchant strategies:', error);
res.status(500).json({
success: false,
message: 'Failed to fetch merchant strategies'
});
}
});
router.post('/my-strategies/:id/pause', authenticate, async (req: Request, res: Response) => {
try {
const { id } = req.params;
const tenantId = (req as any).user?.tenantId;
if (!tenantId) {
return res.status(400).json({
success: false,
message: 'Tenant ID not found'
});
}
await StrategyService.pauseStrategy(id, tenantId);
res.json({
success: true,
message: 'Strategy paused successfully'
});
} catch (error: any) {
logger.error('[StrategyRoutes] Error pausing strategy:', error);
res.status(400).json({
success: false,
message: error.message || 'Failed to pause strategy'
});
}
});
router.post('/my-strategies/:id/resume', authenticate, async (req: Request, res: Response) => {
try {
const { id } = req.params;
const tenantId = (req as any).user?.tenantId;
if (!tenantId) {
return res.status(400).json({
success: false,
message: 'Tenant ID not found'
});
}
await StrategyService.resumeStrategy(id, tenantId);
res.json({
success: true,
message: 'Strategy resumed successfully'
});
} catch (error: any) {
logger.error('[StrategyRoutes] Error resuming strategy:', error);
res.status(400).json({
success: false,
message: error.message || 'Failed to resume strategy'
});
}
});
router.post('/my-strategies/:id/complete', authenticate, async (req: Request, res: Response) => {
try {
const { id } = req.params;
const tenantId = (req as any).user?.tenantId;
const { roi, revenue } = req.body;
if (!tenantId) {
return res.status(400).json({
success: false,
message: 'Tenant ID not found'
});
}
if (roi === undefined || revenue === undefined) {
return res.status(400).json({
success: false,
message: 'ROI and revenue are required'
});
}
await StrategyService.completeStrategy(id, tenantId, { roi, revenue });
res.json({
success: true,
message: 'Strategy completed successfully'
});
} catch (error: any) {
logger.error('[StrategyRoutes] Error completing strategy:', error);
res.status(400).json({
success: false,
message: error.message || 'Failed to complete strategy'
});
}
});
router.get('/recommendations', authenticate, async (req: Request, res: Response) => {
try {
const tenantId = (req as any).user?.tenantId;
if (!tenantId) {
return res.status(400).json({
success: false,
message: 'Tenant ID not found'
});
}
const recommendations = await StrategyRecommendationService.getPersonalizedRecommendations(tenantId);
res.json({
success: true,
data: recommendations
});
} catch (error) {
logger.error('[StrategyRoutes] Error fetching recommendations:', error);
res.status(500).json({
success: false,
message: 'Failed to fetch recommendations'
});
}
});
router.get('/recommendations/category/:category', authenticate, async (req: Request, res: Response) => {
try {
const { category } = req.params;
const tenantId = (req as any).user?.tenantId;
if (!tenantId) {
return res.status(400).json({
success: false,
message: 'Tenant ID not found'
});
}
const recommendations = await StrategyRecommendationService.getCategoryRecommendations(
category,
tenantId
);
res.json({
success: true,
data: recommendations
});
} catch (error) {
logger.error('[StrategyRoutes] Error fetching category recommendations:', error);
res.status(500).json({
success: false,
message: 'Failed to fetch category recommendations'
});
}
});
export default router;