Jelajahi Sumber

fix: 修复调度器 Cookie 未解密、类型错误、硬编码 userId,优化批量更新性能

- scheduler executePublishTask 添加 CookieManager.decrypt 解密 Cookie
- scheduler publishResult.error 改为 publishResult.errorMessage 修复类型错误
- scheduler autoReplyMessages 移除硬编码 userId: 2,改为按 platform 过滤所有活跃账号
- internal batch-dates 使用 Intl.DateTimeFormat(Asia/Shanghai) 替代 setHours(0)
- internal batch-update-from-csv 改为 Promise.allSettled 并行批处理

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
ethanfly 1 hari lalu
induk
melakukan
cbaaef9a12
2 mengubah file dengan 51 tambahan dan 30 penghapusan
  1. 37 25
      server/src/routes/internal.ts
  2. 14 5
      server/src/scheduler/index.ts

+ 37 - 25
server/src/routes/internal.ts

@@ -106,10 +106,22 @@ router.post(
       const workId = item.workId ?? item.work_id;
       const recordDateStr = item.recordDate ?? item.record_date ?? '';
       const recordDate = recordDateStr ? new Date(recordDateStr) : new Date();
-      recordDate.setHours(0, 0, 0, 0);
+      // 使用中国时区标准化日期(与 WorkDayStatisticsService 一致)
+      const formatter = new Intl.DateTimeFormat('en-CA', {
+        timeZone: 'Asia/Shanghai',
+        year: 'numeric',
+        month: '2-digit',
+        day: '2-digit',
+      });
+      const parts = formatter.formatToParts(recordDate);
+      const get = (type: string) => parts.find((p) => p.type === type)?.value ?? '0';
+      const y = parseInt(get('year'), 10);
+      const m = parseInt(get('month'), 10) - 1;
+      const d = parseInt(get('day'), 10);
+      const normalizedDate = new Date(Date.UTC(y, m, d, 0, 0, 0, 0));
       return {
         workId: Number(workId),
-        recordDate,
+        recordDate: normalizedDate,
         playCount: item.playCount ?? item.play_count ?? 0,
         exposureCount: item.exposureCount ?? item.exposure_count ?? 0,
         likeCount: item.likeCount ?? item.like_count ?? 0,
@@ -275,29 +287,29 @@ router.post(
 
     const workRepository = AppDataSource.getRepository(Work);
     let updated = 0;
-    for (const item of updates) {
-      const patch: Partial<{
-        yesterdayPlayCount: number;
-        yesterdayLikeCount: number;
-        yesterdayRecommendCount: number;
-        yesterdayCommentCount: number;
-        yesterdayShareCount: number;
-        yesterdayFollowCount: number;
-        yesterdayCompletionRate: string;
-        yesterdayAvgWatchDuration: string;
-      }> = {};
-      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;
-      if (item.yesterday_completion_rate !== undefined) patch.yesterdayCompletionRate = String(item.yesterday_completion_rate);
-      if (item.yesterday_avg_watch_duration !== undefined) patch.yesterdayAvgWatchDuration = String(item.yesterday_avg_watch_duration);
-      if (Object.keys(patch).length === 0) continue;
-
-      const result = await workRepository.update(item.work_id, patch);
-      if (result.affected && result.affected > 0) updated += result.affected;
+    // 并行批量更新(每批 20 条,减少串行等待)
+    const BATCH_SIZE = 20;
+    for (let i = 0; i < updates.length; i += BATCH_SIZE) {
+      const batch = updates.slice(i, i + BATCH_SIZE);
+      const results = await Promise.allSettled(
+        batch.map(async (item) => {
+          const patch: Record<string, unknown> = {};
+          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;
+          if (item.yesterday_completion_rate !== undefined) patch.yesterdayCompletionRate = String(item.yesterday_completion_rate);
+          if (item.yesterday_avg_watch_duration !== undefined) patch.yesterdayAvgWatchDuration = String(item.yesterday_avg_watch_duration);
+          if (Object.keys(patch).length === 0) return 0;
+          const result = await workRepository.update(item.work_id, patch);
+          return result.affected || 0;
+        })
+      );
+      for (const r of results) {
+        if (r.status === 'fulfilled') updated += r.value;
+      }
     }
 
     res.json({

+ 14 - 5
server/src/scheduler/index.ts

@@ -15,6 +15,7 @@ import { DouyinWorkStatisticsImportService } from '../services/DouyinWorkStatist
 import { WeixinVideoWorkStatisticsImportService } from '../services/WeixinVideoWorkStatisticsImportService.js';
 import { BaijiahaoWorkDailyStatisticsImportService } from '../services/BaijiahaoWorkDailyStatisticsImportService.js';
 import { getPythonServiceBaseUrl } from '../services/PythonServiceConfigService.js';
+import { CookieManager } from '../automation/cookie.js';
 
 /**
  * 定时任务调度器
@@ -189,7 +190,16 @@ export class TaskScheduler {
       
       try {
         const adapter = getAdapter(account.platform);
-        const publishResult = await adapter.publishVideo(account.cookieData || '', {
+
+        // 解密 Cookie(修复:直接使用加密 Cookie 会导致发布失败)
+        let decryptedCookies: string;
+        try {
+          decryptedCookies = CookieManager.decrypt(account.cookieData || '');
+        } catch {
+          decryptedCookies = account.cookieData || '';
+        }
+
+        const publishResult = await adapter.publishVideo(decryptedCookies, {
           videoPath: task.videoPath || '',
           title: task.title || '',
           description: task.description || undefined,
@@ -205,7 +215,7 @@ export class TaskScheduler {
         publishResultRecord.status = publishResult.success ? 'success' : 'failed';
         publishResultRecord.videoUrl = publishResult.videoUrl || null;
         publishResultRecord.platformVideoId = publishResult.platformVideoId || null;
-        publishResultRecord.errorMessage = publishResult.error || null;
+        publishResultRecord.errorMessage = publishResult.errorMessage || null;
         publishResultRecord.publishedAt = publishResult.success ? new Date() : null;
         await resultRepository.save(publishResultRecord);
         
@@ -462,11 +472,10 @@ export class TaskScheduler {
     try {
       const accountRepository = AppDataSource.getRepository(PlatformAccount);
       
-      // 获取微信视频号的活跃账号
+      // 获取所有活跃的微信视频号账号(不再硬编码 userId)
       const accounts = await accountRepository.find({
         where: {
-          // platform: 'weixin_video',
-          userId: 2,
+          platform: 'weixin_video',
           status: 'active',
         },
       });