Files
TODO_list_system/backend/routes/scheduler.py
beabigegg b0c86302ff 1ST
2025-08-29 16:25:46 +08:00

261 lines
8.9 KiB
Python

"""
Scheduler API Routes
處理排程任務的管理和監控功能
"""
from flask import Blueprint, request, jsonify, current_app
from flask_jwt_extended import jwt_required, get_jwt_identity
from datetime import datetime, date, timedelta
from sqlalchemy import and_, or_
from models import (
db, TodoItem, TodoItemResponsible, TodoItemFollower,
TodoUserPref, TodoAuditLog
)
from utils.logger import get_logger
from utils.email_service import EmailService
from utils.notification_service import NotificationService
from tasks_simple import send_daily_reminders, send_weekly_summary, cleanup_old_logs
import json
scheduler_bp = Blueprint('scheduler', __name__)
logger = get_logger(__name__)
@scheduler_bp.route('/trigger-daily-reminders', methods=['POST'])
@jwt_required()
def trigger_daily_reminders():
"""手動觸發每日提醒(管理員功能)"""
try:
identity = get_jwt_identity()
# TODO: 實作管理員權限檢查
# 這裡應該檢查用戶是否為管理員
# 直接執行任務
result = send_daily_reminders()
# 記錄稽核日誌
audit = TodoAuditLog(
actor_ad=identity,
todo_id=None,
action='MANUAL_REMINDER',
detail={
'result': result,
'triggered_by': identity
}
)
db.session.add(audit)
db.session.commit()
logger.info(f"Daily reminders executed manually by {identity}")
return jsonify({
'message': '每日提醒任務已執行',
'result': result
}), 200
except Exception as e:
logger.error(f"Error triggering daily reminders: {str(e)}")
return jsonify({'error': '觸發每日提醒失敗'}), 500
@scheduler_bp.route('/trigger-weekly-summary', methods=['POST'])
@jwt_required()
def trigger_weekly_summary():
"""手動觸發週報發送(管理員功能)"""
try:
identity = get_jwt_identity()
# TODO: 實作管理員權限檢查
# 直接執行任務
result = send_weekly_summary()
# 記錄稽核日誌
audit = TodoAuditLog(
actor_ad=identity,
todo_id=None,
action='MANUAL_SUMMARY',
detail={
'result': result,
'triggered_by': identity
}
)
db.session.add(audit)
db.session.commit()
logger.info(f"Weekly summary executed manually by {identity}")
return jsonify({
'message': '週報發送任務已執行',
'result': result
}), 200
except Exception as e:
logger.error(f"Error triggering weekly summary: {str(e)}")
return jsonify({'error': '觸發週報發送失敗'}), 500
@scheduler_bp.route('/task-status/<task_id>', methods=['GET'])
@jwt_required()
def get_task_status(task_id):
"""取得任務狀態(簡化版本)"""
try:
# 在簡化版本中,任務是同步執行的,所以狀態總是 completed
return jsonify({
'task_id': task_id,
'status': 'completed',
'message': '任務已同步執行完成'
}), 200
except Exception as e:
logger.error(f"Error getting task status: {str(e)}")
return jsonify({'error': '取得任務狀態失敗'}), 500
@scheduler_bp.route('/scheduled-jobs', methods=['GET'])
@jwt_required()
def get_scheduled_jobs():
"""取得排程任務列表和狀態"""
try:
# 這裡可以返回 Celery Beat 的排程資訊
# 簡化版本,返回配置的排程任務
jobs = [
{
'name': 'daily-reminders',
'description': '每日提醒郵件',
'schedule': '每日早上9點',
'status': 'active',
'last_run': None # TODO: 從 Celery 取得實際執行時間
},
{
'name': 'weekly-summary',
'description': '每週摘要報告',
'schedule': '每週一早上9點',
'status': 'active',
'last_run': None # TODO: 從 Celery 取得實際執行時間
},
{
'name': 'cleanup-logs',
'description': '清理舊日誌',
'schedule': '每週執行一次',
'status': 'active',
'last_run': None # TODO: 從 Celery 取得實際執行時間
}
]
return jsonify({'jobs': jobs}), 200
except Exception as e:
logger.error(f"Error getting scheduled jobs: {str(e)}")
return jsonify({'error': '取得排程任務列表失敗'}), 500
@scheduler_bp.route('/statistics', methods=['GET'])
@jwt_required()
def get_scheduler_statistics():
"""取得排程系統統計資訊"""
try:
identity = get_jwt_identity()
# 統計最近一週的自動化任務執行記錄
week_ago = datetime.utcnow() - timedelta(days=7)
auto_tasks = TodoAuditLog.query.filter(
and_(
TodoAuditLog.actor_ad == 'system',
TodoAuditLog.created_at >= week_ago,
TodoAuditLog.action.in_(['DAILY_REMINDER', 'WEEKLY_SUMMARY'])
)
).all()
# 統計手動觸發的任務
manual_tasks = TodoAuditLog.query.filter(
and_(
TodoAuditLog.created_at >= week_ago,
TodoAuditLog.action.in_(['MANUAL_REMINDER', 'MANUAL_SUMMARY'])
)
).all()
# 統計郵件發送情況
email_stats = {}
for task in auto_tasks:
if task.detail:
task_type = task.action.lower()
if 'emails_sent' in task.detail:
if task_type not in email_stats:
email_stats[task_type] = {'count': 0, 'emails': 0}
email_stats[task_type]['count'] += 1
email_stats[task_type]['emails'] += task.detail['emails_sent']
statistics = {
'recent_activity': {
'auto_tasks_count': len(auto_tasks),
'manual_tasks_count': len(manual_tasks),
'email_stats': email_stats
},
'system_health': {
'celery_status': 'running', # TODO: 實際檢查 Celery 狀態
'redis_status': 'connected', # TODO: 實際檢查 Redis 狀態
'last_daily_reminder': None, # TODO: 從記錄中取得
'last_weekly_summary': None # TODO: 從記錄中取得
}
}
return jsonify(statistics), 200
except Exception as e:
logger.error(f"Error getting scheduler statistics: {str(e)}")
return jsonify({'error': '取得排程統計資訊失敗'}), 500
@scheduler_bp.route('/preview-reminders', methods=['GET'])
@jwt_required()
def preview_reminders():
"""預覽即將發送的提醒郵件"""
try:
today = date.today()
tomorrow = today + timedelta(days=1)
# 查找明日到期的待辦事項
due_tomorrow = db.session.query(TodoItem).filter(
and_(
TodoItem.due_date == tomorrow,
TodoItem.status != 'DONE'
)
).all()
# 查找已逾期的待辦事項
overdue = db.session.query(TodoItem).filter(
and_(
TodoItem.due_date < today,
TodoItem.status != 'DONE'
)
).all()
# 統計會收到提醒的使用者
notification_service = NotificationService()
due_tomorrow_recipients = set()
overdue_recipients = set()
for todo in due_tomorrow:
recipients = notification_service.get_notification_recipients(todo)
due_tomorrow_recipients.update(recipients)
for todo in overdue:
recipients = notification_service.get_notification_recipients(todo)
overdue_recipients.update(recipients)
preview = {
'due_tomorrow': {
'todos_count': len(due_tomorrow),
'recipients_count': len(due_tomorrow_recipients),
'todos': [todo.to_dict() for todo in due_tomorrow[:5]] # 只顯示前5個
},
'overdue': {
'todos_count': len(overdue),
'recipients_count': len(overdue_recipients),
'todos': [todo.to_dict() for todo in overdue[:5]] # 只顯示前5個
},
'total_emails': len(due_tomorrow_recipients) + len(overdue_recipients)
}
return jsonify(preview), 200
except Exception as e:
logger.error(f"Error previewing reminders: {str(e)}")
return jsonify({'error': '預覽提醒郵件失敗'}), 500