添加定时任务随机时间执行功能;修复手动执行任务不发送邮件通知

This commit is contained in:
Yu Yon
2025-12-12 10:12:27 +08:00
parent d1e47dca7f
commit 2d0d51da17
3 changed files with 69 additions and 19 deletions

65
app.py
View File

@@ -132,6 +132,10 @@ log_cache_total_count = 0 # 全局日志总数,防止无限增长
batch_task_screenshots = {} # {batch_id: {'user_id': x, 'browse_type': y, 'screenshots': [{'account_name': a, 'path': p, 'items': n, 'attachments': m}], 'total_accounts': n, 'completed': n}}
batch_task_lock = threading.Lock()
# 随机延迟任务存储
pending_random_schedules = {}
pending_random_lock = threading.Lock()
# 日志缓存限制
MAX_LOGS_PER_USER = config.MAX_LOGS_PER_USER # 每个用户最多100条
MAX_TOTAL_LOGS = config.MAX_TOTAL_LOGS # 全局最多1000条,防止内存泄漏
@@ -2181,12 +2185,14 @@ def run_task(user_id, account_id, browse_type, enable_screenshot=True, source="m
source=source
)
# 发送任务完成邮件通知(不截图时在此发送)
try:
user_info = database.get_user_by_id(user_id)
# 检查用户是否开启了邮件通知
if user_info and user_info.get('email') and database.get_user_email_notify(user_id):
account_name = account.remark if account.remark else account.username
email_service.send_task_complete_email_async(
# 只对定时任务发送邮件通知,手动执行不发送
if source and source.startswith('user_scheduled'):
try:
user_info = database.get_user_by_id(user_id)
# 检查用户是否开启了邮件通知
if user_info and user_info.get('email') and database.get_user_email_notify(user_id):
account_name = account.remark if account.remark else account.username
email_service.send_task_complete_email_async(
user_id=user_id,
email=user_info['email'],
username=user_info['username'],
@@ -2197,8 +2203,8 @@ def run_task(user_id, account_id, browse_type, enable_screenshot=True, source="m
screenshot_path=None,
log_callback=lambda msg: log_to_client(msg, user_id, account_id)
)
except Exception as email_error:
logger.warning(f"发送任务完成邮件失败: {email_error}")
except Exception as email_error:
logger.warning(f"发送任务完成邮件失败: {email_error}")
# 成功则跳出重试循环
break
else:
@@ -2599,8 +2605,8 @@ def take_screenshot_for_account(user_id, account_id, browse_type="应读", sourc
print(f"[批次邮件] 已发送打包邮件,包含 {len(batch_info['screenshots'])} 个截图")
except Exception as batch_email_err:
print(f"[批次邮件] 发送失败: {batch_email_err}")
else:
# 非批次任务:直接发送邮件(保持原有逻辑
elif source and source.startswith('user_scheduled'):
# 非批次的定时任务:直接发送邮件(手动执行不发送邮件通知
user_info = database.get_user_by_id(user_id)
if user_info and user_info.get('email') and database.get_user_email_notify(user_id):
email_service.send_task_complete_email_async(
@@ -3683,8 +3689,43 @@ def scheduled_task_worker():
h, m = schedule_time.split(':')
schedule_time = f"{int(h):02d}:{int(m):02d}"
# 检查时间是否匹配
if schedule_time != current_time:
# 获取随机延迟设置
random_delay = schedule_config.get('random_delay', 0)
# 处理随机延迟逻辑
should_execute = False
if random_delay == 1:
import random as rand_module
with pending_random_lock:
if schedule_id in pending_random_schedules:
# 检查是否到达随机执行时间
pending_info = pending_random_schedules[schedule_id]
scheduled_for = pending_info['scheduled_for']
if now >= scheduled_for:
del pending_random_schedules[schedule_id]
should_execute = True
print(f"[定时任务] 任务#{schedule_id} 随机延迟到达,开始执行")
else:
# 计算窗口时间
set_hour, set_minute = map(int, schedule_time.split(':'))
set_time_today = now.replace(hour=set_hour, minute=set_minute, second=0, microsecond=0)
window_start = set_time_today - timedelta(minutes=15)
# 当前时间刚好到达窗口开始
if now.strftime('%H:%M') == window_start.strftime('%H:%M'):
random_minutes = rand_module.randint(0, 30)
random_time = window_start + timedelta(minutes=random_minutes)
pending_random_schedules[schedule_id] = {
'scheduled_for': random_time,
'config': schedule_config
}
print(f"[定时任务] 任务#{schedule_id} 安排随机时间: {random_time.strftime('%H:%M')}")
else:
# 精确时间匹配
if schedule_time == current_time:
should_execute = True
if not should_execute:
continue
# 检查星期是否匹配

View File

@@ -1738,7 +1738,7 @@ def get_schedule_by_id(schedule_id):
def create_user_schedule(user_id, name='我的定时任务', schedule_time='08:00',
weekdays='1,2,3,4,5', browse_type='应读',
enable_screenshot=1, account_ids=None):
enable_screenshot=1, account_ids=None, random_delay=0):
"""创建用户定时任务"""
import json
with db_pool.get_db() as conn:
@@ -1751,10 +1751,10 @@ def create_user_schedule(user_id, name='我的定时任务', schedule_time='08:0
cursor.execute('''
INSERT INTO user_schedules (
user_id, name, enabled, schedule_time, weekdays,
browse_type, enable_screenshot, account_ids, created_at, updated_at
) VALUES (?, ?, 0, ?, ?, ?, ?, ?, ?, ?)
browse_type, enable_screenshot, account_ids, random_delay, created_at, updated_at
) VALUES (?, ?, 0, ?, ?, ?, ?, ?, ?, ?, ?)
''', (user_id, name, schedule_time, weekdays, browse_type,
enable_screenshot, account_ids_str, cst_time, cst_time))
enable_screenshot, account_ids_str, random_delay, cst_time, cst_time))
conn.commit()
return cursor.lastrowid
@@ -1771,7 +1771,7 @@ def update_user_schedule(schedule_id, **kwargs):
params = []
allowed_fields = ['name', 'enabled', 'schedule_time', 'weekdays',
'browse_type', 'enable_screenshot', 'account_ids']
'browse_type', 'enable_screenshot', 'account_ids', 'random_delay']
for field in allowed_fields:
if field in kwargs:

View File

@@ -710,6 +710,13 @@
<label class="form-label">参与账号</label>
<div class="account-select-list" id="scheduleAccountList"></div>
</div>
<div class="form-group">
<label class="checkbox-wrapper">
<input type="checkbox" id="scheduleRandomDelay">
<span>随机时间执行</span>
</label>
<div style="font-size: 12px; color: #666; margin-top: 5px; margin-left: 24px;">勾选后将在设定时间前后15分钟内随机执行</div>
</div>
<div class="form-group">
<label class="checkbox-wrapper">
<input type="checkbox" id="scheduleScreenshot" checked>
@@ -1544,7 +1551,7 @@
'<div class="schedule-info">' +
'<div class="schedule-name">' + escapeHtml(s.name || '未命名任务') + '</div>' +
'<div class="schedule-meta">' +
'<span>⏰ ' + (s.schedule_time || '08:00') + '</span>' +
'<span>⏰ ' + (s.schedule_time || '08:00') + (s.random_delay === 1 ? ' <span style="color:#ff9800;font-size:11px;">±15分钟</span>' : '') + '</span>' +
'<span>📅 ' + (weekdays || '无') + '</span>' +
'<span>📋 ' + (s.browse_type || '应读') + '</span>' +
'<span>👤 ' + accountCount + ' 个账号</span>' +
@@ -1569,6 +1576,7 @@
document.getElementById('scheduleTime').value = scheduleData ? (scheduleData.schedule_time || '08:00') : '08:00';
document.getElementById('scheduleBrowseType').value = scheduleData ? (scheduleData.browse_type || '应读') : '应读';
document.getElementById('scheduleScreenshot').checked = scheduleData ? (scheduleData.enable_screenshot !== 0) : true;
document.getElementById('scheduleRandomDelay').checked = scheduleData ? (scheduleData.random_delay === 1) : false;
const weekdays = scheduleData ? (scheduleData.weekdays || '').split(',') : ['1','2','3','4','5'];
document.querySelectorAll('#weekdaySelector input').forEach(function(input) {
input.checked = weekdays.includes(input.value);
@@ -1608,7 +1616,8 @@
const accountIds = [];
document.querySelectorAll('#scheduleAccountList input:checked').forEach(function(input) { accountIds.push(input.value); });
if (weekdays.length === 0) { showToast('请选择至少一个执行日期', 'warning'); return; }
const data = {name: name, schedule_time: scheduleTime, weekdays: weekdays.join(','), browse_type: browseType, enable_screenshot: enableScreenshot, account_ids: accountIds};
const randomDelay = document.getElementById('scheduleRandomDelay').checked ? 1 : 0;
const data = {name: name, schedule_time: scheduleTime, weekdays: weekdays.join(','), browse_type: browseType, enable_screenshot: enableScreenshot, account_ids: accountIds, random_delay: randomDelay};
const url = editingScheduleId ? '/api/schedules/' + editingScheduleId : '/api/schedules';
const method = editingScheduleId ? 'PUT' : 'POST';
fetch(url, {method: method, headers: {'Content-Type': 'application/json'}, body: JSON.stringify(data)}).then(r => r.json()).then(function(result) {