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

319 lines
13 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
Email Service
處理所有郵件相關功能,包括通知、提醒和摘要郵件
"""
import os
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.application import MIMEApplication
from datetime import datetime, date
from flask import current_app
from jinja2 import Environment, FileSystemLoader, select_autoescape
from utils.logger import get_logger
from utils.ldap_utils import get_user_info
logger = get_logger(__name__)
class EmailService:
"""郵件服務類別"""
def __init__(self):
self.smtp_server = os.getenv('SMTP_SERVER')
self.smtp_port = int(os.getenv('SMTP_PORT', 587))
self.use_tls = os.getenv('SMTP_USE_TLS', 'false').lower() == 'true'
self.use_ssl = os.getenv('SMTP_USE_SSL', 'false').lower() == 'true'
self.auth_required = os.getenv('SMTP_AUTH_REQUIRED', 'false').lower() == 'true'
self.sender_email = os.getenv('SMTP_SENDER_EMAIL')
self.sender_password = os.getenv('SMTP_SENDER_PASSWORD', '')
# 設定 Jinja2 模板環境
template_dir = os.path.join(os.path.dirname(__file__), '..', 'templates', 'emails')
self.jinja_env = Environment(
loader=FileSystemLoader(template_dir),
autoescape=select_autoescape(['html', 'xml'])
)
def _create_smtp_connection(self):
"""建立 SMTP 連線"""
try:
if self.use_ssl:
server = smtplib.SMTP_SSL(self.smtp_server, self.smtp_port)
else:
server = smtplib.SMTP(self.smtp_server, self.smtp_port)
if self.use_tls:
server.starttls()
if self.auth_required and self.sender_password:
server.login(self.sender_email, self.sender_password)
return server
except Exception as e:
logger.error(f"SMTP connection failed: {str(e)}")
return None
def _send_email(self, to_email, subject, html_content, text_content=None):
"""發送郵件的基礎方法"""
try:
if not self.smtp_server or not self.sender_email:
logger.error("SMTP configuration incomplete")
return False
# 建立郵件
msg = MIMEMultipart('alternative')
msg['From'] = self.sender_email
msg['To'] = to_email
msg['Subject'] = subject
# 添加文本內容
if text_content:
text_part = MIMEText(text_content, 'plain', 'utf-8')
msg.attach(text_part)
# 添加 HTML 內容
html_part = MIMEText(html_content, 'html', 'utf-8')
msg.attach(html_part)
# 發送郵件
server = self._create_smtp_connection()
if not server:
return False
server.send_message(msg)
server.quit()
logger.info(f"Email sent successfully to {to_email}")
return True
except Exception as e:
logger.error(f"Failed to send email to {to_email}: {str(e)}")
return False
def _get_user_email(self, ad_account):
"""取得使用者郵件地址"""
user_info = get_user_info(ad_account)
if user_info and user_info.get('email'):
return user_info['email']
# 如果無法從 LDAP 取得,嘗試組合郵件地址
domain = os.getenv('LDAP_DOMAIN', 'panjit.com.tw')
return f"{ad_account}@{domain}"
def send_fire_email(self, todo, recipient, sender, custom_message=''):
"""發送緊急通知郵件"""
try:
recipient_email = self._get_user_email(recipient)
sender_info = get_user_info(sender)
sender_name = sender_info.get('displayName', sender) if sender_info else sender
# 準備模板資料
template_data = {
'todo': todo,
'recipient': recipient,
'sender': sender,
'sender_name': sender_name,
'custom_message': custom_message,
'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
'app_name': current_app.config.get('APP_NAME', 'PANJIT Todo List')
}
# 渲染模板
template = self.jinja_env.get_template('fire_email.html')
html_content = template.render(**template_data)
# 主題
subject = f"🚨 緊急通知 - {todo.title}"
return self._send_email(recipient_email, subject, html_content)
except Exception as e:
logger.error(f"Fire email failed for {recipient}: {str(e)}")
return False
def send_reminder_email(self, todo, recipient, reminder_type):
"""發送提醒郵件"""
try:
recipient_email = self._get_user_email(recipient)
# 根據提醒類型設定主題和模板
if reminder_type == 'due_tomorrow':
subject = f"📅 明日到期提醒 - {todo.title}"
template_name = 'reminder_due_tomorrow.html'
elif reminder_type == 'overdue':
subject = f"⚠️ 逾期提醒 - {todo.title}"
template_name = 'reminder_overdue.html'
else:
subject = f"📋 待辦提醒 - {todo.title}"
template_name = 'reminder_general.html'
# 準備模板資料
template_data = {
'todo': todo,
'recipient': recipient,
'reminder_type': reminder_type,
'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
'app_name': current_app.config.get('APP_NAME', 'PANJIT Todo List')
}
# 渲染模板
template = self.jinja_env.get_template(template_name)
html_content = template.render(**template_data)
return self._send_email(recipient_email, subject, html_content)
except Exception as e:
logger.error(f"Reminder email failed for {recipient}: {str(e)}")
return False
def send_digest_email(self, recipient, digest_data):
"""發送摘要郵件"""
try:
recipient_email = self._get_user_email(recipient)
# 根據摘要類型設定主題
digest_type = digest_data.get('type', 'weekly')
type_names = {
'daily': '每日',
'weekly': '每週',
'monthly': '每月'
}
subject = f"📊 {type_names.get(digest_type, '定期')}摘要報告"
# 準備模板資料
template_data = {
'recipient': recipient,
'digest_data': digest_data,
'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
'app_name': current_app.config.get('APP_NAME', 'PANJIT Todo List')
}
# 渲染模板
template = self.jinja_env.get_template('digest.html')
html_content = template.render(**template_data)
return self._send_email(recipient_email, subject, html_content)
except Exception as e:
logger.error(f"Digest email failed for {recipient}: {str(e)}")
return False
def send_todo_notification(self, todo, recipients, action, actor):
"""發送待辦事項變更通知"""
try:
success_count = 0
for recipient in recipients:
try:
recipient_email = self._get_user_email(recipient)
actor_info = get_user_info(actor)
actor_name = actor_info.get('displayName', actor) if actor_info else actor
# 根據動作類型設定主題和模板
action_names = {
'CREATE': '建立',
'UPDATE': '更新',
'DELETE': '刪除',
'ASSIGN': '指派',
'COMPLETE': '完成'
}
action_name = action_names.get(action, action)
subject = f"📋 待辦事項{action_name} - {todo.title}"
# 準備模板資料
template_data = {
'todo': todo,
'recipient': recipient,
'action': action,
'action_name': action_name,
'actor': actor,
'actor_name': actor_name,
'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
'app_name': current_app.config.get('APP_NAME', 'PANJIT Todo List')
}
# 渲染模板
template = self.jinja_env.get_template('todo_notification.html')
html_content = template.render(**template_data)
if self._send_email(recipient_email, subject, html_content):
success_count += 1
except Exception as e:
logger.error(f"Todo notification failed for {recipient}: {str(e)}")
return success_count
except Exception as e:
logger.error(f"Todo notification batch failed: {str(e)}")
return 0
def send_test_email(self, recipient):
"""發送測試郵件"""
try:
recipient_email = self._get_user_email(recipient)
subject = "✅ 郵件服務測試"
html_content = f"""
<html>
<body>
<h2>郵件服務測試</h2>
<p>您好 {recipient}</p>
<p>這是一封測試郵件,用於驗證 PANJIT Todo List 系統的郵件功能是否正常運作。</p>
<p>如果您收到這封郵件,表示郵件服務配置正確。</p>
<br>
<p>測試時間: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}</p>
<p>此郵件由系統自動發送,請勿回覆。</p>
</body>
</html>
"""
return self._send_email(recipient_email, subject, html_content)
except Exception as e:
logger.error(f"Test email failed for {recipient}: {str(e)}")
return False
def send_test_email_direct(self, recipient_email):
"""直接發送測試郵件到指定郵件地址"""
try:
subject = "✅ PANJIT Todo List 郵件服務測試"
html_content = f"""
<html>
<body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333;">
<div style="max-width: 600px; margin: 0 auto; padding: 20px;">
<h2 style="color: #2563eb; border-bottom: 2px solid #e5e7eb; padding-bottom: 10px;">📧 郵件服務測試</h2>
<p>您好!</p>
<p>這是一封來自 <strong>PANJIT Todo List 系統</strong> 的測試郵件,用於驗證郵件服務功能是否正常運作。</p>
<div style="background-color: #f0f9ff; border-left: 4px solid #2563eb; padding: 15px; margin: 20px 0;">
<p style="margin: 0;"><strong>✅ 如果您收到這封郵件,表示:</strong></p>
<ul style="margin: 10px 0; padding-left: 20px;">
<li>SMTP 服務器連線正常</li>
<li>郵件發送功能運作良好</li>
<li>您的郵件地址設定正確</li>
</ul>
</div>
<hr style="border: none; border-top: 1px solid #e5e7eb; margin: 30px 0;">
<p style="font-size: 14px; color: #6b7280;">
<strong>測試詳細資訊:</strong><br>
📅 測試時間: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}<br>
📧 收件人: {recipient_email}<br>
🏢 發件人: PANJIT Todo List 系統
</p>
<p style="font-size: 12px; color: #9ca3af; margin-top: 30px;">
此郵件由系統自動發送,請勿回覆。如有任何問題,請聯繫系統管理員。
</p>
</div>
</body>
</html>
"""
return self._send_email(recipient_email, subject, html_content)
except Exception as e:
logger.error(f"Direct test email failed for {recipient_email}: {str(e)}")
return False