KOL贴文表现

This commit is contained in:
2025-03-13 21:36:26 +08:00
parent 72c040cf19
commit f9ba8a73ba
8 changed files with 2008 additions and 0 deletions

View File

@@ -184,6 +184,223 @@ export class AnalyticsController {
}, 500);
}
}
/**
* Get KOL post performance data
* Returns table data of posts with key metrics and sentiment scores
*
* @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)}`;
const startTime = Date.now();
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({
success: false,
error: 'Invalid startDate format. Use YYYY-MM-DD.'
}, 400);
}
if (endDate && !this.isValidDateFormat(endDate)) {
logger.warn(`[${requestId}] Invalid endDate format: ${endDate}`);
return c.json({
success: false,
error: 'Invalid endDate format. Use YYYY-MM-DD.'
}, 400);
}
// Get post performance data from service
const data = await analyticsService.getPostPerformance(
kolId,
platform,
startDate,
endDate,
sortBy,
sortOrder,
limit,
offset
);
// 检查返回的数据是否包含真实数据(通过检查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
});
// Return the data
return c.json({
success: true,
data: data.posts,
pagination: {
limit,
offset,
total: data.total
},
is_mock_data: isMockData
});
} 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);
}
}
}
/**
* Validate date string format (YYYY-MM-DD)
* @param dateString Date string to validate
* @returns True if valid date format
*/
private isValidDateFormat(dateString: string): boolean {
const regex = /^\d{4}-\d{2}-\d{2}$/;
if (!regex.test(dateString)) return false;
const date = new Date(dateString);
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}`
};
});
}
}
// Export singleton instance