|
|
@@ -21,7 +21,7 @@ export class TaskScheduler {
|
|
|
private isDyImportRunning = false; // 抖音导入锁,防止任务重叠执行
|
|
|
private isBjImportRunning = false; // 百家号导入锁,防止任务重叠执行
|
|
|
private isWxImportRunning = false; // 视频号导入锁,防止任务重叠执行
|
|
|
-
|
|
|
+ private isAutoReplying = false; // 私信回复锁,防止任务重叠执行
|
|
|
/**
|
|
|
* 启动调度器
|
|
|
*
|
|
|
@@ -48,6 +48,7 @@ export class TaskScheduler {
|
|
|
// 每天 12:30:批量导出视频号“数据中心-各子菜单-增长详情(数据详情)-近30天-下载表格”,导入 user_day_statistics
|
|
|
this.scheduleJob('wx-video-data-center-import', '30 12 * * *', this.importWeixinVideoDataCenterLast30Days.bind(this));
|
|
|
|
|
|
+ this.scheduleJob('auto-reply-messages', '* * * * *', this.autoReplyMessages.bind(this));
|
|
|
// 注意:账号刷新由客户端定时触发,不在服务端自动执行
|
|
|
// 这样可以确保只刷新当前登录用户的账号,避免处理其他用户的数据
|
|
|
|
|
|
@@ -60,6 +61,7 @@ export class TaskScheduler {
|
|
|
logger.info('[Scheduler] - dy-account-overview-import: daily at 12:10 (10 12 * * *)');
|
|
|
logger.info('[Scheduler] - bj-content-overview-import: daily at 12:20 (20 12 * * *)');
|
|
|
logger.info('[Scheduler] - wx-video-data-center-import: daily at 12:30 (30 12 * * *)');
|
|
|
+ logger.info('[Scheduler] - auto-reply-messages: every minute (* * * * *)');
|
|
|
logger.info('[Scheduler] Note: Account refresh is triggered by client, not server');
|
|
|
logger.info('[Scheduler] ========================================');
|
|
|
|
|
|
@@ -352,6 +354,88 @@ export class TaskScheduler {
|
|
|
this.isDyImportRunning = false;
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 自动回复私信(每5分钟执行一次)
|
|
|
+ * 只处理微信视频号平台的账号
|
|
|
+ */
|
|
|
+ private async autoReplyMessages(): Promise<void> {
|
|
|
+ // 检查是否正在执行回复任务
|
|
|
+ if (this.isAutoReplying) {
|
|
|
+ logger.info('[Scheduler] Auto reply is already running, skipping this cycle...');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取锁
|
|
|
+ this.isAutoReplying = true;
|
|
|
+ logger.debug('[Scheduler] Acquired auto reply lock');
|
|
|
+
|
|
|
+ try {
|
|
|
+ const accountRepository = AppDataSource.getRepository(PlatformAccount);
|
|
|
+
|
|
|
+ // 只获取微信视频号的活跃账号
|
|
|
+ const accounts = await accountRepository.find({
|
|
|
+ where: {
|
|
|
+ platform: 'weixin_video',
|
|
|
+ status: 'active',
|
|
|
+ },
|
|
|
+ });
|
|
|
+
|
|
|
+ if (accounts.length === 0) {
|
|
|
+ logger.info('[Scheduler] No active weixin accounts for auto reply');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ logger.info(`[Scheduler] Starting auto reply for ${accounts.length} weixin accounts...`);
|
|
|
+
|
|
|
+ let successCount = 0;
|
|
|
+ let failCount = 0;
|
|
|
+
|
|
|
+ // 为每个账号执行自动回复
|
|
|
+ for (const account of accounts) {
|
|
|
+ try {
|
|
|
+ logger.info(`[Scheduler] Auto replying for account: ${account.accountName} (${account.id})`);
|
|
|
+
|
|
|
+ // 调用 Python 服务执行自动回复
|
|
|
+ const response = await fetch('http://localhost:5005/auto-reply', {
|
|
|
+ method: 'POST',
|
|
|
+ headers: {
|
|
|
+ 'Content-Type': 'application/json',
|
|
|
+ },
|
|
|
+ body: JSON.stringify({
|
|
|
+ platform: 'weixin',
|
|
|
+ cookie: account.cookieData || '',
|
|
|
+ }),
|
|
|
+ signal: AbortSignal.timeout(120000), // 2分钟超时
|
|
|
+ });
|
|
|
+
|
|
|
+ if (!response.ok) {
|
|
|
+ throw new Error(`HTTP ${response.status}`);
|
|
|
+ }
|
|
|
+
|
|
|
+ const result = await response.json();
|
|
|
+
|
|
|
+ if (result.success) {
|
|
|
+ successCount++;
|
|
|
+ logger.info(`[Scheduler] Auto reply success for ${account.accountName}: ${result.replied_count} messages`);
|
|
|
+ } else {
|
|
|
+ failCount++;
|
|
|
+ logger.error(`[Scheduler] Auto reply failed for ${account.accountName}: ${result.error}`);
|
|
|
+ }
|
|
|
+
|
|
|
+ } catch (error) {
|
|
|
+ failCount++;
|
|
|
+ logger.error(`[Scheduler] Auto reply error for account ${account.id}:`, error);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ logger.info(`[Scheduler] Auto reply completed: ${successCount} success, ${failCount} failed`);
|
|
|
+ } finally {
|
|
|
+ // 释放锁
|
|
|
+ this.isAutoReplying = false;
|
|
|
+ logger.debug('[Scheduler] Released auto reply lock');
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
/**
|
|
|
* 百家号:内容分析-基础数据导出(近30天)→ 导入 user_day_statistics
|