✨ 添加定时任务日志管理功能
- 添加用户清空日志按钮 - 添加30天自动清理定时任务执行日志 - 简化日志API代码,移除调试日志 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
69
app.py
69
app.py
@@ -2816,9 +2816,9 @@ def scheduled_task_worker():
|
|||||||
print(f"[定时清理] 清理验证码出错: {str(e)}")
|
print(f"[定时清理] 清理验证码出错: {str(e)}")
|
||||||
|
|
||||||
def cleanup_old_data():
|
def cleanup_old_data():
|
||||||
"""清理7天前的截图和日志"""
|
"""清理旧数据:7天前截图和任务日志,30天前操作日志和定时任务执行日志"""
|
||||||
try:
|
try:
|
||||||
print(f"[定时清理] 开始清理7天前的数据...")
|
print(f"[定时清理] 开始清理旧数据...")
|
||||||
|
|
||||||
# 清理7天前的任务日志
|
# 清理7天前的任务日志
|
||||||
deleted_logs = database.delete_old_task_logs(7)
|
deleted_logs = database.delete_old_task_logs(7)
|
||||||
@@ -2827,6 +2827,10 @@ def scheduled_task_worker():
|
|||||||
# 清理30天前的操作日志
|
# 清理30天前的操作日志
|
||||||
deleted_operation_logs = database.clean_old_operation_logs(30)
|
deleted_operation_logs = database.clean_old_operation_logs(30)
|
||||||
print(f"[定时清理] 已删除 {deleted_operation_logs} 条操作日志")
|
print(f"[定时清理] 已删除 {deleted_operation_logs} 条操作日志")
|
||||||
|
|
||||||
|
# 清理30天前的定时任务执行日志
|
||||||
|
deleted_schedule_logs = database.clean_old_schedule_logs(30)
|
||||||
|
print(f"[定时清理] 已删除 {deleted_schedule_logs} 条定时任务执行日志")
|
||||||
# 清理7天前的截图
|
# 清理7天前的截图
|
||||||
deleted_screenshots = 0
|
deleted_screenshots = 0
|
||||||
if os.path.exists(SCREENSHOTS_DIR):
|
if os.path.exists(SCREENSHOTS_DIR):
|
||||||
@@ -3278,53 +3282,32 @@ def run_schedule_now_api(schedule_id):
|
|||||||
def get_schedule_logs_api(schedule_id):
|
def get_schedule_logs_api(schedule_id):
|
||||||
"""获取定时任务执行日志"""
|
"""获取定时任务执行日志"""
|
||||||
try:
|
try:
|
||||||
print(f"[API日志] 开始处理日志请求 - schedule_id={schedule_id}, user_id={current_user.id}")
|
schedule = database.get_schedule_by_id(schedule_id)
|
||||||
|
if not schedule or schedule['user_id'] != current_user.id:
|
||||||
# 先验证定时任务是否存在和权限
|
|
||||||
try:
|
|
||||||
schedule = database.get_schedule_by_id(schedule_id)
|
|
||||||
print(f"[API日志] 查询定时任务结果: {schedule}")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"[API日志] 查询定时任务失败: {e}")
|
|
||||||
# 即使查询失败,也返回空数组,避免500错误
|
|
||||||
return jsonify([])
|
|
||||||
|
|
||||||
if not schedule:
|
|
||||||
print(f"[API日志] 定时任务#{schedule_id}不存在,返回空数组")
|
|
||||||
return jsonify([])
|
|
||||||
|
|
||||||
try:
|
|
||||||
if schedule['user_id'] != current_user.id:
|
|
||||||
print(f"[API日志] 权限不足,返回空数组")
|
|
||||||
return jsonify([])
|
|
||||||
except Exception as e:
|
|
||||||
print(f"[API日志] 权限检查失败: {e},返回空数组")
|
|
||||||
return jsonify([])
|
|
||||||
|
|
||||||
# 查询日志
|
|
||||||
try:
|
|
||||||
limit = request.args.get('limit', 10, type=int)
|
|
||||||
print(f"[API日志] 开始查询日志,limit={limit}")
|
|
||||||
|
|
||||||
logs = database.get_schedule_execution_logs(schedule_id, limit)
|
|
||||||
print(f"[API日志] 成功查询到{len(logs)}条日志")
|
|
||||||
|
|
||||||
return jsonify(logs if logs else [])
|
|
||||||
except Exception as e:
|
|
||||||
print(f"[API日志] 查询日志失败: {e}")
|
|
||||||
import traceback
|
|
||||||
traceback.print_exc()
|
|
||||||
# 查询失败也返回空数组
|
|
||||||
return jsonify([])
|
return jsonify([])
|
||||||
|
|
||||||
|
limit = request.args.get('limit', 20, type=int)
|
||||||
|
logs = database.get_schedule_execution_logs(schedule_id, limit)
|
||||||
|
return jsonify(logs if logs else [])
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[API日志] 外层异常捕获: {str(e)}")
|
|
||||||
import traceback
|
|
||||||
traceback.print_exc()
|
|
||||||
# 任何错误都返回空数组,不返回500
|
|
||||||
return jsonify([])
|
return jsonify([])
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/api/schedules/<int:schedule_id>/logs', methods=['DELETE'])
|
||||||
|
@login_required
|
||||||
|
def delete_schedule_logs_api(schedule_id):
|
||||||
|
"""清空定时任务执行日志"""
|
||||||
|
try:
|
||||||
|
schedule = database.get_schedule_by_id(schedule_id)
|
||||||
|
if not schedule or schedule['user_id'] != current_user.id:
|
||||||
|
return jsonify({"error": "无权限"}), 403
|
||||||
|
|
||||||
|
deleted = database.delete_schedule_logs(schedule_id, current_user.id)
|
||||||
|
return jsonify({"success": True, "deleted": deleted})
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({"error": str(e)}), 500
|
||||||
|
|
||||||
|
|
||||||
# ==================== 批量操作API ====================
|
# ==================== 批量操作API ====================
|
||||||
|
|
||||||
@app.route('/api/accounts/batch/start', methods=['POST'])
|
@app.route('/api/accounts/batch/start', methods=['POST'])
|
||||||
|
|||||||
24
database.py
24
database.py
@@ -1706,3 +1706,27 @@ def get_user_all_schedule_logs(user_id, limit=50):
|
|||||||
LIMIT ?
|
LIMIT ?
|
||||||
''', (user_id, limit))
|
''', (user_id, limit))
|
||||||
return [dict(row) for row in cursor.fetchall()]
|
return [dict(row) for row in cursor.fetchall()]
|
||||||
|
|
||||||
|
|
||||||
|
def delete_schedule_logs(schedule_id, user_id):
|
||||||
|
"""删除指定定时任务的所有执行日志(需验证用户权限)"""
|
||||||
|
with db_pool.get_db() as conn:
|
||||||
|
cursor = conn.cursor()
|
||||||
|
cursor.execute('''
|
||||||
|
DELETE FROM schedule_execution_logs
|
||||||
|
WHERE schedule_id = ? AND user_id = ?
|
||||||
|
''', (schedule_id, user_id))
|
||||||
|
conn.commit()
|
||||||
|
return cursor.rowcount
|
||||||
|
|
||||||
|
|
||||||
|
def clean_old_schedule_logs(days=30):
|
||||||
|
"""清理指定天数前的定时任务执行日志"""
|
||||||
|
with db_pool.get_db() as conn:
|
||||||
|
cursor = conn.cursor()
|
||||||
|
cursor.execute('''
|
||||||
|
DELETE FROM schedule_execution_logs
|
||||||
|
WHERE execute_time < datetime('now', '-' || ? || ' days')
|
||||||
|
''', (days,))
|
||||||
|
conn.commit()
|
||||||
|
return cursor.rowcount
|
||||||
|
|||||||
@@ -493,6 +493,7 @@
|
|||||||
<div id="scheduleLogsList" style="max-height: 400px; overflow-y: auto;"></div>
|
<div id="scheduleLogsList" style="max-height: 400px; overflow-y: auto;"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
|
<button class="btn btn-text" style="color: var(--md-error);" onclick="clearScheduleLogs()">清空日志</button>
|
||||||
<button class="btn btn-primary" onclick="closeModal('scheduleLogsModal')">关闭</button>
|
<button class="btn btn-primary" onclick="closeModal('scheduleLogsModal')">关闭</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1312,6 +1313,8 @@
|
|||||||
if (schedule) openScheduleModal(schedule);
|
if (schedule) openScheduleModal(schedule);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let currentLogsScheduleId = null;
|
||||||
|
|
||||||
function viewScheduleLogs(scheduleId) {
|
function viewScheduleLogs(scheduleId) {
|
||||||
const numericId = parseInt(scheduleId, 10);
|
const numericId = parseInt(scheduleId, 10);
|
||||||
const schedule = schedules.find(s => s.id == numericId);
|
const schedule = schedules.find(s => s.id == numericId);
|
||||||
@@ -1320,6 +1323,7 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
currentLogsScheduleId = numericId;
|
||||||
document.getElementById("scheduleLogsTitle").textContent = "【" + (schedule.name || "未命名任务") + "】 执行日志";
|
document.getElementById("scheduleLogsTitle").textContent = "【" + (schedule.name || "未命名任务") + "】 执行日志";
|
||||||
|
|
||||||
fetch("/api/schedules/" + numericId + "/logs?limit=20")
|
fetch("/api/schedules/" + numericId + "/logs?limit=20")
|
||||||
@@ -1362,6 +1366,23 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function clearScheduleLogs() {
|
||||||
|
if (!currentLogsScheduleId) return;
|
||||||
|
if (!confirm('确定要清空该任务的所有执行日志吗?')) return;
|
||||||
|
|
||||||
|
fetch("/api/schedules/" + currentLogsScheduleId + "/logs", { method: 'DELETE' })
|
||||||
|
.then(r => r.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.success) {
|
||||||
|
showToast('已清空 ' + data.deleted + ' 条日志', 'success');
|
||||||
|
document.getElementById("scheduleLogsList").innerHTML = "<div class=\"empty-state\"><p>暂无执行日志</p></div>";
|
||||||
|
} else {
|
||||||
|
showToast(data.error || '清空失败', 'error');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(err => showToast('清空失败', 'error'));
|
||||||
|
}
|
||||||
|
|
||||||
function formatDuration(seconds) {
|
function formatDuration(seconds) {
|
||||||
if (seconds < 60) return seconds + "秒";
|
if (seconds < 60) return seconds + "秒";
|
||||||
const minutes = Math.floor(seconds / 60);
|
const minutes = Math.floor(seconds / 60);
|
||||||
|
|||||||
Reference in New Issue
Block a user