rm mock data
This commit is contained in:
@@ -192,159 +192,111 @@ export class AnalyticsController {
|
||||
* @param c Hono Context
|
||||
* @returns Response with post performance data
|
||||
*/
|
||||
async getPostPerformance(c: Context) {
|
||||
const requestId = `req_${Date.now()}_${Math.random().toString(36).substring(2, 10)}`;
|
||||
async getPostPerformance(c: Context): Promise<Response> {
|
||||
const startTime = Date.now();
|
||||
|
||||
// 解析查询参数
|
||||
const kolId = c.req.query('kolId');
|
||||
const platform = c.req.query('platform');
|
||||
const startDate = c.req.query('startDate');
|
||||
const endDate = c.req.query('endDate');
|
||||
const sortBy = c.req.query('sortBy');
|
||||
const sortOrder = c.req.query('sortOrder');
|
||||
const limit = c.req.query('limit');
|
||||
const offset = c.req.query('offset');
|
||||
|
||||
// 记录请求参数
|
||||
logger.info('Post performance data requested', {
|
||||
kolId, platform, startDate, endDate,
|
||||
sortBy, sortOrder, limit, offset
|
||||
});
|
||||
|
||||
try {
|
||||
// Get query parameters
|
||||
const kolId = c.req.query('kolId'); // Optional KOL filter
|
||||
const platform = c.req.query('platform'); // Optional platform filter
|
||||
const startDate = c.req.query('startDate'); // Optional start date
|
||||
const endDate = c.req.query('endDate'); // Optional end date
|
||||
const sortBy = c.req.query('sortBy') || 'publish_date'; // Default sort by publish date
|
||||
const sortOrder = c.req.query('sortOrder') || 'desc'; // Default to descending order
|
||||
const limit = parseInt(c.req.query('limit') || '20', 10); // Default limit to 20 posts
|
||||
const offset = parseInt(c.req.query('offset') || '0', 10); // Default offset to 0
|
||||
const useMockData = c.req.query('useMockData') === 'true'; // 允许用户强制使用模拟数据
|
||||
|
||||
logger.info(`[${requestId}] Post performance request received`, {
|
||||
kolId,
|
||||
platform,
|
||||
startDate,
|
||||
endDate,
|
||||
sortBy,
|
||||
sortOrder,
|
||||
limit,
|
||||
offset,
|
||||
useMockData,
|
||||
userAgent: c.req.header('user-agent'),
|
||||
ip: c.req.header('x-forwarded-for') || 'unknown'
|
||||
});
|
||||
|
||||
// 如果强制使用模拟数据,直接生成并返回
|
||||
if (useMockData) {
|
||||
logger.info(`[${requestId}] Using mock data as requested`);
|
||||
const mockPosts = this.generateMockPostData(limit, platform, kolId);
|
||||
|
||||
return c.json({
|
||||
success: true,
|
||||
data: mockPosts,
|
||||
pagination: {
|
||||
limit,
|
||||
offset,
|
||||
total: 100 // 模拟总数
|
||||
},
|
||||
is_mock_data: true
|
||||
});
|
||||
}
|
||||
|
||||
// Validate sort order
|
||||
if (!['asc', 'desc'].includes(sortOrder)) {
|
||||
logger.warn(`[${requestId}] Invalid sortOrder: ${sortOrder}`);
|
||||
return c.json({
|
||||
success: false,
|
||||
error: 'Invalid sortOrder. Must be asc or desc.'
|
||||
}, 400);
|
||||
}
|
||||
|
||||
// Validate sort field
|
||||
const validSortFields = ['publish_date', 'views', 'likes', 'comments', 'shares', 'sentiment_score'];
|
||||
if (!validSortFields.includes(sortBy)) {
|
||||
logger.warn(`[${requestId}] Invalid sortBy: ${sortBy}`);
|
||||
return c.json({
|
||||
success: false,
|
||||
error: `Invalid sortBy. Must be one of: ${validSortFields.join(', ')}`
|
||||
}, 400);
|
||||
}
|
||||
|
||||
// Validate date formats if provided
|
||||
// 验证时间范围
|
||||
if (startDate && !this.isValidDateFormat(startDate)) {
|
||||
logger.warn(`[${requestId}] Invalid startDate format: ${startDate}`);
|
||||
return c.json({
|
||||
return c.json({
|
||||
success: false,
|
||||
error: 'Invalid startDate format. Use YYYY-MM-DD.'
|
||||
error: 'Invalid startDate format. Expected YYYY-MM-DD'
|
||||
}, 400);
|
||||
}
|
||||
|
||||
if (endDate && !this.isValidDateFormat(endDate)) {
|
||||
logger.warn(`[${requestId}] Invalid endDate format: ${endDate}`);
|
||||
return c.json({
|
||||
return c.json({
|
||||
success: false,
|
||||
error: 'Invalid endDate format. Use YYYY-MM-DD.'
|
||||
error: 'Invalid endDate format. Expected YYYY-MM-DD'
|
||||
}, 400);
|
||||
}
|
||||
|
||||
// Get post performance data from service
|
||||
const data = await analyticsService.getPostPerformance(
|
||||
kolId,
|
||||
platform,
|
||||
startDate,
|
||||
endDate,
|
||||
sortBy,
|
||||
sortOrder,
|
||||
limit,
|
||||
offset
|
||||
// 验证排序字段
|
||||
const validSortFields = ['publish_date', 'views', 'likes', 'comments', 'shares', 'sentiment_score'];
|
||||
if (sortBy && !validSortFields.includes(sortBy)) {
|
||||
return c.json({
|
||||
success: false,
|
||||
error: `Invalid sortBy field. Expected one of: ${validSortFields.join(', ')}`
|
||||
}, 400);
|
||||
}
|
||||
|
||||
// 验证排序顺序
|
||||
if (sortOrder && !['asc', 'desc', 'ASC', 'DESC'].includes(sortOrder)) {
|
||||
return c.json({
|
||||
success: false,
|
||||
error: 'Invalid sortOrder. Expected: asc or desc'
|
||||
}, 400);
|
||||
}
|
||||
|
||||
// 验证限制数量和偏移量
|
||||
const parsedLimit = limit ? parseInt(limit, 10) : 10;
|
||||
if (isNaN(parsedLimit) || parsedLimit < 1 || parsedLimit > 100) {
|
||||
return c.json({
|
||||
success: false,
|
||||
error: 'Invalid limit. Expected a number between 1 and 100'
|
||||
}, 400);
|
||||
}
|
||||
|
||||
const parsedOffset = offset ? parseInt(offset, 10) : 0;
|
||||
if (isNaN(parsedOffset) || parsedOffset < 0) {
|
||||
return c.json({
|
||||
success: false,
|
||||
error: 'Invalid offset. Expected a non-negative number'
|
||||
}, 400);
|
||||
}
|
||||
|
||||
// 获取帖文表现数据
|
||||
const { posts, total } = await analyticsService.getPostPerformance(
|
||||
kolId || undefined,
|
||||
platform || undefined,
|
||||
startDate || undefined,
|
||||
endDate || undefined,
|
||||
sortBy || 'publish_date',
|
||||
sortOrder || 'desc',
|
||||
parsedLimit,
|
||||
parsedOffset
|
||||
);
|
||||
|
||||
// 检查返回的数据是否包含真实数据(通过检查post_id的格式)
|
||||
const realDataCount = data.posts.filter(post =>
|
||||
!post.post_id.startsWith('mock-')
|
||||
).length;
|
||||
|
||||
const isMockData = realDataCount === 0 && data.posts.length > 0;
|
||||
|
||||
// Log successful response
|
||||
// 返回结果
|
||||
const duration = Date.now() - startTime;
|
||||
logger.info(`[${requestId}] Post performance response sent successfully`, {
|
||||
duration,
|
||||
resultCount: data.posts.length,
|
||||
totalPosts: data.total,
|
||||
realDataCount,
|
||||
mockDataCount: data.posts.length - realDataCount,
|
||||
isMockData
|
||||
logger.info(`Post performance data returned (${duration}ms)`, {
|
||||
totalPosts: total,
|
||||
returnedPosts: posts.length
|
||||
});
|
||||
|
||||
// Return the data
|
||||
return c.json({
|
||||
success: true,
|
||||
data: data.posts,
|
||||
pagination: {
|
||||
limit,
|
||||
offset,
|
||||
total: data.total
|
||||
},
|
||||
is_mock_data: isMockData
|
||||
data: posts,
|
||||
meta: {
|
||||
total,
|
||||
returned: posts.length,
|
||||
limit: parsedLimit,
|
||||
offset: parsedOffset
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
// Log error
|
||||
const duration = Date.now() - startTime;
|
||||
logger.error(`[${requestId}] Error fetching post performance data (${duration}ms)`, error);
|
||||
|
||||
try {
|
||||
// 发生错误时尝试返回模拟数据
|
||||
const mockPosts = this.generateMockPostData(20);
|
||||
logger.info(`[${requestId}] Returning mock data due to error`);
|
||||
|
||||
return c.json({
|
||||
success: true,
|
||||
data: mockPosts,
|
||||
pagination: {
|
||||
limit: 20,
|
||||
offset: 0,
|
||||
total: 100
|
||||
},
|
||||
is_mock_data: true,
|
||||
original_error: error instanceof Error ? error.message : 'Unknown error'
|
||||
});
|
||||
} catch (mockError) {
|
||||
// 如果连模拟数据生成都失败,返回错误响应
|
||||
return c.json({
|
||||
success: false,
|
||||
error: 'Failed to fetch post performance data',
|
||||
message: error instanceof Error ? error.message : 'Unknown error'
|
||||
}, 500);
|
||||
}
|
||||
logger.error(`Error getting post performance data (${duration}ms)`, error);
|
||||
|
||||
return c.json({
|
||||
success: false,
|
||||
error: 'Failed to retrieve post performance data'
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -361,47 +313,6 @@ export class AnalyticsController {
|
||||
return date instanceof Date && !isNaN(date.getTime());
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成模拟贴文数据
|
||||
*/
|
||||
private generateMockPostData(count: number, platform?: string, kolId?: string): any[] {
|
||||
const platforms = platform ? [platform] : ['instagram', 'youtube', 'tiktok', 'facebook', 'twitter'];
|
||||
const kolIds = kolId ? [kolId] : Array.from({length: 10}, (_, i) => `mock-kol-${i+1}`);
|
||||
const kolNames = Array.from({length: 10}, (_, i) => `模拟KOL ${i+1}`);
|
||||
|
||||
return Array.from({length: count}, (_, i) => {
|
||||
const selectedPlatform = platforms[Math.floor(Math.random() * platforms.length)];
|
||||
const kolIndex = Math.floor(Math.random() * kolIds.length);
|
||||
const selectedKolId = kolIds[kolIndex];
|
||||
const selectedKolName = kolId ? `指定KOL` : kolNames[kolIndex % kolNames.length];
|
||||
|
||||
const publishDate = new Date();
|
||||
publishDate.setDate(publishDate.getDate() - Math.floor(Math.random() * 90));
|
||||
|
||||
const views = Math.floor(Math.random() * 10000) + 1000;
|
||||
const likes = Math.floor(views * (Math.random() * 0.2 + 0.05));
|
||||
const comments = Math.floor(likes * (Math.random() * 0.2 + 0.02));
|
||||
const shares = Math.floor(likes * (Math.random() * 0.1 + 0.01));
|
||||
|
||||
return {
|
||||
post_id: `mock-post-${i+1}`,
|
||||
title: `模拟贴文 ${i+1} (${selectedPlatform})`,
|
||||
kol_id: selectedKolId,
|
||||
kol_name: selectedKolName,
|
||||
platform: selectedPlatform,
|
||||
publish_date: publishDate.toISOString(),
|
||||
metrics: {
|
||||
views,
|
||||
likes,
|
||||
comments,
|
||||
shares
|
||||
},
|
||||
sentiment_score: parseFloat((Math.random() * 1.6 - 0.6).toFixed(2)),
|
||||
post_url: `https://${selectedPlatform}.com/post/mock-${i+1}`
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取概览卡片数据
|
||||
* 返回包含留言总数、平均互动率和情感分析三个卡片数据
|
||||
|
||||
@@ -838,7 +838,7 @@ export class AnalyticsService {
|
||||
|
||||
// 合并数据,生成最终结果
|
||||
const transformedPosts: PostPerformanceData[] = postsData.map(post => {
|
||||
// 获取帖文的指标数据,如果没有则使用空值或模拟数据
|
||||
// 获取帖文的指标数据
|
||||
const metrics = metricsMap[post.post_id] || {};
|
||||
const postMetrics = {
|
||||
views: Number(metrics.views || 0),
|
||||
@@ -847,16 +847,10 @@ export class AnalyticsService {
|
||||
shares: Number(metrics.shares || 0)
|
||||
};
|
||||
|
||||
// 有真实数据则使用真实数据,否则生成模拟数据
|
||||
const hasRealMetrics = postMetrics.views > 0 || postMetrics.likes > 0 ||
|
||||
postMetrics.comments > 0 || postMetrics.shares > 0;
|
||||
|
||||
const finalMetrics = hasRealMetrics ? postMetrics : this.generateMockMetrics();
|
||||
|
||||
// 同样,有真实情感分数则使用真实数据,否则生成模拟数据
|
||||
// 获取情感分数
|
||||
const sentimentScore = metrics.sentiment_score !== undefined
|
||||
? Number(metrics.sentiment_score)
|
||||
: this.generateMockSentimentScore();
|
||||
: 0; // 默认为0(中性)
|
||||
|
||||
return {
|
||||
post_id: post.post_id,
|
||||
@@ -865,7 +859,7 @@ export class AnalyticsService {
|
||||
kol_name: post.kol_name || '未知KOL',
|
||||
platform: post.platform || 'unknown',
|
||||
publish_date: post.publish_date,
|
||||
metrics: finalMetrics,
|
||||
metrics: postMetrics,
|
||||
sentiment_score: sentimentScore,
|
||||
post_url: post.post_url || `https://${post.platform || 'example'}.com/post/${post.post_id}`
|
||||
};
|
||||
@@ -893,19 +887,11 @@ export class AnalyticsService {
|
||||
});
|
||||
}
|
||||
|
||||
// 统计真实数据vs模拟数据的比例
|
||||
const realDataCount = transformedPosts.filter(post =>
|
||||
post.metrics.views > 0 || post.metrics.likes > 0 ||
|
||||
post.metrics.comments > 0 || post.metrics.shares > 0
|
||||
).length;
|
||||
|
||||
const duration = Date.now() - startTime;
|
||||
logger.info('KOL post performance data fetched successfully', {
|
||||
duration,
|
||||
resultCount: transformedPosts.length,
|
||||
totalPosts: total,
|
||||
realDataCount,
|
||||
mockDataCount: transformedPosts.length - realDataCount
|
||||
totalPosts: total
|
||||
});
|
||||
|
||||
return {
|
||||
@@ -916,73 +902,13 @@ export class AnalyticsService {
|
||||
const duration = Date.now() - startTime;
|
||||
logger.error(`Error in getPostPerformance (${duration}ms)`, error);
|
||||
|
||||
// 发生错误时,尝试返回模拟数据
|
||||
try {
|
||||
const mockPosts = this.generateMockPostPerformanceData(limit);
|
||||
logger.info('Returning mock data due to error', {
|
||||
mockDataCount: mockPosts.length,
|
||||
error: error instanceof Error ? error.message : 'Unknown error'
|
||||
});
|
||||
|
||||
return {
|
||||
posts: mockPosts,
|
||||
total: 100 // 模拟总数
|
||||
};
|
||||
} catch (mockError) {
|
||||
// 如果连模拟数据都无法生成,则抛出原始错误
|
||||
throw error;
|
||||
}
|
||||
// 发生错误时返回空数据
|
||||
return {
|
||||
posts: [],
|
||||
total: 0
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成模拟贴文互动指标
|
||||
*/
|
||||
private generateMockMetrics(): {views: number, likes: number, comments: number, shares: number} {
|
||||
// 生成在合理范围内的随机数
|
||||
const views = Math.floor(Math.random() * 10000) + 1000;
|
||||
const likes = Math.floor(views * (Math.random() * 0.2 + 0.05)); // 5-25% 的观看转化为点赞
|
||||
const comments = Math.floor(likes * (Math.random() * 0.2 + 0.02)); // 2-22% 的点赞转化为评论
|
||||
const shares = Math.floor(likes * (Math.random() * 0.1 + 0.01)); // 1-11% 的点赞转化为分享
|
||||
|
||||
return { views, likes, comments, shares };
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成模拟情感分数 (-1 到 1 之间)
|
||||
*/
|
||||
private generateMockSentimentScore(): number {
|
||||
// 生成-1到1之间的随机数,倾向于正面情绪
|
||||
return parseFloat((Math.random() * 1.6 - 0.6).toFixed(2));
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成完整的模拟贴文表现数据
|
||||
*/
|
||||
private generateMockPostPerformanceData(count: number): PostPerformanceData[] {
|
||||
const platforms = ['instagram', 'youtube', 'tiktok', 'facebook', 'twitter'];
|
||||
const mockPosts: PostPerformanceData[] = [];
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
const platform = platforms[Math.floor(Math.random() * platforms.length)];
|
||||
const publishDate = new Date();
|
||||
publishDate.setDate(publishDate.getDate() - Math.floor(Math.random() * 90));
|
||||
|
||||
mockPosts.push({
|
||||
post_id: `mock-post-${i+1}`,
|
||||
title: `模拟贴文 ${i+1}`,
|
||||
kol_id: `mock-kol-${Math.floor(Math.random() * 10) + 1}`,
|
||||
kol_name: `模拟KOL ${Math.floor(Math.random() * 10) + 1}`,
|
||||
platform,
|
||||
publish_date: publishDate.toISOString(),
|
||||
metrics: this.generateMockMetrics(),
|
||||
sentiment_score: this.generateMockSentimentScore(),
|
||||
post_url: `https://${platform}.com/post/mock-${i+1}`
|
||||
});
|
||||
}
|
||||
|
||||
return mockPosts;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取概览卡片数据
|
||||
|
||||
@@ -383,11 +383,7 @@ const Analytics: React.FC = () => {
|
||||
const fetchAnalyticsData = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
|
||||
// 删除平台分布的硬编码数据,使用API数据替代
|
||||
|
||||
// 删除情感分析的硬编码数据,使用API数据替代
|
||||
|
||||
|
||||
// Set mock status data
|
||||
setStatusData([
|
||||
{ name: 'approved', value: 45, color: '#10B981' },
|
||||
@@ -395,14 +391,6 @@ const Analytics: React.FC = () => {
|
||||
{ name: 'rejected', value: 25, color: '#EF4444' }
|
||||
]);
|
||||
|
||||
// 删除热门文章的硬编码数据,使用API数据替代
|
||||
// setPopularArticles([
|
||||
// { id: '1', title: 'How to Increase Engagement', views: 1200, engagement: 85, platform: 'Facebook' },
|
||||
// { id: '2', title: 'Top 10 Marketing Strategies', views: 980, engagement: 72, platform: 'LinkedIn' },
|
||||
// { id: '3', title: 'Social Media in 2023', views: 850, engagement: 68, platform: 'Twitter' },
|
||||
// { id: '4', title: 'Building Your Brand Online', views: 750, engagement: 63, platform: 'Instagram' },
|
||||
// { id: '5', title: 'Content Creation Tips', views: 620, engagement: 57, platform: 'YouTube' }
|
||||
// ]);
|
||||
|
||||
// 从API获取漏斗数据
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user