471 lines
19 KiB
Markdown
471 lines
19 KiB
Markdown
# Система управления пользователями
|
||
|
||
## Обзор
|
||
|
||
Система управления пользователями предоставляет администраторам инструменты для:
|
||
- Поиска пользователей по различным критериям
|
||
- Просмотра детальной информации о пользователях
|
||
- Блокировки/разблокировки пользователей в чате
|
||
- Управления большими базами пользователей (1000+)
|
||
|
||
## Функциональность
|
||
|
||
### 1. Блокировка в чате
|
||
|
||
**Назначение:** Запрет отправки сообщений в чат бота для конкретного пользователя.
|
||
|
||
**Как работает:**
|
||
- Заблокированный пользователь не может отправлять сообщения в P2P чат
|
||
- Блокировка не затрагивает другие функции бота (участие в розыгрышах, просмотр статистики)
|
||
- Блокировка фиксируется в поле `is_chat_banned` в базе данных
|
||
|
||
**Применение:**
|
||
1. Найти пользователя через поиск или список
|
||
2. Открыть карточку пользователя
|
||
3. Нажать кнопку "🚫 Заблокировать в чате"
|
||
|
||
### 2. Поиск пользователей
|
||
|
||
**Критерии поиска:**
|
||
- Username (с @ или без)
|
||
- Имя или фамилия
|
||
- Telegram ID
|
||
- Номер клубной карты
|
||
- Никнейм
|
||
|
||
**Пример запросов:**
|
||
- `@username` - поиск по username
|
||
- `Иван` - поиск по имени/фамилии
|
||
- `123456789` - поиск по Telegram ID или номеру карты
|
||
- `nickname` - поиск по никнейму
|
||
|
||
**Особенности:**
|
||
- Поиск работает по принципу "ИЛИ" (OR) - ищет во всех полях одновременно
|
||
- Поддержка частичного совпадения (ILIKE)
|
||
- Автоматическая пагинация (15 пользователей на странице)
|
||
|
||
### 3. Списки пользователей
|
||
|
||
#### Все пользователи
|
||
- Отображаются все зарегистрированные в боте
|
||
- Сортировка по дате создания (новые первые)
|
||
- Пагинация по 15 пользователей
|
||
|
||
#### Заблокированные пользователи
|
||
- Отображаются только пользователи с блокировкой в чате
|
||
- Быстрый доступ для управления блокировками
|
||
|
||
### 4. Карточка пользователя
|
||
|
||
**Отображаемая информация:**
|
||
- **Основное:**
|
||
- Имя и фамилия
|
||
- Username (если есть)
|
||
- Telegram ID
|
||
|
||
- **Статистика:**
|
||
- Дата регистрации
|
||
- Последняя активность
|
||
- Статус регистрации
|
||
- Статус администратора
|
||
|
||
- **Дополнительно:**
|
||
- Никнейм (если установлен)
|
||
- Номер клубной карты (если привязан)
|
||
- Телефон (если указан)
|
||
- Статус блокировки в чате
|
||
|
||
## Архитектура
|
||
|
||
### Компоненты системы
|
||
|
||
#### 1. UserManagementService
|
||
**Файл:** `src/core/user_management.py`
|
||
|
||
**Методы:**
|
||
```python
|
||
# Поиск с фильтрами и пагинацией
|
||
async def search_users(
|
||
session: AsyncSession,
|
||
query: str = None, # Поисковый запрос
|
||
page: int = 1, # Номер страницы
|
||
per_page: int = 15, # Размер страницы
|
||
filters: Dict = None # Фильтры (is_registered, is_admin, is_chat_banned)
|
||
) -> Tuple[List[User], int]:
|
||
"""Возвращает (список пользователей, общее количество)"""
|
||
|
||
# Получение по ID
|
||
async def get_user_by_id(session: AsyncSession, user_id: int) -> Optional[User]:
|
||
"""Получить пользователя по внутреннему ID"""
|
||
|
||
# Блокировка в чате
|
||
async def ban_user_in_chat(session: AsyncSession, user_id: int) -> bool:
|
||
"""Заблокировать пользователя в чате"""
|
||
|
||
# Разблокировка в чате
|
||
async def unban_user_in_chat(session: AsyncSession, user_id: int) -> bool:
|
||
"""Разблокировать пользователя в чате"""
|
||
|
||
# Статистика
|
||
async def get_user_stats(session: AsyncSession) -> Dict[str, int]:
|
||
"""Получить статистику: total, registered, admins, chat_banned"""
|
||
|
||
# Форматирование
|
||
def format_user_info(user: User, detailed: bool = False) -> str:
|
||
"""Красивый HTML вывод информации о пользователе"""
|
||
```
|
||
|
||
**Константы:**
|
||
- `USERS_PER_PAGE = 15` - количество пользователей на странице
|
||
|
||
#### 2. Обработчики админ-панели
|
||
**Файл:** `src/handlers/admin_panel.py`
|
||
|
||
**Основные обработчики:**
|
||
|
||
```python
|
||
# Главное меню управления пользователями
|
||
@admin_router.callback_query(F.data == "admin_users")
|
||
async def admin_users_menu(callback: CallbackQuery):
|
||
"""Показывает статистику и главное меню"""
|
||
|
||
# Запрос поискового запроса
|
||
@admin_router.callback_query(F.data == "admin_users_search")
|
||
async def admin_users_search_prompt(callback: CallbackQuery, state: FSMContext):
|
||
"""Переводит в режим ожидания поискового запроса"""
|
||
|
||
# Обработка поиска
|
||
@admin_router.message(AdminStates.user_management_search)
|
||
async def admin_users_search_process(message: Message, state: FSMContext):
|
||
"""Выполняет поиск и показывает результаты"""
|
||
|
||
# Список всех пользователей
|
||
@admin_router.callback_query(F.data.startswith("admin_users_list:"))
|
||
async def admin_users_list(callback: CallbackQuery):
|
||
"""Постраничный список всех пользователей"""
|
||
|
||
# Список заблокированных
|
||
@admin_router.callback_query(F.data.startswith("admin_users_banned:"))
|
||
async def admin_users_banned_list(callback: CallbackQuery):
|
||
"""Список только заблокированных пользователей"""
|
||
|
||
# Просмотр пользователя
|
||
@admin_router.callback_query(F.data.startswith("admin_user_view:"))
|
||
async def admin_user_view(callback: CallbackQuery):
|
||
"""Детальная карточка пользователя"""
|
||
|
||
# Блокировка
|
||
@admin_router.callback_query(F.data.startswith("admin_user_ban:"))
|
||
async def admin_user_ban(callback: CallbackQuery):
|
||
"""Заблокировать пользователя"""
|
||
|
||
# Разблокировка
|
||
@admin_router.callback_query(F.data.startswith("admin_user_unban:"))
|
||
async def admin_user_unban(callback: CallbackQuery):
|
||
"""Разблокировать пользователя"""
|
||
```
|
||
|
||
#### 3. FSM состояния
|
||
**Файл:** `src/handlers/admin_panel.py`
|
||
|
||
```python
|
||
class AdminStates(StatesGroup):
|
||
# ... другие состояния ...
|
||
user_management_search = State() # Ожидание поискового запроса
|
||
user_management_view = State() # Просмотр пользователя
|
||
```
|
||
|
||
### Модель данных
|
||
|
||
#### Поле is_chat_banned
|
||
**Файл:** `src/core/models.py`
|
||
|
||
```python
|
||
class User(Base):
|
||
# ... другие поля ...
|
||
is_chat_banned: Mapped[bool] = mapped_column(Boolean, default=False)
|
||
last_activity: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), nullable=True)
|
||
```
|
||
|
||
**Миграция:** `migrations/versions/20260215_0403_00_b4c435a7dc5f_add_is_chat_banned_to_users.py`
|
||
|
||
### Интеграция с чатом
|
||
|
||
**Файл:** `src/core/chat_services.py`
|
||
|
||
```python
|
||
class ChatPermissionService:
|
||
@staticmethod
|
||
async def can_send_message(session: AsyncSession, telegram_id: int) -> Tuple[bool, str]:
|
||
"""
|
||
Проверяет, может ли пользователь отправлять сообщения в чат
|
||
|
||
Порядок проверок:
|
||
1. Глобальная блокировка (BanService)
|
||
2. Блокировка в чате (is_chat_banned)
|
||
3. Регистрация в боте
|
||
|
||
Returns:
|
||
(может_отправить: bool, причина_если_нет: str)
|
||
"""
|
||
```
|
||
|
||
**Проверка в обработчиках P2P сообщений:**
|
||
```python
|
||
can_send, reason = await ChatPermissionService.can_send_message(session, message.from_user.id)
|
||
if not can_send:
|
||
await message.answer(f"❌ {reason}")
|
||
return
|
||
```
|
||
|
||
## Производительность
|
||
|
||
### Оптимизация для больших баз (1000+ пользователей)
|
||
|
||
#### 1. Пагинация
|
||
- Все списки ограничены 15 пользователями на странице
|
||
- SQL LIMIT/OFFSET для эффективных запросов
|
||
- Подсчет общего количества отдельным запросом
|
||
|
||
#### 2. Индексы (рекомендуется добавить)
|
||
```sql
|
||
-- Для поиска по username
|
||
CREATE INDEX idx_users_username ON users(username);
|
||
|
||
-- Для поиска по имени/фамилии
|
||
CREATE INDEX idx_users_names ON users(first_name, last_name);
|
||
|
||
-- Для поиска по клубной карте
|
||
CREATE INDEX idx_users_card ON users(club_card_number);
|
||
|
||
-- Для фильтрации заблокированных
|
||
CREATE INDEX idx_users_chat_banned ON users(is_chat_banned) WHERE is_chat_banned = true;
|
||
```
|
||
|
||
#### 3. ILIKE оптимизация
|
||
Для больших баз рекомендуется добавить индекс с расширением pg_trgm:
|
||
```sql
|
||
CREATE EXTENSION IF NOT EXISTS pg_trgm;
|
||
CREATE INDEX idx_users_username_trgm ON users USING gin(username gin_trgm_ops);
|
||
CREATE INDEX idx_users_names_trgm ON users USING gin((first_name || ' ' || last_name) gin_trgm_ops);
|
||
```
|
||
|
||
#### 4. Кеширование (будущее улучшение)
|
||
- Кеш для статистики (Redis)
|
||
- Кеш для часто просматриваемых пользователей
|
||
- TTL: 5 минут
|
||
|
||
## Использование
|
||
|
||
### Для администраторов
|
||
|
||
#### Шаг 1: Доступ к управлению
|
||
1. Открыть админ-панель: `/admin`
|
||
2. Выбрать "👤 Управление пользователями"
|
||
|
||
#### Шаг 2: Поиск пользователя
|
||
**Вариант A: Быстрый поиск**
|
||
1. Нажать "🔍 Поиск пользователей"
|
||
2. Ввести запрос (username, имя, ID)
|
||
3. Выбрать пользователя из результатов
|
||
|
||
**Вариант B: Просмотр списка**
|
||
1. Нажать "📋 Все пользователи"
|
||
2. Перемещаться по страницам (⬅️ Назад / ➡️ Далее)
|
||
3. Выбрать пользователя
|
||
|
||
**Вариант C: Только заблокированные**
|
||
1. Нажать "🚫 Заблокированные"
|
||
2. Просмотреть список заблокированных
|
||
3. Выбрать для разблокировки
|
||
|
||
#### Шаг 3: Управление блокировкой
|
||
1. В карточке пользователя нажать:
|
||
- "🚫 Заблокировать в чате" - для блокировки
|
||
- "✅ Разблокировать в чате" - для разблокировки
|
||
2. Подтвердить действие
|
||
|
||
### Для разработчиков
|
||
|
||
#### Добавление новых фильтров
|
||
|
||
```python
|
||
# В UserManagementService.search_users()
|
||
if filters:
|
||
if 'custom_field' in filters:
|
||
conditions.append(User.custom_field == filters['custom_field'])
|
||
```
|
||
|
||
#### Добавление новых полей в карточку
|
||
|
||
```python
|
||
# В UserManagementService.format_user_info()
|
||
if detailed:
|
||
text += f"🆕 Кастомное поле: {user.custom_field}\n"
|
||
```
|
||
|
||
#### Создание нового списка
|
||
|
||
```python
|
||
@admin_router.callback_query(F.data.startswith("admin_users_custom:"))
|
||
async def admin_users_custom_list(callback: CallbackQuery):
|
||
page = int(callback.data.split(":")[1])
|
||
|
||
async with async_session_maker() as session:
|
||
users, total = await UserManagementService.search_users(
|
||
session,
|
||
page=page,
|
||
filters={'custom_field': True}
|
||
)
|
||
|
||
# ... рендер списка ...
|
||
```
|
||
|
||
## Мониторинг
|
||
|
||
### Логирование
|
||
```python
|
||
logger.info(f"Пользователь {user.telegram_id} заблокирован в чате")
|
||
logger.error(f"Ошибка блокировки пользователя {user_id}: {e}")
|
||
```
|
||
|
||
### Метрики для мониторинга
|
||
- Количество поисковых запросов
|
||
- Среднее время ответа на поиск
|
||
- Количество блокировок/разблокировок
|
||
- Топ-10 самых активных поисковых запросов
|
||
|
||
## Безопасность
|
||
|
||
### Проверка прав доступа
|
||
Все обработчики проверяют права администратора:
|
||
```python
|
||
if not is_admin(callback.from_user.id):
|
||
await callback.answer("❌ Доступ запрещен", show_alert=True)
|
||
return
|
||
```
|
||
|
||
### Защита от SQL-инъекций
|
||
- Все запросы используют параметризованные запросы SQLAlchemy
|
||
- Нет прямой конкатенации SQL
|
||
|
||
### Аудит действий (рекомендуется добавить)
|
||
```sql
|
||
CREATE TABLE admin_actions (
|
||
id SERIAL PRIMARY KEY,
|
||
admin_telegram_id BIGINT NOT NULL,
|
||
action VARCHAR(50) NOT NULL, -- 'ban_user', 'unban_user', 'search_user'
|
||
target_user_id INTEGER,
|
||
details JSONB,
|
||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||
);
|
||
```
|
||
|
||
## Расширения
|
||
|
||
### Планируемые улучшения
|
||
|
||
1. **Массовые операции**
|
||
- Блокировка нескольких пользователей одновременно
|
||
- Экспорт списка в CSV/JSON
|
||
|
||
2. **Расширенные фильтры**
|
||
- Диапазон дат регистрации
|
||
- Неактивные пользователи (по last_activity)
|
||
- Пользователи без клубной карты
|
||
|
||
3. **История блокировок**
|
||
- Журнал всех блокировок/разблокировок
|
||
- Кто и когда заблокировал
|
||
- Причина блокировки (опциональное поле)
|
||
|
||
4. **Автоматическая блокировка**
|
||
- При определенном количестве жалоб от других пользователей
|
||
- При обнаружении спам-активности
|
||
|
||
5. **Уведомления**
|
||
- Уведомление пользователя о блокировке (опционально)
|
||
- Уведомление администраторов о подозрительной активности
|
||
|
||
## Troubleshooting
|
||
|
||
### Проблема: Поиск не находит пользователя
|
||
**Решение:**
|
||
- Убедиться, что пользователь существует в базе
|
||
- Проверить правильность написания (регистр не важен)
|
||
- Попробовать поиск по Telegram ID
|
||
|
||
### Проблема: Блокировка не применяется
|
||
**Решение:**
|
||
- Проверить логи: `docker logs lottery_bot | grep "блокирован"`
|
||
- Убедиться, что транзакция закоммитилась
|
||
- Проверить поле в БД: `SELECT telegram_id, is_chat_banned FROM users WHERE id = ?`
|
||
|
||
### Проблема: Медленный поиск (>2 секунд)
|
||
**Решение:**
|
||
- Добавить индексы (см. раздел "Производительность")
|
||
- Проверить EXPLAIN ANALYZE для поискового запроса
|
||
- Рассмотреть использование полнотекстового поиска PostgreSQL
|
||
|
||
## Тестирование
|
||
|
||
### Ручное тестирование
|
||
|
||
1. **Тест поиска:**
|
||
```
|
||
- Поиск по существующему username
|
||
- Поиск по несуществующему username
|
||
- Поиск с частичным совпадением
|
||
- Поиск по Telegram ID
|
||
```
|
||
|
||
2. **Тест блокировки:**
|
||
```
|
||
- Заблокировать пользователя
|
||
- Попытаться отправить сообщение от заблокированного пользователя
|
||
- Разблокировать пользователя
|
||
- Убедиться, что сообщения проходят
|
||
```
|
||
|
||
3. **Тест пагинации:**
|
||
```
|
||
- Создать >15 пользователей
|
||
- Проверить навигацию вперед/назад
|
||
- Проверить корректность подсчета страниц
|
||
```
|
||
|
||
### Автоматическое тестирование
|
||
|
||
```python
|
||
# tests/test_user_management.py
|
||
import pytest
|
||
from src.core.user_management import UserManagementService
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_search_users_by_username(session):
|
||
users, total = await UserManagementService.search_users(
|
||
session, query="@testuser"
|
||
)
|
||
assert len(users) > 0
|
||
assert users[0].username == "testuser"
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_ban_user(session):
|
||
success = await UserManagementService.ban_user_in_chat(session, 1)
|
||
assert success == True
|
||
|
||
user = await UserManagementService.get_user_by_id(session, 1)
|
||
assert user.is_chat_banned == True
|
||
```
|
||
|
||
## Заключение
|
||
|
||
Система управления пользователями предоставляет:
|
||
- ✅ Быстрый и удобный поиск среди больших баз данных
|
||
- ✅ Простое управление блокировками в чате
|
||
- ✅ Масштабируемость для 1000+ пользователей
|
||
- ✅ Интуитивный интерфейс для администраторов
|
||
- ✅ Интеграцию с системой разрешений чата
|
||
|
||
Система готова к использованию и может быть расширена дополнительными функциями по мере необходимости.
|