pre-release
This commit is contained in:
@@ -111,7 +111,7 @@ class ExternalDBSettingsAdmin(admin.ModelAdmin):
|
|||||||
|
|
||||||
@admin.register(UserActivityLog)
|
@admin.register(UserActivityLog)
|
||||||
class UserActivityLogAdmin(admin.ModelAdmin):
|
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")
|
search_fields = ("page_title", "url_parameters")
|
||||||
list_filter = ("page_title", "created")
|
list_filter = ("page_title", "created")
|
||||||
readonly_fields = ("created", "timestamp")
|
readonly_fields = ("created", "timestamp")
|
||||||
@@ -208,7 +208,7 @@ class SyncLogAdmin(admin.ModelAdmin):
|
|||||||
|
|
||||||
@admin.register(ViolationLog)
|
@admin.register(ViolationLog)
|
||||||
class ViolationLogAdmin(admin.ModelAdmin):
|
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']
|
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']
|
list_filter = ['id', 'hotel', 'room_number', 'created_at', 'violation_type', 'violation_details', 'detected_at']
|
||||||
|
|
||||||
|
|||||||
@@ -24,94 +24,104 @@ class ReservationChecker:
|
|||||||
self.violations = []
|
self.violations = []
|
||||||
|
|
||||||
def log_info(self, message):
|
def log_info(self, message):
|
||||||
"""Логирование информационных сообщений."""
|
|
||||||
logger.info(message)
|
logger.info(message)
|
||||||
|
|
||||||
def log_warning(self, message):
|
def log_warning(self, message):
|
||||||
"""Логирование предупреждений."""
|
|
||||||
logger.warning(message)
|
logger.warning(message)
|
||||||
|
|
||||||
def log_error(self, message):
|
def log_error(self, message):
|
||||||
"""Логирование ошибок."""
|
|
||||||
logger.error(message)
|
logger.error(message)
|
||||||
|
|
||||||
def fetch_user_logs(self):
|
def fetch_user_logs(self):
|
||||||
"""
|
""" Извлекает записи из UserActivityLog за период. """
|
||||||
Извлекает записи из UserActivityLog за последние 12 часов.
|
|
||||||
"""
|
|
||||||
print(f"Fetching user logs from {self.start_time} to {self.end_time}")
|
|
||||||
user_logs = UserActivityLog.objects.filter(created__range=(self.start_time, self.end_time))
|
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
|
return user_logs
|
||||||
|
|
||||||
def fetch_hotels(self, hotel_ids):
|
def fetch_reservations(self):
|
||||||
"""
|
""" Извлекает бронирования за период. """
|
||||||
Извлекает отели по hotel_id из логов.
|
reservations = Reservation.objects.filter(
|
||||||
"""
|
Q(check_in__lte=self.end_time) & Q(check_out__gte=self.start_time)
|
||||||
hotels = {hotel.hotel_id: hotel for hotel in Hotel.objects.filter(hotel_id__in=hotel_ids)}
|
)
|
||||||
self.log_info(f"Найдено {len(hotels)} отелей для сверки.")
|
self.log_info(f"Найдено {reservations.count()} бронирований для анализа.")
|
||||||
return hotels
|
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:
|
for log in user_logs:
|
||||||
if not log.url_parameters:
|
params = parse_qs(log.url_parameters or "")
|
||||||
self.log_warning(f"Пропущена запись ID {log.id}: отсутствуют URL-параметры.")
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Парсинг параметров URL
|
|
||||||
params = parse_qs(log.url_parameters)
|
|
||||||
hotel_id = params.get("utm_content", [None])[0]
|
hotel_id = params.get("utm_content", [None])[0]
|
||||||
room_number = params.get("utm_term", [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:
|
# Бронь со статусом "заселен" без сканирования QR
|
||||||
self.log_warning(f"Пропущена запись ID {log.id}: некорректные параметры URL.")
|
if reservation.status == "заселен" and not logs:
|
||||||
continue
|
self.record_violation(
|
||||||
|
hotel=reservation.hotel,
|
||||||
if hotel_id not in hotels:
|
room_number=reservation.room_number,
|
||||||
self.log_warning(f"Пропущена запись ID {log.id}: отель с ID {hotel_id} не найден.")
|
violation_type="no_qr_scan",
|
||||||
continue
|
details=f"Бронирование для номера {reservation.room_number} в отеле '{reservation.hotel.name}' "
|
||||||
|
f"не имеет записи сканирования QR."
|
||||||
hotel = hotels[hotel_id]
|
|
||||||
log_time = timezone.localtime(log.created)
|
|
||||||
|
|
||||||
# Проверка существования бронирования
|
|
||||||
matching_reservations = Reservation.objects.filter(
|
|
||||||
hotel=hotel,
|
|
||||||
room_number=room_number,
|
|
||||||
check_in__lte=log_time,
|
|
||||||
check_out__gte=log_time
|
|
||||||
)
|
)
|
||||||
|
|
||||||
print(f"Found {matching_reservations.count()} matching reservations")
|
# Раннее заселение
|
||||||
|
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}."
|
||||||
|
)
|
||||||
|
|
||||||
|
# Сканирование 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():
|
if not matching_reservations.exists():
|
||||||
violation_details = (
|
for log in logs:
|
||||||
f"Не найдено бронирование для номера {room_number} в отеле '{hotel.name}' на {log_time}."
|
self.record_violation(
|
||||||
)
|
hotel=Hotel.objects.filter(hotel_id=hotel_id).first(),
|
||||||
# Добавляем нарушение, если его ещё нет в базе
|
|
||||||
if not ViolationLog.objects.filter(
|
|
||||||
hotel=hotel,
|
|
||||||
room_number=room_number,
|
room_number=room_number,
|
||||||
violation_type="missed",
|
violation_type="no_reservation",
|
||||||
violation_details=violation_details
|
details=f"Сканирование QR {log.created} для номера {room_number} в отеле с ID '{hotel_id}' "
|
||||||
).exists():
|
f"не соответствует ни одному бронированию."
|
||||||
|
)
|
||||||
|
|
||||||
|
def record_violation(self, hotel, room_number, violation_type, details):
|
||||||
|
"""
|
||||||
|
Записывает нарушение в список для последующего сохранения.
|
||||||
|
"""
|
||||||
|
if hotel:
|
||||||
self.violations.append(ViolationLog(
|
self.violations.append(ViolationLog(
|
||||||
hotel=hotel,
|
hotel=hotel,
|
||||||
room_number=room_number,
|
room_number=room_number,
|
||||||
violation_type="missed",
|
violation_type=violation_type,
|
||||||
violation_details=violation_details
|
violation_details=details
|
||||||
))
|
))
|
||||||
self.log_warning(f"Зафиксировано нарушение: {violation_details}")
|
self.log_warning(f"Зафиксировано нарушение: {details}")
|
||||||
|
|
||||||
def save_violations(self):
|
def save_violations(self):
|
||||||
"""
|
""" Сохраняет найденные нарушения в базу данных. """
|
||||||
Сохраняет найденные нарушения в базу данных.
|
|
||||||
"""
|
|
||||||
if self.violations:
|
if self.violations:
|
||||||
ViolationLog.objects.bulk_create(self.violations)
|
ViolationLog.objects.bulk_create(self.violations)
|
||||||
self.log_info(f"Создано {len(self.violations)} записей в ViolationLog.")
|
self.log_info(f"Создано {len(self.violations)} записей в ViolationLog.")
|
||||||
@@ -119,42 +129,19 @@ class ReservationChecker:
|
|||||||
self.log_info("Нарушений не обнаружено.")
|
self.log_info("Нарушений не обнаружено.")
|
||||||
|
|
||||||
def run_check(self):
|
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:
|
try:
|
||||||
# Получаем логи пользователей
|
self.find_violations()
|
||||||
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.save_violations()
|
self.save_violations()
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.log_error(f"Произошла ошибка при выполнении проверки: {e}")
|
self.log_error(f"Ошибка при выполнении проверки: {e}")
|
||||||
|
self.log_info("Проверка завершена.")
|
||||||
|
|
||||||
self.log_info("Проверка бронирований завершена.")
|
# Функция для запуска из планировщика
|
||||||
|
|
||||||
# Функция для запуска проверки из планировщика
|
|
||||||
def run_reservation_check():
|
def run_reservation_check():
|
||||||
"""
|
|
||||||
Функция для запуска проверки бронирований.
|
|
||||||
"""
|
|
||||||
checker = ReservationChecker()
|
checker = ReservationChecker()
|
||||||
checker.run_check()
|
checker.run_check()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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 logging
|
||||||
import pymysql
|
import pymysql
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
@@ -422,10 +22,24 @@ class DatabaseConnector:
|
|||||||
self.db_settings = None
|
self.db_settings = None
|
||||||
|
|
||||||
def setup_logger(self):
|
def setup_logger(self):
|
||||||
logger = logging.getLogger(__name__)
|
default_level = logging.INFO # Уровень по умолчанию
|
||||||
logger.setLevel(logging.DEBUG)
|
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 = logging.FileHandler("data_sync.log")
|
||||||
handler.setFormatter(logging.Formatter("%(asctime)s - %(levelname)s - %(message)s"))
|
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)
|
logger.addHandler(handler)
|
||||||
return logger
|
return logger
|
||||||
|
|
||||||
@@ -528,32 +142,66 @@ class HotelRoomManager:
|
|||||||
def __init__(self, logger):
|
def __init__(self, logger):
|
||||||
self.logger = logger
|
self.logger = logger
|
||||||
|
|
||||||
def get_or_create_hotel(self, hotel_name, hotel_id):
|
def get_or_create_hotel(self, hotel_id, page_title):
|
||||||
"""Создает или получает отель."""
|
"""
|
||||||
if not hotel_name:
|
Создает или получает отель.
|
||||||
|
|
||||||
|
:param hotel_id: Значение из utm_content (индекс отеля)
|
||||||
|
:param page_title: Название отеля из поля page_title
|
||||||
|
"""
|
||||||
|
if not hotel_id:
|
||||||
|
self.logger.warning("Пропущено создание отеля: отсутствует hotel_id.")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
# Создаем или получаем отель с hotel_id и устанавливаем name по page_title
|
||||||
hotel, created = Hotel.objects.get_or_create(
|
hotel, created = Hotel.objects.get_or_create(
|
||||||
name=hotel_name,
|
|
||||||
hotel_id=hotel_id,
|
hotel_id=hotel_id,
|
||||||
defaults={"description": "Автоматически добавленный отель"}
|
defaults={
|
||||||
|
"name": html.unescape(page_title) or f"Отель {hotel_id}",
|
||||||
|
"description": "Автоматически добавленный отель"
|
||||||
|
}
|
||||||
)
|
)
|
||||||
if created:
|
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
|
return hotel
|
||||||
|
|
||||||
def get_or_create_room(self, hotel, room_number):
|
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
|
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(
|
room, created = Room.objects.get_or_create(
|
||||||
hotel=hotel,
|
hotel=hotel,
|
||||||
number=room_number,
|
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:
|
class DataSyncManager:
|
||||||
"""
|
"""
|
||||||
@@ -585,6 +233,7 @@ class DataSyncManager:
|
|||||||
WHERE id > {last_id}
|
WHERE id > {last_id}
|
||||||
AND url_parameters IS NOT NULL
|
AND url_parameters IS NOT NULL
|
||||||
AND url_parameters LIKE '%utm_medium%'
|
AND url_parameters LIKE '%utm_medium%'
|
||||||
|
AND page_url IS NOT NULL
|
||||||
ORDER BY id ASC
|
ORDER BY id ASC
|
||||||
LIMIT 1000;
|
LIMIT 1000;
|
||||||
"""
|
"""
|
||||||
@@ -592,52 +241,48 @@ class DataSyncManager:
|
|||||||
return self.db_connector.execute_query(query)
|
return self.db_connector.execute_query(query)
|
||||||
|
|
||||||
def process_and_save_data(self, rows):
|
def process_and_save_data(self, rows):
|
||||||
"""Обрабатывает и сохраняет данные, включая парсинг отсутствующих значений."""
|
"""
|
||||||
|
Обрабатывает и сохраняет данные из внешней базы данных.
|
||||||
|
"""
|
||||||
for row in rows:
|
for row in rows:
|
||||||
try:
|
try:
|
||||||
# Декодирование параметров URL
|
# Декодирование URL-параметров
|
||||||
url_params = self.data_processor.decode_html_entities(row.get("url_parameters", ""))
|
url_params = self.data_processor.decode_html_entities(row.get("url_parameters", ""))
|
||||||
params = parse_qs(url_params)
|
params = self.data_processor.url_parameters_parser(url_params)
|
||||||
param_dict = DataProcessor.url_parameters_parser(self.data_processor, url_params)
|
timestamp = params.get("timestamp")
|
||||||
self.logger.info(f"Параметры URL успешно декодированы: {param_dict}")
|
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")
|
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)
|
room = self.hotel_manager.get_or_create_room(hotel, room_number)
|
||||||
|
page_url = row.get("page_url")
|
||||||
# Заполнение отсутствующих данных
|
# Заполнение записи
|
||||||
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
|
|
||||||
UserActivityLog.objects.update_or_create(
|
UserActivityLog.objects.update_or_create(
|
||||||
external_id=external_id,
|
external_id=external_id,
|
||||||
defaults={
|
defaults={
|
||||||
"user_id": user_id,
|
"user_id": row.get("user_id") or 0,
|
||||||
"ip": ip,
|
"timestamp": row.get("timestamp"),
|
||||||
"timestamp": timeatamp,
|
"date_time": row.get("date_time"),
|
||||||
"date_time": date_time,
|
"ip": row.get("ip") or "0.0.0.0",
|
||||||
"created": created,
|
"created": self.data_processor.parse_datetime(row.get("created")) or timezone.now(),
|
||||||
"page_id": room.id if room else None,
|
|
||||||
"url_parameters": url_params,
|
"url_parameters": url_params,
|
||||||
|
"page_id": room.id if room else None,
|
||||||
"page_title": html.unescape(page_title),
|
"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:
|
except Exception as e:
|
||||||
self.logger.error(f"Ошибка при обработке записи ID {row.get('id')}: {e}")
|
self.logger.error(f"Ошибка при обработке записи ID {row.get('id')}: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def sync(self):
|
def sync(self):
|
||||||
"""Запускает процесс синхронизации."""
|
"""Запускает процесс синхронизации."""
|
||||||
self.db_connector.connect()
|
self.db_connector.connect()
|
||||||
|
|||||||
19
antifroud/migrations/0017_violationlog_hits.py
Normal file
19
antifroud/migrations/0017_violationlog_hits.py
Normal file
@@ -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,
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -188,6 +188,7 @@ class ViolationLog(models.Model):
|
|||||||
verbose_name="Тип нарушения"
|
verbose_name="Тип нарушения"
|
||||||
)
|
)
|
||||||
violation_details = models.TextField(verbose_name="Детали нарушения", blank=True, null=True)
|
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="Дата обнаружения")
|
detected_at = models.DateTimeField(auto_now_add=True, verbose_name="Дата обнаружения")
|
||||||
created_at = models.DateTimeField(auto_now_add=True, verbose_name="Дата создания")
|
created_at = models.DateTimeField(auto_now_add=True, verbose_name="Дата создания")
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user