Небольшие изменения

This commit is contained in:
brusnitsyn
2026-03-13 17:11:39 +09:00
parent c201d36ae6
commit de2dd82fa1
18 changed files with 1140 additions and 491 deletions

View File

@@ -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()