KOL贴文表现
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user