# 智策定时分析通知重复问题 - 修复说明 ## 📋 问题描述 **症状**:智策定时分析完成后 - ✉️ 邮箱收到 **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 添加并发保护锁 ```python def __init__(self): # ... self._analysis_lock = threading.Lock() # 防止并发执行 ``` #### 1.2 安全的任务执行包装 ```python 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 改进任务清除逻辑 **修复前**: ```python schedule.clear('sector_strategy') # 可能无法正确清除 ``` **修复后**: ```python # 手动遍历并清除所有带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 添加通知去重机制 ⭐ 核心防护 ```python 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 使用标签化任务管理 **修复前**: ```python schedule.clear() # 危险!会清除所有任务 ``` **修复后**: ```python # 只清除持仓定时分析的任务,不影响智策和监测 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 添加任务标签 ```python 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:验证任务唯一性 ```python # 在智策定时分析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:验证去重机制 ```python # 手动触发两次分析 1. 点击"立即运行" 2. 等待1分钟 3. 再次点击"立即运行" # 第二次应该显示"距离上次通知仅XX秒,跳过重复发送" ``` ### 测试3:验证模块隔离 ```python # 同时运行智策和持仓定时分析 1. 启动智策定时任务 2. 启动持仓定时任务 3. 检查两个任务是否都正常运行 ``` ### 测试4:验证实际效果(最重要) ```python # 等待下一次定时触发(明天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:重启系统 ```bash # 停止当前运行的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秒)内的重复请求直接跳过 ## ✅ 修复清单 - [x] 智策调度器添加并发保护锁 - [x] 智策调度器添加通知去重机制(5分钟) - [x] 智策调度器改进任务清除逻辑 - [x] 持仓调度器使用标签化任务管理 - [x] 持仓调度器避免清除其他模块任务 - [x] 添加详细日志输出 - [x] 创建测试方案 - [x] 编写修复说明文档 ## 🎯 预期效果 修复后,智策定时分析应该: 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