KOL贴文表现
This commit is contained in:
546
backend/db/sql/clickhouse/insert_events_simple.sql
Normal file
546
backend/db/sql/clickhouse/insert_events_simple.sql
Normal file
@@ -0,0 +1,546 @@
|
|||||||
|
-- 为post_1_1添加view事件
|
||||||
|
INSERT INTO
|
||||||
|
events (
|
||||||
|
user_id,
|
||||||
|
influencer_id,
|
||||||
|
content_id,
|
||||||
|
project_id,
|
||||||
|
event_type,
|
||||||
|
funnel_stage,
|
||||||
|
platform,
|
||||||
|
content_type,
|
||||||
|
sentiment
|
||||||
|
)
|
||||||
|
VALUES
|
||||||
|
(
|
||||||
|
'user_1',
|
||||||
|
'influencer_1',
|
||||||
|
'post_1_1',
|
||||||
|
'project_3',
|
||||||
|
'view',
|
||||||
|
'exposure',
|
||||||
|
'Twitter',
|
||||||
|
'text',
|
||||||
|
'positive'
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'user_2',
|
||||||
|
'influencer_1',
|
||||||
|
'post_1_1',
|
||||||
|
'project_3',
|
||||||
|
'view',
|
||||||
|
'exposure',
|
||||||
|
'Twitter',
|
||||||
|
'text',
|
||||||
|
'positive'
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'user_3',
|
||||||
|
'influencer_1',
|
||||||
|
'post_1_1',
|
||||||
|
'project_3',
|
||||||
|
'view',
|
||||||
|
'exposure',
|
||||||
|
'Twitter',
|
||||||
|
'text',
|
||||||
|
'neutral'
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'user_4',
|
||||||
|
'influencer_1',
|
||||||
|
'post_1_1',
|
||||||
|
'project_3',
|
||||||
|
'view',
|
||||||
|
'exposure',
|
||||||
|
'Twitter',
|
||||||
|
'text',
|
||||||
|
'neutral'
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'user_5',
|
||||||
|
'influencer_1',
|
||||||
|
'post_1_1',
|
||||||
|
'project_3',
|
||||||
|
'view',
|
||||||
|
'exposure',
|
||||||
|
'Twitter',
|
||||||
|
'text',
|
||||||
|
'negative'
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 为post_1_1添加like事件
|
||||||
|
INSERT INTO
|
||||||
|
events (
|
||||||
|
user_id,
|
||||||
|
influencer_id,
|
||||||
|
content_id,
|
||||||
|
project_id,
|
||||||
|
event_type,
|
||||||
|
funnel_stage,
|
||||||
|
platform,
|
||||||
|
content_type,
|
||||||
|
sentiment
|
||||||
|
)
|
||||||
|
VALUES
|
||||||
|
(
|
||||||
|
'user_1',
|
||||||
|
'influencer_1',
|
||||||
|
'post_1_1',
|
||||||
|
'project_3',
|
||||||
|
'like',
|
||||||
|
'interest',
|
||||||
|
'Twitter',
|
||||||
|
'text',
|
||||||
|
'positive'
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'user_2',
|
||||||
|
'influencer_1',
|
||||||
|
'post_1_1',
|
||||||
|
'project_3',
|
||||||
|
'like',
|
||||||
|
'interest',
|
||||||
|
'Twitter',
|
||||||
|
'text',
|
||||||
|
'positive'
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 为post_1_1添加comment事件
|
||||||
|
INSERT INTO
|
||||||
|
events (
|
||||||
|
user_id,
|
||||||
|
influencer_id,
|
||||||
|
content_id,
|
||||||
|
project_id,
|
||||||
|
event_type,
|
||||||
|
funnel_stage,
|
||||||
|
platform,
|
||||||
|
content_type,
|
||||||
|
sentiment,
|
||||||
|
comment_text
|
||||||
|
)
|
||||||
|
VALUES
|
||||||
|
(
|
||||||
|
'user_1',
|
||||||
|
'influencer_1',
|
||||||
|
'post_1_1',
|
||||||
|
'project_3',
|
||||||
|
'comment',
|
||||||
|
'consideration',
|
||||||
|
'Twitter',
|
||||||
|
'text',
|
||||||
|
'positive',
|
||||||
|
'很期待这次直播!'
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 为post_2_1添加事件
|
||||||
|
INSERT INTO
|
||||||
|
events (
|
||||||
|
user_id,
|
||||||
|
influencer_id,
|
||||||
|
content_id,
|
||||||
|
project_id,
|
||||||
|
event_type,
|
||||||
|
funnel_stage,
|
||||||
|
platform,
|
||||||
|
content_type,
|
||||||
|
sentiment
|
||||||
|
)
|
||||||
|
VALUES
|
||||||
|
(
|
||||||
|
'user_10',
|
||||||
|
'influencer_2',
|
||||||
|
'post_2_1',
|
||||||
|
'project_2',
|
||||||
|
'view',
|
||||||
|
'exposure',
|
||||||
|
'TikTok',
|
||||||
|
'video',
|
||||||
|
'positive'
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'user_11',
|
||||||
|
'influencer_2',
|
||||||
|
'post_2_1',
|
||||||
|
'project_2',
|
||||||
|
'view',
|
||||||
|
'exposure',
|
||||||
|
'TikTok',
|
||||||
|
'video',
|
||||||
|
'positive'
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'user_12',
|
||||||
|
'influencer_2',
|
||||||
|
'post_2_1',
|
||||||
|
'project_2',
|
||||||
|
'view',
|
||||||
|
'exposure',
|
||||||
|
'TikTok',
|
||||||
|
'video',
|
||||||
|
'neutral'
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'user_10',
|
||||||
|
'influencer_2',
|
||||||
|
'post_2_1',
|
||||||
|
'project_2',
|
||||||
|
'like',
|
||||||
|
'interest',
|
||||||
|
'TikTok',
|
||||||
|
'video',
|
||||||
|
'positive'
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'user_11',
|
||||||
|
'influencer_2',
|
||||||
|
'post_2_1',
|
||||||
|
'project_2',
|
||||||
|
'comment',
|
||||||
|
'consideration',
|
||||||
|
'TikTok',
|
||||||
|
'video',
|
||||||
|
'positive'
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'user_12',
|
||||||
|
'influencer_2',
|
||||||
|
'post_2_1',
|
||||||
|
'project_2',
|
||||||
|
'share',
|
||||||
|
'intent',
|
||||||
|
'TikTok',
|
||||||
|
'video',
|
||||||
|
'positive'
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 为post_6_1添加事件
|
||||||
|
INSERT INTO
|
||||||
|
events (
|
||||||
|
user_id,
|
||||||
|
influencer_id,
|
||||||
|
content_id,
|
||||||
|
project_id,
|
||||||
|
event_type,
|
||||||
|
funnel_stage,
|
||||||
|
platform,
|
||||||
|
content_type,
|
||||||
|
sentiment
|
||||||
|
)
|
||||||
|
VALUES
|
||||||
|
(
|
||||||
|
'user_20',
|
||||||
|
'influencer_6',
|
||||||
|
'post_6_1',
|
||||||
|
'project_3',
|
||||||
|
'view',
|
||||||
|
'exposure',
|
||||||
|
'Instagram',
|
||||||
|
'image',
|
||||||
|
'positive'
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'user_21',
|
||||||
|
'influencer_6',
|
||||||
|
'post_6_1',
|
||||||
|
'project_3',
|
||||||
|
'view',
|
||||||
|
'exposure',
|
||||||
|
'Instagram',
|
||||||
|
'image',
|
||||||
|
'positive'
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'user_22',
|
||||||
|
'influencer_6',
|
||||||
|
'post_6_1',
|
||||||
|
'project_3',
|
||||||
|
'view',
|
||||||
|
'exposure',
|
||||||
|
'Instagram',
|
||||||
|
'image',
|
||||||
|
'neutral'
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'user_23',
|
||||||
|
'influencer_6',
|
||||||
|
'post_6_1',
|
||||||
|
'project_3',
|
||||||
|
'view',
|
||||||
|
'exposure',
|
||||||
|
'Instagram',
|
||||||
|
'image',
|
||||||
|
'negative'
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'user_20',
|
||||||
|
'influencer_6',
|
||||||
|
'post_6_1',
|
||||||
|
'project_3',
|
||||||
|
'like',
|
||||||
|
'interest',
|
||||||
|
'Instagram',
|
||||||
|
'image',
|
||||||
|
'positive'
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'user_21',
|
||||||
|
'influencer_6',
|
||||||
|
'post_6_1',
|
||||||
|
'project_3',
|
||||||
|
'like',
|
||||||
|
'interest',
|
||||||
|
'Instagram',
|
||||||
|
'image',
|
||||||
|
'positive'
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'user_22',
|
||||||
|
'influencer_6',
|
||||||
|
'post_6_1',
|
||||||
|
'project_3',
|
||||||
|
'comment',
|
||||||
|
'consideration',
|
||||||
|
'Instagram',
|
||||||
|
'image',
|
||||||
|
'neutral'
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'user_20',
|
||||||
|
'influencer_6',
|
||||||
|
'post_6_1',
|
||||||
|
'project_3',
|
||||||
|
'share',
|
||||||
|
'intent',
|
||||||
|
'Instagram',
|
||||||
|
'image',
|
||||||
|
'positive'
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 为post_9_1添加事件
|
||||||
|
INSERT INTO
|
||||||
|
events (
|
||||||
|
user_id,
|
||||||
|
influencer_id,
|
||||||
|
content_id,
|
||||||
|
project_id,
|
||||||
|
event_type,
|
||||||
|
funnel_stage,
|
||||||
|
platform,
|
||||||
|
content_type,
|
||||||
|
sentiment
|
||||||
|
)
|
||||||
|
VALUES
|
||||||
|
(
|
||||||
|
'user_30',
|
||||||
|
'influencer_9',
|
||||||
|
'post_9_1',
|
||||||
|
'project_2',
|
||||||
|
'view',
|
||||||
|
'exposure',
|
||||||
|
'TikTok',
|
||||||
|
'video',
|
||||||
|
'positive'
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'user_31',
|
||||||
|
'influencer_9',
|
||||||
|
'post_9_1',
|
||||||
|
'project_2',
|
||||||
|
'view',
|
||||||
|
'exposure',
|
||||||
|
'TikTok',
|
||||||
|
'video',
|
||||||
|
'positive'
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'user_32',
|
||||||
|
'influencer_9',
|
||||||
|
'post_9_1',
|
||||||
|
'project_2',
|
||||||
|
'view',
|
||||||
|
'exposure',
|
||||||
|
'TikTok',
|
||||||
|
'video',
|
||||||
|
'neutral'
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'user_33',
|
||||||
|
'influencer_9',
|
||||||
|
'post_9_1',
|
||||||
|
'project_2',
|
||||||
|
'view',
|
||||||
|
'exposure',
|
||||||
|
'TikTok',
|
||||||
|
'video',
|
||||||
|
'positive'
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'user_34',
|
||||||
|
'influencer_9',
|
||||||
|
'post_9_1',
|
||||||
|
'project_2',
|
||||||
|
'view',
|
||||||
|
'exposure',
|
||||||
|
'TikTok',
|
||||||
|
'video',
|
||||||
|
'negative'
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'user_35',
|
||||||
|
'influencer_9',
|
||||||
|
'post_9_1',
|
||||||
|
'project_2',
|
||||||
|
'view',
|
||||||
|
'exposure',
|
||||||
|
'TikTok',
|
||||||
|
'video',
|
||||||
|
'positive'
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'user_30',
|
||||||
|
'influencer_9',
|
||||||
|
'post_9_1',
|
||||||
|
'project_2',
|
||||||
|
'like',
|
||||||
|
'interest',
|
||||||
|
'TikTok',
|
||||||
|
'video',
|
||||||
|
'positive'
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'user_31',
|
||||||
|
'influencer_9',
|
||||||
|
'post_9_1',
|
||||||
|
'project_2',
|
||||||
|
'like',
|
||||||
|
'interest',
|
||||||
|
'TikTok',
|
||||||
|
'video',
|
||||||
|
'positive'
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'user_32',
|
||||||
|
'influencer_9',
|
||||||
|
'post_9_1',
|
||||||
|
'project_2',
|
||||||
|
'like',
|
||||||
|
'interest',
|
||||||
|
'TikTok',
|
||||||
|
'video',
|
||||||
|
'neutral'
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'user_30',
|
||||||
|
'influencer_9',
|
||||||
|
'post_9_1',
|
||||||
|
'project_2',
|
||||||
|
'comment',
|
||||||
|
'consideration',
|
||||||
|
'TikTok',
|
||||||
|
'video',
|
||||||
|
'positive'
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'user_31',
|
||||||
|
'influencer_9',
|
||||||
|
'post_9_1',
|
||||||
|
'project_2',
|
||||||
|
'share',
|
||||||
|
'intent',
|
||||||
|
'TikTok',
|
||||||
|
'video',
|
||||||
|
'positive'
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 为post_4_1添加事件
|
||||||
|
INSERT INTO
|
||||||
|
events (
|
||||||
|
user_id,
|
||||||
|
influencer_id,
|
||||||
|
content_id,
|
||||||
|
project_id,
|
||||||
|
event_type,
|
||||||
|
funnel_stage,
|
||||||
|
platform,
|
||||||
|
content_type,
|
||||||
|
sentiment
|
||||||
|
)
|
||||||
|
VALUES
|
||||||
|
(
|
||||||
|
'user_40',
|
||||||
|
'influencer_4',
|
||||||
|
'post_4_1',
|
||||||
|
'project_2',
|
||||||
|
'view',
|
||||||
|
'exposure',
|
||||||
|
'Facebook',
|
||||||
|
'image',
|
||||||
|
'positive'
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'user_41',
|
||||||
|
'influencer_4',
|
||||||
|
'post_4_1',
|
||||||
|
'project_2',
|
||||||
|
'view',
|
||||||
|
'exposure',
|
||||||
|
'Facebook',
|
||||||
|
'image',
|
||||||
|
'positive'
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'user_42',
|
||||||
|
'influencer_4',
|
||||||
|
'post_4_1',
|
||||||
|
'project_2',
|
||||||
|
'view',
|
||||||
|
'exposure',
|
||||||
|
'Facebook',
|
||||||
|
'image',
|
||||||
|
'neutral'
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'user_43',
|
||||||
|
'influencer_4',
|
||||||
|
'post_4_1',
|
||||||
|
'project_2',
|
||||||
|
'view',
|
||||||
|
'exposure',
|
||||||
|
'Facebook',
|
||||||
|
'image',
|
||||||
|
'negative'
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'user_40',
|
||||||
|
'influencer_4',
|
||||||
|
'post_4_1',
|
||||||
|
'project_2',
|
||||||
|
'like',
|
||||||
|
'interest',
|
||||||
|
'Facebook',
|
||||||
|
'image',
|
||||||
|
'positive'
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'user_41',
|
||||||
|
'influencer_4',
|
||||||
|
'post_4_1',
|
||||||
|
'project_2',
|
||||||
|
'comment',
|
||||||
|
'consideration',
|
||||||
|
'Facebook',
|
||||||
|
'image',
|
||||||
|
'neutral'
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'user_40',
|
||||||
|
'influencer_4',
|
||||||
|
'post_4_1',
|
||||||
|
'project_2',
|
||||||
|
'share',
|
||||||
|
'intent',
|
||||||
|
'Facebook',
|
||||||
|
'image',
|
||||||
|
'positive'
|
||||||
|
);
|
||||||
219
backend/db/sql/clickhouse/insert_post_events.sql
Normal file
219
backend/db/sql/clickhouse/insert_post_events.sql
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
-- 为新增的posts添加互动事件数据
|
||||||
|
INSERT INTO
|
||||||
|
events (
|
||||||
|
user_id,
|
||||||
|
influencer_id,
|
||||||
|
content_id,
|
||||||
|
project_id,
|
||||||
|
event_type,
|
||||||
|
funnel_stage,
|
||||||
|
platform,
|
||||||
|
content_type,
|
||||||
|
content_status,
|
||||||
|
sentiment,
|
||||||
|
timestamp,
|
||||||
|
date
|
||||||
|
) -- 为post_1_1添加事件数据(Twitter文本帖文)
|
||||||
|
SELECT
|
||||||
|
concat('user_', toString(number % 500 + 1)),
|
||||||
|
'influencer_1',
|
||||||
|
'post_1_1',
|
||||||
|
'project_3',
|
||||||
|
multiIf(
|
||||||
|
number % 10 < 5,
|
||||||
|
'view',
|
||||||
|
number % 10 < 7,
|
||||||
|
'like',
|
||||||
|
number % 10 < 8,
|
||||||
|
'comment',
|
||||||
|
number % 10 < 9,
|
||||||
|
'share',
|
||||||
|
'impression'
|
||||||
|
),
|
||||||
|
multiIf(
|
||||||
|
number % 10 < 3,
|
||||||
|
'exposure',
|
||||||
|
number % 10 < 6,
|
||||||
|
'interest',
|
||||||
|
number % 10 < 8,
|
||||||
|
'consideration',
|
||||||
|
'intent'
|
||||||
|
),
|
||||||
|
'Twitter',
|
||||||
|
'text',
|
||||||
|
'approved',
|
||||||
|
multiIf(
|
||||||
|
number % 3 = 0,
|
||||||
|
'positive',
|
||||||
|
number % 3 = 1,
|
||||||
|
'neutral',
|
||||||
|
'negative'
|
||||||
|
),
|
||||||
|
now() - INTERVAL (number % 48) HOUR,
|
||||||
|
today() - INTERVAL (number % 2) DAY
|
||||||
|
FROM
|
||||||
|
numbers(1, 350)
|
||||||
|
UNION
|
||||||
|
ALL -- 为post_2_1添加事件数据(TikTok视频帖文)
|
||||||
|
SELECT
|
||||||
|
concat('user_', toString(number % 500 + 1)),
|
||||||
|
'influencer_2',
|
||||||
|
'post_2_1',
|
||||||
|
'project_2',
|
||||||
|
multiIf(
|
||||||
|
number % 10 < 5,
|
||||||
|
'view',
|
||||||
|
number % 10 < 7,
|
||||||
|
'like',
|
||||||
|
number % 10 < 8,
|
||||||
|
'comment',
|
||||||
|
number % 10 < 9,
|
||||||
|
'share',
|
||||||
|
'impression'
|
||||||
|
),
|
||||||
|
multiIf(
|
||||||
|
number % 10 < 3,
|
||||||
|
'exposure',
|
||||||
|
number % 10 < 6,
|
||||||
|
'interest',
|
||||||
|
number % 10 < 8,
|
||||||
|
'consideration',
|
||||||
|
'intent'
|
||||||
|
),
|
||||||
|
'TikTok',
|
||||||
|
'video',
|
||||||
|
'approved',
|
||||||
|
multiIf(
|
||||||
|
number % 3 = 0,
|
||||||
|
'positive',
|
||||||
|
number % 3 = 1,
|
||||||
|
'neutral',
|
||||||
|
'negative'
|
||||||
|
),
|
||||||
|
now() - INTERVAL (number % 24) HOUR,
|
||||||
|
today() - INTERVAL (number % 1) DAY
|
||||||
|
FROM
|
||||||
|
numbers(1, 450)
|
||||||
|
UNION
|
||||||
|
ALL -- 为post_6_1添加事件数据(Instagram图片帖文)
|
||||||
|
SELECT
|
||||||
|
concat('user_', toString(number % 500 + 1)),
|
||||||
|
'influencer_6',
|
||||||
|
'post_6_1',
|
||||||
|
'project_3',
|
||||||
|
multiIf(
|
||||||
|
number % 10 < 5,
|
||||||
|
'view',
|
||||||
|
number % 10 < 7,
|
||||||
|
'like',
|
||||||
|
number % 10 < 8,
|
||||||
|
'comment',
|
||||||
|
number % 10 < 9,
|
||||||
|
'share',
|
||||||
|
'impression'
|
||||||
|
),
|
||||||
|
multiIf(
|
||||||
|
number % 10 < 3,
|
||||||
|
'exposure',
|
||||||
|
number % 10 < 6,
|
||||||
|
'interest',
|
||||||
|
number % 10 < 8,
|
||||||
|
'consideration',
|
||||||
|
'intent'
|
||||||
|
),
|
||||||
|
'Instagram',
|
||||||
|
'image',
|
||||||
|
'approved',
|
||||||
|
multiIf(
|
||||||
|
number % 3 = 0,
|
||||||
|
'positive',
|
||||||
|
number % 3 = 1,
|
||||||
|
'neutral',
|
||||||
|
'negative'
|
||||||
|
),
|
||||||
|
now() - INTERVAL (number % 24) HOUR,
|
||||||
|
today() - INTERVAL (number % 1) DAY
|
||||||
|
FROM
|
||||||
|
numbers(1, 400)
|
||||||
|
UNION
|
||||||
|
ALL -- 为post_9_1添加事件数据(TikTok视频帖文)
|
||||||
|
SELECT
|
||||||
|
concat('user_', toString(number % 500 + 1)),
|
||||||
|
'influencer_9',
|
||||||
|
'post_9_1',
|
||||||
|
'project_2',
|
||||||
|
multiIf(
|
||||||
|
number % 10 < 5,
|
||||||
|
'view',
|
||||||
|
number % 10 < 7,
|
||||||
|
'like',
|
||||||
|
number % 10 < 8,
|
||||||
|
'comment',
|
||||||
|
number % 10 < 9,
|
||||||
|
'share',
|
||||||
|
'impression'
|
||||||
|
),
|
||||||
|
multiIf(
|
||||||
|
number % 10 < 3,
|
||||||
|
'exposure',
|
||||||
|
number % 10 < 6,
|
||||||
|
'interest',
|
||||||
|
number % 10 < 8,
|
||||||
|
'consideration',
|
||||||
|
'intent'
|
||||||
|
),
|
||||||
|
'TikTok',
|
||||||
|
'video',
|
||||||
|
'approved',
|
||||||
|
multiIf(
|
||||||
|
number % 3 = 0,
|
||||||
|
'positive',
|
||||||
|
number % 3 = 1,
|
||||||
|
'neutral',
|
||||||
|
'negative'
|
||||||
|
),
|
||||||
|
now() - INTERVAL (number % 24) HOUR,
|
||||||
|
today() - INTERVAL (number % 1) DAY
|
||||||
|
FROM
|
||||||
|
numbers(1, 380)
|
||||||
|
UNION
|
||||||
|
ALL -- 为post_4_1添加事件数据(Facebook图片帖文)
|
||||||
|
SELECT
|
||||||
|
concat('user_', toString(number % 500 + 1)),
|
||||||
|
'influencer_4',
|
||||||
|
'post_4_1',
|
||||||
|
'project_2',
|
||||||
|
multiIf(
|
||||||
|
number % 10 < 5,
|
||||||
|
'view',
|
||||||
|
number % 10 < 7,
|
||||||
|
'like',
|
||||||
|
number % 10 < 8,
|
||||||
|
'comment',
|
||||||
|
number % 10 < 9,
|
||||||
|
'share',
|
||||||
|
'impression'
|
||||||
|
),
|
||||||
|
multiIf(
|
||||||
|
number % 10 < 3,
|
||||||
|
'exposure',
|
||||||
|
number % 10 < 6,
|
||||||
|
'interest',
|
||||||
|
number % 10 < 8,
|
||||||
|
'consideration',
|
||||||
|
'intent'
|
||||||
|
),
|
||||||
|
'Facebook',
|
||||||
|
'image',
|
||||||
|
'approved',
|
||||||
|
multiIf(
|
||||||
|
number % 3 = 0,
|
||||||
|
'positive',
|
||||||
|
number % 3 = 1,
|
||||||
|
'neutral',
|
||||||
|
'negative'
|
||||||
|
),
|
||||||
|
now() - INTERVAL (number % 96) HOUR,
|
||||||
|
today() - INTERVAL (number % 4) DAY
|
||||||
|
FROM
|
||||||
|
numbers(1, 320);
|
||||||
292
backend/db/sql/clickhouse/insert_posts.sql
Normal file
292
backend/db/sql/clickhouse/insert_posts.sql
Normal file
@@ -0,0 +1,292 @@
|
|||||||
|
-- 插入帖文数据,确保与现有的influencers和project_id保持关联
|
||||||
|
INSERT INTO
|
||||||
|
posts (
|
||||||
|
post_id,
|
||||||
|
influencer_id,
|
||||||
|
project_id,
|
||||||
|
title,
|
||||||
|
platform,
|
||||||
|
content_type,
|
||||||
|
created_at
|
||||||
|
)
|
||||||
|
VALUES
|
||||||
|
-- KOL 1 (Twitter, project_3)的帖文
|
||||||
|
(
|
||||||
|
'post_1_1',
|
||||||
|
'influencer_1',
|
||||||
|
'project_3',
|
||||||
|
'新产品发布会直播预告',
|
||||||
|
'Twitter',
|
||||||
|
'text',
|
||||||
|
now() - INTERVAL 2 DAY
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'post_1_2',
|
||||||
|
'influencer_1',
|
||||||
|
'project_3',
|
||||||
|
'产品开箱视频',
|
||||||
|
'Twitter',
|
||||||
|
'video',
|
||||||
|
now() - INTERVAL 5 DAY
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'post_1_3',
|
||||||
|
'influencer_1',
|
||||||
|
'project_3',
|
||||||
|
'用户评价汇总',
|
||||||
|
'Twitter',
|
||||||
|
'text',
|
||||||
|
now() - INTERVAL 10 DAY
|
||||||
|
),
|
||||||
|
-- KOL 2 (TikTok, project_2)的帖文
|
||||||
|
(
|
||||||
|
'post_2_1',
|
||||||
|
'influencer_2',
|
||||||
|
'project_2',
|
||||||
|
'春季新品穿搭推荐',
|
||||||
|
'TikTok',
|
||||||
|
'video',
|
||||||
|
now() - INTERVAL 1 DAY
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'post_2_2',
|
||||||
|
'influencer_2',
|
||||||
|
'project_2',
|
||||||
|
'30秒快速美妆教程',
|
||||||
|
'TikTok',
|
||||||
|
'video',
|
||||||
|
now() - INTERVAL 7 DAY
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'post_2_3',
|
||||||
|
'influencer_2',
|
||||||
|
'project_2',
|
||||||
|
'产品使用小技巧',
|
||||||
|
'TikTok',
|
||||||
|
'video',
|
||||||
|
now() - INTERVAL 15 DAY
|
||||||
|
),
|
||||||
|
-- KOL 3 (Twitter, project_1)的帖文
|
||||||
|
(
|
||||||
|
'post_3_1',
|
||||||
|
'influencer_3',
|
||||||
|
'project_1',
|
||||||
|
'最新游戏评测',
|
||||||
|
'Twitter',
|
||||||
|
'text',
|
||||||
|
now() - INTERVAL 3 DAY
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'post_3_2',
|
||||||
|
'influencer_3',
|
||||||
|
'project_1',
|
||||||
|
'游戏攻略分享',
|
||||||
|
'Twitter',
|
||||||
|
'text',
|
||||||
|
now() - INTERVAL 8 DAY
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'post_3_3',
|
||||||
|
'influencer_3',
|
||||||
|
'project_1',
|
||||||
|
'游戏直播预告',
|
||||||
|
'Twitter',
|
||||||
|
'text',
|
||||||
|
now() - INTERVAL 12 DAY
|
||||||
|
),
|
||||||
|
-- KOL 4 (Facebook, project_2)的帖文
|
||||||
|
(
|
||||||
|
'post_4_1',
|
||||||
|
'influencer_4',
|
||||||
|
'project_2',
|
||||||
|
'新品上市特惠活动',
|
||||||
|
'Facebook',
|
||||||
|
'image',
|
||||||
|
now() - INTERVAL 4 DAY
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'post_4_2',
|
||||||
|
'influencer_4',
|
||||||
|
'project_2',
|
||||||
|
'产品功能详解',
|
||||||
|
'Facebook',
|
||||||
|
'image',
|
||||||
|
now() - INTERVAL 9 DAY
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'post_4_3',
|
||||||
|
'influencer_4',
|
||||||
|
'project_2',
|
||||||
|
'用户真实反馈',
|
||||||
|
'Facebook',
|
||||||
|
'text',
|
||||||
|
now() - INTERVAL 14 DAY
|
||||||
|
),
|
||||||
|
-- KOL 5 (Facebook, project_2)的帖文
|
||||||
|
(
|
||||||
|
'post_5_1',
|
||||||
|
'influencer_5',
|
||||||
|
'project_2',
|
||||||
|
'新品首发体验',
|
||||||
|
'Facebook',
|
||||||
|
'image',
|
||||||
|
now() - INTERVAL 2 DAY
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'post_5_2',
|
||||||
|
'influencer_5',
|
||||||
|
'project_2',
|
||||||
|
'产品对比评测',
|
||||||
|
'Facebook',
|
||||||
|
'image',
|
||||||
|
now() - INTERVAL 6 DAY
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'post_5_3',
|
||||||
|
'influencer_5',
|
||||||
|
'project_2',
|
||||||
|
'限时折扣活动',
|
||||||
|
'Facebook',
|
||||||
|
'text',
|
||||||
|
now() - INTERVAL 11 DAY
|
||||||
|
),
|
||||||
|
-- KOL 6 (Instagram, project_3)的帖文
|
||||||
|
(
|
||||||
|
'post_6_1',
|
||||||
|
'influencer_6',
|
||||||
|
'project_3',
|
||||||
|
'夏日穿搭灵感',
|
||||||
|
'Instagram',
|
||||||
|
'image',
|
||||||
|
now() - INTERVAL 1 DAY
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'post_6_2',
|
||||||
|
'influencer_6',
|
||||||
|
'project_3',
|
||||||
|
'旅行必备单品',
|
||||||
|
'Instagram',
|
||||||
|
'image',
|
||||||
|
now() - INTERVAL 5 DAY
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'post_6_3',
|
||||||
|
'influencer_6',
|
||||||
|
'project_3',
|
||||||
|
'品牌故事分享',
|
||||||
|
'Instagram',
|
||||||
|
'story',
|
||||||
|
now() - INTERVAL 9 DAY
|
||||||
|
),
|
||||||
|
-- KOL 7 (Instagram, project_2)的帖文
|
||||||
|
(
|
||||||
|
'post_7_1',
|
||||||
|
'influencer_7',
|
||||||
|
'project_2',
|
||||||
|
'日常护肤步骤',
|
||||||
|
'Instagram',
|
||||||
|
'reel',
|
||||||
|
now() - INTERVAL 3 DAY
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'post_7_2',
|
||||||
|
'influencer_7',
|
||||||
|
'project_2',
|
||||||
|
'新品试用报告',
|
||||||
|
'Instagram',
|
||||||
|
'image',
|
||||||
|
now() - INTERVAL 7 DAY
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'post_7_3',
|
||||||
|
'influencer_7',
|
||||||
|
'project_2',
|
||||||
|
'粉丝互动问答',
|
||||||
|
'Instagram',
|
||||||
|
'live',
|
||||||
|
now() - INTERVAL 13 DAY
|
||||||
|
),
|
||||||
|
-- KOL 8 (Twitter, project_3)的帖文
|
||||||
|
(
|
||||||
|
'post_8_1',
|
||||||
|
'influencer_8',
|
||||||
|
'project_3',
|
||||||
|
'行业趋势分析',
|
||||||
|
'Twitter',
|
||||||
|
'text',
|
||||||
|
now() - INTERVAL 2 DAY
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'post_8_2',
|
||||||
|
'influencer_8',
|
||||||
|
'project_3',
|
||||||
|
'专家访谈实录',
|
||||||
|
'Twitter',
|
||||||
|
'text',
|
||||||
|
now() - INTERVAL 8 DAY
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'post_8_3',
|
||||||
|
'influencer_8',
|
||||||
|
'project_3',
|
||||||
|
'活动现场直播',
|
||||||
|
'Twitter',
|
||||||
|
'live',
|
||||||
|
now() - INTERVAL 16 DAY
|
||||||
|
),
|
||||||
|
-- KOL 9 (TikTok, project_2)的帖文
|
||||||
|
(
|
||||||
|
'post_9_1',
|
||||||
|
'influencer_9',
|
||||||
|
'project_2',
|
||||||
|
'一分钟挑战赛',
|
||||||
|
'TikTok',
|
||||||
|
'video',
|
||||||
|
now() - INTERVAL 1 DAY
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'post_9_2',
|
||||||
|
'influencer_9',
|
||||||
|
'project_2',
|
||||||
|
'创意广告拍摄花絮',
|
||||||
|
'TikTok',
|
||||||
|
'video',
|
||||||
|
now() - INTERVAL 6 DAY
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'post_9_3',
|
||||||
|
'influencer_9',
|
||||||
|
'project_2',
|
||||||
|
'产品使用教程',
|
||||||
|
'TikTok',
|
||||||
|
'video',
|
||||||
|
now() - INTERVAL 10 DAY
|
||||||
|
),
|
||||||
|
-- KOL 10 (Twitter, project_3)的帖文
|
||||||
|
(
|
||||||
|
'post_10_1',
|
||||||
|
'influencer_10',
|
||||||
|
'project_3',
|
||||||
|
'行业洞察报告',
|
||||||
|
'Twitter',
|
||||||
|
'text',
|
||||||
|
now() - INTERVAL 4 DAY
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'post_10_2',
|
||||||
|
'influencer_10',
|
||||||
|
'project_3',
|
||||||
|
'专题讨论总结',
|
||||||
|
'Twitter',
|
||||||
|
'text',
|
||||||
|
now() - INTERVAL 9 DAY
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'post_10_3',
|
||||||
|
'influencer_10',
|
||||||
|
'project_3',
|
||||||
|
'社区调研结果',
|
||||||
|
'Twitter',
|
||||||
|
'text',
|
||||||
|
now() - INTERVAL 15 DAY
|
||||||
|
);
|
||||||
215
backend/db/sql/clickhouse/insert_posts_simple.sql
Normal file
215
backend/db/sql/clickhouse/insert_posts_simple.sql
Normal file
@@ -0,0 +1,215 @@
|
|||||||
|
-- 插入KOL 1的帖文
|
||||||
|
INSERT INTO
|
||||||
|
posts (
|
||||||
|
post_id,
|
||||||
|
influencer_id,
|
||||||
|
project_id,
|
||||||
|
title,
|
||||||
|
platform,
|
||||||
|
content_type,
|
||||||
|
created_at
|
||||||
|
)
|
||||||
|
VALUES
|
||||||
|
(
|
||||||
|
'post_1_1',
|
||||||
|
'influencer_1',
|
||||||
|
'project_3',
|
||||||
|
'新产品发布会直播预告',
|
||||||
|
'Twitter',
|
||||||
|
'text',
|
||||||
|
now() - INTERVAL 2 DAY
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO
|
||||||
|
posts (
|
||||||
|
post_id,
|
||||||
|
influencer_id,
|
||||||
|
project_id,
|
||||||
|
title,
|
||||||
|
platform,
|
||||||
|
content_type,
|
||||||
|
created_at
|
||||||
|
)
|
||||||
|
VALUES
|
||||||
|
(
|
||||||
|
'post_1_2',
|
||||||
|
'influencer_1',
|
||||||
|
'project_3',
|
||||||
|
'产品开箱视频',
|
||||||
|
'Twitter',
|
||||||
|
'video',
|
||||||
|
now() - INTERVAL 5 DAY
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO
|
||||||
|
posts (
|
||||||
|
post_id,
|
||||||
|
influencer_id,
|
||||||
|
project_id,
|
||||||
|
title,
|
||||||
|
platform,
|
||||||
|
content_type,
|
||||||
|
created_at
|
||||||
|
)
|
||||||
|
VALUES
|
||||||
|
(
|
||||||
|
'post_1_3',
|
||||||
|
'influencer_1',
|
||||||
|
'project_3',
|
||||||
|
'用户评价汇总',
|
||||||
|
'Twitter',
|
||||||
|
'text',
|
||||||
|
now() - INTERVAL 10 DAY
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 插入KOL 2的帖文
|
||||||
|
INSERT INTO
|
||||||
|
posts (
|
||||||
|
post_id,
|
||||||
|
influencer_id,
|
||||||
|
project_id,
|
||||||
|
title,
|
||||||
|
platform,
|
||||||
|
content_type,
|
||||||
|
created_at
|
||||||
|
)
|
||||||
|
VALUES
|
||||||
|
(
|
||||||
|
'post_2_1',
|
||||||
|
'influencer_2',
|
||||||
|
'project_2',
|
||||||
|
'春季新品穿搭推荐',
|
||||||
|
'TikTok',
|
||||||
|
'video',
|
||||||
|
now() - INTERVAL 1 DAY
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO
|
||||||
|
posts (
|
||||||
|
post_id,
|
||||||
|
influencer_id,
|
||||||
|
project_id,
|
||||||
|
title,
|
||||||
|
platform,
|
||||||
|
content_type,
|
||||||
|
created_at
|
||||||
|
)
|
||||||
|
VALUES
|
||||||
|
(
|
||||||
|
'post_2_2',
|
||||||
|
'influencer_2',
|
||||||
|
'project_2',
|
||||||
|
'30秒快速美妆教程',
|
||||||
|
'TikTok',
|
||||||
|
'video',
|
||||||
|
now() - INTERVAL 7 DAY
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 插入KOL 3的帖文
|
||||||
|
INSERT INTO
|
||||||
|
posts (
|
||||||
|
post_id,
|
||||||
|
influencer_id,
|
||||||
|
project_id,
|
||||||
|
title,
|
||||||
|
platform,
|
||||||
|
content_type,
|
||||||
|
created_at
|
||||||
|
)
|
||||||
|
VALUES
|
||||||
|
(
|
||||||
|
'post_3_1',
|
||||||
|
'influencer_3',
|
||||||
|
'project_1',
|
||||||
|
'最新游戏评测',
|
||||||
|
'Twitter',
|
||||||
|
'text',
|
||||||
|
now() - INTERVAL 3 DAY
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 插入KOL 6的帖文
|
||||||
|
INSERT INTO
|
||||||
|
posts (
|
||||||
|
post_id,
|
||||||
|
influencer_id,
|
||||||
|
project_id,
|
||||||
|
title,
|
||||||
|
platform,
|
||||||
|
content_type,
|
||||||
|
created_at
|
||||||
|
)
|
||||||
|
VALUES
|
||||||
|
(
|
||||||
|
'post_6_1',
|
||||||
|
'influencer_6',
|
||||||
|
'project_3',
|
||||||
|
'夏日穿搭灵感',
|
||||||
|
'Instagram',
|
||||||
|
'image',
|
||||||
|
now() - INTERVAL 1 DAY
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO
|
||||||
|
posts (
|
||||||
|
post_id,
|
||||||
|
influencer_id,
|
||||||
|
project_id,
|
||||||
|
title,
|
||||||
|
platform,
|
||||||
|
content_type,
|
||||||
|
created_at
|
||||||
|
)
|
||||||
|
VALUES
|
||||||
|
(
|
||||||
|
'post_6_2',
|
||||||
|
'influencer_6',
|
||||||
|
'project_3',
|
||||||
|
'旅行必备单品',
|
||||||
|
'Instagram',
|
||||||
|
'image',
|
||||||
|
now() - INTERVAL 5 DAY
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 插入KOL 9的帖文
|
||||||
|
INSERT INTO
|
||||||
|
posts (
|
||||||
|
post_id,
|
||||||
|
influencer_id,
|
||||||
|
project_id,
|
||||||
|
title,
|
||||||
|
platform,
|
||||||
|
content_type,
|
||||||
|
created_at
|
||||||
|
)
|
||||||
|
VALUES
|
||||||
|
(
|
||||||
|
'post_9_1',
|
||||||
|
'influencer_9',
|
||||||
|
'project_2',
|
||||||
|
'一分钟挑战赛',
|
||||||
|
'TikTok',
|
||||||
|
'video',
|
||||||
|
now() - INTERVAL 1 DAY
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 插入KOL 4的帖文
|
||||||
|
INSERT INTO
|
||||||
|
posts (
|
||||||
|
post_id,
|
||||||
|
influencer_id,
|
||||||
|
project_id,
|
||||||
|
title,
|
||||||
|
platform,
|
||||||
|
content_type,
|
||||||
|
created_at
|
||||||
|
)
|
||||||
|
VALUES
|
||||||
|
(
|
||||||
|
'post_4_1',
|
||||||
|
'influencer_4',
|
||||||
|
'project_2',
|
||||||
|
'新品上市特惠活动',
|
||||||
|
'Facebook',
|
||||||
|
'image',
|
||||||
|
now() - INTERVAL 4 DAY
|
||||||
|
);
|
||||||
@@ -184,6 +184,223 @@ export class AnalyticsController {
|
|||||||
}, 500);
|
}, 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
|
// Export singleton instance
|
||||||
|
|||||||
@@ -29,4 +29,7 @@ analyticsRouter.get('/kol-overview', (c) => analyticsController.getKolOverview(c
|
|||||||
// Add new funnel analysis route
|
// Add new funnel analysis route
|
||||||
analyticsRouter.get('/kol-funnel', (c) => analyticsController.getKolFunnel(c));
|
analyticsRouter.get('/kol-funnel', (c) => analyticsController.getKolFunnel(c));
|
||||||
|
|
||||||
|
// Add new post performance route
|
||||||
|
analyticsRouter.get('/post-performance', (c) => analyticsController.getPostPerformance(c));
|
||||||
|
|
||||||
export default analyticsRouter;
|
export default analyticsRouter;
|
||||||
@@ -55,6 +55,34 @@ export interface FunnelResponse {
|
|||||||
overview: FunnelOverview; // 总览数据
|
overview: FunnelOverview; // 总览数据
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 贴文表现数据
|
||||||
|
*/
|
||||||
|
export interface PostPerformanceData {
|
||||||
|
post_id: string; // 贴文ID
|
||||||
|
title: string; // 标题
|
||||||
|
kol_id: string; // KOL ID
|
||||||
|
kol_name: string; // KOL 名称
|
||||||
|
platform: string; // 平台
|
||||||
|
publish_date: string; // 发布日期
|
||||||
|
metrics: {
|
||||||
|
views: number; // 观看数
|
||||||
|
likes: number; // 点赞数
|
||||||
|
comments: number; // 留言数
|
||||||
|
shares: number; // 分享数
|
||||||
|
};
|
||||||
|
sentiment_score: number; // 情绪指标评分
|
||||||
|
post_url: string; // 贴文链接
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 贴文表现响应
|
||||||
|
*/
|
||||||
|
export interface PostPerformanceResponse {
|
||||||
|
posts: PostPerformanceData[]; // 贴文数据
|
||||||
|
total: number; // 总数
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Analytics service for KOL performance data
|
* Analytics service for KOL performance data
|
||||||
*/
|
*/
|
||||||
@@ -547,6 +575,318 @@ export class AnalyticsService {
|
|||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取KOL贴文表现数据
|
||||||
|
* @param kolId 可选KOL ID
|
||||||
|
* @param platform 可选平台
|
||||||
|
* @param startDate 可选开始日期
|
||||||
|
* @param endDate 可选结束日期
|
||||||
|
* @param sortBy 排序字段 (views, likes, comments, shares, sentiment)
|
||||||
|
* @param sortOrder 排序方向 (asc, desc)
|
||||||
|
* @param limit 限制数量
|
||||||
|
* @param offset 偏移量
|
||||||
|
* @returns 贴文表现数据
|
||||||
|
*/
|
||||||
|
async getPostPerformance(
|
||||||
|
kolId?: string,
|
||||||
|
platform?: string,
|
||||||
|
startDate?: string,
|
||||||
|
endDate?: string,
|
||||||
|
sortBy: string = 'publish_date',
|
||||||
|
sortOrder: string = 'desc',
|
||||||
|
limit: number = 20,
|
||||||
|
offset: number = 0
|
||||||
|
): Promise<PostPerformanceResponse> {
|
||||||
|
const startTime = Date.now();
|
||||||
|
logger.info('Fetching KOL post performance', {
|
||||||
|
kolId,
|
||||||
|
platform,
|
||||||
|
startDate,
|
||||||
|
endDate,
|
||||||
|
sortBy,
|
||||||
|
sortOrder,
|
||||||
|
limit,
|
||||||
|
offset
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Check data existence first
|
||||||
|
await this.checkDataExistence();
|
||||||
|
|
||||||
|
// Prepare filters
|
||||||
|
const filters: string[] = [];
|
||||||
|
|
||||||
|
if (kolId) {
|
||||||
|
filters.push(`AND p.influencer_id = '${kolId}'`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (platform) {
|
||||||
|
filters.push(`AND p.platform = '${platform}'`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (startDate) {
|
||||||
|
filters.push(`AND p.date >= toDate('${startDate}')`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (endDate) {
|
||||||
|
filters.push(`AND p.date <= toDate('${endDate}')`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const filterCondition = filters.join(' ');
|
||||||
|
|
||||||
|
// Validate and prepare sortBy field
|
||||||
|
const validSortFields = ['publish_date', 'views', 'likes', 'comments', 'shares', 'sentiment_score'];
|
||||||
|
const sortField = validSortFields.includes(sortBy) ? sortBy : 'publish_date';
|
||||||
|
|
||||||
|
// Prepare sort order
|
||||||
|
const order = sortOrder.toLowerCase() === 'asc' ? 'ASC' : 'DESC';
|
||||||
|
|
||||||
|
// 查询帖文基本数据
|
||||||
|
const postsQuery = `
|
||||||
|
SELECT
|
||||||
|
p.post_id,
|
||||||
|
p.title,
|
||||||
|
p.influencer_id AS kol_id,
|
||||||
|
i.name AS kol_name,
|
||||||
|
p.platform,
|
||||||
|
p.created_at AS publish_date,
|
||||||
|
CONCAT('https://', p.platform, '.com/post/', p.post_id) AS post_url
|
||||||
|
FROM
|
||||||
|
posts p
|
||||||
|
LEFT JOIN
|
||||||
|
influencers i ON p.influencer_id = i.influencer_id
|
||||||
|
WHERE
|
||||||
|
1=1 ${filterCondition}
|
||||||
|
ORDER BY
|
||||||
|
p.created_at ${order}
|
||||||
|
LIMIT ${limit}
|
||||||
|
OFFSET ${offset}
|
||||||
|
`;
|
||||||
|
|
||||||
|
// 从events表聚合互动指标和情感评分
|
||||||
|
const metricsQuery = `
|
||||||
|
SELECT
|
||||||
|
content_id AS post_id,
|
||||||
|
SUM(CASE WHEN event_type = 'view' THEN 1 ELSE 0 END) AS views,
|
||||||
|
SUM(CASE WHEN event_type = 'like' THEN 1 ELSE 0 END) AS likes,
|
||||||
|
SUM(CASE WHEN event_type = 'comment' THEN 1 ELSE 0 END) AS comments,
|
||||||
|
SUM(CASE WHEN event_type = 'share' THEN 1 ELSE 0 END) AS shares,
|
||||||
|
AVG(CASE
|
||||||
|
WHEN sentiment = 'positive' THEN 1
|
||||||
|
WHEN sentiment = 'neutral' THEN 0
|
||||||
|
WHEN sentiment = 'negative' THEN -1
|
||||||
|
ELSE NULL
|
||||||
|
END) AS sentiment_score
|
||||||
|
FROM
|
||||||
|
events
|
||||||
|
WHERE
|
||||||
|
content_id IS NOT NULL
|
||||||
|
GROUP BY
|
||||||
|
content_id
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Query to get total count for pagination
|
||||||
|
const countQuery = `
|
||||||
|
SELECT
|
||||||
|
COUNT(*) as total
|
||||||
|
FROM
|
||||||
|
posts p
|
||||||
|
WHERE
|
||||||
|
1=1 ${filterCondition}
|
||||||
|
`;
|
||||||
|
|
||||||
|
logger.debug('Executing ClickHouse queries for post performance', {
|
||||||
|
postsQuery: postsQuery.replace(/\n\s+/g, ' ').trim(),
|
||||||
|
metricsQuery: metricsQuery.replace(/\n\s+/g, ' ').trim(),
|
||||||
|
countQuery: countQuery.replace(/\n\s+/g, ' ').trim()
|
||||||
|
});
|
||||||
|
|
||||||
|
// 同时执行所有查询
|
||||||
|
const [postsData, countResult, metricsData] = await Promise.all([
|
||||||
|
this.executeClickhouseQuery(postsQuery),
|
||||||
|
this.executeClickhouseQuery(countQuery),
|
||||||
|
this.executeClickhouseQuery(metricsQuery).catch(err => {
|
||||||
|
logger.warn('Failed to fetch metrics data, using mock data instead', { error: err.message });
|
||||||
|
return [];
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Parse results
|
||||||
|
const total = this.parseCountResult(countResult);
|
||||||
|
|
||||||
|
// If no posts found, return empty result
|
||||||
|
if (!Array.isArray(postsData) || postsData.length === 0) {
|
||||||
|
logger.info('No posts found for the given criteria');
|
||||||
|
return {
|
||||||
|
posts: [],
|
||||||
|
total: 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建指标Map,方便查找
|
||||||
|
const metricsMap: Record<string, any> = {};
|
||||||
|
if (Array.isArray(metricsData)) {
|
||||||
|
metricsData.forEach(item => {
|
||||||
|
if (item.post_id) {
|
||||||
|
metricsMap[item.post_id] = {
|
||||||
|
views: Number(item.views || 0),
|
||||||
|
likes: Number(item.likes || 0),
|
||||||
|
comments: Number(item.comments || 0),
|
||||||
|
shares: Number(item.shares || 0),
|
||||||
|
sentiment_score: Number(item.sentiment_score || 0)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 合并数据,生成最终结果
|
||||||
|
const transformedPosts: PostPerformanceData[] = postsData.map(post => {
|
||||||
|
// 获取帖文的指标数据,如果没有则使用空值或模拟数据
|
||||||
|
const metrics = metricsMap[post.post_id] || {};
|
||||||
|
const postMetrics = {
|
||||||
|
views: Number(metrics.views || 0),
|
||||||
|
likes: Number(metrics.likes || 0),
|
||||||
|
comments: Number(metrics.comments || 0),
|
||||||
|
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();
|
||||||
|
|
||||||
|
return {
|
||||||
|
post_id: post.post_id,
|
||||||
|
title: post.title || '无标题',
|
||||||
|
kol_id: post.kol_id,
|
||||||
|
kol_name: post.kol_name || '未知KOL',
|
||||||
|
platform: post.platform || 'unknown',
|
||||||
|
publish_date: post.publish_date,
|
||||||
|
metrics: finalMetrics,
|
||||||
|
sentiment_score: sentimentScore,
|
||||||
|
post_url: post.post_url || `https://${post.platform || 'example'}.com/post/${post.post_id}`
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// 如果按照指标排序,则在内存中重新排序
|
||||||
|
if (sortField !== 'publish_date') {
|
||||||
|
transformedPosts.sort((a, b) => {
|
||||||
|
let aValue = 0;
|
||||||
|
let bValue = 0;
|
||||||
|
|
||||||
|
if (sortField === 'sentiment_score') {
|
||||||
|
aValue = a.sentiment_score;
|
||||||
|
bValue = b.sentiment_score;
|
||||||
|
} else {
|
||||||
|
// 处理metrics内部字段排序
|
||||||
|
const metricField = sortField as keyof typeof a.metrics;
|
||||||
|
aValue = a.metrics[metricField] || 0;
|
||||||
|
bValue = b.metrics[metricField] || 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sortOrder.toLowerCase() === 'asc'
|
||||||
|
? aValue - bValue
|
||||||
|
: bValue - aValue;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 统计真实数据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
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
posts: transformedPosts,
|
||||||
|
total
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成模拟贴文互动指标
|
||||||
|
*/
|
||||||
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Export singleton instance
|
// Export singleton instance
|
||||||
|
|||||||
@@ -2353,6 +2353,182 @@ export const openAPISpec = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
'/api/analytics/post-performance': {
|
||||||
|
get: {
|
||||||
|
summary: 'Get KOL post performance data',
|
||||||
|
description: 'Returns table data of posts with key metrics including views, likes, comments, shares and sentiment scores',
|
||||||
|
tags: ['Analytics'],
|
||||||
|
parameters: [
|
||||||
|
{
|
||||||
|
name: 'kolId',
|
||||||
|
in: 'query',
|
||||||
|
description: 'Filter by KOL ID',
|
||||||
|
schema: {
|
||||||
|
type: 'string'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'platform',
|
||||||
|
in: 'query',
|
||||||
|
description: 'Filter by platform',
|
||||||
|
schema: {
|
||||||
|
type: 'string',
|
||||||
|
enum: ['youtube', 'instagram', 'tiktok', 'twitter', 'facebook']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'startDate',
|
||||||
|
in: 'query',
|
||||||
|
description: 'Start date filter (YYYY-MM-DD)',
|
||||||
|
schema: {
|
||||||
|
type: 'string',
|
||||||
|
format: 'date'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'endDate',
|
||||||
|
in: 'query',
|
||||||
|
description: 'End date filter (YYYY-MM-DD)',
|
||||||
|
schema: {
|
||||||
|
type: 'string',
|
||||||
|
format: 'date'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'sortBy',
|
||||||
|
in: 'query',
|
||||||
|
description: 'Field to sort by',
|
||||||
|
schema: {
|
||||||
|
type: 'string',
|
||||||
|
enum: ['publish_date', 'views', 'likes', 'comments', 'shares', 'sentiment_score'],
|
||||||
|
default: 'publish_date'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'sortOrder',
|
||||||
|
in: 'query',
|
||||||
|
description: 'Sort order',
|
||||||
|
schema: {
|
||||||
|
type: 'string',
|
||||||
|
enum: ['asc', 'desc'],
|
||||||
|
default: 'desc'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'limit',
|
||||||
|
in: 'query',
|
||||||
|
description: 'Number of posts to return',
|
||||||
|
schema: {
|
||||||
|
type: 'integer',
|
||||||
|
default: 20
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'offset',
|
||||||
|
in: 'query',
|
||||||
|
description: 'Offset for pagination',
|
||||||
|
schema: {
|
||||||
|
type: 'integer',
|
||||||
|
default: 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'useMockData',
|
||||||
|
in: 'query',
|
||||||
|
description: 'Force use of mock data (useful for testing and UI development)',
|
||||||
|
schema: {
|
||||||
|
type: 'boolean',
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
responses: {
|
||||||
|
'200': {
|
||||||
|
description: 'Successful response with post performance data',
|
||||||
|
content: {
|
||||||
|
'application/json': {
|
||||||
|
schema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
success: {
|
||||||
|
type: 'boolean',
|
||||||
|
example: true
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
type: 'array',
|
||||||
|
items: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
post_id: { type: 'string', example: 'post-123' },
|
||||||
|
title: { type: 'string', example: '夏季新品分享' },
|
||||||
|
kol_id: { type: 'string', example: 'kol-456' },
|
||||||
|
kol_name: { type: 'string', example: '時尚達人' },
|
||||||
|
platform: { type: 'string', example: 'instagram' },
|
||||||
|
publish_date: { type: 'string', format: 'date-time', example: '2023-06-15T08:30:00Z' },
|
||||||
|
metrics: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
views: { type: 'integer', example: 15000 },
|
||||||
|
likes: { type: 'integer', example: 1200 },
|
||||||
|
comments: { type: 'integer', example: 85 },
|
||||||
|
shares: { type: 'integer', example: 45 }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
sentiment_score: { type: 'number', example: 0.75 },
|
||||||
|
post_url: { type: 'string', format: 'uri', example: 'https://instagram.com/p/abc123' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
pagination: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
limit: { type: 'integer', example: 20 },
|
||||||
|
offset: { type: 'integer', example: 0 },
|
||||||
|
total: { type: 'integer', example: 156 }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
is_mock_data: {
|
||||||
|
type: 'boolean',
|
||||||
|
description: '标识返回的是否是模拟数据',
|
||||||
|
example: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'400': {
|
||||||
|
description: 'Bad request - invalid parameters',
|
||||||
|
content: {
|
||||||
|
'application/json': {
|
||||||
|
schema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
success: { type: 'boolean', example: false },
|
||||||
|
error: { type: 'string', example: 'Invalid sortBy. Must be one of: publish_date, views, likes, comments, shares, sentiment_score' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'500': {
|
||||||
|
description: 'Server error',
|
||||||
|
content: {
|
||||||
|
'application/json': {
|
||||||
|
schema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
success: { type: 'boolean', example: false },
|
||||||
|
error: { type: 'string', example: 'Failed to fetch post performance data' },
|
||||||
|
message: { type: 'string', example: 'ClickHouse query error: Connection refused' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
schemas: {
|
schemas: {
|
||||||
|
|||||||
Reference in New Issue
Block a user