319 lines
13 KiB
Python
319 lines
13 KiB
Python
"""
|
||
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 |