From 7f925ef2bdfd0424ad3c9218c722b4f87ba5c0c3 Mon Sep 17 00:00:00 2001 From: Verum Date: Thu, 5 Mar 2026 18:46:13 +0700 Subject: [PATCH] =?UTF-8?q?=D0=A0=D0=B0=D0=B1=D0=BE=D1=82=D0=B0=20=D1=81?= =?UTF-8?q?=20"=D0=BF=D0=BE=D0=B4=D0=B4=D0=B5=D0=BB=D0=BA=D0=BE=D0=B9"=20?= =?UTF-8?q?=D0=BB=D0=B8=D1=86=D0=B5=D0=BD=D0=B7=D0=B8=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/routes/license_routes.py | 47 ++++++++++++++++++++++++++++ backend/services/license_service.py | 48 +++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+) create mode 100644 backend/routes/license_routes.py create mode 100644 backend/services/license_service.py diff --git a/backend/routes/license_routes.py b/backend/routes/license_routes.py new file mode 100644 index 0000000..c5bc8ad --- /dev/null +++ b/backend/routes/license_routes.py @@ -0,0 +1,47 @@ +from typing import Any +from fastapi import APIRouter, Form, HTTPException, BackgroundTasks +from fastapi.responses import FileResponse +from ..services.license_service import generate_license_file + +router: APIRouter = APIRouter() + + +@router.post("/generate", response_class=FileResponse) +async def generate_license( + background_tasks: BackgroundTasks, + name: str = Form(...), + version: str = Form(...) +) -> Any: + """ + Генерация лицензии Custom.mxtpro. + Параметры: + - name: str - имя пользователя + - version: str - версия формата X.Y + Возвращает: + - FileResponse: сгенерированный ZIP файл с именем Custom.mxtpro + """ + if not name.strip() or not version.strip(): + raise HTTPException( + status_code=400, + detail="NAME и VERSION обязательны." + ) + try: + # Создаём временный файл на сервере + filepath, temp_dir = generate_license_file(name, version) + except Exception as e: + raise HTTPException( + status_code=500, + detail=f"Ошибка генерации лицензии: {e}" + ) + + # Удаляем директорию (и файл внутри) после отправки пользователю + background_tasks.add_task(temp_dir.cleanup) + + # Отправляем с фиксированным именем Custom.mxtpro + return FileResponse( + filepath, + media_type="application/octet-stream", + headers={ + "Content-Disposition": 'attachment; filename="Custom.mxtpro"' + } + ) diff --git a/backend/services/license_service.py b/backend/services/license_service.py new file mode 100644 index 0000000..3348a6a --- /dev/null +++ b/backend/services/license_service.py @@ -0,0 +1,48 @@ +import tempfile +from zipfile import ZipFile +from pathlib import Path +from ..utils.encoding import variant_base64_encode +from ..utils.crypto import encrypt_bytes + + +class LicenseType: + Professional: int = 1 + Educational: int = 3 + Personal: int = 4 + + +def generate_license_file( + user_name: str, + version: str, + lic_type: int = LicenseType.Professional, + count: int = 1 +) -> tuple[str, tempfile.TemporaryDirectory]: + """ + Генерация временного ZIP-файла с лицензией Custom.mxtpro. + Возвращает путь к файлу и объект временной директории для cleanup. + """ + # Разбор версии + try: + major_str, minor_str = version.split(".") + major_version: int = int(major_str) + minor_version: int = int(minor_str) + except Exception as e: + raise ValueError(f"Неверный формат версии: {version}") from e + + # Формирование строки лицензии + license_str: str = ( + f"{lic_type}#{user_name}|{major_version}{minor_version}" + f"#{count}#{major_version}3{minor_version}6{minor_version}#0#0#0#" + ) + + # Шифрование и кодирование + encrypted: bytes = encrypt_bytes(0x0787, license_str.encode("utf-8")) + encoded: str = variant_base64_encode(encrypted).decode("ascii") + + # Создание временной директории и файла с фиксированным именем + temp_dir = tempfile.TemporaryDirectory() + filepath = Path(temp_dir.name) / "Custom.mxtpro" + with ZipFile(filepath, "w") as zf: + zf.writestr("Pro.key", encoded) + + return str(filepath), temp_dir