1ST
This commit is contained in:
261
backend/routes/scheduler.py
Normal file
261
backend/routes/scheduler.py
Normal file
@@ -0,0 +1,261 @@
|
||||
"""
|
||||
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
|
Reference in New Issue
Block a user