add: Custom Command Синхронизация s_hash_id с солью
This commit is contained in:
@@ -65,7 +65,34 @@ poetry run python hypn0/manage.py rescore_hypn0_items --dry-run
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 2. Планируемые команды (Roadmap)
|
## 2. `rehash` — Проверка целостности и перегенерация `s_hash_id`
|
||||||
|
|
||||||
|
- **Путь:** `hypn0/hypn0_site/management/commands/rehash.py`
|
||||||
|
- **Назначение:** Синхронизация публичных коротких хэшей `s_hash_id` в базе данных с текущим значением `HASHIDS_SALT` и `HASHIDS_MIN_LENGTH`.
|
||||||
|
|
||||||
|
### Когда требуется запуск:
|
||||||
|
1. При смене или ротации `HASHIDS_SALT` в `.env` / `settings.py`.
|
||||||
|
2. При переносе/миграции базы данных между разными окружениями (Dev $\rightarrow$ Prod).
|
||||||
|
3. При обнаружении записей с пустыми или устаревшими `s_hash_id`.
|
||||||
|
|
||||||
|
### Базовый запуск:
|
||||||
|
```bash
|
||||||
|
# Тестовая проверка нестыковок (без записи в БД)
|
||||||
|
poetry run python hypn0/manage.py rehash --dry-run
|
||||||
|
|
||||||
|
# Боевая перегенерация
|
||||||
|
poetry run python hypn0/manage.py rehash
|
||||||
|
```
|
||||||
|
|
||||||
|
### Параметры командной строки (CLI):
|
||||||
|
| Флаг | Тип | По умолчанию | Описание |
|
||||||
|
|----------------|--------|--------------|----------------------------------------------------------------|
|
||||||
|
| `--dry-run` | `flag` | `False` | Только проверить нестыковки без изменения базы данных |
|
||||||
|
| `--batch-size` | `int` | `500` | Размер пачки для `bulk_update` (оптимизация памяти и скорости) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Планируемые команды (Roadmap)
|
||||||
|
|
||||||
- **`cleanup_storage`** — Удаление старых и низкорейтинговых картин (Smart Retention) при превышении квоты хранилища (использует рассчитанный `f_score`).
|
- **`cleanup_storage`** — Удаление старых и низкорейтинговых картин (Smart Retention) при превышении квоты хранилища (использует рассчитанный `f_score`).
|
||||||
- **`cleanup_reports`** — Автоматическая изоляция или удаление картин со статусом `SHAMED` после истечения тайм-аута модерации.
|
- **`cleanup_reports`** — Автоматическая изоляция или удаление картин со статусом `SHAMED` после истечения тайм-аута модерации.
|
||||||
|
|||||||
+29
-28
@@ -558,34 +558,35 @@ CLI больше не нужен.
|
|||||||
|
|
||||||
## Файлы для создания (итого)
|
## Файлы для создания (итого)
|
||||||
|
|
||||||
| Файл | Назначение |
|
| Файл | Назначение |
|
||||||
|-----------------------------------------------------------|------------------------|
|
|---------------------------------------------------------------|---------------------------------|
|
||||||
| `hypn0/manage.py` | Django CLI |
|
| `hypn0/manage.py` | Django CLI |
|
||||||
| `hypn0/hypn0/__init__.py` | Settings pkg |
|
| `hypn0/hypn0/__init__.py` | Settings pkg |
|
||||||
| `hypn0/hypn0/settings.py` | Конфиг |
|
| `hypn0/hypn0/settings.py` | Конфиг |
|
||||||
| `hypn0/hypn0/urls.py` | URL routing |
|
| `hypn0/hypn0/urls.py` | URL routing |
|
||||||
| `hypn0/hypn0/wsgi.py` | WSGI entry |
|
| `hypn0/hypn0/wsgi.py` | WSGI entry |
|
||||||
| `hypn0/hypn0/templates/base.html` | Базовый шаблон |
|
| `hypn0/hypn0/templates/base.html` | Базовый шаблон |
|
||||||
| `hypn0/hypn0_site/__init__.py` | App pkg |
|
| `hypn0/hypn0_site/__init__.py` | App pkg |
|
||||||
| `hypn0/hypn0_site/urls.py` | URL app |
|
| `hypn0/hypn0_site/urls.py` | URL app |
|
||||||
| `hypn0/hypn0_site/views.py` | Views |
|
| `hypn0/hypn0_site/views.py` | Views |
|
||||||
| `hypn0/hypn0_site/forms.py` | Forms |
|
| `hypn0/hypn0_site/forms.py` | Forms |
|
||||||
| `hypn0/hypn0_site/models.py` | TbHypn0Item, TbVote... |
|
| `hypn0/hypn0_site/models.py` | TbHypn0Item, TbVote... |
|
||||||
| `hypn0/hypn0_site/services/halftone.py` | Ядро |
|
| `hypn0/hypn0_site/services/halftone.py` | Ядро |
|
||||||
| `hypn0/hypn0_site/templates/halftone/index.html` | Главная |
|
| `hypn0/hypn0_site/templates/halftone/index.html` | Главная |
|
||||||
| `hypn0/hypn0_site/management/commands/rescore_hypn0_items.py` | Скоринг и детекция аномалий |
|
| `hypn0/hypn0_site/management/commands/rescore_hypn0_items.py` | Скоринг и детекция аномалий |
|
||||||
| `hypn0/hypn0_site/management/commands/cleanup_storage.py` | Очистка SmartRetention |
|
| `hypn0/hypn0_site/management/commands/rehash.py` | Синхронизация s_hash_id с солью |
|
||||||
| `hypn0/hypn0_site/management/commands/cleanup_reports.py` | Очистка по жалобам |
|
| `hypn0/hypn0_site/management/commands/cleanup_storage.py` | Очистка SmartRetention |
|
||||||
| `_blueprint/management-commands.md` | Документация CLI-команд|
|
| `hypn0/hypn0_site/management/commands/cleanup_reports.py` | Очистка по жалобам |
|
||||||
| `public/static/css/site.css` | Стили |
|
| `_blueprint/management-commands.md` | Документация CLI-команд |
|
||||||
| `public/static/js/site.js` | JS |
|
| `public/static/css/site.css` | Стили |
|
||||||
| `.env.sample` | Env template |
|
| `public/static/js/site.js` | JS |
|
||||||
| `Dockerfile` | Сборка |
|
| `.env.sample` | Env template |
|
||||||
| `docker-compose.local.yml` | Dev |
|
| `Dockerfile` | Сборка |
|
||||||
| `docker-compose.prod.yml` | Prod |
|
| `docker-compose.local.yml` | Dev |
|
||||||
| `config/nginx/hypn0-app--external-nginx.conf` | Nginx |
|
| `docker-compose.prod.yml` | Prod |
|
||||||
| `.gitea/workflows/docker-publish.yaml` | CI/CD |
|
| `config/nginx/hypn0-app--external-nginx.conf` | Nginx |
|
||||||
| `database/.gitignore` | Игнор БД |
|
| `.gitea/workflows/docker-publish.yaml` | CI/CD |
|
||||||
|
| `database/.gitignore` | Игнор БД |
|
||||||
|
|
||||||
## Файлы для модификации
|
## Файлы для модификации
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,105 @@
|
|||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
from django.conf import settings
|
||||||
|
from hashids import Hashids
|
||||||
|
|
||||||
|
from hypn0_site.models import TbHypn0Item
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
"""
|
||||||
|
Проверка и синхронизация s_hash_id с текущими настройками HASHIDS_SALT.
|
||||||
|
|
||||||
|
АРХИТЕКТУРНОЕ НАЗНАЧЕНИЕ:
|
||||||
|
=========================
|
||||||
|
1. Изоляция сред (Dev / Stage / Prod) и ротация секретов:
|
||||||
|
В разных окружениях или при переносе базы данных значение HASHIDS_SALT может
|
||||||
|
отличаться от исходного.
|
||||||
|
|
||||||
|
2. Детерминированность и обратимость:
|
||||||
|
Hashids — это однозначное обратимое кодирование: pk <-> s_hash_id.
|
||||||
|
Если соль изменилась, старые s_hash_id в базе перестают соответствовать
|
||||||
|
текущей конфигурации проекта.
|
||||||
|
|
||||||
|
3. Назначение команды:
|
||||||
|
- Сверяет каждый s_hash_id в базе с тем, что должен генерироваться при текущем settings.HASHIDS_SALT.
|
||||||
|
- Выявляет битые, пустые или сгенерированные со старой солью идентификаторы.
|
||||||
|
- Безопасно обновляет s_hash_id без изменения первичных ключей (pk) и других полей картины.
|
||||||
|
"""
|
||||||
|
|
||||||
|
help = "Проверка целостности и перегенерация s_hash_id картин под текущий HASHIDS_SALT"
|
||||||
|
|
||||||
|
def add_arguments(self, parser):
|
||||||
|
parser.add_argument(
|
||||||
|
"--dry-run",
|
||||||
|
action="store_true",
|
||||||
|
help="Тестовый прогон: только проверить несоответствия без записи изменений в БД",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--batch-size",
|
||||||
|
type=int,
|
||||||
|
default=500,
|
||||||
|
help="Размер пакета для пакетного обновления (по умолчанию: 500)",
|
||||||
|
)
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
dry_run = options["dry_run"]
|
||||||
|
batch_size = options["batch_size"]
|
||||||
|
|
||||||
|
salt = getattr(settings, "HASHIDS_SALT", "hypn0_default_salt")
|
||||||
|
min_length = getattr(settings, "HASHIDS_MIN_LENGTH", 6)
|
||||||
|
hasher = Hashids(salt=salt, min_length=min_length)
|
||||||
|
|
||||||
|
self.stdout.write(self.style.NOTICE(f"Старт проверки s_hash_id (соль: '{salt[:4]}***', min_length: {min_length})..."))
|
||||||
|
|
||||||
|
items = TbHypn0Item.objects.all().only("id", "s_hash_id")
|
||||||
|
total_items = items.count()
|
||||||
|
|
||||||
|
if total_items == 0:
|
||||||
|
self.stdout.write(self.style.WARNING("В базе данных нет картин для проверки."))
|
||||||
|
return
|
||||||
|
|
||||||
|
to_update = []
|
||||||
|
mismatched_count = 0
|
||||||
|
empty_count = 0
|
||||||
|
|
||||||
|
for item in items.iterator(chunk_size=batch_size):
|
||||||
|
expected_hash = hasher.encode(item.id)
|
||||||
|
|
||||||
|
if not item.s_hash_id:
|
||||||
|
empty_count += 1
|
||||||
|
item.s_hash_id = expected_hash
|
||||||
|
to_update.append(item)
|
||||||
|
elif item.s_hash_id != expected_hash:
|
||||||
|
mismatched_count += 1
|
||||||
|
self.stdout.write(
|
||||||
|
f"Несоответствие [ID={item.id}]: текущий='{item.s_hash_id}' -> ожидаемый='{expected_hash}'"
|
||||||
|
)
|
||||||
|
item.s_hash_id = expected_hash
|
||||||
|
to_update.append(item)
|
||||||
|
|
||||||
|
# Пакетное сохранение порциями
|
||||||
|
if not dry_run and len(to_update) >= batch_size:
|
||||||
|
TbHypn0Item.objects.bulk_update(to_update, fields=["s_hash_id"])
|
||||||
|
to_update.clear()
|
||||||
|
|
||||||
|
# Сохранение остатка
|
||||||
|
if not dry_run and to_update:
|
||||||
|
TbHypn0Item.objects.bulk_update(to_update, fields=["s_hash_id"])
|
||||||
|
|
||||||
|
total_issues = empty_count + mismatched_count
|
||||||
|
self.stdout.write("--------------------------------------------------")
|
||||||
|
self.stdout.write(f"Всего проверено записей: {total_items}")
|
||||||
|
self.stdout.write(f"Пустых s_hash_id: {empty_count}")
|
||||||
|
self.stdout.write(f"Несоответствующих s_hash_id: {mismatched_count}")
|
||||||
|
|
||||||
|
if total_issues == 0:
|
||||||
|
self.stdout.write(self.style.SUCCESS("Все s_hash_id в базе идеально соответствуют текущей соли!"))
|
||||||
|
else:
|
||||||
|
if dry_run:
|
||||||
|
self.stdout.write(
|
||||||
|
self.style.WARNING(f"[DRY RUN] Найдено {total_issues} несоответствий. Запустите без --dry-run для применения.")
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self.stdout.write(
|
||||||
|
self.style.SUCCESS(f"Успешно обновлено {total_issues} записей под текущую конфигурацию.")
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user