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

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

@@ -3,6 +3,7 @@ from typing import Optional, List, Dict, Any
import traceback
from datetime import datetime
import pandas as pd
from app.models.replication import ReplicationSchedule
from app.services.replication_state import replication_state
from app.services.data_reader import data_reader
from app.services.data_writer import data_writer
@@ -88,13 +89,12 @@ class DatabaseMigrator:
parsed = self._parse_table_name(table_name)
return f"{parsed['basename']}ID"
def migrate_table_by_time(self, table_name: str, last_sync_time: datetime) -> Dict[str, int]:
def migrate_table_by_time(self, table_name: str, life_table_name: str, last_sync_time: datetime) -> Dict[str, int]:
"""Миграция таблицы через Life-механизм по времени"""
life_table = self._get_life_table_name(table_name)
base_id_field = self._get_base_id_field(table_name)
life_id_field = self._get_life_id_field(table_name)
migration_logger.info(f"Миграция {table_name} через {life_table} с {last_sync_time}")
migration_logger.info(f"Миграция {table_name} через {life_table_name} с {last_sync_time}")
stats = {'inserted': 0, 'updated': 0, 'deleted': 0, 'total': 0}
@@ -105,12 +105,12 @@ class DatabaseMigrator:
SELECT
{base_id_field},
MAX({life_id_field}) as MaxLifeID
FROM {life_table}
FROM {life_table_name}
WHERE x_DateTime > CAST(? AS datetime)
GROUP BY {base_id_field}
)
SELECT dl.*
FROM {life_table} dl
FROM {life_table_name} dl
INNER JOIN LatestLife ll
ON dl.{life_id_field} = ll.MaxLifeID
"""
@@ -211,28 +211,24 @@ class DatabaseMigrator:
return result
def migrate_table(self, table_name: str, full_reload: bool = False) -> bool:
def migrate_table(self, table_name: str, schedule_id: int, metadata_id: int, life_table_name: Optional[str], uses_life: bool = False, full_reload: bool = False) -> bool:
"""Миграция одной таблицы (поддерживает и ID, и Life)"""
migration_logger.table_start(table_name)
self.current_table = table_name
table_start_time = datetime.now()
try:
# Получаем ID колонку для статистики
id_column = get_primary_key(table_name)
# Определяем, использует ли таблица Life-механизм
uses_life = table_name in self.life_tables
if uses_life and not full_reload:
if uses_life and not full_reload and life_table_name:
# МИГРАЦИЯ ЧЕРЕЗ LIFE-ТАБЛИЦУ ПО ВРЕМЕНИ
last_sync = self.state.get_table_last_sync(table_name)
last_sync = self.state.get_table_last_sync(metadata_id)
if last_sync:
stats = self.migrate_table_by_time(table_name, last_sync)
stats = self.migrate_table_by_time(table_name, life_table_name, last_sync)
# Обновляем время синхронизации
self.state.update_table_sync_time(table_name)
self.state.update_table_sync_time(schedule_id)
# Обновляем статистику
if id_column:
@@ -340,7 +336,7 @@ class DatabaseMigrator:
})
return False
def _incremental_by_id(self, table_name: str) -> bool:
def _incremental_by_id(self, table_name: str, metadata) -> bool:
"""Инкрементальная загрузка по ID (для таблиц без Life)"""
migration_logger.info(f"Инкрементальная загрузка {table_name} по ID")
@@ -419,11 +415,11 @@ class DatabaseMigrator:
def create_all_foreign_keys(self):
"""Создать все внешние ключи после завершения миграции"""
if not self.all_foreign_keys:
migration_logger.info(" Нет внешних ключей для создания")
migration_logger.info("Нет внешних ключей для создания")
return
migration_logger.info("="*60)
migration_logger.info("🔗 СОЗДАНИЕ ВНЕШНИХ КЛЮЧЕЙ")
migration_logger.info("СОЗДАНИЕ ВНЕШНИХ КЛЮЧЕЙ")
migration_logger.info("="*60)
for table_name, foreign_keys in self.all_foreign_keys.items():
@@ -448,16 +444,17 @@ class DatabaseMigrator:
'time': datetime.now()
})
def run_migration(self, tables: Optional[List[str]] = None, full_reload: bool = False, send_email: bool = True):
"""Запуск миграции для всех таблиц"""
def run_migration(
self, table_name: str, schedule_id: int, metadata_id: int,
life_table_name: Optional[str] = None, uses_life: bool = False,
full_reload: bool = False, send_email: bool = True
):
"""Запуск миграции таблицы"""
self.is_running = True
self.start_time = datetime.now()
self.all_foreign_keys = {}
self.errors = []
if tables is None:
tables = settings.TABLES_TO_COPY
last_replication = self.state.get_last_replication_time()
migration_logger.info("="*70)
@@ -465,19 +462,16 @@ class DatabaseMigrator:
migration_logger.info(f"Время старта: {self.start_time}")
if last_replication:
migration_logger.info(f"Последняя миграция: {last_replication}")
migration_logger.info(f"Таблиц для обработки: {len(tables)}")
migration_logger.info(f"Таблица для обработки: {table_name}")
migration_logger.info(f"Режим: {'ПОЛНАЯ' if full_reload else 'ИНКРЕМЕНТАЛЬНАЯ'}")
migration_logger.info(f"Таблицы с Life-механизмом: {self.life_tables}")
migration_logger.info("="*70)
results = {}
for i, table_name in enumerate(tables, 1):
if not self.is_running:
migration_logger.warning("Миграция остановлена пользователем")
break
if not self.is_running:
migration_logger.warning("Миграция остановлена пользователем")
return
migration_logger.info(f"\n[{i}/{len(tables)}] Обработка таблицы {table_name}")
results[table_name] = self.migrate_table(table_name, full_reload)
migration_logger.info(f"Обработка таблицы {table_name}")
results = self.migrate_table(table_name, schedule_id, metadata_id, life_table_name, uses_life, full_reload)
# Создаем внешние ключи после всех таблиц
self.create_all_foreign_keys()
@@ -494,21 +488,20 @@ class DatabaseMigrator:
self.is_running = False
return results
def _log_final_stats(self, results: dict, stats: dict, total_time: float):
def _log_final_stats(self, has_migrated: bool, stats: dict, total_time: float):
"""Логирует финальную статистику"""
migration_logger.info("="*70)
migration_logger.info("ИТОГОВАЯ СТАТИСТИКА")
migration_logger.info("="*70)
migration_logger.info(f"Успешно: {sum(1 for r in results.values() if r)}/{len(results)}")
migration_logger.info(f"Ошибок: {len(self.errors)}")
migration_logger.info(f"Всего строк в БД: {stats.get('total_rows', 0)}")
migration_logger.info(f"Общее время: {total_time:.1f}с")
migration_logger.info("="*70)
def _send_notification(self, results: dict, stats: dict, total_time: float):
def _send_notification(self, has_migrated: bool, stats: dict, total_time: float):
"""Отправляет уведомление о результате"""
if self.errors:
error_body = self._build_error_email_body(results, stats, total_time)
error_body = self._build_error_email_body(has_migrated, stats, total_time)
email_sender.send_email(
subject=f"МИГРАЦИЯ С ОШИБКАМИ - {datetime.now().strftime('%Y-%m-%d %H:%M')}",
body=error_body
@@ -516,10 +509,10 @@ class DatabaseMigrator:
# else:
# email_sender.send_success_notification(stats, total_time)
def _build_error_email_body(self, results: dict, stats: dict, total_time: float) -> str:
def _build_error_email_body(self, has_migrated: bool, stats: dict, total_time: float) -> str:
"""Строит тело письма с ошибками"""
body = f"""
🚨 МИГРАЦИЯ ЗАВЕРШЕНА С ОШИБКАМИ
МИГРАЦИЯ ЗАВЕРШЕНА С ОШИБКАМИ
{'='*60}
Время: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
@@ -527,7 +520,6 @@ class DatabaseMigrator:
СТАТИСТИКА:
{'='*40}
Успешно: {sum(1 for r in results.values() if r)}/{len(results)}
Ошибок: {len(self.errors)}
Всего строк: {stats.get('total_rows', 0)}
@@ -584,7 +576,7 @@ class DatabaseMigrator:
# Поэтому используем отдельный метод для установки
replication_state._set_table_total_rows(table_name, dst_stats['total_rows'])
migration_logger.info(f" Статистика обновлена: {dst_stats['total_rows']} строк, max_id={dst_stats['max_id']}")
migration_logger.info(f"Статистика обновлена: {dst_stats['total_rows']} строк, max_id={dst_stats['max_id']}")
# Логируем операцию
self.state.log_operation(