Ethanfly 21 giờ trước cách đây
mục cha
commit
457016f9d7

+ 118 - 0
database/migrations/add_yesterday_fields_to_works.sql

@@ -0,0 +1,118 @@
+-- 为 works 表添加 yesterday_* 字段(昨日数据快照)
+-- 执行前请先备份数据库
+-- MySQL 不支持 IF NOT EXISTS,这里提供两种方式:
+-- 方式1:直接执行(如果列已存在会报错,可忽略或手动删除已存在的列)
+-- 方式2:使用存储过程检查后添加(见下方)
+
+USE media_manager;
+
+-- ========== 方式1:直接添加(推荐,如果列已存在会报错,可忽略报错继续执行) ==========
+
+-- 1. 播放数
+ALTER TABLE works 
+ADD COLUMN yesterday_play_count INT DEFAULT 0 COMMENT '昨日播放数';
+
+-- 2. 点赞数
+ALTER TABLE works 
+ADD COLUMN yesterday_like_count INT DEFAULT 0 COMMENT '昨日点赞数';
+
+-- 3. 评论数
+ALTER TABLE works 
+ADD COLUMN yesterday_comment_count INT DEFAULT 0 COMMENT '昨日评论数';
+
+-- 4. 分享数
+ALTER TABLE works 
+ADD COLUMN yesterday_share_count INT DEFAULT 0 COMMENT '昨日分享数';
+
+-- 5. 收藏数
+ALTER TABLE works 
+ADD COLUMN yesterday_collect_count INT DEFAULT 0 COMMENT '昨日收藏数';
+
+-- 6. 推荐数(视频号)
+ALTER TABLE works 
+ADD COLUMN yesterday_recommend_count INT DEFAULT 0 COMMENT '昨日推荐数';
+
+-- 7. 涨粉数
+ALTER TABLE works 
+ADD COLUMN yesterday_fans_increase INT DEFAULT 0 COMMENT '昨日涨粉数';
+
+-- 8. 关注数(视频号)
+ALTER TABLE works 
+ADD COLUMN yesterday_follow_count INT DEFAULT 0 COMMENT '昨日关注数';
+
+-- 9. 封面点击率
+ALTER TABLE works 
+ADD COLUMN yesterday_cover_click_rate VARCHAR(50) DEFAULT '0' COMMENT '昨日封面点击率';
+
+-- 10. 平均观看时长
+ALTER TABLE works 
+ADD COLUMN yesterday_avg_watch_duration VARCHAR(50) DEFAULT '0' COMMENT '昨日平均观看时长';
+
+-- 11. 总观看时长
+ALTER TABLE works 
+ADD COLUMN yesterday_total_watch_duration VARCHAR(50) DEFAULT '0' COMMENT '昨日总观看时长';
+
+-- 12. 完播率
+ALTER TABLE works 
+ADD COLUMN yesterday_completion_rate VARCHAR(50) DEFAULT '0' COMMENT '昨日完播率';
+
+-- 13. 2秒退出率
+ALTER TABLE works 
+ADD COLUMN yesterday_two_second_exit_rate VARCHAR(50) DEFAULT '0' COMMENT '昨日2秒退出率';
+
+-- 14. 5秒完播率
+ALTER TABLE works 
+ADD COLUMN yesterday_completion_rate_5s VARCHAR(50) DEFAULT '0' COMMENT '昨日5秒完播率';
+
+-- 15. 曝光数
+ALTER TABLE works 
+ADD COLUMN yesterday_exposure_count INT DEFAULT 0 COMMENT '昨日曝光数';
+
+-- ========== 方式2:使用存储过程安全添加(如果列已存在则跳过) ==========
+/*
+DELIMITER $$
+
+DROP PROCEDURE IF EXISTS add_column_if_not_exists$$
+
+CREATE PROCEDURE add_column_if_not_exists(
+    IN table_name VARCHAR(255),
+    IN column_name VARCHAR(255),
+    IN column_definition TEXT
+)
+BEGIN
+    DECLARE column_exists INT DEFAULT 0;
+    
+    SELECT COUNT(*) INTO column_exists
+    FROM information_schema.COLUMNS
+    WHERE TABLE_SCHEMA = DATABASE()
+      AND TABLE_NAME = table_name
+      AND COLUMN_NAME = column_name;
+    
+    IF column_exists = 0 THEN
+        SET @sql = CONCAT('ALTER TABLE ', table_name, ' ADD COLUMN ', column_name, ' ', column_definition);
+        PREPARE stmt FROM @sql;
+        EXECUTE stmt;
+        DEALLOCATE PREPARE stmt;
+    END IF;
+END$$
+
+DELIMITER ;
+
+CALL add_column_if_not_exists('works', 'yesterday_play_count', 'INT DEFAULT 0 COMMENT ''昨日播放数''');
+CALL add_column_if_not_exists('works', 'yesterday_like_count', 'INT DEFAULT 0 COMMENT ''昨日点赞数''');
+CALL add_column_if_not_exists('works', 'yesterday_comment_count', 'INT DEFAULT 0 COMMENT ''昨日评论数''');
+CALL add_column_if_not_exists('works', 'yesterday_share_count', 'INT DEFAULT 0 COMMENT ''昨日分享数''');
+CALL add_column_if_not_exists('works', 'yesterday_collect_count', 'INT DEFAULT 0 COMMENT ''昨日收藏数''');
+CALL add_column_if_not_exists('works', 'yesterday_recommend_count', 'INT DEFAULT 0 COMMENT ''昨日推荐数''');
+CALL add_column_if_not_exists('works', 'yesterday_fans_increase', 'INT DEFAULT 0 COMMENT ''昨日涨粉数''');
+CALL add_column_if_not_exists('works', 'yesterday_follow_count', 'INT DEFAULT 0 COMMENT ''昨日关注数''');
+CALL add_column_if_not_exists('works', 'yesterday_cover_click_rate', 'VARCHAR(50) DEFAULT ''0'' COMMENT ''昨日封面点击率''');
+CALL add_column_if_not_exists('works', 'yesterday_avg_watch_duration', 'VARCHAR(50) DEFAULT ''0'' COMMENT ''昨日平均观看时长''');
+CALL add_column_if_not_exists('works', 'yesterday_total_watch_duration', 'VARCHAR(50) DEFAULT ''0'' COMMENT ''昨日总观看时长''');
+CALL add_column_if_not_exists('works', 'yesterday_completion_rate', 'VARCHAR(50) DEFAULT ''0'' COMMENT ''昨日完播率''');
+CALL add_column_if_not_exists('works', 'yesterday_two_second_exit_rate', 'VARCHAR(50) DEFAULT ''0'' COMMENT ''昨日2秒退出率''');
+CALL add_column_if_not_exists('works', 'yesterday_completion_rate_5s', 'VARCHAR(50) DEFAULT ''0'' COMMENT ''昨日5秒完播率''');
+CALL add_column_if_not_exists('works', 'yesterday_exposure_count', 'INT DEFAULT 0 COMMENT ''昨日曝光数''');
+
+DROP PROCEDURE IF EXISTS add_column_if_not_exists;
+*/

+ 22 - 0
database/schema.sql

@@ -128,6 +128,7 @@ CREATE TABLE IF NOT EXISTS comments (
     id INT PRIMARY KEY AUTO_INCREMENT,
     user_id INT NOT NULL,
     account_id INT NOT NULL,
+    work_id INT NULL COMMENT '作品ID(关联works.id,可为空)',
     platform VARCHAR(50),
     video_id VARCHAR(100),
     platform_video_url VARCHAR(500),
@@ -146,6 +147,7 @@ CREATE TABLE IF NOT EXISTS comments (
     created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
     FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
     FOREIGN KEY (account_id) REFERENCES platform_accounts(id) ON DELETE CASCADE,
+    FOREIGN KEY (work_id) REFERENCES works(id) ON DELETE SET NULL,
     INDEX idx_comment_user_read (user_id, is_read),
     INDEX idx_comment_account (account_id)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
@@ -204,6 +206,22 @@ CREATE TABLE IF NOT EXISTS works (
     comment_count INT DEFAULT 0,
     share_count INT DEFAULT 0,
     collect_count INT DEFAULT 0,
+    -- ===== 昨日数据快照(yesterday_*)=====
+    yesterday_play_count INT DEFAULT 0 COMMENT '昨日播放数',
+    yesterday_like_count INT DEFAULT 0 COMMENT '昨日点赞数',
+    yesterday_comment_count INT DEFAULT 0 COMMENT '昨日评论数',
+    yesterday_share_count INT DEFAULT 0 COMMENT '昨日分享数',
+    yesterday_collect_count INT DEFAULT 0 COMMENT '昨日收藏数',
+    yesterday_recommend_count INT DEFAULT 0 COMMENT '昨日推荐数',
+    yesterday_fans_increase INT DEFAULT 0 COMMENT '昨日涨粉数',
+    yesterday_follow_count INT DEFAULT 0 COMMENT '昨日关注数',
+    yesterday_cover_click_rate VARCHAR(50) DEFAULT '0' COMMENT '昨日封面点击率',
+    yesterday_avg_watch_duration VARCHAR(50) DEFAULT '0' COMMENT '昨日平均观看时长',
+    yesterday_total_watch_duration VARCHAR(50) DEFAULT '0' COMMENT '昨日总观看时长',
+    yesterday_completion_rate VARCHAR(50) DEFAULT '0' COMMENT '昨日完播率',
+    yesterday_two_second_exit_rate VARCHAR(50) DEFAULT '0' COMMENT '昨日2秒退出率',
+    yesterday_completion_rate_5s VARCHAR(50) DEFAULT '0' COMMENT '昨日5秒完播率',
+    yesterday_exposure_count INT DEFAULT 0 COMMENT '昨日曝光数',
     created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
     updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
     INDEX idx_user_platform (userId, platform),
@@ -219,10 +237,12 @@ CREATE TABLE IF NOT EXISTS work_day_statistics (
     play_count INT DEFAULT 0 COMMENT '播放数',
     exposure_count INT DEFAULT 0 COMMENT '曝光数/展现量',
     like_count INT DEFAULT 0 COMMENT '点赞数',
+    recommend_count INT DEFAULT 0 COMMENT '推荐量',
     comment_count INT DEFAULT 0 COMMENT '评论数',
     share_count INT DEFAULT 0 COMMENT '分享数',
     collect_count INT DEFAULT 0 COMMENT '收藏数',
     fans_increase INT DEFAULT 0 COMMENT '涨粉数',
+    follow_count INT DEFAULT 0 COMMENT '关注数',
     cover_click_rate VARCHAR(50) DEFAULT '0' COMMENT '封面点击率',
     avg_watch_duration VARCHAR(50) DEFAULT '0' COMMENT '平均观看时长(秒)',
     total_watch_duration VARCHAR(50) DEFAULT '0' COMMENT '总观看时长(秒)',
@@ -250,6 +270,8 @@ CREATE TABLE IF NOT EXISTS user_day_statistics (
     like_count INT DEFAULT 0 COMMENT '点赞数',
     share_count INT DEFAULT 0 COMMENT '分享数',
     collect_count INT DEFAULT 0 COMMENT '收藏数',
+    recommend_count INT DEFAULT 0 COMMENT '推荐数',
+    follow_count INT DEFAULT 0 COMMENT '关注数(来自视频等)',
     cover_click_rate VARCHAR(50) DEFAULT '0' COMMENT '封面点击率',
     avg_watch_duration VARCHAR(50) DEFAULT '0' COMMENT '平均观看时长(秒)',
     total_watch_duration VARCHAR(50) DEFAULT '0' COMMENT '观看总时长(秒)',

+ 11 - 6
server/python/platforms/weixin.py

@@ -1176,11 +1176,11 @@ class WeixinPublisher(BasePublisher):
                     else:
                         publish_time = str(create_ts) if create_ts else ""
                     
+                    # likeCount=推荐, favCount=点赞
                     read_count = int(item.get("readCount") or 0)
-                    like_count = int(item.get("likeCount") or 0)
+                    like_count = int(item.get("favCount") or 0)
                     comment_count = int(item.get("commentCount") or 0)
                     forward_count = int(item.get("forwardCount") or 0)
-                    fav_count = int(item.get("favCount") or 0)
                     
                     works.append(WorkItem(
                         work_id=work_id,
@@ -1193,7 +1193,7 @@ class WeixinPublisher(BasePublisher):
                         like_count=like_count,
                         comment_count=comment_count,
                         share_count=forward_count,
-                        collect_count=fav_count,
+                        collect_count=0,
                     ))
                 except Exception as e:
                     print(f"[{self.platform_name}] 解析作品项失败: {e}", flush=True)
@@ -1577,8 +1577,10 @@ class WeixinPublisher(BasePublisher):
                                     break
                         if work_id is None:
                             continue
+                        # likeCount=推荐, favCount=点赞
                         read_count = int(it.get("readCount") or 0)
-                        like_count = int(it.get("likeCount") or 0)
+                        recommend_count = int(it.get("likeCount") or 0)
+                        like_count = int(it.get("favCount") or 0)
                         comment_count = int(it.get("commentCount") or 0)
                         forward_count = int(it.get("forwardCount") or 0)
                         follow_count = int(it.get("followCount") or 0)
@@ -1596,6 +1598,7 @@ class WeixinPublisher(BasePublisher):
                             "work_id": work_id,
                             "yesterday_play_count": read_count,
                             "yesterday_like_count": like_count,
+                            "yesterday_recommend_count": recommend_count,
                             "yesterday_comment_count": comment_count,
                             "yesterday_share_count": forward_count,
                             "yesterday_follow_count": follow_count,
@@ -1737,6 +1740,7 @@ class WeixinPublisher(BasePublisher):
                 yesterday = today - timedelta(days=1)
                 start_date = yesterday - timedelta(days=n - 1)
 
+                # like=推荐, fav=点赞
                 like_arr = tab_all.get("like", [])
                 comment_arr = tab_all.get("comment", [])
                 forward_arr = tab_all.get("forward", [])
@@ -1748,16 +1752,17 @@ class WeixinPublisher(BasePublisher):
                     rec_dt = start_date + timedelta(days=i)
                     rec_date = rec_dt.strftime("%Y-%m-%d")
                     play = self._parse_count(browse[i] if i < len(browse) else "0")
-                    like = self._parse_count(like_arr[i] if i < len(like_arr) else "0")
+                    recommend = self._parse_count(like_arr[i] if i < len(like_arr) else "0")
+                    like = self._parse_count(fav_arr[i] if i < len(fav_arr) else "0")
                     comment = self._parse_count(comment_arr[i] if i < len(comment_arr) else "0")
                     share = self._parse_count(forward_arr[i] if i < len(forward_arr) else "0")
                     follow = self._parse_count(follow_arr[i] if i < len(follow_arr) else "0")
-                    # fav[i] 不入库,follow[i] 入 follow_count
                     stats_list.append({
                         "work_id": work_id,
                         "record_date": rec_date,
                         "play_count": play,
                         "like_count": like,
+                        "recommend_count": recommend,
                         "comment_count": comment,
                         "share_count": share,
                         "collect_count": 0,

+ 4 - 0
server/src/routes/internal.ts

@@ -113,6 +113,7 @@ router.post(
         playCount: item.playCount ?? item.play_count ?? 0,
         exposureCount: item.exposureCount ?? item.exposure_count ?? 0,
         likeCount: item.likeCount ?? item.like_count ?? 0,
+        recommendCount: item.recommendCount ?? item.recommend_count ?? 0,
         commentCount: item.commentCount ?? item.comment_count ?? 0,
         shareCount: item.shareCount ?? item.share_count ?? 0,
         collectCount: item.collectCount ?? item.collect_count ?? 0,
@@ -263,6 +264,7 @@ router.post(
         work_id: item.work_id ?? item.workId,
         yesterday_play_count: item.yesterday_play_count ?? item.yesterdayPlayCount,
         yesterday_like_count: item.yesterday_like_count ?? item.yesterdayLikeCount,
+        yesterday_recommend_count: item.yesterday_recommend_count ?? item.yesterdayRecommendCount,
         yesterday_comment_count: item.yesterday_comment_count ?? item.yesterdayCommentCount,
         yesterday_share_count: item.yesterday_share_count ?? item.yesterdayShareCount,
         yesterday_follow_count: item.yesterday_follow_count ?? item.yesterdayFollowCount,
@@ -277,6 +279,7 @@ router.post(
       const patch: Partial<{
         yesterdayPlayCount: number;
         yesterdayLikeCount: number;
+        yesterdayRecommendCount: number;
         yesterdayCommentCount: number;
         yesterdayShareCount: number;
         yesterdayFollowCount: number;
@@ -285,6 +288,7 @@ router.post(
       }> = {};
       if (item.yesterday_play_count !== undefined) patch.yesterdayPlayCount = item.yesterday_play_count;
       if (item.yesterday_like_count !== undefined) patch.yesterdayLikeCount = item.yesterday_like_count;
+      if (item.yesterday_recommend_count !== undefined) patch.yesterdayRecommendCount = item.yesterday_recommend_count;
       if (item.yesterday_comment_count !== undefined) patch.yesterdayCommentCount = item.yesterday_comment_count;
       if (item.yesterday_share_count !== undefined) patch.yesterdayShareCount = item.yesterday_share_count;
       if (item.yesterday_follow_count !== undefined) patch.yesterdayFollowCount = item.yesterday_follow_count;

+ 5 - 0
server/src/services/WorkDayStatisticsService.ts

@@ -7,6 +7,7 @@ interface StatisticsItem {
   playCount?: number;
   exposureCount?: number;
   likeCount?: number;
+  recommendCount?: number;
   commentCount?: number;
   shareCount?: number;
   collectCount?: number;
@@ -159,6 +160,7 @@ export class WorkDayStatisticsService {
           playCount: stat.playCount ?? existing.playCount,
           exposureCount: stat.exposureCount ?? existing.exposureCount,
           likeCount: stat.likeCount ?? existing.likeCount,
+          recommendCount: stat.recommendCount ?? existing.recommendCount,
           commentCount: stat.commentCount ?? existing.commentCount,
           shareCount: stat.shareCount ?? existing.shareCount,
           collectCount: stat.collectCount ?? existing.collectCount,
@@ -179,6 +181,7 @@ export class WorkDayStatisticsService {
           playCount: stat.playCount ?? 0,
           exposureCount: stat.exposureCount ?? 0,
           likeCount: stat.likeCount ?? 0,
+          recommendCount: stat.recommendCount ?? 0,
           commentCount: stat.commentCount ?? 0,
           shareCount: stat.shareCount ?? 0,
           collectCount: stat.collectCount ?? 0,
@@ -238,6 +241,7 @@ export class WorkDayStatisticsService {
         playCount: patch.playCount ?? existing.playCount,
         exposureCount: patch.exposureCount ?? existing.exposureCount,
         likeCount: patch.likeCount ?? existing.likeCount,
+        recommendCount: patch.recommendCount ?? existing.recommendCount,
         commentCount: patch.commentCount ?? existing.commentCount,
         shareCount: patch.shareCount ?? existing.shareCount,
         collectCount: patch.collectCount ?? existing.collectCount,
@@ -258,6 +262,7 @@ export class WorkDayStatisticsService {
       playCount: patch.playCount ?? 0,
       exposureCount: patch.exposureCount ?? 0,
       likeCount: patch.likeCount ?? 0,
+      recommendCount: patch.recommendCount ?? 0,
       commentCount: patch.commentCount ?? 0,
       shareCount: patch.shareCount ?? 0,
       collectCount: patch.collectCount ?? 0,