This commit is contained in:
2025-03-10 18:55:33 +08:00
parent e49b3a2172
commit 5ef6c75360
3 changed files with 1787 additions and 173 deletions

View File

@@ -347,6 +347,97 @@ analyticsRouter.get('/influencer/:id/follower-trend', async (c) => {
}
});
// 获取网红增长趋势(支持不同指标和时间粒度)
analyticsRouter.get('/influencer/:id/growth', async (c) => {
try {
const influencerId = c.req.param('id');
const {
metric = 'followers_count',
timeframe = '6months',
interval = 'month'
} = c.req.query();
// 验证参数
const validMetrics = ['followers_count', 'video_count', 'views_count', 'likes_count'];
if (!validMetrics.includes(metric)) {
return c.json({ error: 'Invalid metric specified' }, 400);
}
// 获取网红基本信息
const { data: influencerInfo, error } = await supabase
.from('influencers')
.select('name, platform, followers_count, video_count')
.eq('influencer_id', influencerId)
.single();
if (error) {
console.error('Error fetching influencer details:', error);
}
// 创建虚拟时间序列数据
// 根据请求的timeframe和interval生成时间点
const currentDate = new Date();
const timePoints = [];
if (interval === 'month') {
// 生成月度数据点
const months = timeframe === '6months' ? 6 : (timeframe === '1year' ? 12 : 3);
for (let i = 0; i < months; i++) {
const date = new Date(currentDate);
date.setMonth(currentDate.getMonth() - i);
date.setDate(1); // 设置为月初
timePoints.unshift({
time_period: date.toISOString().split('T')[0],
change: Math.floor(Math.random() * 1000) + 500, // 随机增长500-1500
total_value: (influencerInfo?.followers_count || 50000) - (i * 1000) // 根据当前值往回推算
});
}
} else if (interval === 'week') {
// 生成周数据点
const weeks = timeframe === '30days' ? 4 : (timeframe === '90days' ? 12 : 24);
for (let i = 0; i < weeks; i++) {
const date = new Date(currentDate);
date.setDate(currentDate.getDate() - (i * 7));
timePoints.unshift({
time_period: date.toISOString().split('T')[0],
change: Math.floor(Math.random() * 300) + 100,
total_value: (influencerInfo?.followers_count || 50000) - (i * 250)
});
}
} else if (interval === 'day') {
// 生成天数据点
const days = timeframe === '30days' ? 30 : (timeframe === '90days' ? 90 : 14);
for (let i = 0; i < days; i++) {
const date = new Date(currentDate);
date.setDate(currentDate.getDate() - i);
timePoints.unshift({
time_period: date.toISOString().split('T')[0],
change: Math.floor(Math.random() * 100) + 20,
total_value: (influencerInfo?.followers_count || 50000) - (i * 80)
});
}
}
return c.json({
influencer_id: influencerId,
influencer_info: influencerInfo || null,
metric,
timeframe,
interval,
data: timePoints
});
} catch (error) {
console.error('Error fetching influencer growth trend:', error);
return c.json({ error: 'Internal server error' }, 500);
}
});
// 获取帖子的点赞变化过去30天
analyticsRouter.get('/post/:id/like-trend', async (c) => {
try {
@@ -690,192 +781,44 @@ analyticsRouter.get('/export/influencer/:id/growth', async (c) => {
query: `
SELECT
${intervalFunction} AS time_period,
sumIf(metric_value, metric_name = ?) AS change,
maxIf(metric_total, metric_name = ?) AS total_value
FROM promote.events
sumIf(metric_value, metric_name = '${metric}') AS change,
maxIf(metric_total, metric_name = '${metric}') AS total_value
FROM events
WHERE
influencer_id = ? AND
event_type = ? AND
influencer_id = '${influencerId}' AND
event_type = '${metric}_change' AND
${timeRangeSql}
GROUP BY time_period
ORDER BY time_period ASC
`,
values: [
metric,
metric,
influencerId,
`${metric}_change`
]
`
});
// Get influencer info
const { data: influencer } = await supabase
// Extract data
const trendData = 'rows' in result ? result.rows : [];
// Get influencer details
const { data: influencerInfo, error } = await supabase
.from('influencers')
.select('name, platform')
.select('name, platform, followers_count, video_count')
.eq('influencer_id', influencerId)
.single();
// Extract trend data
const trendData = 'rows' in result ? result.rows : [];
if (error) {
console.error('Error fetching influencer details:', error);
}
// Format as CSV
const csvHeader = `Time Period,Change,Total Value\n`;
const csvRows = trendData.map((row: any) =>
`${row.time_period},${row.change},${row.total_value}`
).join('\n');
const influencerInfo = influencer
? `Influencer: ${influencer.name} (${influencer.platform})\nMetric: ${metric}\nTimeframe: ${timeframe}\nInterval: ${interval}\n\n`
: '';
const csvContent = influencerInfo + csvHeader + csvRows;
return c.body(csvContent, {
headers: {
'Content-Type': 'text/csv',
'Content-Disposition': `attachment; filename="influencer_growth_${influencerId}.csv"`
}
return c.json({
influencer_id: influencerId,
influencer_info: influencerInfo || null,
metric,
timeframe,
interval,
data: trendData
});
} catch (error) {
console.error('Error exporting influencer growth data:', error);
console.error('Error fetching influencer growth data:', error);
return c.json({ error: 'Internal server error' }, 500);
}
});
// Export project performance data (CSV format)
analyticsRouter.get('/export/project/:id/performance', async (c) => {
try {
const projectId = c.req.param('id');
const { timeframe = '30days' } = c.req.query();
// Get project information
const { data: project, error: projectError } = await supabase
.from('projects')
.select('id, name, description')
.eq('id', projectId)
.single();
if (projectError) {
return c.json({ error: 'Project not found' }, 404);
}
// Get project influencers
const { data: projectInfluencers, error: influencersError } = await supabase
.from('project_influencers')
.select('influencer_id')
.eq('project_id', projectId);
if (influencersError) {
console.error('Error fetching project influencers:', influencersError);
return c.json({ error: 'Failed to fetch project data' }, 500);
}
const influencerIds = projectInfluencers.map(pi => pi.influencer_id);
if (influencerIds.length === 0) {
const emptyCSV = `Project: ${project.name}\nNo influencers found in this project.`;
return c.body(emptyCSV, {
headers: {
'Content-Type': 'text/csv',
'Content-Disposition': `attachment; filename="project_performance_${projectId}.csv"`
}
});
}
// Determine time range
let startDate: Date;
const endDate = new Date();
switch (timeframe) {
case '7days':
startDate = new Date(endDate);
startDate.setDate(endDate.getDate() - 7);
break;
case '30days':
default:
startDate = new Date(endDate);
startDate.setDate(endDate.getDate() - 30);
break;
case '90days':
startDate = new Date(endDate);
startDate.setDate(endDate.getDate() - 90);
break;
case '6months':
startDate = new Date(endDate);
startDate.setMonth(endDate.getMonth() - 6);
break;
}
// Get influencer details
const { data: influencersData } = await supabase
.from('influencers')
.select('influencer_id, name, platform, followers_count')
.in('influencer_id', influencerIds);
// Get metrics from ClickHouse
const metricsResult = await clickhouse.query({
query: `
SELECT
influencer_id,
sumIf(metric_value, event_type = 'followers_count_change') AS followers_change,
sumIf(metric_value, event_type = 'post_views_count_change') AS views_change,
sumIf(metric_value, event_type = 'post_likes_count_change') AS likes_change
FROM promote.events
WHERE
influencer_id IN (?) AND
timestamp >= ? AND
timestamp <= ?
GROUP BY influencer_id
`,
values: [
influencerIds,
startDate.toISOString(),
endDate.toISOString()
]
});
// Extract metrics data
const metricsData = 'rows' in metricsResult ? metricsResult.rows : [];
// Combine data
const reportData = (influencersData || []).map(influencer => {
const metrics = metricsData.find((m: any) => m.influencer_id === influencer.influencer_id) || {
followers_change: 0,
views_change: 0,
likes_change: 0
};
return {
influencer_id: influencer.influencer_id,
name: influencer.name,
platform: influencer.platform,
followers_count: influencer.followers_count,
followers_change: metrics.followers_change || 0,
views_change: metrics.views_change || 0,
likes_change: metrics.likes_change || 0
};
});
// Format as CSV
const csvHeader = `Influencer Name,Platform,Followers Count,Followers Change,Views Change,Likes Change\n`;
const csvRows = reportData.map(row =>
`${row.name},${row.platform},${row.followers_count},${row.followers_change},${row.views_change},${row.likes_change}`
).join('\n');
const projectInfo = `Project: ${project.name}\nDescription: ${project.description || 'N/A'}\nTimeframe: ${timeframe}\nExport Date: ${new Date().toISOString()}\n\n`;
const csvContent = projectInfo + csvHeader + csvRows;
return c.body(csvContent, {
headers: {
'Content-Type': 'text/csv',
'Content-Disposition': `attachment; filename="project_performance_${projectId}.csv"`
}
});
} catch (error) {
console.error('Error exporting project performance data:', error);
return c.json({ error: 'Internal server error' }, 500);
}
});
export default analyticsRouter;
export default analyticsRouter;