antifroud_check

This commit is contained in:
2024-12-17 21:39:33 +09:00
parent 66750015e2
commit 0bf2bb8dff
17 changed files with 1298 additions and 524 deletions

View File

@@ -5,7 +5,8 @@ from django.shortcuts import redirect, get_object_or_404
from django.contrib import messages
from django.db import transaction
from antifroud.models import UserActivityLog, ExternalDBSettings, RoomDiscrepancy, ImportedHotel, SyncLog, ViolationLog
from hotels.models import Hotel
from hotels.models import Hotel, Room
import pymysql
import logging
from django.urls import reverse
@@ -114,14 +115,32 @@ class UserActivityLogAdmin(admin.ModelAdmin):
search_fields = ("page_title", "url_parameters")
list_filter = ("page_title", "created")
readonly_fields = ("created", "timestamp")
def get_hotel_name(self):
"""
Возвращает название отеля на основе связанного page_id.
"""
if self.page_id:
try:
room = Room.objects.get(id=self.page_id)
return room.hotel.name
except Room.DoesNotExist:
return "Отель не найден"
return "Нет данных"
def get_room_number(self):
"""
Возвращает номер комнаты на основе связанного page_id.
"""
if self.page_id:
try:
room = Room.objects.get(id=self.page_id)
return room.number
except Room.DoesNotExist:
return "Комната не найдена"
return "Нет данных"
@admin.register(RoomDiscrepancy)
class RoomDiscrepancyAdmin(admin.ModelAdmin):
list_display = ("hotel", "room_number", "booking_id", "check_in_date_expected", "check_in_date_actual", "discrepancy_type", "created_at")
search_fields = ("hotel__name", "room_number", "booking_id")
list_filter = ("discrepancy_type", "created_at")
readonly_fields = ("created_at",)
get_hotel_name.short_description = "Отель"
get_room_number.short_description = "Комната"
from .views import import_selected_hotels

View File

@@ -1,79 +1,160 @@
import logging
from datetime import datetime, timedelta
from datetime import timedelta
from urllib.parse import parse_qs
from django.utils import timezone
from django.db.models import Q
from hotels.models import Reservation, Hotel
from .models import UserActivityLog, ViolationLog
# Настройка логирования
logging.basicConfig(level=logging.INFO) # Устанавливаем уровень логирования
logger = logging.getLogger(__name__) # Создаем логгер для текущего модуля
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
def check_reservations_and_generate_report():
now = datetime.now()
start_time = (now - timedelta(hours=12))
end_time = now
logger.info(f"Starting reservation check from {start_time} to {end_time}")
class ReservationChecker:
"""
Класс для проверки несоответствий между бронированиями и логами заселения.
"""
# Получаем логи активности за период
user_logs = UserActivityLog.objects.filter(created__range=(start_time, end_time))
logger.info(f"Found {len(user_logs)} logs for analysis.")
def __init__(self):
"""
Инициализация времени проверки и списка нарушений.
"""
self.start_time = timezone.now() - timedelta(days=30)
self.end_time = timezone.now()
self.violations = []
violations = [] # Список для записи нарушений
def log_info(self, message):
"""Логирование информационных сообщений."""
logger.info(message)
for i, log in enumerate(user_logs, start=1):
logger.debug(f"Processing log {i}: {log}")
def log_warning(self, message):
"""Логирование предупреждений."""
logger.warning(message)
if not log.url_parameters:
logger.warning(f"Log {i} skipped due to missing URL parameters.")
continue # Пропускаем записи без параметров URL
def log_error(self, message):
"""Логирование ошибок."""
logger.error(message)
# Парсим параметры URL
params = parse_qs(log.url_parameters)
external_id = params.get("utm_content", [None])[0] # ID отеля
room_number = params.get("utm_term", [None])[0] # Номер комнаты
def fetch_user_logs(self):
"""
Извлекает записи из 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))
print(f"Found {user_logs.count()} logs for analysis.")
return user_logs
logger.debug(f"Log {i} parsed parameters: external_id={external_id}, room_number={room_number}")
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
if not external_id or not room_number:
logger.warning(f"Log {i} skipped due to missing external_id or room_number.")
continue # Пропускаем, если данные не извлечены
def find_violations(self, user_logs, hotels):
"""
Сопоставляет логи активности с бронированиями и фиксирует нарушения.
"""
for log in user_logs:
if not log.url_parameters:
self.log_warning(f"Пропущена запись ID {log.id}: отсутствуют URL-параметры.")
continue
try:
# Находим отель по external_id
hotel = Hotel.objects.get(external_id=external_id)
logger.debug(f"Log {i}: Found hotel {hotel.name} with external_id {external_id}.")
except Hotel.DoesNotExist:
logger.error(f"Log {i} skipped: No hotel found with external_id {external_id}.")
continue
# Парсинг параметров URL
params = parse_qs(log.url_parameters)
hotel_id = params.get("utm_content", [None])[0]
room_number = params.get("utm_term", [None])[0]
# Ищем бронирование в Reservation
matching_reservations = Reservation.objects.filter(
hotel=hotel,
room_number=room_number,
check_in__lte=log.created,
check_out__gte=log.created
)
logger.debug(f"Log {i}: Found {len(matching_reservations)} matching reservations.")
print(f"Processing log ID {log.id} with hotel ID {hotel_id} and room number {room_number}")
if not matching_reservations.exists():
# Если бронирование не найдено — записываем нарушение
violation_details = (
f"Log {i}: No reservation found for room {room_number} in hotel {external_id} at {log.created}."
)
violations.append(ViolationLog(
if not hotel_id or not room_number:
self.log_warning(f"Пропущена запись ID {log.id}: некорректные параметры URL.")
continue
if hotel_id not in hotels:
self.log_warning(f"Пропущена запись ID {log.id}: отель с ID {hotel_id} не найден.")
continue
hotel = hotels[hotel_id]
log_time = timezone.localtime(log.created)
# Проверка существования бронирования
matching_reservations = Reservation.objects.filter(
hotel=hotel,
room_number=room_number,
violation_type="missed",
violation_details=violation_details
))
logger.warning(f"Log {i}: Violation recorded - {violation_details}")
check_in__lte=log_time,
check_out__gte=log_time
)
# Сохраняем все нарушения в базу
if violations:
ViolationLog.objects.bulk_create(violations)
logger.info(f"Created {len(violations)} records in violation log.")
else:
logger.info("No violations found during this check.")
print(f"Found {matching_reservations.count()} matching reservations")
logger.info("Reservation check completed.")
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}")
def save_violations(self):
"""
Сохраняет найденные нарушения в базу данных.
"""
if self.violations:
ViolationLog.objects.bulk_create(self.violations)
self.log_info(f"Создано {len(self.violations)} записей в ViolationLog.")
else:
self.log_info("Нарушений не обнаружено.")
def run_check(self):
"""
Основной метод для запуска проверки.
"""
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.save_violations()
except Exception as e:
self.log_error(f"Произошла ошибка при выполнении проверки: {e}")
self.log_info("Проверка бронирований завершена.")
# Функция для запуска проверки из планировщика
def run_reservation_check():
"""
Функция для запуска проверки бронирований.
"""
checker = ReservationChecker()
checker.run_check()

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.1.4 on 2024-12-17 03:23
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('antifroud', '0012_violationlog'),
]
operations = [
migrations.AlterField(
model_name='useractivitylog',
name='timestamp',
field=models.BigIntegerField(blank=True, null=True, verbose_name='Метка времени'),
),
]

View File

@@ -0,0 +1,68 @@
# Generated by Django 5.1.4 on 2024-12-17 03:26
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('antifroud', '0013_alter_useractivitylog_timestamp'),
]
operations = [
migrations.AlterField(
model_name='useractivitylog',
name='UAString',
field=models.TextField(blank=True, null=True, verbose_name='User-Agent строка'),
),
migrations.AlterField(
model_name='useractivitylog',
name='agent',
field=models.TextField(blank=True, null=True, verbose_name='Агент пользователя'),
),
migrations.AlterField(
model_name='useractivitylog',
name='created',
field=models.DateTimeField(blank=True, null=True, verbose_name='Дата создания'),
),
migrations.AlterField(
model_name='useractivitylog',
name='date_time',
field=models.DateTimeField(blank=True, null=True, verbose_name='Дата и время'),
),
migrations.AlterField(
model_name='useractivitylog',
name='hits',
field=models.IntegerField(blank=True, null=True, verbose_name='Количество обращений'),
),
migrations.AlterField(
model_name='useractivitylog',
name='honeypot',
field=models.BooleanField(blank=True, null=True, verbose_name='Метка honeypot'),
),
migrations.AlterField(
model_name='useractivitylog',
name='ip',
field=models.GenericIPAddressField(blank=True, null=True, verbose_name='IP-адрес'),
),
migrations.AlterField(
model_name='useractivitylog',
name='last_counter',
field=models.IntegerField(blank=True, null=True, verbose_name='Последний счетчик'),
),
migrations.AlterField(
model_name='useractivitylog',
name='reply',
field=models.BooleanField(blank=True, null=True, verbose_name='Ответ пользователя'),
),
migrations.AlterField(
model_name='useractivitylog',
name='type',
field=models.CharField(blank=True, max_length=50, null=True, verbose_name='Тип'),
),
migrations.AlterField(
model_name='useractivitylog',
name='user_id',
field=models.BigIntegerField(blank=True, null=True, verbose_name='ID пользователя'),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.1.4 on 2024-12-17 04:49
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('antifroud', '0014_alter_useractivitylog_uastring_and_more'),
]
operations = [
migrations.AlterField(
model_name='useractivitylog',
name='page_id',
field=models.CharField(blank=True, max_length=255, null=True, verbose_name='ID страницы'),
),
]

View File

@@ -0,0 +1,39 @@
# Generated by Django 5.1.4 on 2024-12-17 05:27
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('antifroud', '0015_alter_useractivitylog_page_id'),
]
operations = [
migrations.AlterField(
model_name='useractivitylog',
name='created',
field=models.DateTimeField(blank=True, db_index=True, null=True, verbose_name='Дата создания'),
),
migrations.AlterField(
model_name='useractivitylog',
name='external_id',
field=models.CharField(db_index=True, default=1, max_length=255, unique=True, verbose_name='Внешний ID'),
preserve_default=False,
),
migrations.AlterField(
model_name='useractivitylog',
name='ip',
field=models.GenericIPAddressField(blank=True, db_index=True, null=True, verbose_name='IP-адрес'),
),
migrations.AlterField(
model_name='useractivitylog',
name='page_id',
field=models.BigIntegerField(blank=True, db_index=True, null=True, verbose_name='ID страницы'),
),
migrations.AlterField(
model_name='useractivitylog',
name='user_id',
field=models.BigIntegerField(blank=True, db_index=True, null=True, verbose_name='ID пользователя'),
),
]

View File

@@ -4,30 +4,40 @@ from hotels.models import Reservation
class UserActivityLog(models.Model):
external_id = models.CharField(max_length=255, null=True, blank=True)
user_id = models.BigIntegerField(verbose_name="ID пользователя")
ip = models.GenericIPAddressField(verbose_name="IP-адрес")
created = models.DateTimeField(verbose_name="Дата создания")
timestamp = models.BigIntegerField(verbose_name="Метка времени")
date_time = models.DateTimeField(verbose_name="Дата и время")
external_id = models.CharField(max_length=255, unique=True, verbose_name="Внешний ID", db_index=True)
user_id = models.BigIntegerField(verbose_name="ID пользователя", blank=True, null=True, db_index=True)
ip = models.GenericIPAddressField(verbose_name="IP-адрес", blank=True, null=True, db_index=True)
created = models.DateTimeField(verbose_name="Дата создания", blank=True, null=True, db_index=True)
timestamp = models.BigIntegerField(verbose_name="Метка времени", blank=True, null=True)
date_time = models.DateTimeField(verbose_name="Дата и время", blank=True, null=True)
referred = models.TextField(blank=True, null=True, verbose_name="Реферальная ссылка")
agent = models.TextField(verbose_name="Агент пользователя")
agent = models.TextField(verbose_name="Агент пользователя", blank=True, null=True)
platform = models.CharField(max_length=255, blank=True, null=True, verbose_name="Платформа")
version = models.CharField(max_length=255, blank=True, null=True, verbose_name="Версия")
model = models.CharField(max_length=255, blank=True, null=True, verbose_name="Модель устройства")
device = models.CharField(max_length=255, blank=True, null=True, verbose_name="Тип устройства")
UAString = models.TextField(verbose_name="User-Agent строка")
UAString = models.TextField(verbose_name="User-Agent строка", blank=True, null=True)
location = models.CharField(max_length=255, blank=True, null=True, verbose_name="Местоположение")
page_id = models.BigIntegerField(blank=True, null=True, verbose_name="ID страницы")
page_id = models.BigIntegerField(blank=True, null=True, verbose_name="ID страницы", db_index=True)
url_parameters = models.TextField(blank=True, null=True, verbose_name="Параметры URL")
page_title = models.TextField(blank=True, null=True, verbose_name="Заголовок страницы")
type = models.CharField(max_length=50, verbose_name="Тип")
last_counter = models.IntegerField(verbose_name="Последний счетчик")
hits = models.IntegerField(verbose_name="Количество обращений")
honeypot = models.BooleanField(verbose_name="Метка honeypot")
reply = models.BooleanField(verbose_name="Ответ пользователя")
type = models.CharField(max_length=50, verbose_name="Тип", blank=True, null=True)
last_counter = models.IntegerField(verbose_name="Последний счетчик", blank=True, null=True)
hits = models.IntegerField(verbose_name="Количество обращений", blank=True, null=True)
honeypot = models.BooleanField(verbose_name="Метка honeypot", blank=True, null=True)
reply = models.BooleanField(verbose_name="Ответ пользователя", blank=True, null=True)
page_url = models.URLField(blank=True, null=True, verbose_name="URL страницы")
class Meta:
indexes = [
models.Index(fields=["external_id"], name="idx_external_id"),
models.Index(fields=["user_id"], name="idx_user_id"),
models.Index(fields=["ip"], name="idx_ip"),
models.Index(fields=["created"], name="idx_created"),
models.Index(fields=["page_id"], name="idx_page_id"),
]
verbose_name = "Лог активности пользователя"
verbose_name_plural = "Логи активности пользователей"
def __str__(self):
return f"UserActivityLog {self.id}: {self.page_title}"