🎯 重构目标: - 将 3700+ 行单文件拆分为多个模块 - 清晰的职责分离,便于维护和扩展 📁 模块结构: - config.py: 配置常量和路径 - database.py: 数据库操作 - utils.py: 工具函数(日志、进程、cron等) - routes_cron.py: Cron管理API - routes_system.py: 系统监控API - routes_config.py: 配置管理API - routes_email.py: 邮件通知API - routes_project.py: 项目管理API - templates.py: HTML模板 - app.py: 主入口文件 ✨ 优势: - 易于维护:每个模块职责明确 - 易于扩展:新增功能只需添加对应模块 - 易于测试:模块独立,便于单元测试 - 易于理解:新人能快速上手 📝 更新 README.md,详细说明项目结构和API接口
254 lines
11 KiB
Python
254 lines
11 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
Cron 管理 API 路由
|
||
"""
|
||
|
||
from flask import jsonify, request
|
||
from database import get_db
|
||
from utils import (
|
||
log_message, sync_crontab_from_db, get_system_crons,
|
||
cron_expression_to_human, get_next_run_time, guess_cron_name, guess_cron_category
|
||
)
|
||
|
||
|
||
def register_cron_routes(app):
|
||
"""注册 Cron 相关路由"""
|
||
|
||
@app.route('/api/cron/tasks')
|
||
def api_cron_tasks():
|
||
"""获取数据库中的 Cron 任务列表"""
|
||
db = get_db()
|
||
c = db.cursor()
|
||
c.execute("SELECT * FROM cron_tasks ORDER BY created_at DESC")
|
||
tasks = [dict(row) for row in c.fetchall()]
|
||
|
||
# 添加人类可读描述和下次执行时间
|
||
for task in tasks:
|
||
task['human_time'] = cron_expression_to_human(task['expression'])
|
||
task['next_run'] = get_next_run_time(task['expression'])
|
||
|
||
return jsonify({'tasks': tasks})
|
||
|
||
@app.route('/api/cron/tasks', methods=['POST'])
|
||
def api_cron_add():
|
||
"""添加新的 Cron 任务"""
|
||
data = request.json
|
||
|
||
if not data.get('name') or not data.get('expression') or not data.get('command'):
|
||
return jsonify({'error': '缺少必要字段'}), 400
|
||
|
||
db = get_db()
|
||
c = db.cursor()
|
||
|
||
# 插入任务
|
||
c.execute('''INSERT INTO cron_tasks (name, expression, command, description, category, enabled)
|
||
VALUES (?, ?, ?, ?, ?, ?)''',
|
||
(data['name'], data['expression'], data['command'], data.get('description', ''), data.get('category', 'other'), 1))
|
||
|
||
task_id = c.lastrowid
|
||
db.commit()
|
||
|
||
# 记录历史(版本1)
|
||
c.execute('''INSERT INTO cron_history (task_id, version, name, expression, command, description, category, enabled, operation)
|
||
VALUES (?, 1, ?, ?, ?, ?, ?, ?, 'create')''',
|
||
(task_id, data['name'], data['expression'], data['command'], data.get('description', ''), data.get('category', 'other'), 1))
|
||
db.commit()
|
||
|
||
# 同步到系统 crontab
|
||
sync_crontab_from_db(db)
|
||
|
||
log_message(f"添加 Cron 任务: {data['name']} ({data['expression']})")
|
||
|
||
return jsonify({'message': '任务添加成功', 'task_id': task_id})
|
||
|
||
@app.route('/api/cron/tasks/<int:task_id>', methods=['PUT'])
|
||
def api_cron_update(task_id):
|
||
"""更新 Cron 任务"""
|
||
data = request.json
|
||
db = get_db()
|
||
c = db.cursor()
|
||
|
||
# 获取当前任务
|
||
c.execute("SELECT * FROM cron_tasks WHERE id = ?", (task_id,))
|
||
old_task = c.fetchone()
|
||
if not old_task:
|
||
return jsonify({'error': '任务不存在'}), 404
|
||
|
||
# 获取当前版本号
|
||
c.execute("SELECT MAX(version) FROM cron_history WHERE task_id = ?", (task_id,))
|
||
max_version = c.fetchone()[0] or 0
|
||
new_version = max_version + 1
|
||
|
||
# 更新任务
|
||
update_fields = []
|
||
update_values = []
|
||
for field in ['name', 'expression', 'command', 'description', 'category', 'enabled']:
|
||
if field in data:
|
||
update_fields.append(f"{field} = ?")
|
||
update_values.append(data[field])
|
||
|
||
if update_fields:
|
||
update_fields.append("updated_at = CURRENT_TIMESTAMP")
|
||
c.execute(f"UPDATE cron_tasks SET {','.join(update_fields)} WHERE id = ?", update_values + [task_id])
|
||
db.commit()
|
||
|
||
# 记录历史
|
||
c.execute('''INSERT INTO cron_history (task_id, version, name, expression, command, description, category, enabled, operation)
|
||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'update')''',
|
||
(task_id, new_version, data.get('name', old_task['name']), data.get('expression', old_task['expression']),
|
||
data.get('command', old_task['command']), data.get('description', old_task['description']),
|
||
data.get('category', old_task['category']), data.get('enabled', old_task['enabled'])))
|
||
db.commit()
|
||
|
||
# 同步到系统 crontab
|
||
sync_crontab_from_db(db)
|
||
|
||
log_message(f"更新 Cron 任务 ID={task_id}: 版本 {new_version}")
|
||
|
||
return jsonify({'message': '任务更新成功', 'version': new_version})
|
||
|
||
@app.route('/api/cron/tasks/<int:task_id>', methods=['DELETE'])
|
||
def api_cron_delete(task_id):
|
||
"""删除 Cron 任务"""
|
||
db = get_db()
|
||
c = db.cursor()
|
||
|
||
c.execute("SELECT * FROM cron_tasks WHERE id = ?", (task_id,))
|
||
task = c.fetchone()
|
||
if not task:
|
||
return jsonify({'error': '任务不存在'}), 404
|
||
|
||
# 记录删除历史
|
||
c.execute("SELECT MAX(version) FROM cron_history WHERE task_id = ?", (task_id,))
|
||
max_version = c.fetchone()[0] or 0
|
||
|
||
c.execute('''INSERT INTO cron_history (task_id, version, name, expression, command, description, category, enabled, operation)
|
||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'delete')''',
|
||
(task_id, max_version + 1, task['name'], task['expression'], task['command'], task['description'], task['category'], task['enabled']))
|
||
db.commit()
|
||
|
||
# 删除任务
|
||
c.execute("DELETE FROM cron_tasks WHERE id = ?", (task_id,))
|
||
db.commit()
|
||
|
||
# 同步到系统 crontab
|
||
sync_crontab_from_db(db)
|
||
|
||
log_message(f"删除 Cron 任务 ID={task_id}: {task['name']}")
|
||
|
||
return jsonify({'message': '任务删除成功'})
|
||
|
||
@app.route('/api/cron/tasks/<int:task_id>/toggle', methods=['POST'])
|
||
def api_cron_toggle(task_id):
|
||
"""启用/禁用 Cron 任务"""
|
||
db = get_db()
|
||
c = db.cursor()
|
||
|
||
c.execute("SELECT enabled FROM cron_tasks WHERE id = ?", (task_id,))
|
||
task = c.fetchone()
|
||
if not task:
|
||
return jsonify({'error': '任务不存在'}), 404
|
||
|
||
new_enabled = 0 if task['enabled'] == 1 else 1
|
||
c.execute("UPDATE cron_tasks SET enabled = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?", (new_enabled, task_id))
|
||
db.commit()
|
||
|
||
# 同步到系统 crontab
|
||
sync_crontab_from_db(db)
|
||
|
||
log_message(f"切换 Cron 任务 ID={task_id} 状态: {new_enabled}")
|
||
|
||
return jsonify({'message': '状态已切换', 'enabled': new_enabled})
|
||
|
||
@app.route('/api/cron/tasks/<int:task_id>/history')
|
||
def api_cron_history(task_id):
|
||
"""获取任务历史版本"""
|
||
db = get_db()
|
||
c = db.cursor()
|
||
c.execute("SELECT * FROM cron_history WHERE task_id = ? ORDER BY version DESC", (task_id,))
|
||
history = [dict(row) for row in c.fetchall()]
|
||
|
||
for h in history:
|
||
if h['expression']:
|
||
h['human_time'] = cron_expression_to_human(h['expression'])
|
||
|
||
return jsonify({'history': history})
|
||
|
||
@app.route('/api/cron/tasks/<int:task_id>/rollback/<int:version>', methods=['POST'])
|
||
def api_cron_rollback(task_id, version):
|
||
"""回退到指定版本"""
|
||
db = get_db()
|
||
c = db.cursor()
|
||
|
||
c.execute("SELECT * FROM cron_history WHERE task_id = ? AND version = ?", (task_id, version))
|
||
history = c.fetchone()
|
||
if not history:
|
||
return jsonify({'error': '版本不存在'}), 404
|
||
|
||
# 更新任务到历史版本
|
||
c.execute('''UPDATE cron_tasks SET name = ?, expression = ?, command = ?, description = ?, category = ?, enabled = ?, updated_at = CURRENT_TIMESTAMP
|
||
WHERE id = ?''',
|
||
(history['name'], history['expression'], history['command'], history['description'], history['category'], history['enabled'], task_id))
|
||
db.commit()
|
||
|
||
# 同步到系统 crontab
|
||
sync_crontab_from_db(db)
|
||
|
||
log_message(f"回退 Cron 任务 ID={task_id} 到版本 {version}")
|
||
|
||
return jsonify({'message': f'已回退到版本 {version}'})
|
||
|
||
@app.route('/api/cron/tasks/<int:task_id>/logs')
|
||
def api_cron_logs(task_id):
|
||
"""获取任务执行日志"""
|
||
db = get_db()
|
||
c = db.cursor()
|
||
c.execute("SELECT * FROM cron_logs WHERE task_id = ? ORDER BY execution_time DESC LIMIT 100", (task_id,))
|
||
logs = [dict(row) for row in c.fetchall()]
|
||
|
||
return jsonify({'logs': logs})
|
||
|
||
@app.route('/api/cron/categories')
|
||
def api_cron_categories():
|
||
"""获取任务分类列表"""
|
||
categories = [
|
||
{'id': 'monitor', 'name': '系统监控', 'icon': 'ri-heart-pulse-line', 'color': 'blue'},
|
||
{'id': 'backup', 'name': '数据备份', 'icon': 'ri-archive-line', 'color': 'green'},
|
||
{'id': 'cleanup', 'name': '清理任务', 'icon': 'ri-delete-bin-line', 'color': 'yellow'},
|
||
{'id': 'report', 'name': '报表生成', 'icon': 'ri-file-chart-line', 'color': 'purple'},
|
||
{'id': 'sync', 'name': '数据同步', 'icon': 'ri-refresh-line', 'color': 'cyan'},
|
||
{'id': 'notification', 'name': '通知提醒', 'icon': 'ri-notification-line', 'color': 'orange'},
|
||
{'id': 'other', 'name': '其他任务', 'icon': 'ri-more-line', 'color': 'gray'},
|
||
]
|
||
return jsonify({'categories': categories})
|
||
|
||
@app.route('/api/cron/sync', methods=['POST'])
|
||
def api_cron_sync():
|
||
"""从系统 crontab 同步到数据库(清空旧的,只保留当前系统实际存在的)"""
|
||
crons = get_system_crons()
|
||
user_crons = [c for c in crons if c['source'] == 'user']
|
||
|
||
db = get_db()
|
||
c = db.cursor()
|
||
|
||
# 清空数据库中的旧任务
|
||
c.execute("DELETE FROM cron_history WHERE task_id IN (SELECT id FROM cron_tasks)")
|
||
c.execute("DELETE FROM cron_logs WHERE task_id IN (SELECT id FROM cron_tasks)")
|
||
c.execute("DELETE FROM cron_tasks")
|
||
|
||
synced_count = 0
|
||
for cron in user_crons:
|
||
# 尝试从命令推断分类和名称
|
||
name = guess_cron_name(cron['command'])
|
||
category = guess_cron_category(cron['command'])
|
||
|
||
c.execute('''INSERT INTO cron_tasks (name, expression, command, description, category, enabled)
|
||
VALUES (?, ?, ?, ?, ?, 1)''',
|
||
(name, cron['expression'], cron['command'], '', category))
|
||
synced_count += 1
|
||
|
||
db.commit()
|
||
|
||
log_message(f"从系统 crontab 同步了 {synced_count} 个任务(已清空旧任务)")
|
||
|
||
return jsonify({'message': f'同步完成,共 {synced_count} 个任务', 'count': synced_count}) |