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

303 lines
8.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 智策定时分析通知重复问题 - 修复说明
## 📋 问题描述
**症状**:智策定时分析完成后
- ✉️ 邮箱收到 **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