获取概览卡片数据 (/api/analytics/dashboard-cards)
返回包含留言总数、平均互动率和情感分析三个核心指标的卡片数据及环比变化 支持时间范围和项目ID过滤 获取留言趋势数据 (/api/analytics/comment-trend) 返回一段时间内留言数量的变化趋势,用于绘制柱状图 支持时间范围、项目ID和平台过滤 获取平台分布数据 (/api/analytics/platform-distribution) 返回不同社交平台上的评论或互动分布情况 支持时间范围、项目ID和事件类型过滤 获取情感分析详情 (/api/analytics/sentiment-analysis) 返回正面、中性、负面评论的比例和平均情感得分 支持时间范围、项目ID和平台过滤 获取热门文章数据 (/api/analytics/popular-posts) 返回按互动数量或互动率排序的热门帖文列表 支持时间范围、项目ID、平台过滤,以及排序字段和限制返回数量
This commit is contained in:
@@ -401,6 +401,372 @@ export class AnalyticsController {
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取概览卡片数据
|
||||
* 返回包含留言总数、平均互动率和情感分析三个卡片数据
|
||||
*
|
||||
* @param c Hono Context
|
||||
* @returns Response with dashboard card data
|
||||
*/
|
||||
async getDashboardCards(c: Context) {
|
||||
const requestId = `req_${Date.now()}_${Math.random().toString(36).substring(2, 10)}`;
|
||||
const startTime = Date.now();
|
||||
|
||||
try {
|
||||
// 获取查询参数
|
||||
const timeRange = c.req.query('timeRange') || '30'; // 默认30天
|
||||
const projectId = c.req.query('projectId'); // 可选项目过滤
|
||||
|
||||
logger.info(`[${requestId}] Dashboard cards request received`, {
|
||||
timeRange,
|
||||
projectId,
|
||||
userAgent: c.req.header('user-agent'),
|
||||
ip: c.req.header('x-forwarded-for') || 'unknown'
|
||||
});
|
||||
|
||||
// 验证时间范围
|
||||
if (!['7', '30', '90'].includes(timeRange)) {
|
||||
logger.warn(`[${requestId}] Invalid timeRange: ${timeRange}`);
|
||||
return c.json({
|
||||
success: false,
|
||||
error: 'Invalid timeRange. Must be 7, 30, or 90.'
|
||||
}, 400);
|
||||
}
|
||||
|
||||
// 获取概览卡片数据
|
||||
const data = await analyticsService.getDashboardCardData(
|
||||
parseInt(timeRange, 10),
|
||||
projectId
|
||||
);
|
||||
|
||||
// 返回成功响应
|
||||
const duration = Date.now() - startTime;
|
||||
logger.info(`[${requestId}] Dashboard cards response sent successfully`, {
|
||||
duration
|
||||
});
|
||||
|
||||
return c.json({
|
||||
success: true,
|
||||
data
|
||||
});
|
||||
} catch (error) {
|
||||
// 记录错误
|
||||
const duration = Date.now() - startTime;
|
||||
logger.error(`[${requestId}] Error fetching dashboard cards (${duration}ms)`, error);
|
||||
|
||||
// 返回错误响应
|
||||
return c.json({
|
||||
success: false,
|
||||
error: 'Failed to fetch dashboard card data',
|
||||
message: error instanceof Error ? error.message : 'Unknown error'
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取留言趋势数据
|
||||
* 返回一段时间内留言数量的变化趋势
|
||||
*
|
||||
* @param c Hono Context
|
||||
* @returns Response with comment trend data
|
||||
*/
|
||||
async getCommentTrend(c: Context) {
|
||||
const requestId = `req_${Date.now()}_${Math.random().toString(36).substring(2, 10)}`;
|
||||
const startTime = Date.now();
|
||||
|
||||
try {
|
||||
// 获取查询参数
|
||||
const timeRange = c.req.query('timeRange') || '30'; // 默认30天
|
||||
const projectId = c.req.query('projectId'); // 可选项目过滤
|
||||
const platform = c.req.query('platform'); // 可选平台过滤
|
||||
|
||||
logger.info(`[${requestId}] Comment trend request received`, {
|
||||
timeRange,
|
||||
projectId,
|
||||
platform,
|
||||
userAgent: c.req.header('user-agent'),
|
||||
ip: c.req.header('x-forwarded-for') || 'unknown'
|
||||
});
|
||||
|
||||
// 验证时间范围
|
||||
if (!['7', '30', '90'].includes(timeRange)) {
|
||||
logger.warn(`[${requestId}] Invalid timeRange: ${timeRange}`);
|
||||
return c.json({
|
||||
success: false,
|
||||
error: 'Invalid timeRange. Must be 7, 30, or 90.'
|
||||
}, 400);
|
||||
}
|
||||
|
||||
// 获取留言趋势数据
|
||||
const data = await analyticsService.getCommentTrend(
|
||||
parseInt(timeRange, 10),
|
||||
projectId,
|
||||
platform
|
||||
);
|
||||
|
||||
// 返回成功响应
|
||||
const duration = Date.now() - startTime;
|
||||
logger.info(`[${requestId}] Comment trend response sent successfully`, {
|
||||
duration,
|
||||
dataPoints: data.data.length,
|
||||
totalComments: data.total_count
|
||||
});
|
||||
|
||||
return c.json({
|
||||
success: true,
|
||||
data: data.data,
|
||||
metadata: {
|
||||
max_count: data.max_count,
|
||||
total_count: data.total_count
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
// 记录错误
|
||||
const duration = Date.now() - startTime;
|
||||
logger.error(`[${requestId}] Error fetching comment trend (${duration}ms)`, error);
|
||||
|
||||
// 返回错误响应
|
||||
return c.json({
|
||||
success: false,
|
||||
error: 'Failed to fetch comment trend data',
|
||||
message: error instanceof Error ? error.message : 'Unknown error'
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取平台分布数据
|
||||
* 返回不同社交平台上的事件分布情况
|
||||
*
|
||||
* @param c Hono Context
|
||||
* @returns Response with platform distribution data
|
||||
*/
|
||||
async getPlatformDistribution(c: Context) {
|
||||
const requestId = `req_${Date.now()}_${Math.random().toString(36).substring(2, 10)}`;
|
||||
const startTime = Date.now();
|
||||
|
||||
try {
|
||||
// 获取查询参数
|
||||
const timeRange = c.req.query('timeRange') || '30'; // 默认30天
|
||||
const projectId = c.req.query('projectId'); // 可选项目过滤
|
||||
const eventType = c.req.query('eventType') || 'comment'; // 默认分析评论事件
|
||||
|
||||
logger.info(`[${requestId}] Platform distribution request received`, {
|
||||
timeRange,
|
||||
projectId,
|
||||
eventType,
|
||||
userAgent: c.req.header('user-agent'),
|
||||
ip: c.req.header('x-forwarded-for') || 'unknown'
|
||||
});
|
||||
|
||||
// 验证时间范围
|
||||
if (!['7', '30', '90'].includes(timeRange)) {
|
||||
logger.warn(`[${requestId}] Invalid timeRange: ${timeRange}`);
|
||||
return c.json({
|
||||
success: false,
|
||||
error: 'Invalid timeRange. Must be 7, 30, or 90.'
|
||||
}, 400);
|
||||
}
|
||||
|
||||
// 验证事件类型
|
||||
const validEventTypes = ['comment', 'like', 'view', 'share'];
|
||||
if (!validEventTypes.includes(eventType)) {
|
||||
logger.warn(`[${requestId}] Invalid eventType: ${eventType}`);
|
||||
return c.json({
|
||||
success: false,
|
||||
error: `Invalid eventType. Must be one of: ${validEventTypes.join(', ')}`
|
||||
}, 400);
|
||||
}
|
||||
|
||||
// 获取平台分布数据
|
||||
const data = await analyticsService.getPlatformDistribution(
|
||||
parseInt(timeRange, 10),
|
||||
projectId,
|
||||
eventType
|
||||
);
|
||||
|
||||
// 返回成功响应
|
||||
const duration = Date.now() - startTime;
|
||||
logger.info(`[${requestId}] Platform distribution response sent successfully`, {
|
||||
duration,
|
||||
platformCount: data.data.length,
|
||||
total: data.total
|
||||
});
|
||||
|
||||
return c.json({
|
||||
success: true,
|
||||
data: data.data,
|
||||
metadata: {
|
||||
total: data.total,
|
||||
event_type: eventType
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
// 记录错误
|
||||
const duration = Date.now() - startTime;
|
||||
logger.error(`[${requestId}] Error fetching platform distribution (${duration}ms)`, error);
|
||||
|
||||
// 返回错误响应
|
||||
return c.json({
|
||||
success: false,
|
||||
error: 'Failed to fetch platform distribution data',
|
||||
message: error instanceof Error ? error.message : 'Unknown error'
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取情感分析详情数据
|
||||
* 返回正面、中性、负面评论的比例和分析
|
||||
*
|
||||
* @param c Hono Context
|
||||
* @returns Response with sentiment analysis data
|
||||
*/
|
||||
async getSentimentAnalysis(c: Context) {
|
||||
const requestId = `req_${Date.now()}_${Math.random().toString(36).substring(2, 10)}`;
|
||||
const startTime = Date.now();
|
||||
|
||||
try {
|
||||
// 获取查询参数
|
||||
const timeRange = c.req.query('timeRange') || '30'; // 默认30天
|
||||
const projectId = c.req.query('projectId'); // 可选项目过滤
|
||||
const platform = c.req.query('platform'); // 可选平台过滤
|
||||
|
||||
logger.info(`[${requestId}] Sentiment analysis request received`, {
|
||||
timeRange,
|
||||
projectId,
|
||||
platform,
|
||||
userAgent: c.req.header('user-agent'),
|
||||
ip: c.req.header('x-forwarded-for') || 'unknown'
|
||||
});
|
||||
|
||||
// 验证时间范围
|
||||
if (!['7', '30', '90'].includes(timeRange)) {
|
||||
logger.warn(`[${requestId}] Invalid timeRange: ${timeRange}`);
|
||||
return c.json({
|
||||
success: false,
|
||||
error: 'Invalid timeRange. Must be 7, 30, or 90.'
|
||||
}, 400);
|
||||
}
|
||||
|
||||
// 获取情感分析数据
|
||||
const data = await analyticsService.getSentimentAnalysis(
|
||||
parseInt(timeRange, 10),
|
||||
projectId,
|
||||
platform
|
||||
);
|
||||
|
||||
// 返回成功响应
|
||||
const duration = Date.now() - startTime;
|
||||
logger.info(`[${requestId}] Sentiment analysis response sent successfully`, {
|
||||
duration,
|
||||
total: data.total
|
||||
});
|
||||
|
||||
return c.json({
|
||||
success: true,
|
||||
data
|
||||
});
|
||||
} catch (error) {
|
||||
// 记录错误
|
||||
const duration = Date.now() - startTime;
|
||||
logger.error(`[${requestId}] Error fetching sentiment analysis (${duration}ms)`, error);
|
||||
|
||||
// 返回错误响应
|
||||
return c.json({
|
||||
success: false,
|
||||
error: 'Failed to fetch sentiment analysis data',
|
||||
message: error instanceof Error ? error.message : 'Unknown error'
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取热门文章数据
|
||||
* 返回按互动数量或互动率排序的热门帖文
|
||||
*
|
||||
* @param c Hono Context
|
||||
* @returns Response with popular posts data
|
||||
*/
|
||||
async getPopularPosts(c: Context) {
|
||||
const requestId = `req_${Date.now()}_${Math.random().toString(36).substring(2, 10)}`;
|
||||
const startTime = Date.now();
|
||||
|
||||
try {
|
||||
// 获取查询参数
|
||||
const timeRange = c.req.query('timeRange') || '30'; // 默认30天
|
||||
const projectId = c.req.query('projectId'); // 可选项目过滤
|
||||
const platform = c.req.query('platform'); // 可选平台过滤
|
||||
const sortBy = c.req.query('sortBy') || 'engagement_count'; // 默认按互动数量排序
|
||||
const limit = parseInt(c.req.query('limit') || '10', 10); // 默认返回10个
|
||||
|
||||
logger.info(`[${requestId}] Popular posts request received`, {
|
||||
timeRange,
|
||||
projectId,
|
||||
platform,
|
||||
sortBy,
|
||||
limit,
|
||||
userAgent: c.req.header('user-agent'),
|
||||
ip: c.req.header('x-forwarded-for') || 'unknown'
|
||||
});
|
||||
|
||||
// 验证时间范围
|
||||
if (!['7', '30', '90'].includes(timeRange)) {
|
||||
logger.warn(`[${requestId}] Invalid timeRange: ${timeRange}`);
|
||||
return c.json({
|
||||
success: false,
|
||||
error: 'Invalid timeRange. Must be 7, 30, or 90.'
|
||||
}, 400);
|
||||
}
|
||||
|
||||
// 验证排序字段
|
||||
if (!['engagement_count', 'engagement_rate'].includes(sortBy)) {
|
||||
logger.warn(`[${requestId}] Invalid sortBy: ${sortBy}`);
|
||||
return c.json({
|
||||
success: false,
|
||||
error: 'Invalid sortBy. Must be engagement_count or engagement_rate.'
|
||||
}, 400);
|
||||
}
|
||||
|
||||
// 获取热门文章数据
|
||||
const data = await analyticsService.getPopularPosts(
|
||||
parseInt(timeRange, 10),
|
||||
projectId,
|
||||
platform,
|
||||
sortBy,
|
||||
Math.min(limit, 50) // 最多返回50条
|
||||
);
|
||||
|
||||
// 返回成功响应
|
||||
const duration = Date.now() - startTime;
|
||||
logger.info(`[${requestId}] Popular posts response sent successfully`, {
|
||||
duration,
|
||||
resultCount: data.posts.length,
|
||||
total: data.total
|
||||
});
|
||||
|
||||
return c.json({
|
||||
success: true,
|
||||
data: data.posts,
|
||||
metadata: {
|
||||
total: data.total,
|
||||
high_engagement_count: data.posts.filter(post => post.is_high_engagement).length
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
// 记录错误
|
||||
const duration = Date.now() - startTime;
|
||||
logger.error(`[${requestId}] Error fetching popular posts (${duration}ms)`, error);
|
||||
|
||||
// 返回错误响应
|
||||
return c.json({
|
||||
success: false,
|
||||
error: 'Failed to fetch popular posts data',
|
||||
message: error instanceof Error ? error.message : 'Unknown error'
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Export singleton instance
|
||||
|
||||
Reference in New Issue
Block a user