- 修复添加账号按钮无反应问题
- 添加账号备注字段(可选)
- 添加账号设置按钮(修改密码/备注)
- 修复用户反馈���能
- 添加定时任务执行日志
- 修复容器重启后账号加载问题
- 修复所有JavaScript语法错误
- 优化账号加载机制(4层保障)
🤖 Generated with Claude Code
404 lines
16 KiB
Python
Executable File
404 lines
16 KiB
Python
Executable File
#!/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()
|