Небольшие изменения
This commit is contained in:
@@ -1,8 +1,9 @@
|
||||
import asyncio
|
||||
import smtplib
|
||||
from email.mime.text import MIMEText
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
from email.mime.application import MIMEApplication
|
||||
from typing import List, Optional
|
||||
from typing import Dict, List, Optional
|
||||
from datetime import datetime
|
||||
from app.core.config import settings
|
||||
from app.core.logging import migration_logger
|
||||
@@ -134,5 +135,221 @@ class EmailSender:
|
||||
|
||||
return self.send_email(subject, body)
|
||||
|
||||
async def send_migration_summary_email(self, email_data: Dict) -> bool:
|
||||
"""
|
||||
Отправить сводный email о результатах миграции.
|
||||
|
||||
Args:
|
||||
email_data: Словарь с данными:
|
||||
- batch_id: ID батча
|
||||
- total: Всего таблиц
|
||||
- completed: Завершено
|
||||
- successful: Успешно
|
||||
- failed: Ошибок
|
||||
- results: Список результатов по таблицам
|
||||
- timestamp: Время отчёта
|
||||
|
||||
Returns:
|
||||
bool: True если email отправлен успешно
|
||||
"""
|
||||
try:
|
||||
# Формируем HTML-отчёт
|
||||
html_content = self._create_email_html(email_data)
|
||||
|
||||
# Создаём письмо
|
||||
msg = MIMEMultipart('alternative')
|
||||
msg['Subject'] = f"Отчёт о миграции: {email_data.get('successful', 0)} успешно, {email_data.get('failed', 0)} ошибок"
|
||||
msg['From'] = self.from_addr
|
||||
msg['To'] = ', '.join(self.to_addrs)
|
||||
|
||||
# Текстовая версия
|
||||
text_content = self._create_email_text(email_data)
|
||||
msg.attach(MIMEText(text_content, 'plain', 'utf-8'))
|
||||
|
||||
# HTML версия
|
||||
msg.attach(MIMEText(html_content, 'html', 'utf-8'))
|
||||
|
||||
# Отправляем
|
||||
await asyncio.to_thread(
|
||||
self._send_email_sync,
|
||||
smtp_server=self.smtp_server,
|
||||
smtp_port=self.smtp_port,
|
||||
login=self.username,
|
||||
password=self.password,
|
||||
from_addr=self.from_addr,
|
||||
to_addrs=self.to_addrs,
|
||||
message=msg.as_string()
|
||||
)
|
||||
|
||||
migration_logger.info(f"Email отправлен: {len(self.to_addrs)} получателей")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
migration_logger.error(f"Ошибка отправки email: {e}")
|
||||
return False
|
||||
|
||||
def _create_email_html(self, data: Dict) -> str:
|
||||
"""Создать HTML-версию письма"""
|
||||
|
||||
batch_id = data.get('batch_id', 'N/A')
|
||||
total = data.get('total', 0)
|
||||
successful = data.get('successful', 0)
|
||||
failed = data.get('failed', 0)
|
||||
timestamp = data.get('timestamp', 'N/A')
|
||||
results = data.get('results', [])
|
||||
|
||||
# Цвет статуса
|
||||
if failed == 0:
|
||||
status_color = "#28a745"
|
||||
status_text = "✅ Все успешно"
|
||||
elif failed < total:
|
||||
status_color = "#ffc107"
|
||||
status_text = "⚠️ Частичный успех"
|
||||
else:
|
||||
status_color = "#dc3545"
|
||||
status_text = "❌ Все плохо"
|
||||
|
||||
# Таблица результатов
|
||||
rows = ""
|
||||
for r in results:
|
||||
table_name = r.get('table', 'Unknown')
|
||||
success = r.get('success', False)
|
||||
error = r.get('error', '')
|
||||
|
||||
if success:
|
||||
row_color = "#d4edda"
|
||||
status_icon = "✅"
|
||||
else:
|
||||
row_color = "#f8d7da"
|
||||
status_icon = "❌"
|
||||
|
||||
rows += f"""
|
||||
<tr style="background-color: {row_color};">
|
||||
<td style="padding: 8px; border: 1px solid #ddd;">{table_name}</td>
|
||||
<td style="padding: 8px; border: 1px solid #ddd;">{"Успешно" if success else "Ошибка"}</td>
|
||||
<td style="padding: 8px; border: 1px solid #ddd;">{error[:50] if error else "-"}</td>
|
||||
</tr>
|
||||
"""
|
||||
|
||||
html = f"""
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<style>
|
||||
body {{ font-family: Arial, sans-serif; line-height: 1.6; }}
|
||||
.container {{ max-width: 100%; margin: 0 auto; padding: 16px; }}
|
||||
.header {{ background-color: #007bff; color: white; padding: 16px; border-radius: 5px; }}
|
||||
.summary {{ margin: 16px 0; padding: 16px; background-color: #f8f9fa; border-radius: 5px; }}
|
||||
.status {{ font-size: 18px; font-weight: bold; color: {status_color}; }}
|
||||
table {{ width: 100%; border-collapse: collapse; margin-top: 16px; }}
|
||||
th {{ background-color: #007bff; color: white; padding: 10px; text-align: left; }}
|
||||
.footer {{ margin-top: 30px; padding-top: 16px; border-top: 1px solid #ddd; color: #666; font-size: 12px; }}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h1>Репликация данных</h1>
|
||||
</div>
|
||||
|
||||
<div class="summary">
|
||||
<p><strong>Batch ID:</strong> {batch_id}</p>
|
||||
<p><strong>Время:</strong> {timestamp}</p>
|
||||
<p class="status">{status_text}</p>
|
||||
<ul>
|
||||
<li>Всего таблиц: <strong>{total}</strong></li>
|
||||
<li>Успешно: <strong style="color: #28a745;">{successful}</strong></li>
|
||||
<li>Ошибок: <strong style="color: #dc3545;">{failed}</strong></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<h2>Детали по таблицам</h2>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Таблица</th>
|
||||
<th>Результат</th>
|
||||
<th>Ошибка</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{rows}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="footer">
|
||||
<p>Автоматическое сообщение от системы репликации</p>
|
||||
<p>Не отвечайте на это письмо</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
return html
|
||||
|
||||
def _create_email_text(self, data: Dict) -> str:
|
||||
"""Создать текстовую версию письма"""
|
||||
|
||||
batch_id = data.get('batch_id', 'N/A')
|
||||
total = data.get('total', 0)
|
||||
successful = data.get('successful', 0)
|
||||
failed = data.get('failed', 0)
|
||||
timestamp = data.get('timestamp', 'N/A')
|
||||
results = data.get('results', [])
|
||||
|
||||
text = f"""
|
||||
ОТЧЁТ О МИГРАЦИИ ДАННЫХ
|
||||
=======================
|
||||
|
||||
Batch ID: {batch_id}
|
||||
Время: {timestamp}
|
||||
|
||||
СТАТИСТИКА:
|
||||
- Всего таблиц: {total}
|
||||
- Успешно: {successful}
|
||||
- Ошибок: {failed}
|
||||
|
||||
ДЕТАЛИ ПО ТАБЛИЦАМ:
|
||||
"""
|
||||
|
||||
for r in results:
|
||||
table_name = r.get('table', 'Unknown')
|
||||
success = r.get('success', False)
|
||||
error = r.get('error', '')
|
||||
|
||||
status = "✅ Успешно" if success else f"❌ Ошибка: {error}"
|
||||
text += f"\n • {table_name}: {status}"
|
||||
|
||||
text += """
|
||||
|
||||
=======================
|
||||
Автоматическое сообщение от системы репликации
|
||||
Не отвечайте на это письмо
|
||||
"""
|
||||
return text
|
||||
|
||||
def _send_email_sync(
|
||||
self,
|
||||
smtp_server: str,
|
||||
smtp_port: int,
|
||||
login: str,
|
||||
password: str,
|
||||
from_addr: str,
|
||||
to_addrs: List[str],
|
||||
message: str
|
||||
):
|
||||
"""Синхронная отправка email (запускается в потоке)"""
|
||||
|
||||
with smtplib.SMTP_SSL(smtp_server, smtp_port) as server:
|
||||
server.login(login, password)
|
||||
server.sendmail(from_addr, to_addrs, message)
|
||||
# server = smtplib.SMTP_SSL(smtp_server, smtp_port)
|
||||
# try:
|
||||
# server.starttls()
|
||||
# server.login(login, password)
|
||||
# server.sendmail(from_addr, to_addrs, message)
|
||||
# finally:
|
||||
# server.quit()
|
||||
|
||||
email_sender = EmailSender()
|
||||
Reference in New Issue
Block a user