Files
aiagents-stock/修复说明-智策定时分析通知重复.md
T
2025-10-21 15:40:58 +08:00

8.5 KiB
Raw Blame History

智策定时分析通知重复问题 - 修复说明

📋 问题描述

症状:智策定时分析完成后

  • ✉️ 邮箱收到 3 封相同邮件
  • 📱 钉钉收到 2 次相同通知
  • 所有通知内容完全一样,同时收到

定时设置

  • 定时时间: 08:15
  • 任务状态: 运行中
  • 上次运行: 2025-10-21 08:21:55

🔍 根本原因分析

原因1:schedule库任务重复添加 主要原因

问题schedule.clear('sector_strategy') 在某些情况下无法正确清除所有旧任务,导致任务累积。

证据

  • 3封邮件表示 _send_analysis_notification() 被调用了3次
  • 说明同一时刻有3个定时任务被触发

原因2:持仓定时分析的干扰

问题portfolio_scheduler.py 中使用 schedule.clear() 会清除所有定时任务,包括智策的!

代码位置

  • portfolio_scheduler.py 第540行:schedule.clear()
  • portfolio_scheduler.py 第492行:schedule.clear()
  • portfolio_scheduler.py 第573行:schedule.clear()

原因3:缺少并发保护

问题:如果分析还没完成,定时器再次触发,会导致多个分析任务并发执行。

修复方案

修复1:智策调度器改进(sector_strategy_scheduler.py

1.1 添加并发保护锁

def __init__(self):
    # ...
    self._analysis_lock = threading.Lock()  # 防止并发执行

1.2 安全的任务执行包装

def _run_analysis_safe(self):
    """运行智策分析(带锁保护,防止并发执行)"""
    if not self._analysis_lock.acquire(blocking=False):
        print("[智策定时] ⚠️ 上一次分析还未完成,跳过本次执行")
        return
    
    try:
        self._run_analysis()
    finally:
        self._analysis_lock.release()

1.3 改进任务清除逻辑

修复前

schedule.clear('sector_strategy')  # 可能无法正确清除

修复后

# 手动遍历并清除所有带sector_strategy标签的任务
jobs_to_remove = [job for job in schedule.jobs if 'sector_strategy' in job.tags]
for job in jobs_to_remove:
    schedule.cancel_job(job)
print(f"[智策定时] 清除了 {len(jobs_to_remove)} 个旧任务")

1.4 添加通知去重机制 核心防护

def _send_analysis_notification(self, result):
    # 去重检查:如果5分钟内已发送过通知,则跳过
    current_time = datetime.now()
    if self.last_notification_time:
        time_diff = (current_time - self.last_notification_time).total_seconds()
        if time_diff < 300:  # 5分钟 = 300秒
            print(f"[智策定时] ⚠️ 距离上次通知仅{time_diff:.0f}秒,跳过重复发送")
            return
    
    # ... 发送通知 ...
    
    # 更新最后通知时间
    if sent_count > 0:
        self.last_notification_time = current_time
        print(f"[智策定时] 📝 已记录通知时间: {current_time.strftime('%H:%M:%S')}")

修复2:持仓调度器改进(portfolio_scheduler.py

2.1 使用标签化任务管理

修复前

schedule.clear()  # 危险!会清除所有任务

修复后

# 只清除持仓定时分析的任务,不影响智策和监测
jobs_to_remove = [job for job in schedule.jobs if 'portfolio_analysis' in job.tags]
for job in jobs_to_remove:
    schedule.cancel_job(job)

2.2 添加任务标签

for time_str in self.schedule_times:
    job = schedule.every().day.at(time_str).do(self._scheduled_job)
    job.tag('portfolio_analysis')  # 打标签,方便管理

📊 修复效果

修复前

08:15:00 → 触发任务1 → 发送邮件 ✉️
08:15:00 → 触发任务2 → 发送邮件 ✉️  (重复!)
08:15:00 → 触发任务3 → 发送邮件 ✉️  (重复!)

结果:收到3封邮件

修复后

08:15:00 → 触发任务1 → 获取锁 → 发送邮件 ✉️
08:15:00 → 触发任务2 → 无法获取锁 → 跳过
08:15:00 → 触发任务3 → 无法获取锁 → 跳过

结果:只收到1封邮件

多层防护机制

  1. 任务去重:改进的清除逻辑防止任务累积
  2. 并发锁:防止多个任务同时执行
  3. 时间去重5分钟内重复通知直接跳过
  4. 模块隔离:各模块任务独立管理,互不影响

🧪 测试方法

测试1:验证任务唯一性

# 在智策定时分析UI中
1. 启动定时任务
2. 运行以下代码检查任务数量
import schedule
sector_jobs = [job for job in schedule.jobs if 'sector_strategy' in job.tags]
print(f"智策任务数量: {len(sector_jobs)}")  # 应该只有1个

测试2:验证去重机制

# 手动触发两次分析
1. 点击"立即运行"
2. 等待1分钟
3. 再次点击"立即运行"
# 第二次应该显示"距离上次通知仅XX秒,跳过重复发送"

测试3:验证模块隔离

# 同时运行智策和持仓定时分析
1. 启动智策定时任务
2. 启动持仓定时任务
3. 检查两个任务是否都正常运行

测试4:验证实际效果(最重要)

# 等待下一次定时触发(明天08:15)
1. 确认只收到1封邮件
2. 确认只收到1次钉钉通知
3. 查看日志输出确认只有1次发送记录

📝 日志输出示例

正常情况(修复后)

[智策定时] 开始定时分析 - 2025-10-22 08:15:00
[智策定时] [1/3] 获取市场数据...
[智策定时] ✓ 数据获取成功
[智策定时] [2/3] AI智能体分析中...
[智策定时] ✓ 分析完成
[智策定时] [3/3] 发送邮件通知...
[智策定时] [Webhook] 准备发送...
[智策定时] ✓ Webhook发送成功
[智策定时] [邮件] 准备发送...
[智策定时] ✓ 邮件发送成功
[智策定时] 📝 已记录通知时间: 08:15:23
[智策定时] ✓ 定时分析完成!

重复触发时(修复后)

[智策定时] ⚠️ 上一次分析还未完成,跳过本次执行

5分钟内重复通知时(修复后)

[智策定时] ⚠️ 距离上次通知仅123秒,跳过重复发送

🔧 如何使用修复后的版本

步骤1:重启系统

# 停止当前运行的streamlit
Ctrl+C

# 重新启动
python run.py
# 或
streamlit run app.py

步骤2:重新配置定时任务

  1. 进入"智策板块分析"页面
  2. 找到"定时分析"标签页
  3. 点击"停止"(如果正在运行)
  4. 重新设置定时时间为 08:15
  5. 点击"启动"

步骤3:验证状态

查看页面显示:

✅ 定时任务运行中
⏰ 定时时间: 08:15
📅 下次运行: 2025-10-22 08:15:00

步骤4:等待下次执行

明天08:15自动执行后,检查:

  • 邮箱只收到1封邮件
  • 钉钉只收到1次通知
  • UI显示正常的执行时间

🛡️ 预防措施

1. 避免重复启动

  • 在启动新任务前,先检查是否已有任务在运行
  • 如果要修改时间,先"停止"再重新"启动"

2. 避免频繁手动触发

  • "立即运行"功能会立即执行分析并发送通知
  • 避免在5分钟内多次点击

3. 查看日志

运行时在控制台查看日志输出,确认:

  • 任务数量是否正确
  • 是否有重复执行的警告
  • 通知发送次数是否正确

📚 技术要点

任务标签体系

  • sector_strategy - 智策定时分析
  • portfolio_analysis - 持仓定时分析
  • monitor - 实时监测定时功能

锁机制

使用 threading.Lock() 的非阻塞获取:

  • acquire(blocking=False) - 如果锁已被占用,立即返回False
  • 保证同一时刻只有一个分析任务在执行

时间去重

  • 记录 last_notification_time
  • 每次发送前检查时间差
  • 5分钟(300秒)内的重复请求直接跳过

修复清单

  • 智策调度器添加并发保护锁
  • 智策调度器添加通知去重机制(5分钟)
  • 智策调度器改进任务清除逻辑
  • 持仓调度器使用标签化任务管理
  • 持仓调度器避免清除其他模块任务
  • 添加详细日志输出
  • 创建测试方案
  • 编写修复说明文档

🎯 预期效果

修复后,智策定时分析应该:

  1. 每次只发送 1封 邮件
  2. 每次只发送 1次 钉钉通知
  3. 不会受到持仓定时分析的影响
  4. 即使误操作多次启动,也有去重保护
  5. 日志清晰,便于排查问题

修复日期2025-10-21
修复版本v1.1.0
修复文件

  • sector_strategy_scheduler.py
  • portfolio_scheduler.py

下次执行验证2025-10-22 08:15:00