Files
zsglpt/fix_schedule_and_add_logs.py
Yu Yon b5344cd55e 修复所有bug并添加新功能
- 修复添加账号按钮无反应问题
- 添加账号备注字段(可选)
- 添加账号设置按钮(修改密码/备注)
- 修复用户反馈���能
- 添加定时任务执行日志
- 修复容器重启后账号加载问题
- 修复所有JavaScript语法错误
- 优化账号加载机制(4层保障)

🤖 Generated with Claude Code
2025-12-10 11:19:16 +08:00

404 lines
16 KiB
Python
Executable File
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.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""修复定时任务并添加执行日志功能"""
def add_schedule_logs_table(database_content):
"""在database.py中添加定时任务执行日志表"""
# 在init_database函数中添加表创建代码
insert_position = ''' print(" ✓ 创建 user_schedules 表 (用户定时任务)")'''
new_table_code = ''' print(" ✓ 创建 user_schedules 表 (用户定时任务)")
# 定时任务执行日志表
cursor.execute('''
CREATE TABLE IF NOT EXISTS schedule_execution_logs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
schedule_id INTEGER NOT NULL,
user_id INTEGER NOT NULL,
schedule_name TEXT,
execute_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
total_accounts INTEGER DEFAULT 0,
success_accounts INTEGER DEFAULT 0,
failed_accounts INTEGER DEFAULT 0,
total_items INTEGER DEFAULT 0,
total_attachments INTEGER DEFAULT 0,
total_screenshots INTEGER DEFAULT 0,
duration_seconds INTEGER DEFAULT 0,
status TEXT DEFAULT 'running',
error_message TEXT,
FOREIGN KEY (schedule_id) REFERENCES user_schedules (id) ON DELETE CASCADE,
FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
)
''')
print(" ✓ 创建 schedule_execution_logs 表 (定时任务执行日志)")'''
if insert_position in database_content:
database_content = database_content.replace(insert_position, new_table_code)
print("✓ 已添加schedule_execution_logs表创建代码")
else:
print("❌ 未找到插入位置")
return database_content
# 添加数据库操作函数
functions_code = '''
# ==================== 定时任务执行日志 ====================
def create_schedule_execution_log(schedule_id, user_id, schedule_name):
"""创建定时任务执行日志"""
with db_pool.get_db() as conn:
cursor = conn.cursor()
cst_tz = pytz.timezone("Asia/Shanghai")
execute_time = datetime.now(cst_tz).strftime("%Y-%m-%d %H:%M:%S")
cursor.execute('''
INSERT INTO schedule_execution_logs (
schedule_id, user_id, schedule_name, execute_time, status
) VALUES (?, ?, ?, ?, 'running')
''', (schedule_id, user_id, schedule_name, execute_time))
conn.commit()
return cursor.lastrowid
def update_schedule_execution_log(log_id, **kwargs):
"""更新定时任务执行日志"""
with db_pool.get_db() as conn:
cursor = conn.cursor()
updates = []
params = []
allowed_fields = ['total_accounts', 'success_accounts', 'failed_accounts',
'total_items', 'total_attachments', 'total_screenshots',
'duration_seconds', 'status', 'error_message']
for field in allowed_fields:
if field in kwargs:
updates.append(f'{field} = ?')
params.append(kwargs[field])
if not updates:
return False
params.append(log_id)
sql = f"UPDATE schedule_execution_logs SET {', '.join(updates)} WHERE id = ?"
cursor.execute(sql, params)
conn.commit()
return cursor.rowcount > 0
def get_schedule_execution_logs(schedule_id, limit=10):
"""获取定时任务执行日志"""
with db_pool.get_db() as conn:
cursor = conn.cursor()
cursor.execute('''
SELECT * FROM schedule_execution_logs
WHERE schedule_id = ?
ORDER BY execute_time DESC
LIMIT ?
''', (schedule_id, limit))
return [dict(row) for row in cursor.fetchall()]
def get_user_all_schedule_logs(user_id, limit=50):
"""获取用户所有定时任务的执行日志"""
with db_pool.get_db() as conn:
cursor = conn.cursor()
cursor.execute('''
SELECT * FROM schedule_execution_logs
WHERE user_id = ?
ORDER BY execute_time DESC
LIMIT ?
''', (user_id, limit))
return [dict(row) for row in cursor.fetchall()]
'''
# 在文件末尾添加这些函数
database_content += functions_code
print("✓ 已添加定时任务日志操作函数")
return database_content
def add_schedule_log_tracking(app_content):
"""在app.py中添加定时任务执行日志记录"""
# 修改check_user_schedules函数添加日志记录
old_code = ''' print(f"[用户定时任务] 用户 {schedule_config.get('user_username', user_id)} 的任务 '{schedule_config.get('name', '')}' 开始执行")
started_count = 0
for account_id in account_ids:'''
new_code = ''' print(f"[用户定时任务] 用户 {schedule_config.get('user_username', user_id)} 的任务 '{schedule_config.get('name', '')}' 开始执行")
# 创建执行日志
import time as time_mod
execution_start_time = time_mod.time()
log_id = database.create_schedule_execution_log(
schedule_id=schedule_id,
user_id=user_id,
schedule_name=schedule_config.get('name', '未命名任务')
)
started_count = 0
for account_id in account_ids:'''
if old_code in app_content:
app_content = app_content.replace(old_code, new_code)
print("✓ 已添加执行日志创建代码")
else:
print("⚠ 未找到执行日志创建位置")
# 添加日志更新代码(在任务执行完成后)
old_code2 = ''' # 更新最后执行时间
database.update_schedule_last_run(schedule_id)
print(f"[用户定时任务] 已启动 {started_count} 个账号")'''
new_code2 = ''' # 更新最后执行时间
database.update_schedule_last_run(schedule_id)
# 更新执行日志
execution_duration = int(time_mod.time() - execution_start_time)
database.update_schedule_execution_log(
log_id,
total_accounts=len(account_ids),
success_accounts=started_count,
failed_accounts=len(account_ids) - started_count,
duration_seconds=execution_duration,
status='completed'
)
print(f"[用户定时任务] 已启动 {started_count} 个账号")'''
if old_code2 in app_content:
app_content = app_content.replace(old_code2, new_code2)
print("✓ 已添加执行日志更新代码")
else:
print("⚠ 未找到执行日志更新位置")
# 添加日志查询API
api_code = '''
# ==================== 定时任务执行日志API ====================
@app.route('/api/schedules/<int:schedule_id>/logs', methods=['GET'])
@login_required
def get_schedule_logs_api(schedule_id):
"""获取定时任务执行日志"""
schedule = database.get_schedule_by_id(schedule_id)
if not schedule:
return jsonify({"error": "定时任务不存在"}), 404
if schedule['user_id'] != current_user.id:
return jsonify({"error": "无权访问"}), 403
limit = request.args.get('limit', 10, type=int)
logs = database.get_schedule_execution_logs(schedule_id, limit)
return jsonify(logs)
'''
# 在批量操作API之前插入
insert_marker = '# ==================== 批量操作API ===================='
if insert_marker in app_content:
app_content = app_content.replace(insert_marker, api_code + insert_marker)
print("✓ 已添加日志查询API")
else:
print("⚠ 未找到API插入位置")
return app_content
def add_frontend_log_button(html_content):
"""在前端添加日志按钮"""
# 修改定时任务卡片,添加日志按钮
old_html = ''' '<button class="btn btn-text btn-small" onclick="editSchedule(' + s.id + ')">编辑</button>' +
'<button class="btn btn-text btn-small" style="color: var(--md-error);" onclick="deleteSchedule(' + s.id + ')">删除</button>' +'''
new_html = ''' '<button class="btn btn-text btn-small" onclick="editSchedule(' + s.id + ')">编辑</button>' +
'<button class="btn btn-text btn-small" onclick="viewScheduleLogs(' + s.id + ')">日志</button>' +
'<button class="btn btn-text btn-small" style="color: var(--md-error);" onclick="deleteSchedule(' + s.id + ')">删除</button>' +'''
if old_html in html_content:
html_content = html_content.replace(old_html, new_html)
print("✓ 已添加日志按钮HTML")
else:
print("⚠ 未找到日志按钮插入位置")
# 添加日志弹窗HTML
modal_html = ''' <!-- 定时任务执行日志弹窗 -->
<div class="modal-overlay" id="scheduleLogsModal">
<div class="modal" style="max-width: 800px;">
<div class="modal-header"><h3 class="modal-title" id="scheduleLogsTitle">执行日志</h3></div>
<div class="modal-body">
<div id="scheduleLogsList" style="max-height: 500px; overflow-y: auto;"></div>
<div id="emptyScheduleLogs" class="empty-state" style="display: none;">
<div class="empty-state-icon">📝</div>
<p>暂无执行记录</p>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-text" onclick="closeModal('scheduleLogsModal')">关闭</button>
</div>
</div>
</div>
<script>'''
insert_marker = ' <script>'
if insert_marker in html_content:
html_content = html_content.replace(insert_marker, modal_html, 1)
print("✓ 已添加日志弹窗HTML")
else:
print("⚠ 未找到弹窗插入位置")
# 添加JavaScript函数
js_code = '''
// ==================== 定时任务执行日志 ====================
function viewScheduleLogs(scheduleId) {
const schedule = schedules.find(s => s.id === scheduleId);
if (!schedule) {
showToast('定时任务不存在', 'error');
return;
}
document.getElementById('scheduleLogsTitle').textContent = schedule.name + ' - 执行日志';
loadScheduleLogs(scheduleId);
openModal('scheduleLogsModal');
}
function loadScheduleLogs(scheduleId) {
fetch('/api/schedules/' + scheduleId + '/logs?limit=20')
.then(r => r.json())
.then(logs => {
const container = document.getElementById('scheduleLogsList');
const empty = document.getElementById('emptyScheduleLogs');
if (!logs || logs.length === 0) {
container.innerHTML = '';
empty.style.display = 'block';
return;
}
empty.style.display = 'none';
let html = '<div style="display: grid; gap: 12px;">';
logs.forEach(log => {
const statusColor = log.status === 'completed' ? '#4CAF50' :
log.status === 'failed' ? '#F44336' : '#FF9800';
const statusText = log.status === 'completed' ? '已完成' :
log.status === 'failed' ? '失败' : '运行中';
html += '<div style="background: #f5f5f5; border-radius: 8px; padding: 16px; border-left: 4px solid ' + statusColor + ';">';
html += '<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px;">';
html += '<div style="font-weight: 500; font-size: 14px;">' + log.execute_time + '</div>';
html += '<div style="color: ' + statusColor + '; font-weight: 600; font-size: 13px;">' + statusText + '</div>';
html += '</div>';
html += '<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 12px; font-size: 13px;">';
html += '<div><span style="color: #666;">账号数:</span> <strong>' + (log.total_accounts || 0) + '</strong></div>';
html += '<div><span style="color: #666;">成功:</span> <strong style="color: #4CAF50;">' + (log.success_accounts || 0) + '</strong></div>';
html += '<div><span style="color: #666;">失败:</span> <strong style="color: #F44336;">' + (log.failed_accounts || 0) + '</strong></div>';
html += '<div><span style="color: #666;">浏览内容:</span> <strong>' + (log.total_items || 0) + '</strong></div>';
html += '<div><span style="color: #666;">查看附件:</span> <strong>' + (log.total_attachments || 0) + '</strong></div>';
html += '<div><span style="color: #666;">截图数:</span> <strong>' + (log.total_screenshots || 0) + '</strong></div>';
if (log.duration_seconds) {
const mins = Math.floor(log.duration_seconds / 60);
const secs = log.duration_seconds % 60;
html += '<div><span style="color: #666;">耗时:</span> <strong>' + mins + '' + secs + '秒</strong></div>';
}
html += '</div>';
if (log.error_message) {
html += '<div style="margin-top: 8px; padding: 8px; background: #FFEBEE; border-radius: 4px; color: #C62828; font-size: 12px;">';
html += '<strong>错误:</strong> ' + escapeHtml(log.error_message);
html += '</div>';
}
html += '</div>';
});
html += '</div>';
container.innerHTML = html;
})
.catch(err => {
showToast('加载日志失败', 'error');
console.error(err);
});
}
'''
# 在logout函数之前插入
insert_marker2 = ' function logout() {'
if insert_marker2 in html_content:
html_content = html_content.replace(insert_marker2, js_code + insert_marker2)
print("✓ 已添加日志查看JavaScript函数")
else:
print("⚠ 未找到JavaScript插入位置")
return html_content
def main():
import sys
print("=" * 60)
print("定时任务修复和日志功能添加脚本")
print("=" * 60)
print()
# 读取database.py
print("[1/3] 修改 database.py...")
with open('database.py', 'r', encoding='utf-8') as f:
database_content = f.read()
database_content = add_schedule_logs_table(database_content)
with open('database.py', 'w', encoding='utf-8') as f:
f.write(database_content)
print()
# 读取app.py
print("[2/3] 修改 app.py...")
with open('app.py', 'r', encoding='utf-8') as f:
app_content = f.read()
app_content = add_schedule_log_tracking(app_content)
with open('app.py', 'w', encoding='utf-8') as f:
f.write(app_content)
print()
# 读取templates/index.html
print("[3/3] 修改 templates/index.html...")
with open('templates/index.html', 'r', encoding='utf-8') as f:
html_content = f.read()
html_content = add_frontend_log_button(html_content)
with open('templates/index.html', 'w', encoding='utf-8') as f:
f.write(html_content)
print()
print("=" * 60)
print("✅ 所有修改完成!")
print("=" * 60)
print()
print("下一步操作:")
print("1. 重启Docker容器: docker-compose restart")
print("2. 检查定时任务是否启用: enabled = 1")
print("3. 测试定时任务执行")
print()
if __name__ == '__main__':
main()