diff --git a/antifroud/admin.py b/antifroud/admin.py index c5cefa90..452ceeaf 100644 --- a/antifroud/admin.py +++ b/antifroud/admin.py @@ -111,7 +111,7 @@ class ExternalDBSettingsAdmin(admin.ModelAdmin): @admin.register(UserActivityLog) class UserActivityLogAdmin(admin.ModelAdmin): - list_display = ("id", "timestamp", "date_time", "page_id", "url_parameters", "created", "page_title", "type", "hits") + list_display = ("id", "timestamp", "date_time", "page_id", "url_parameters", "page_url" ,"created", "page_title", "type", "hits") search_fields = ("page_title", "url_parameters") list_filter = ("page_title", "created") readonly_fields = ("created", "timestamp") @@ -208,7 +208,7 @@ class SyncLogAdmin(admin.ModelAdmin): @admin.register(ViolationLog) class ViolationLogAdmin(admin.ModelAdmin): - list_display = ['id', 'hotel', 'room_number', 'created_at', 'violation_type', 'violation_details', 'detected_at'] + list_display = ['id', 'hotel', 'room_number' , 'hits', 'created_at', 'violation_type', 'violation_details', 'detected_at'] search_fields = ['id', 'hotel', 'room_number', 'created_at', 'violation_type', 'violation_details', 'detected_at'] list_filter = ['id', 'hotel', 'room_number', 'created_at', 'violation_type', 'violation_details', 'detected_at'] diff --git a/antifroud/check_fraud.py b/antifroud/check_fraud.py index 1471816d..d04b28d9 100644 --- a/antifroud/check_fraud.py +++ b/antifroud/check_fraud.py @@ -24,94 +24,104 @@ class ReservationChecker: self.violations = [] def log_info(self, message): - """Логирование информационных сообщений.""" logger.info(message) def log_warning(self, message): - """Логирование предупреждений.""" logger.warning(message) def log_error(self, message): - """Логирование ошибок.""" logger.error(message) def fetch_user_logs(self): - """ - Извлекает записи из UserActivityLog за последние 12 часов. - """ - print(f"Fetching user logs from {self.start_time} to {self.end_time}") + """ Извлекает записи из UserActivityLog за период. """ user_logs = UserActivityLog.objects.filter(created__range=(self.start_time, self.end_time)) - print(f"Found {user_logs.count()} logs for analysis.") + self.log_info(f"Найдено {user_logs.count()} логов активности для анализа.") return user_logs - def fetch_hotels(self, hotel_ids): - """ - Извлекает отели по hotel_id из логов. - """ - hotels = {hotel.hotel_id: hotel for hotel in Hotel.objects.filter(hotel_id__in=hotel_ids)} - self.log_info(f"Найдено {len(hotels)} отелей для сверки.") - return hotels + def fetch_reservations(self): + """ Извлекает бронирования за период. """ + reservations = Reservation.objects.filter( + Q(check_in__lte=self.end_time) & Q(check_out__gte=self.start_time) + ) + self.log_info(f"Найдено {reservations.count()} бронирований для анализа.") + return reservations - def find_violations(self, user_logs, hotels): + def find_violations(self): """ - Сопоставляет логи активности с бронированиями и фиксирует нарушения. + Сравнивает записи бронирований и логи активности для выявления нарушений: + - Сканирование QR без бронирования. + - Бронь со статусом "заселен" без сканирования QR. + - Раннее заселение. """ + user_logs = self.fetch_user_logs() + reservations = self.fetch_reservations() + + # Сопоставляем записи + log_lookup = {} # Словарь: (hotel_id, room_number) -> список логов for log in user_logs: - if not log.url_parameters: - self.log_warning(f"Пропущена запись ID {log.id}: отсутствуют URL-параметры.") - continue - - # Парсинг параметров URL - params = parse_qs(log.url_parameters) + params = parse_qs(log.url_parameters or "") hotel_id = params.get("utm_content", [None])[0] room_number = params.get("utm_term", [None])[0] + if hotel_id and room_number: + key = (hotel_id, room_number) + log_lookup.setdefault(key, []).append(log) - print(f"Processing log ID {log.id} with hotel ID {hotel_id} and room number {room_number}") + for reservation in reservations: + key = (reservation.hotel.hotel_id, reservation.room_number) + logs = log_lookup.get(key, []) - if not hotel_id or not room_number: - self.log_warning(f"Пропущена запись ID {log.id}: некорректные параметры URL.") - continue + # Бронь со статусом "заселен" без сканирования QR + if reservation.status == "заселен" and not logs: + self.record_violation( + hotel=reservation.hotel, + room_number=reservation.room_number, + violation_type="no_qr_scan", + details=f"Бронирование для номера {reservation.room_number} в отеле '{reservation.hotel.name}' " + f"не имеет записи сканирования QR." + ) - if hotel_id not in hotels: - self.log_warning(f"Пропущена запись ID {log.id}: отель с ID {hotel_id} не найден.") - continue + # Раннее заселение + for log in logs: + if log.created < reservation.check_in: + self.record_violation( + hotel=reservation.hotel, + room_number=reservation.room_number, + violation_type="early_check_in", + details=f"Раннее заселение: сканирование QR {log.created} раньше времени заезда " + f"{reservation.check_in} для номера {reservation.room_number}." + ) - hotel = hotels[hotel_id] - log_time = timezone.localtime(log.created) + # Сканирование QR без бронирования + for (hotel_id, room_number), logs in log_lookup.items(): + matching_reservations = reservations.filter( + hotel__hotel_id=hotel_id, + room_number=room_number + ) + if not matching_reservations.exists(): + for log in logs: + self.record_violation( + hotel=Hotel.objects.filter(hotel_id=hotel_id).first(), + room_number=room_number, + violation_type="no_reservation", + details=f"Сканирование QR {log.created} для номера {room_number} в отеле с ID '{hotel_id}' " + f"не соответствует ни одному бронированию." + ) - # Проверка существования бронирования - matching_reservations = Reservation.objects.filter( + def record_violation(self, hotel, room_number, violation_type, details): + """ + Записывает нарушение в список для последующего сохранения. + """ + if hotel: + self.violations.append(ViolationLog( hotel=hotel, room_number=room_number, - check_in__lte=log_time, - check_out__gte=log_time - ) - - print(f"Found {matching_reservations.count()} matching reservations") - - if not matching_reservations.exists(): - violation_details = ( - f"Не найдено бронирование для номера {room_number} в отеле '{hotel.name}' на {log_time}." - ) - # Добавляем нарушение, если его ещё нет в базе - if not ViolationLog.objects.filter( - hotel=hotel, - room_number=room_number, - violation_type="missed", - violation_details=violation_details - ).exists(): - self.violations.append(ViolationLog( - hotel=hotel, - room_number=room_number, - violation_type="missed", - violation_details=violation_details - )) - self.log_warning(f"Зафиксировано нарушение: {violation_details}") + violation_type=violation_type, + violation_details=details + )) + self.log_warning(f"Зафиксировано нарушение: {details}") def save_violations(self): - """ - Сохраняет найденные нарушения в базу данных. - """ + """ Сохраняет найденные нарушения в базу данных. """ if self.violations: ViolationLog.objects.bulk_create(self.violations) self.log_info(f"Создано {len(self.violations)} записей в ViolationLog.") @@ -119,42 +129,19 @@ class ReservationChecker: self.log_info("Нарушений не обнаружено.") def run_check(self): - """ - Основной метод для запуска проверки. - """ - self.log_info(f"Запуск проверки бронирований с {self.start_time} по {self.end_time}.") - + """ Основной метод для запуска проверки. """ + self.log_info(f"Запуск проверки с {self.start_time} по {self.end_time}.") try: - # Получаем логи пользователей - user_logs = self.fetch_user_logs() - - # Извлекаем hotel_id из логов - hotel_ids = set() - for log in user_logs: - if log.url_parameters: - params = parse_qs(log.url_parameters) - hotel_id = params.get("utm_content", [None])[0] - if hotel_id: - hotel_ids.add(hotel_id) - - # Предзагружаем отели - hotels = self.fetch_hotels(hotel_ids) - - # Сравниваем логи с бронированиями - self.find_violations(user_logs, hotels) - - # Сохраняем результаты + self.find_violations() self.save_violations() - except Exception as e: - self.log_error(f"Произошла ошибка при выполнении проверки: {e}") + self.log_error(f"Ошибка при выполнении проверки: {e}") + self.log_info("Проверка завершена.") - self.log_info("Проверка бронирований завершена.") - -# Функция для запуска проверки из планировщика +# Функция для запуска из планировщика def run_reservation_check(): - """ - Функция для запуска проверки бронирований. - """ checker = ReservationChecker() - checker.run_check() \ No newline at end of file + checker.run_check() + + + diff --git a/antifroud/data_sync.py b/antifroud/data_sync.py index 20d435e2..4c3c87f6 100644 --- a/antifroud/data_sync.py +++ b/antifroud/data_sync.py @@ -1,403 +1,3 @@ -# import logging -# import pymysql -# from datetime import datetime -# from urllib.parse import unquote, parse_qs -# import pytz -# from django.utils import timezone -# from django.db import transaction -# from django.conf import settings -# import chardet -# import html -# from .models import SyncLog, ExternalDBSettings, UserActivityLog, RoomDiscrepancy -# from hotels.models import Room, Hotel, Reservation - -# class DatabaseConnector: -# """ -# Класс для подключения к внешней или локальной базе данных. -# """ -# def __init__(self, db_settings_id, use_local_db=False): -# self.db_settings_id = db_settings_id -# self.use_local_db = use_local_db -# self.connection = None -# self.db_settings = None -# self.table_name = None -# self.logger = self.setup_logger() - -# def setup_logger(self): -# logger = logging.getLogger(__name__) -# logger.setLevel(logging.DEBUG) -# handler = logging.FileHandler("data_sync.log") -# handler.setFormatter(logging.Formatter("%(asctime)s - %(levelname)s - %(message)s")) -# logger.addHandler(handler) -# return logger - -# def connect(self): -# """Подключение к базе данных.""" -# try: -# if self.use_local_db: -# self.db_settings = settings.LocalDataBase.objects.first() -# else: -# self.db_settings = ExternalDBSettings.objects.get(id=self.db_settings_id) - -# self.connection = pymysql.connect( -# host=self.db_settings.host, -# port=self.db_settings.port, -# user=self.db_settings.user, -# password=self.db_settings.password, -# database=self.db_settings.database, -# charset="utf8mb4", -# cursorclass=pymysql.cursors.DictCursor, -# ) -# self.table_name = self.db_settings.table_name -# self.logger.info("Подключение к базе данных успешно установлено.") -# except Exception as e: -# self.logger.error(f"Ошибка подключения к БД: {e}") -# raise ConnectionError(e) - -# def close(self): -# """Закрывает соединение с базой данных.""" -# if self.connection: -# self.connection.close() -# self.logger.info("Соединение с базой данных закрыто.") - -# def execute_query(self, query): -# """Выполнение запроса и возврат результатов.""" -# with self.connection.cursor() as cursor: -# cursor.execute(query) -# return cursor.fetchall() - - -# class DataProcessor: -# """ -# Обрабатывает и сохраняет данные. -# """ -# def __init__(self, logger): -# self.logger = logger - -# def decode_html_entities(self, text): -# """Декодирует URL и HTML-сущности.""" -# if text and isinstance(text, str): -# text = unquote(text) -# text = html.unescape(text) -# try: -# detected = chardet.detect(text.encode()) -# encoding = detected['encoding'] -# if encoding and encoding != 'utf-8': -# text = text.encode(encoding).decode('utf-8', errors='ignore') -# except Exception as e: -# self.logger.error(f"Ошибка кодировки: {e}") -# return text - -# def process_url_parameters(self, url_params): -# """Парсит параметры URL и возвращает информацию о отеле и номере.""" -# decoded = unquote(url_params) -# qs = parse_qs(decoded) -# hotel_name = qs.get('utm_content', [None])[0] -# room_number = qs.get('utm_term', [None])[0] -# return {"hotel_name": hotel_name, "room_number": room_number} - -# def parse_datetime(self, dt_str): -# """Преобразует строку даты в aware datetime.""" -# try: -# if isinstance(dt_str, datetime): -# return timezone.make_aware(dt_str) if timezone.is_naive(dt_str) else dt_str -# if dt_str: -# return timezone.make_aware(datetime.strptime(dt_str, "%Y-%m-%d %H:%M:%S")) -# except Exception as e: -# self.logger.error(f"Ошибка парсинга даты: {e}") -# return None - - -# class HotelRoomManager: -# """ -# Управляет созданием отелей и номеров. -# """ -# def __init__(self, logger): -# self.logger = logger - -# def get_or_create_hotel(self, hotel_name): -# """Создает или получает отель.""" -# if not hotel_name: -# return None -# hotel, created = Hotel.objects.get_or_create( -# name=hotel_name, -# defaults={"description": "Автоматически добавленный отель"} -# ) -# if created: -# self.logger.info(f"Создан отель: {hotel_name}") -# return hotel - -# def get_or_create_room(self, hotel, room_number): -# """Создает или получает номер отеля.""" -# if not hotel or not room_number: -# return None -# room, created = Room.objects.get_or_create( -# hotel=hotel, -# number=room_number, -# defaults={"external_id": f"{hotel.name}_{room_number}"} -# ) -# if created: -# self.logger.info(f"Добавлен номер: {room_number} в отель {hotel.name}") -# return room - - -# class DataSyncManager: -# """ -# Главный класс для синхронизации данных. -# """ -# def __init__(self, db_settings_id, use_local_db=False): -# self.logger = self.setup_logger() -# self.db_connector = DatabaseConnector(db_settings_id, use_local_db) -# self.data_processor = DataProcessor(self.logger) -# self.hotel_manager = HotelRoomManager(self.logger) - -# def setup_logger(self): -# logger = logging.getLogger(__name__) -# logger.setLevel(logging.DEBUG) -# handler = logging.FileHandler("data_sync.log") -# handler.setFormatter(logging.Formatter("%(asctime)s - %(levelname)s - %(message)s")) -# logger.addHandler(handler) -# return logger - -# def get_last_saved_record(self): -# """Получает ID последней записи.""" -# record = UserActivityLog.objects.order_by("-id").first() -# return record.id if record else 0 - -# def fetch_new_data(self, last_id): -# """Получает новые данные из БД для переноса 1-в-1.""" -# query = f""" -# SELECT id AS external_id, user_id, ip, created, timestamp, date_time, -# referred, agent, platform, version, model, device, UAString, location, -# page_id, url_parameters, page_title, type, last_counter, hits, -# honeypot, reply, page_url -# FROM `{self.db_connector.table_name}` -# WHERE id > {last_id} -# ORDER BY id ASC LIMIT 100; -# """ -# return self.db_connector.execute_query(query) - -# def process_and_save_data(self, rows): -# """Обрабатывает данные и сохраняет их 1-в-1 в UserActivityLog.""" -# for row in rows: -# try: -# # Парсинг и сохранение всех полей -# UserActivityLog.objects.update_or_create( -# external_id=row["external_id"], -# defaults={ -# "user_id": row.get("user_id"), -# "ip": row.get("ip"), -# "created": self.data_processor.parse_datetime(row.get("created")), -# "timestamp": row.get("timestamp"), -# "date_time": self.data_processor.parse_datetime(row.get("date_time")), -# "referred": row.get("referred"), -# "agent": row.get("agent"), -# "platform": row.get("platform"), -# "version": row.get("version"), -# "model": row.get("model"), -# "device": row.get("device"), -# "UAString": row.get("UAString"), -# "location": row.get("location"), -# "page_id": row.get("page_id"), -# "url_parameters": row.get("url_parameters"), -# "page_title": row.get("page_title"), -# "type": row.get("type"), -# "last_counter": row.get("last_counter"), -# "hits": row.get("hits"), -# "honeypot": row.get("honeypot"), -# "reply": row.get("reply"), -# "page_url": row.get("page_url"), -# } -# ) -# self.logger.info(f"Запись external_id={row['external_id']} успешно сохранена.") -# except Exception as e: -# self.logger.error(f"Ошибка обработки записи {row['external_id']}: {e}") - - -# def check_and_store_room(self, hotel, room_number): -# """ -# Проверяет и создает номер в отеле, если он еще не существует. -# """ -# try: -# Room.objects.get_or_create( -# hotel=hotel, -# number=room_number, -# defaults={ -# "external_id": f"{hotel.hotel_id}_{room_number}", -# "description": "Автоматически добавленный номер", -# } -# ) -# self.logger.info(f"Добавлен номер: {room_number} в отель {hotel.name}") -# except Exception as e: -# self.logger.error(f"Ошибка при добавлении номера {room_number} для отеля {hotel.name}: {e}") -# def reconcile_data(self): -# """ -# Сверяет данные UserActivityLog с Reservation и фиксирует несоответствия. -# """ -# discrepancies = [] -# reservations = Reservation.objects.values("hotel_id", "room_number", "check_in", "check_out") - -# for log in UserActivityLog.objects.all(): -# try: -# # Преобразование page_id в число -# page_id = int(float(log.page_id)) -# except (ValueError, TypeError): -# self.logger.warning(f"Некорректное значение page_id: {log.page_id} - пропущено.") -# continue - -# # Сверка с бронированиями -# matching_reservation = reservations.filter( -# hotel_id=log.url_parameters, room_number=str(page_id) -# ).first() - -# if not matching_reservation: -# discrepancy = RoomDiscrepancy( -# hotel_id=log.hotel_id, -# room_number=page_id, -# booking_id=f"Log-{log.id}", -# check_in_date_expected=None, -# check_in_date_actual=log.created.date() if log.created else None, -# discrepancy_type="Mismatch", -# ) -# discrepancies.append(discrepancy) - -# RoomDiscrepancy.objects.bulk_create(discrepancies) -# self.logger.info(f"Обнаружено несоответствий: {len(discrepancies)}") - -# def write_to_db(self, data): -# """ -# Записывает данные в UserActivityLog и при необходимости в ImportedHotel. -# """ -# processed_records = 0 -# received_records = len(data["rows"]) - -# self.logger.info(f"Начата обработка {received_records} записей.") - -# for row in data["rows"]: -# # Обработка page_id с валидацией -# raw_page_id = row.get("page_id") -# try: -# page_id = int(float(raw_page_id)) # Пытаемся привести к числу -# except (ValueError, TypeError): -# self.logger.warning(f"Некорректное значение page_id: {raw_page_id} - установлено 0") -# page_id = 0 # Используем значение по умолчанию - -# # Получаем url_parameters -# url_parameters = self.encode_html_entities(self.process_url_parameters(self.decode_html_entities(row.get("url_parameters", "")))) - -# # Проверка на пустое значение url_parameters -# if not url_parameters: -# self.logger.warning("Пропущена запись из-за отсутствующих url_parameters.") -# continue - -# # Извлечение информации об отеле из url_parameters -# hotel_info = self.process_url_parameters(url_parameters) -# hotel_name = hotel_info.get("hotel_name") -# hotel_id = hotel_info.get("hotel_id") - -# if not hotel_id: -# self.logger.warning("Пропущена запись из-за отсутствующего hotel_id.") -# continue - -# # Проверяем или создаем отель -# hotel = self.check_and_store_imported_hotel(hotel_name=hotel_name, hotel_id=hotel_id) -# if not hotel: -# self.logger.error(f"Не удалось найти или создать отель: {hotel_id}") -# continue - -# # Преобразование дат -# created = self.parse_datetime(row.get("created")) -# date_time = self.parse_datetime(row.get("date_time")) - -# # Запись данных в UserActivityLog -# UserActivityLog.objects.update_or_create( -# external_id=row.get("id"), -# defaults={ -# "user_id": row.get("user_id"), -# "ip": row.get("ip") or "0.0.0.0", -# "created": created, -# "timestamp": row.get("timestamp") or timezone.now().timestamp(), -# "date_time": date_time, -# "referred": self.decode_html_entities(row.get("referred", "")), -# "agent": self.decode_html_entities(row.get("agent", "")), -# "platform": self.decode_html_entities(row.get("platform", "")), -# "version": self.decode_html_entities(row.get("version", "")), -# "model": self.decode_html_entities(row.get("model", "")), -# "device": self.decode_html_entities(row.get("device", "")), -# "UAString": self.decode_html_entities(row.get("UAString", "")), -# "location": self.decode_html_entities(row.get("location", "")), -# "page_id": page_id, # Обновленный page_id -# "url_parameters": url_parameters, -# "page_title": self.decode_html_entities(row.get("page_title", "")), -# "type": row.get("type"), -# "last_counter": row.get("last_counter") or 0, -# "hits": row.get("hits") or 0, -# "honeypot": row.get("honeypot") or False, -# "reply": row.get("reply") or False, -# "page_url": self.decode_html_entities(row.get("page_url", "")), -# }, -# ) - -# processed_records += 1 -# self.logger.info(f"Запись успешно обработана: external_id={row.get('id')}") - -# self.logger.info(f"Обработано записей: {processed_records} из {received_records}.") - -# def sync(self): -# """Запускает процесс синхронизации.""" -# self.db_connector.connect() -# try: -# last_id = self.get_last_saved_record() -# rows = self.fetch_new_data(last_id) -# self.process_and_save_data(rows) -# self.logger.info("Синхронизация завершена.") -# finally: -# self.db_connector.close() - - -# import logging -# from concurrent.futures import ThreadPoolExecutor -# from .models import ExternalDBSettings - -# logger = logging.getLogger(__name__) -# logger.setLevel(logging.DEBUG) - - -# def scheduled_sync(): -# """ -# Планировщик синхронизации для всех активных подключений. -# Каждое подключение обрабатывается отдельно. -# """ -# logger.info("Запуск планировщика синхронизации.") - -# # Получаем все активные настройки подключения -# active_db_settings = ExternalDBSettings.objects.filter(is_active=True) -# if not active_db_settings.exists(): -# logger.warning("Не найдено активных подключений для синхронизации.") -# return - -# logger.info(f"Найдено активных подключений: {len(active_db_settings)}") - -# def sync_task(db_settings): -# """ -# Выполняет синхронизацию для одного подключения. -# """ -# try: -# logger.info(f"Начало синхронизации для подключения: {db_settings.name} (ID={db_settings.id})") -# sync_manager = DataSyncManager(db_settings.id) -# sync_manager.sync() -# logger.info(f"Синхронизация успешно завершена для подключения: {db_settings.name}") -# except Exception as e: -# logger.error(f"Ошибка синхронизации для подключения {db_settings.name}: {e}") - -# # Параллельное выполнение задач синхронизации -# with ThreadPoolExecutor(max_workers=5) as executor: # Максимальное количество потоков = 5 -# for db_settings in active_db_settings: -# executor.submit(sync_task, db_settings) - -# logger.info("Планировщик синхронизации завершил работу.") - - import logging import pymysql from datetime import datetime @@ -422,10 +22,24 @@ class DatabaseConnector: self.db_settings = None def setup_logger(self): - logger = logging.getLogger(__name__) - logger.setLevel(logging.DEBUG) + default_level = logging.INFO # Уровень по умолчанию + level_name = getattr(settings, "DATA_SYNC_LOG_LEVEL", "INFO").upper() + log_level = getattr(logging, level_name, default_level) + logger.setLevel(log_level) + + # Настройка обработчика для файла handler = logging.FileHandler("data_sync.log") handler.setFormatter(logging.Formatter("%(asctime)s - %(levelname)s - %(message)s")) + handler.setLevel(log_level) + + # Удаляем старые обработчики, чтобы избежать дублирования + if logger.hasHandlers(): + logger.handlers.clear() + logger.addHandler(handler) + + # Сообщение о текущем уровне логирования + logger.info(f"Уровень логирования установлен: {logging.getLevelName(log_level)}") + logger.addHandler(handler) return logger @@ -528,32 +142,66 @@ class HotelRoomManager: def __init__(self, logger): self.logger = logger - def get_or_create_hotel(self, hotel_name, hotel_id): - """Создает или получает отель.""" - if not hotel_name: + def get_or_create_hotel(self, hotel_id, page_title): + """ + Создает или получает отель. + + :param hotel_id: Значение из utm_content (индекс отеля) + :param page_title: Название отеля из поля page_title + """ + if not hotel_id: + self.logger.warning("Пропущено создание отеля: отсутствует hotel_id.") return None + + # Создаем или получаем отель с hotel_id и устанавливаем name по page_title hotel, created = Hotel.objects.get_or_create( - name=hotel_name, - hotel_id = hotel_id, - defaults={"description": "Автоматически добавленный отель"} + hotel_id=hotel_id, + defaults={ + "name": html.unescape(page_title) or f"Отель {hotel_id}", + "description": "Автоматически добавленный отель" + } ) if created: - self.logger.info(f"Создан отель: {hotel_name}") + self.logger.info(f"Создан отель '{hotel.name}' с hotel_id: {hotel_id}") + else: + self.logger.info(f"Отель '{hotel.name}' уже существует с hotel_id: {hotel_id}") return hotel def get_or_create_room(self, hotel, room_number): - """Создает или получает номер отеля.""" - if not hotel or not room_number: + """ + Создает или получает номер отеля. + + :param hotel: Экземпляр модели Hotel + :param room_number: Номер комнаты из utm_term + """ + if not hotel: + self.logger.warning("Пропущено создание номера: отсутствует отель.") return None + + if not room_number: + self.logger.warning(f"Пропущено создание номера: отсутствует room_number для отеля {hotel.name}.") + return None + + # Генерация уникального external_id на основе hotel_id и room_number + external_id = f"{hotel.hotel_id}_{room_number}".lower() + + # Создаем или получаем номер room, created = Room.objects.get_or_create( hotel=hotel, number=room_number, - defaults={"external_id": f"{hotel.name}_{room_number}"} + defaults={ + "number": room_number, # Используем room_number как название номера + "external_id": external_id, + "description": "Автоматически добавленный номер" + } ) - if created: - self.logger.info(f"Добавлен номер: {room_number} в отель {hotel.name}") - return room + if created: + self.logger.info(f"Создан номер '{room.number}' (external_id: {external_id}) в отеле '{hotel.name}'") + else: + self.logger.info(f"Номер '{room.number}' уже существует в отеле '{hotel.name}'") + + return room class DataSyncManager: """ @@ -585,6 +233,7 @@ class DataSyncManager: WHERE id > {last_id} AND url_parameters IS NOT NULL AND url_parameters LIKE '%utm_medium%' + AND page_url IS NOT NULL ORDER BY id ASC LIMIT 1000; """ @@ -592,52 +241,48 @@ class DataSyncManager: return self.db_connector.execute_query(query) def process_and_save_data(self, rows): - """Обрабатывает и сохраняет данные, включая парсинг отсутствующих значений.""" + """ + Обрабатывает и сохраняет данные из внешней базы данных. + """ for row in rows: try: - # Декодирование параметров URL + # Декодирование URL-параметров url_params = self.data_processor.decode_html_entities(row.get("url_parameters", "")) - params = parse_qs(url_params) - param_dict = DataProcessor.url_parameters_parser(self.data_processor, url_params) - self.logger.info(f"Параметры URL успешно декодированы: {param_dict}") - # Извлечение и обработка данных + params = self.data_processor.url_parameters_parser(url_params) + timestamp = params.get("timestamp") + date_time = params.get("date_time") + # Извлечение данных + hotel_id = params.get("utm_content") + room_number = params.get("utm_term") + page_title = row.get("page_title") # Название отеля из page_title external_id = row.get("id") - created = self.data_processor.parse_datetime(row.get("created")) or timezone.now() - hotel_name = params.get("utm_content", [None])[0] or "Неизвестный отель" - room_number = params.get("utm_term", [None])[0] or "000" - page_title = row.get("page_title") or f"Информация отсутствует для ID {external_id}" - # Создание отеля и номера, если они отсутствуют - hotel = self.hotel_manager.get_or_create_hotel(hotel_name, hotel_id=row.get("hotel_id")) + # Создание отеля и комнаты + hotel = self.hotel_manager.get_or_create_hotel(hotel_id, page_title) room = self.hotel_manager.get_or_create_room(hotel, room_number) - - # Заполнение отсутствующих данных - user_id = row.get("user_id") or 0 - ip = row.get("ip") or "0.0.0.0" - hits = row.get("hits") or 0 - date_time = row.get("date_time") or timezone.now().strftime("%Y-%m-%d %H:%M:%S") - timeatamp = row.get("timestamp") or timezone.now().timestamp() - # Создание или обновление записи в UserActivityLog + page_url = row.get("page_url") + # Заполнение записи UserActivityLog.objects.update_or_create( external_id=external_id, defaults={ - "user_id": user_id, - "ip": ip, - "timestamp": timeatamp, - "date_time": date_time, - "created": created, - "page_id": room.id if room else None, + "user_id": row.get("user_id") or 0, + "timestamp": row.get("timestamp"), + "date_time": row.get("date_time"), + "ip": row.get("ip") or "0.0.0.0", + "created": self.data_processor.parse_datetime(row.get("created")) or timezone.now(), "url_parameters": url_params, + "page_id": room.id if room else None, "page_title": html.unescape(page_title), - "hits": hits, + "hits": row.get("hits") or 0, + "page_url": html.unescape(page_url), } ) - - self.logger.info(f"Запись ID {external_id} успешно обработана и дополнена.") + self.logger.info(f"Запись ID {external_id} успешно обработана.") except Exception as e: self.logger.error(f"Ошибка при обработке записи ID {row.get('id')}: {e}") + def sync(self): """Запускает процесс синхронизации.""" self.db_connector.connect() diff --git a/antifroud/migrations/0017_violationlog_hits.py b/antifroud/migrations/0017_violationlog_hits.py new file mode 100644 index 00000000..42a002ff --- /dev/null +++ b/antifroud/migrations/0017_violationlog_hits.py @@ -0,0 +1,19 @@ +# Generated by Django 5.1.4 on 2024-12-18 01:13 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('antifroud', '0016_alter_useractivitylog_created_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='violationlog', + name='hits', + field=models.IntegerField(default=1, verbose_name='Срабатывания'), + preserve_default=False, + ), + ] diff --git a/antifroud/models.py b/antifroud/models.py index 9d899a85..582d4682 100644 --- a/antifroud/models.py +++ b/antifroud/models.py @@ -188,6 +188,7 @@ class ViolationLog(models.Model): verbose_name="Тип нарушения" ) violation_details = models.TextField(verbose_name="Детали нарушения", blank=True, null=True) + hits = models.IntegerField(verbose_name="Срабатывания") detected_at = models.DateTimeField(auto_now_add=True, verbose_name="Дата обнаружения") created_at = models.DateTimeField(auto_now_add=True, verbose_name="Дата создания")