1729 lines
76 KiB
Markdown
1729 lines
76 KiB
Markdown
# 🤖 AnonBot - Telegram бот для анонимных вопросов
|
||
|
||
Telegram-бот для приема и обработки анонимных вопросов с использованием aiogram 3.x.
|
||
|
||
## ✨ Возможности
|
||
|
||
- 🔗 **Персональные ссылки** - каждый пользователь получает уникальную ссылку для приема вопросов
|
||
- 👤 **Анонимность** - вопросы отправляются анонимно, личность отправителя скрыта
|
||
- 💬 **Интерактивные ответы** - удобный интерфейс для ответов на вопросы через inline кнопки
|
||
- 🔢 **Локальная нумерация** - каждый пользователь видит свои вопросы с номерами #1, #2, #3... вместо глобальных ID
|
||
- 📊 **Статистика** - подробная статистика для администраторов
|
||
- 🗄️ **База данных** - SQLite для хранения пользователей и вопросов с автоматической нумерацией
|
||
- 👑 **Админ панель** - управление ботом для администраторов
|
||
- 🔍 **Суперпользователи** - расширенные права для модерации с отображением информации об авторах вопросов
|
||
- 🏗️ **Современная архитектура** - система инъекции зависимостей для лучшей тестируемости
|
||
- 📝 **Продвинутое логирование** - автоматические декораторы, контекстное логирование, FSM отслеживание
|
||
- 🛡️ **Безопасность** - валидация данных, обработка ошибок, система ролей
|
||
- 🔍 **Централизованная валидация** - автоматическая валидация всех входных данных с санитизацией
|
||
- 🔐 **Система разрешений** - гибкая система разрешений с соблюдением принципа OCP
|
||
- 📈 **Prometheus метрики** - полный мониторинг производительности и состояния бота
|
||
|
||
## 🚀 Быстрый старт
|
||
|
||
### 1. Установка зависимостей
|
||
|
||
```bash
|
||
pip install -r requirements.txt
|
||
pip install dependency-injector # Для системы инъекции зависимостей
|
||
```
|
||
|
||
### 2. Настройка конфигурации
|
||
|
||
Создайте файл `.env` в корневой директории проекта:
|
||
|
||
```env
|
||
# Токен бота от @BotFather
|
||
BOT_TOKEN=your_bot_token_here
|
||
|
||
# ID администраторов через запятую (можно получить у @userinfobot)
|
||
ADMINS=123456789,987654321
|
||
|
||
# Путь к базе данных SQLite
|
||
DATABASE_PATH=database/anon_qna.db
|
||
|
||
# Режим отладки (true/false)
|
||
DEBUG=false
|
||
|
||
|
||
# Максимальная длина вопроса
|
||
MAX_QUESTION_LENGTH=1000
|
||
|
||
# Максимальная длина ответа
|
||
MAX_ANSWER_LENGTH=2000
|
||
```
|
||
|
||
### 3. Запуск бота
|
||
|
||
#### Локальный запуск
|
||
|
||
```bash
|
||
python main.py
|
||
```
|
||
|
||
или
|
||
|
||
```bash
|
||
python bot.py
|
||
```
|
||
|
||
#### Запуск в Docker
|
||
|
||
1. Соберите Docker образ:
|
||
```bash
|
||
docker build -t anon-bot .
|
||
```
|
||
|
||
2. Запустите контейнер:
|
||
```bash
|
||
docker run -d \
|
||
--name anon-bot \
|
||
--restart unless-stopped \
|
||
-p 8081:8081 \
|
||
-v $(pwd)/database:/app/database \
|
||
-v $(pwd)/logs:/app/logs \
|
||
-e BOT_TOKEN=your_bot_token_here \
|
||
-e ADMINS=123456789,987654321 \
|
||
-e DEBUG=false \
|
||
anon-bot
|
||
```
|
||
|
||
3. Проверьте статус:
|
||
```bash
|
||
docker logs anon-bot
|
||
```
|
||
|
||
4. Проверьте метрики:
|
||
```bash
|
||
curl http://localhost:8081/health
|
||
```
|
||
|
||
## 📄 PID файл и мониторинг процесса
|
||
|
||
AnonBot автоматически создает PID файл для отслеживания процесса и предоставляет детальную информацию о состоянии через HTTP эндпоинты.
|
||
|
||
### PID файл
|
||
|
||
- **Расположение**: `/tmp/anon_bot.pid`
|
||
- **Содержимое**: PID процесса бота
|
||
- **Автоматическое управление**: создается при запуске, удаляется при остановке
|
||
- **Проверка дублирования**: предотвращает запуск нескольких экземпляров
|
||
|
||
### Эндпоинт /status
|
||
|
||
Предоставляет детальную информацию о процессе:
|
||
|
||
```bash
|
||
curl http://localhost:8081/status
|
||
```
|
||
|
||
**Пример ответа:**
|
||
```json
|
||
{
|
||
"status": "running",
|
||
"pid": 12345,
|
||
"uptime": "2ч 15м",
|
||
"memory_usage_mb": 45.2,
|
||
"cpu_percent": 0.1,
|
||
"timestamp": 1705312200.5
|
||
}
|
||
```
|
||
|
||
**Поля ответа:**
|
||
- `status` - статус процесса (running/stopped/not_found/error)
|
||
- `pid` - идентификатор процесса
|
||
- `uptime` - время работы в человекочитаемом формате
|
||
- `memory_usage_mb` - использование памяти в МБ
|
||
- `cpu_percent` - загрузка CPU в процентах
|
||
- `timestamp` - время ответа
|
||
|
||
### Тестирование
|
||
|
||
Для тестирования PID функционала можно использовать curl:
|
||
|
||
```bash
|
||
# Проверка статуса процесса
|
||
curl http://localhost:8081/status
|
||
|
||
# Проверка всех эндпоинтов
|
||
curl http://localhost:8081/
|
||
```
|
||
|
||
## 📁 Структура проекта
|
||
|
||
```
|
||
AnonBot/
|
||
├── main.py # Точка входа
|
||
├── bot.py # Основной файл бота
|
||
├── loader.py # Инициализация бота
|
||
├── dependencies.py # Система инъекции зависимостей
|
||
├── utils.py # Общие утилиты
|
||
├── requirements.txt # Зависимости
|
||
├── README.md # Документация
|
||
├── Dockerfile # Docker образ
|
||
├── .dockerignore # Исключения для Docker
|
||
├── .env_example # Пример переменных окружения
|
||
├── prometheus.yml # Конфигурация Prometheus
|
||
├── config/ # Конфигурация
|
||
│ ├── __init__.py
|
||
│ ├── config.py # Основная конфигурация
|
||
│ └── constants.py # Константы приложения
|
||
├── handlers/ # Обработчики сообщений
|
||
│ ├── __init__.py
|
||
│ ├── start.py # Команды /start, /help
|
||
│ ├── questions.py # Обработка анонимных вопросов
|
||
│ ├── answers.py # Обработка ответов
|
||
│ ├── admin.py # Админ функции
|
||
│ └── errors.py # Глобальная обработка ошибок
|
||
├── keyboards/ # Клавиатуры
|
||
│ ├── __init__.py
|
||
│ ├── inline.py # Inline клавиатуры
|
||
│ └── reply.py # Reply клавиатуры
|
||
├── models/ # Модели данных
|
||
│ ├── __init__.py
|
||
│ ├── user.py # Модель пользователя
|
||
│ ├── question.py # Модель вопроса
|
||
│ ├── user_block.py # Модель блокировки
|
||
│ └── user_settings.py # Модель настроек
|
||
├── services/ # Сервисы (реорганизованы по категориям)
|
||
│ ├── __init__.py
|
||
│ ├── utils.py # Общие утилиты
|
||
│ ├── auth/ # Авторизация и разрешения
|
||
│ │ ├── __init__.py
|
||
│ │ └── auth_new.py # Сервис авторизации с системой разрешений
|
||
│ ├── validation/ # Валидация входных данных
|
||
│ │ ├── __init__.py
|
||
│ │ └── input_validator.py # Централизованный валидатор
|
||
│ ├── business/ # Бизнес-логика
|
||
│ │ ├── __init__.py
|
||
│ │ ├── user_service.py # Сервис пользователей
|
||
│ │ ├── question_service.py # Сервис вопросов
|
||
│ │ ├── message_service.py # Сервис сообщений
|
||
│ │ └── pagination_service.py # Сервис пагинации
|
||
│ ├── infrastructure/ # Инфраструктурные сервисы
|
||
│ │ ├── __init__.py
|
||
│ │ ├── database.py # Работа с БД
|
||
│ │ ├── logger.py # Система логирования
|
||
│ │ ├── logging_decorators.py # Декораторы для автоматического логирования
|
||
│ │ ├── logging_utils.py # Утилиты для контекстного логирования
|
||
│ │ ├── metrics.py # Prometheus метрики
|
||
│ │ ├── http_server.py # HTTP сервер для метрик
|
||
│ │ └── pid_manager.py # Менеджер PID файлов
|
||
│ ├── rate_limiting/ # Rate limiting
|
||
│ │ ├── __init__.py
|
||
│ │ ├── rate_limit_config.py # Конфигурация rate limiting
|
||
│ │ ├── rate_limiter.py # Основной rate limiter
|
||
│ │ └── rate_limit_service.py # Сервис rate limiting
|
||
│ └── permissions/ # Система разрешений
|
||
│ ├── __init__.py
|
||
│ ├── base.py # Базовые классы
|
||
│ ├── registry.py # Реестр разрешений
|
||
│ ├── permissions.py # Стандартные разрешения
|
||
│ ├── decorators.py # Декораторы для проверки
|
||
│ └── init_permissions.py # Инициализация
|
||
├── examples/ # Примеры использования
|
||
│ └── dependency_injection_example.py
|
||
├── database/ # База данных
|
||
│ ├── __init__.py
|
||
│ ├── schema.sql # Схема БД
|
||
│ ├── crud.py # CRUD операции
|
||
│ └── examples.py # Примеры использования
|
||
├── middlewares/ # Middleware
|
||
│ ├── __init__.py
|
||
│ ├── rate_limit_middleware.py # Middleware для rate limiting
|
||
│ └── validation_middleware.py # Middleware для валидации данных
|
||
├── docs/ # Документация
|
||
└── logs/ # Логи приложения
|
||
```
|
||
|
||
### 🏗️ Архитектурные улучшения
|
||
|
||
Проект был реорганизован для улучшения структуры и масштабируемости:
|
||
|
||
#### 📁 Новая структура services
|
||
|
||
**До реорганизации:**
|
||
- Все сервисы в корне `services/`
|
||
- Смешанные категории (бизнес-логика + инфраструктура)
|
||
- Дублирование функциональности
|
||
|
||
**После реорганизации:**
|
||
- **`services/auth/`** - авторизация и разрешения
|
||
- **`services/business/`** - бизнес-логика (пользователи, вопросы, сообщения)
|
||
- **`services/infrastructure/`** - инфраструктурные сервисы (БД, логи, метрики)
|
||
- **`services/rate_limiting/`** - rate limiting компоненты
|
||
- **`services/permissions/`** - система разрешений (без изменений)
|
||
|
||
#### 📁 Новая структура config
|
||
|
||
**До реорганизации:**
|
||
- `config.py` и `constants.py` в корне проекта
|
||
|
||
**После реорганизации:**
|
||
- **`config/`** - папка для всех конфигурационных файлов
|
||
- **`config/config.py`** - основная конфигурация
|
||
- **`config/constants.py`** - константы приложения
|
||
|
||
#### ✅ Преимущества новой структуры
|
||
|
||
1. **Логическая группировка** - связанные сервисы в одних папках
|
||
2. **Разделение ответственности** - бизнес-логика отдельно от инфраструктуры
|
||
3. **Масштабируемость** - легко добавлять новые сервисы в нужные категории
|
||
4. **Читаемость** - понятно, где что находится
|
||
5. **Обратная совместимость** - старые импорты продолжают работать
|
||
|
||
#### 🔄 Обратная совместимость
|
||
|
||
Все импорты обновлены, но старые импорты продолжают работать благодаря `__init__.py` файлам:
|
||
|
||
```python
|
||
# Старый способ (все еще работает)
|
||
from services.database import DatabaseService
|
||
from config import config
|
||
|
||
# Новый способ (рекомендуется)
|
||
from services.infrastructure.database import DatabaseService
|
||
from config import config
|
||
```
|
||
|
||
### 🔍 Система валидации входных данных
|
||
|
||
Реализована централизованная система валидации всех входящих данных для обеспечения безопасности и стабильности:
|
||
|
||
#### 📋 Что валидируется
|
||
|
||
- **Telegram ID** - проверка диапазона и типа данных
|
||
- **Username** - валидация формата и допустимых символов
|
||
- **Текстовый контент** - проверка длины, HTML санитизация, защита от спама
|
||
- **Deep links** - валидация формата и длины
|
||
- **Callback data** - проверка безопасности и формата
|
||
- **Параметры пагинации** - валидация диапазонов
|
||
|
||
#### 🛡️ Безопасность
|
||
|
||
- **HTML санитизация** - автоматическое экранирование опасных тегов
|
||
- **Защита от спама** - проверка на повторяющиеся символы и слова
|
||
- **Валидация ID** - проверка корректности всех идентификаторов
|
||
- **Логирование** - полное логирование всех ошибок валидации с автоматическими декораторами
|
||
|
||
#### 🏗️ Архитектура валидации
|
||
|
||
```python
|
||
# Централизованный валидатор
|
||
from services.validation import InputValidator
|
||
|
||
validator = InputValidator()
|
||
|
||
# Валидация текста вопроса
|
||
result = validator.validate_question_text(text, max_length=1000)
|
||
if not result:
|
||
print(f"Ошибка: {result.error_message}")
|
||
else:
|
||
sanitized_text = result.sanitized_value
|
||
|
||
# Валидация callback data
|
||
result = validator.validate_callback_data(callback_data)
|
||
```
|
||
|
||
#### 🔄 Middleware валидации
|
||
|
||
Автоматическая валидация всех входящих данных через middleware:
|
||
|
||
```python
|
||
# ValidationMiddleware автоматически валидирует:
|
||
# - Все callback queries
|
||
# - Все сообщения
|
||
# - Telegram ID пользователей
|
||
# - Username (если есть)
|
||
# - Chat ID
|
||
```
|
||
|
||
#### ✅ Преимущества
|
||
|
||
1. **Безопасность** - защита от некорректных данных и атак
|
||
2. **Стабильность** - предотвращение ошибок от невалидных данных
|
||
3. **UX** - понятные сообщения об ошибках для пользователей
|
||
4. **Мониторинг** - полное логирование проблем валидации
|
||
5. **Централизация** - единая точка валидации для всего приложения
|
||
|
||
## 🎯 Как это работает
|
||
|
||
### Для пользователей:
|
||
|
||
1. **Регистрация**: Пользователь запускает бота командой `/start`
|
||
2. **Получение ссылки**: Бот генерирует персональную ссылку формата `t.me/bot_username?start=ref_{anonymous_id}`
|
||
3. **Публикация ссылки**: Пользователь делится ссылкой в социальных сетях
|
||
4. **Получение вопросов**: Друзья переходят по ссылке и задают анонимные вопросы
|
||
5. **Ответы**: Пользователь получает уведомления и может отвечать на вопросы
|
||
|
||
### Для отправителей вопросов:
|
||
|
||
1. **Переход по ссылке**: Отправитель переходит по персональной ссылке пользователя
|
||
2. **Задание вопроса**: Отправляет вопрос боту
|
||
3. **Анонимность**: Вопрос передается получателю анонимно
|
||
4. **Получение ответа**: Если получатель ответит, ответ будет показан
|
||
|
||
## 🔧 Команды бота
|
||
|
||
### Основные команды:
|
||
- `/start` - Запуск бота и получение персональной ссылки
|
||
- `/help` - Справка по использованию бота
|
||
|
||
### Админ команды:
|
||
- `/stats` - Показать статистику бота (только для админов)
|
||
|
||
|
||
## 👑 Админ панель
|
||
|
||
Администраторы имеют доступ к дополнительным функциям:
|
||
|
||
- 📊 **Статистика** - общая статистика бота, пользователей и вопросов
|
||
- 👥 **Пользователи** - список всех пользователей бота
|
||
- ❓ **Все вопросы** - статистика по всем вопросам
|
||
- 📢 **Рассылка** - функция рассылки (планируется)
|
||
- ⚙️ **Настройки** - просмотр текущих настроек бота
|
||
|
||
## 🔍 Система ролей и суперпользователи
|
||
|
||
Бот поддерживает трехуровневую систему ролей:
|
||
|
||
### 👑 Администраторы (admin)
|
||
- Определяются в конфигурации (`ADMINS` в `.env`)
|
||
- Имеют все права доступа
|
||
- Не могут быть изменены через базу данных
|
||
|
||
### 🔍 Суперпользователи (superuser)
|
||
- Определяются в базе данных (поле `is_superuser = TRUE`)
|
||
- Имеют расширенные права для модерации
|
||
- **Могут видеть информацию об авторах вопросов**
|
||
|
||
#### Особенности для суперпользователей:
|
||
|
||
**Отображение списка вопросов:**
|
||
```
|
||
10. ✅ #2 Вопрос от @username FirstName LastName
|
||
```
|
||
|
||
**Уведомления о новых вопросах:**
|
||
```
|
||
❓ Новый вопрос от @username FirstName LastName!
|
||
|
||
📝 Вопрос:
|
||
Текст вопроса...
|
||
|
||
📅 05.09.2025 23:27
|
||
```
|
||
|
||
### 👤 Обычные пользователи (user)
|
||
- Стандартные права доступа
|
||
- Видят анонимные вопросы без информации об авторах
|
||
|
||
### Назначение суперпользователя
|
||
|
||
Суперпользователей можно назначать через базу данных:
|
||
|
||
```sql
|
||
UPDATE users SET is_superuser = TRUE WHERE telegram_id = 123456789;
|
||
```
|
||
|
||
Или программно:
|
||
|
||
```python
|
||
from services.infrastructure.database import DatabaseService
|
||
|
||
async def make_superuser(telegram_id: int):
|
||
db_service = DatabaseService("database/anon_qna.db")
|
||
user = await db_service.get_user(telegram_id)
|
||
if user:
|
||
user.is_superuser = True
|
||
await db_service.update_user(user)
|
||
```
|
||
|
||
## 🔐 Система разрешений
|
||
|
||
AnonBot использует современную систему разрешений, построенную с соблюдением принципа открытости/закрытости (OCP):
|
||
|
||
### ✅ Преимущества новой системы
|
||
|
||
- **Открытость для расширения** - новые разрешения добавляются без изменения существующего кода
|
||
- **Закрытость для модификации** - существующий код не изменяется при добавлении новых разрешений
|
||
- **Типобезопасность** - использование классов вместо строк
|
||
- **Единая точка входа** - все проверки разрешений через один интерфейс
|
||
- **Удобные декораторы** - простое применение проверок разрешений
|
||
|
||
### 🎯 Стандартные разрешения
|
||
|
||
| Разрешение | Описание | Доступ |
|
||
|------------|----------|--------|
|
||
| `admin` | Права администратора | Только администраторы |
|
||
| `superuser` | Права суперпользователя | Только суперпользователи |
|
||
| `view_stats` | Просмотр статистики | Администраторы + суперпользователи |
|
||
| `admin_panel` | Доступ к админ панели | Администраторы + суперпользователи |
|
||
| `manage_users` | Управление пользователями | Администраторы + суперпользователи |
|
||
| `broadcast` | Рассылка сообщений | Только администраторы |
|
||
| `view_questions` | Просмотр вопросов | Все активные пользователи |
|
||
| `ask_questions` | Задавание вопросов | Все активные незабаненные пользователи |
|
||
| `answer_questions` | Ответы на вопросы | Все активные незабаненные пользователи |
|
||
|
||
### 🚀 Использование
|
||
|
||
#### С декораторами (рекомендуется)
|
||
|
||
```python
|
||
from services.permissions.decorators import require_permission
|
||
|
||
@router.message(Command("my_command"))
|
||
@require_permission("view_stats", "❌ У вас нет прав для выполнения этой команды.")
|
||
async def my_command_handler(message: Message):
|
||
# Логика обработчика
|
||
await message.answer("Команда выполнена!")
|
||
```
|
||
|
||
#### Готовые декораторы
|
||
|
||
```python
|
||
@require_permission("view_stats") # Конкретное разрешение
|
||
@require_admin() # Только администраторы
|
||
@require_superuser() # Только суперпользователи
|
||
@require_admin_or_superuser() # Администраторы или суперпользователи
|
||
@require_active_user() # Активные пользователи
|
||
@require_unbanned_user() # Незабаненные пользователи
|
||
```
|
||
|
||
#### Добавление нового разрешения
|
||
|
||
```python
|
||
# 1. Создаем класс разрешения
|
||
class MyCustomPermission(Permission):
|
||
async def check(self, user_id: int, database: DatabaseService, config) -> bool:
|
||
return user_id in config.ADMINS
|
||
|
||
# 2. Регистрируем разрешение
|
||
register_permission(MyCustomPermission())
|
||
|
||
# 3. Используем в обработчике
|
||
@require_permission("my_custom_permission")
|
||
async def my_handler(message: Message):
|
||
# Логика обработчика
|
||
pass
|
||
```
|
||
|
||
## 🛡️ Безопасность
|
||
|
||
- **Валидация**: Проверка всех входящих данных
|
||
- **Обработка ошибок**: Глобальная обработка ошибок с уведомлениями админов
|
||
- **Логирование**: Автоматическое логирование с декораторами, контекстная информация, FSM отслеживание
|
||
- **Система ролей**: Трехуровневая система доступа (админ/суперпользователь/пользователь)
|
||
- **Система разрешений**: Гибкая система разрешений с соблюдением принципа OCP
|
||
|
||
## 🏗️ Система инъекции зависимостей
|
||
|
||
AnonBot использует современную систему инъекции зависимостей, построенную на основе **MagicData** из aiogram 3.x. Это обеспечивает:
|
||
|
||
- **Тестируемость**: Легко мокать зависимости в тестах
|
||
- **Читаемость**: Явные зависимости в сигнатурах функций
|
||
- **Гибкость**: Легко заменять реализации сервисов
|
||
- **Обратная совместимость**: Старый код продолжает работать
|
||
|
||
### Доступные сервисы
|
||
|
||
1. **DatabaseService** - работа с базой данных, CRUD операции
|
||
2. **AuthService** - авторизация, проверка прав, управление ролями
|
||
3. **InputValidator** - централизованная валидация всех входных данных
|
||
4. **UtilsService** - форматирование данных, утилиты
|
||
5. **RateLimitService** - управление rate limiting, статистика
|
||
6. **Config** - доступ к конфигурации приложения
|
||
|
||
### Быстрый старт с DI
|
||
|
||
```python
|
||
# Подключение в loader.py
|
||
from dependencies import DependencyMiddleware, get_dependencies
|
||
|
||
async def init_dispatcher() -> Dispatcher:
|
||
dp = Dispatcher(storage=storage)
|
||
|
||
# Инициализируем зависимости
|
||
deps = get_dependencies()
|
||
await deps.init()
|
||
|
||
# Добавляем middleware
|
||
dp.update.middleware(DependencyMiddleware(deps))
|
||
|
||
return dp
|
||
|
||
# Использование в обработчиках
|
||
from dependencies import inject_start_services, inject_question_services, inject_answer_services
|
||
from services.infrastructure.database import DatabaseService
|
||
from services.auth.auth_new import AuthService
|
||
from services.validation import InputValidator
|
||
|
||
@router.message(Command("start"))
|
||
@inject_start_services
|
||
async def cmd_start(
|
||
message: Message,
|
||
user_service: UserService,
|
||
auth: AuthService,
|
||
utils: UtilsService,
|
||
message_service: MessageService,
|
||
validator: InputValidator
|
||
):
|
||
# Валидируем входные данные
|
||
user_id_validation = validator.validate_telegram_id(message.from_user.id)
|
||
if not user_id_validation:
|
||
await message.answer("❌ Ошибка: недопустимый ID пользователя")
|
||
return
|
||
|
||
# Используем сервисы без хардкода
|
||
is_admin = auth.is_admin(message.from_user.id)
|
||
user = await user_service.get_user_by_telegram_id(message.from_user.id)
|
||
```
|
||
|
||
### Способы инъекции
|
||
|
||
1. **Специализированные декораторы** (рекомендуется):
|
||
- `@inject_question_services` - для обработки вопросов
|
||
- `@inject_answer_services` - для обработки ответов
|
||
- `@inject_start_services` - для команды /start
|
||
- `@inject_link_services` - для кнопки "Моя ссылка"
|
||
- `@inject_main_menu_services` - для кнопки "Главное меню"
|
||
|
||
2. **Базовые декораторы**:
|
||
- `@inject_database` - только DatabaseService
|
||
- `@inject_auth` - только AuthService
|
||
- `@inject_utils` - только UtilsService
|
||
|
||
3. **Автоматически**: aiogram инжектит зависимости из middleware
|
||
|
||
4. **Обратная совместимость**: старые функции продолжают работать
|
||
|
||
### Примеры использования специализированных декораторов
|
||
|
||
#### Обработка вопросов
|
||
```python
|
||
@router.message(StateFilter(QuestionStates.waiting_for_question))
|
||
@inject_question_services
|
||
async def process_anonymous_question(
|
||
message: Message,
|
||
state: FSMContext,
|
||
question_service: QuestionService,
|
||
user_service: UserService,
|
||
message_service: MessageService,
|
||
validator: InputValidator
|
||
):
|
||
# Валидируем и обрабатываем вопрос
|
||
validation_result = validator.validate_question_text(message.text)
|
||
if not validation_result:
|
||
await message_service.send_message(message, "❌ Неверный формат вопроса")
|
||
return
|
||
|
||
# Создаем вопрос через сервис
|
||
question = await question_service.create_question(
|
||
message.from_user.id,
|
||
target_user_id,
|
||
validation_result.sanitized_value
|
||
)
|
||
```
|
||
|
||
#### Обработка ответов
|
||
```python
|
||
@router.message(StateFilter(AnswerStates.waiting_for_answer))
|
||
@inject_answer_services
|
||
async def process_new_answer(
|
||
message: Message,
|
||
state: FSMContext,
|
||
validator: InputValidator
|
||
):
|
||
# Валидируем ответ
|
||
validation_result = validator.validate_answer_text(message.text)
|
||
if not validation_result:
|
||
await message.answer("❌ Неверный формат ответа")
|
||
return
|
||
|
||
# Сохраняем ответ
|
||
# ... логика сохранения
|
||
```
|
||
|
||
### Локальная нумерация вопросов
|
||
|
||
#### Отображение вопросов с локальными номерами
|
||
```python
|
||
# В модели Question добавлен метод get_display_number()
|
||
def get_display_number(self) -> int:
|
||
"""Получить номер вопроса для отображения (приоритет user_question_number)"""
|
||
return self.user_question_number if self.user_question_number is not None else self.id
|
||
|
||
# В обработчиках используется локальная нумерация
|
||
questions_text += f"{i}. {emoji} <b>#{question.get_display_number()}</b>"
|
||
```
|
||
|
||
#### Автоматическая нумерация через триггеры БД
|
||
```sql
|
||
-- Триггер для автоматического вычисления номера при создании
|
||
CREATE TRIGGER calculate_user_question_number
|
||
AFTER INSERT ON questions
|
||
FOR EACH ROW
|
||
WHEN NEW.user_question_number IS NULL
|
||
BEGIN
|
||
UPDATE questions
|
||
SET user_question_number = (
|
||
SELECT COALESCE(MAX(user_question_number), 0) + 1
|
||
FROM questions q2
|
||
WHERE q2.to_user_id = NEW.to_user_id
|
||
AND q2.status != 'deleted'
|
||
)
|
||
WHERE id = NEW.id;
|
||
END;
|
||
|
||
-- Триггер для пересчета номеров при удалении
|
||
CREATE TRIGGER recalculate_user_question_numbers_on_delete
|
||
AFTER UPDATE ON questions
|
||
FOR EACH ROW
|
||
WHEN NEW.status = 'deleted' AND OLD.status != 'deleted'
|
||
BEGIN
|
||
UPDATE questions
|
||
SET user_question_number = user_question_number - 1
|
||
WHERE to_user_id = NEW.to_user_id
|
||
AND user_question_number > OLD.user_question_number
|
||
AND status != 'deleted';
|
||
|
||
UPDATE questions
|
||
SET user_question_number = NULL
|
||
WHERE id = NEW.id;
|
||
END;
|
||
```
|
||
|
||
#### Обработка команд
|
||
```python
|
||
@router.message(Command("start"))
|
||
@inject_start_services
|
||
async def cmd_start(
|
||
message: Message,
|
||
state: FSMContext,
|
||
user_service: UserService,
|
||
auth: AuthService,
|
||
utils: UtilsService,
|
||
message_service: MessageService,
|
||
validator: InputValidator
|
||
):
|
||
# Создаем или обновляем пользователя
|
||
user = await user_service.create_or_update_user(message.from_user, message.chat.id)
|
||
|
||
# Проверяем права
|
||
is_admin = auth.is_admin(user.telegram_id)
|
||
|
||
# Отправляем приветствие
|
||
await message_service.send_message(message, welcome_text, keyboard)
|
||
```
|
||
|
||
### Преимущества специализированных декораторов
|
||
|
||
✅ **Нет проблем с `dispatcher`** - aiogram не передает лишние параметры
|
||
✅ **Меньше зависимостей** - инжектируются только нужные сервисы
|
||
✅ **Лучшая производительность** - меньше объектов создается
|
||
✅ **Более явный код** - видно, какие зависимости используются
|
||
✅ **Легче тестировать** - меньше моков нужно создавать
|
||
|
||
### Тестирование
|
||
|
||
Система DI значительно упрощает тестирование:
|
||
|
||
```python
|
||
# Создание тестовых зависимостей
|
||
@pytest.fixture
|
||
async def test_dependencies():
|
||
deps = Dependencies()
|
||
deps._database = AsyncMock() # Мокаем БД
|
||
deps._auth = MagicMock() # Мокаем авторизацию
|
||
return deps
|
||
|
||
# Тестирование обработчиков
|
||
@pytest.mark.asyncio
|
||
async def test_cmd_start(test_dependencies):
|
||
# Настраиваем моки
|
||
test_dependencies._user_service.get_user_by_telegram_id.return_value = mock_user
|
||
test_dependencies._auth.is_admin.return_value = False
|
||
|
||
# Тестируем обработчик
|
||
result = await cmd_start(
|
||
message,
|
||
state,
|
||
user_service=test_dependencies.user_service,
|
||
auth=test_dependencies.auth,
|
||
utils=test_dependencies.utils,
|
||
message_service=test_dependencies.message_service,
|
||
validator=test_dependencies.validator
|
||
)
|
||
|
||
# Проверяем результат
|
||
assert result is not None
|
||
```
|
||
|
||
Подробная документация: [DI_SETUP.md](DI_SETUP.md)
|
||
|
||
## 📊 База данных
|
||
|
||
Бот использует SQLite для хранения данных:
|
||
|
||
### Таблицы:
|
||
- **users**: Информация о пользователях (ID, имя, ссылка, статус, права суперпользователя)
|
||
- **questions**: Вопросы и ответы (текст, статус, анонимность, локальная нумерация)
|
||
- **user_blocks**: Блокировки пользователей
|
||
- **user_settings**: Настройки пользователей (уведомления, язык)
|
||
|
||
### Особенности:
|
||
- **Внешние ключи**: Связи между таблицами
|
||
- **Триггеры**: Автоматическое обновление timestamps и нумерация вопросов
|
||
- **Индексы**: Оптимизация запросов, включая индексы для локальной нумерации
|
||
- **CRUD операции**: Полный набор операций для каждой таблицы
|
||
- **Локальная нумерация**: Каждый пользователь видит свои вопросы с номерами #1, #2, #3...
|
||
|
||
### Автоматическая нумерация вопросов:
|
||
- **Триггер `calculate_user_question_number`**: Автоматически присваивает номер при создании вопроса
|
||
- **Триггер `recalculate_user_question_numbers_on_delete`**: Пересчитывает номера при удалении вопроса
|
||
- **Удаленные вопросы**: Не участвуют в нумерации (status = 'deleted')
|
||
- **Уникальные номера**: Гарантируется уникальность номеров в рамках каждого пользователя
|
||
|
||
### Схема:
|
||
Схема базы данных находится в файле `database/schema.sql`
|
||
|
||
## 🔧 Настройка
|
||
|
||
Все настройки находятся в файле `.env`:
|
||
|
||
- `BOT_TOKEN` - токен бота (обязательно)
|
||
- `ADMINS` - список ID администраторов
|
||
- `DATABASE_PATH` - путь к файлу базы данных
|
||
- `DEBUG` - режим отладки
|
||
- `MAX_QUESTION_LENGTH` - максимальная длина вопроса
|
||
- `MAX_ANSWER_LENGTH` - максимальная длина ответа
|
||
|
||
#### Настройки Rate Limiting:
|
||
- `RATE_LIMIT_ENV` - окружение (development/production/strict)
|
||
- `RATE_LIMIT_MESSAGES_PER_SECOND` - сообщений в секунду на чат
|
||
- `RATE_LIMIT_BURST_LIMIT` - максимум сообщений подряд
|
||
- `RATE_LIMIT_RETRY_MULTIPLIER` - множитель для задержки при retry
|
||
- `RATE_LIMIT_MAX_RETRY_DELAY` - максимальная задержка между попытками
|
||
- `RATE_LIMIT_MAX_RETRIES` - максимальное количество повторных попыток
|
||
|
||
## 🚀 Развертывание
|
||
|
||
### Локальный запуск:
|
||
```bash
|
||
python main.py
|
||
```
|
||
|
||
### Docker (планируется):
|
||
```bash
|
||
docker build -t anonbot .
|
||
docker run -d --name anonbot anonbot
|
||
```
|
||
|
||
### VPS/Сервер:
|
||
1. Загрузите код на сервер
|
||
2. Установите зависимости: `pip install -r requirements.txt`
|
||
3. Настройте `.env` файл
|
||
4. **Для обновления существующей базы данных** (если нужно добавить поле `is_superuser`):
|
||
```bash
|
||
python3 -c "
|
||
import asyncio
|
||
import aiosqlite
|
||
|
||
async def migrate():
|
||
async with aiosqlite.connect('database/anon_qna.db') as conn:
|
||
await conn.execute('ALTER TABLE users ADD COLUMN is_superuser BOOLEAN DEFAULT FALSE')
|
||
await conn.execute('CREATE INDEX IF NOT EXISTS idx_users_is_superuser ON users(is_superuser)')
|
||
await conn.commit()
|
||
print('Миграция завершена!')
|
||
|
||
asyncio.run(migrate())
|
||
"
|
||
```
|
||
5. Запустите: `python main.py`
|
||
6. Рекомендуется использовать systemd или supervisor для автозапуска
|
||
|
||
## 📝 Система логирования
|
||
|
||
В проекте настроена продвинутая система логирования с использованием библиотеки **loguru** и автоматических декораторов. Логи выводятся в stderr для корректной работы в Docker контейнерах.
|
||
|
||
### Основные компоненты
|
||
|
||
- **services/infrastructure/logger.py** - основная настройка системы логирования
|
||
- **services/infrastructure/logging_decorators.py** - декораторы для автоматического логирования
|
||
- **services/infrastructure/logging_utils.py** - утилиты для контекстного логирования
|
||
- **loguru** - библиотека для логирования (уже добавлена в requirements.txt)
|
||
|
||
### Уровни логирования
|
||
|
||
- **INFO** - основная информация о работе бота
|
||
- **WARNING** - предупреждения о потенциальных проблемах
|
||
- **ERROR** - ошибки, требующие внимания
|
||
|
||
### Формат логов
|
||
|
||
```
|
||
2024-01-15 10:30:45 | INFO | bot:main:25 - 🚀 Запуск бота в режиме polling
|
||
2024-01-15 10:30:45 | INFO | loader:init_bot:28 - 🤖 Инициализация Telegram бота
|
||
2024-01-15 10:30:45 | INFO | loader:init_bot:33 - ✅ Бот успешно инициализирован
|
||
```
|
||
|
||
### 🎯 Автоматические декораторы логирования
|
||
|
||
Система включает в себя набор декораторов для автоматического логирования:
|
||
|
||
#### 1. Основные декораторы
|
||
|
||
**`@log_function_call`** - логирование входа/выхода из функций
|
||
```python
|
||
@log_function_call(log_params=True, log_result=True)
|
||
async def create_question(self, from_user_id: int, to_user_id: int, message_text: str):
|
||
# Автоматически логирует вход с параметрами и выход с результатом
|
||
```
|
||
|
||
**`@log_business_event`** - логирование бизнес-событий
|
||
```python
|
||
@log_business_event("create_question", log_params=True, log_result=True)
|
||
async def create_question(self, ...):
|
||
# Логирует бизнес-событие с контекстом
|
||
```
|
||
|
||
**`@log_fsm_transition`** - логирование FSM переходов
|
||
```python
|
||
@log_fsm_transition(to_state="waiting_for_answer")
|
||
async def answer_question_callback(callback: CallbackQuery, state: FSMContext):
|
||
# Логирует переходы между состояниями FSM
|
||
```
|
||
|
||
#### 2. Оптимизированные декораторы
|
||
|
||
**`@log_middleware`** - тихое логирование для middleware
|
||
```python
|
||
@log_middleware(log_params=True, log_result=False)
|
||
async def __call__(self, handler, event, data):
|
||
# Логирует только ошибки, вход/выход в DEBUG режиме
|
||
```
|
||
|
||
**`@log_utility`** - декоратор для служебных функций
|
||
```python
|
||
@log_utility
|
||
def _has_attachments(message: Message) -> bool:
|
||
# Логирует только ошибки
|
||
```
|
||
|
||
#### 3. Контекстное логирование
|
||
|
||
**`LoggingContext`** - контекстное логирование с дополнительной информацией
|
||
```python
|
||
context = get_logging_context(__name__)
|
||
context.add_context("user_id", user_id)
|
||
context.log_info("Пользователь выполнил действие")
|
||
```
|
||
|
||
**Специальные функции** для бизнес-событий:
|
||
```python
|
||
log_question_created(logger, question_id, from_user_id, to_user_id)
|
||
log_user_created(logger, user_id, username)
|
||
log_user_blocked(logger, user_id, reason)
|
||
```
|
||
|
||
### Покрытие логированием
|
||
|
||
#### 1. Запуск и инициализация
|
||
- ✅ Инициализация бота
|
||
- ✅ Инициализация диспетчера
|
||
- ✅ Инициализация базы данных
|
||
- ✅ Регистрация обработчиков
|
||
- ✅ Уведомления администраторов
|
||
|
||
#### 2. База данных (CRUD операции)
|
||
- ✅ Создание пользователей
|
||
- ✅ Создание вопросов
|
||
- ✅ Обновление вопросов
|
||
- ✅ Инициализация БД
|
||
|
||
#### 3. Обработчики команд
|
||
- ✅ Команда /start
|
||
- ✅ Обработка deep links
|
||
- ✅ Создание/обновление пользователей
|
||
|
||
#### 4. Бизнес-логика
|
||
- ✅ Отправка ответов авторам
|
||
- ✅ Обработка ошибок
|
||
- ✅ Валидация данных
|
||
|
||
#### 5. Системные события
|
||
- ✅ Ошибки в обработчиках
|
||
- ✅ Очистка ресурсов
|
||
- ✅ Остановка бота
|
||
|
||
#### 6. FSM состояния
|
||
- ✅ Переходы между состояниями
|
||
- ✅ Обработка FSM событий
|
||
- ✅ Отслеживание пользовательских сессий
|
||
|
||
### Использование в коде
|
||
|
||
#### Импорт логгера и декораторов
|
||
|
||
```python
|
||
from services.infrastructure.logger import get_logger
|
||
from services.infrastructure.logging_decorators import (
|
||
log_function_call, log_business_event, log_fsm_transition,
|
||
log_middleware, log_utility
|
||
)
|
||
from services.infrastructure.logging_utils import (
|
||
log_user_action, log_business_operation, log_question_created
|
||
)
|
||
|
||
logger = get_logger(__name__)
|
||
```
|
||
|
||
#### Примеры использования декораторов
|
||
|
||
```python
|
||
# Бизнес-события
|
||
@log_business_event("create_question", log_params=True, log_result=True)
|
||
async def create_question(self, from_user_id: int, to_user_id: int, message_text: str):
|
||
# Автоматически логирует создание вопроса
|
||
pass
|
||
|
||
# FSM переходы
|
||
@log_fsm_transition(to_state="waiting_for_answer")
|
||
async def answer_question_callback(callback: CallbackQuery, state: FSMContext):
|
||
# Логирует переход в состояние ответа
|
||
pass
|
||
|
||
# Middleware (тихое логирование)
|
||
@log_middleware(log_params=True, log_result=False)
|
||
async def __call__(self, handler, event, data):
|
||
# Логирует только ошибки
|
||
pass
|
||
|
||
# Служебные функции
|
||
@log_utility
|
||
def _has_attachments(message: Message) -> bool:
|
||
# Логирует только ошибки
|
||
pass
|
||
```
|
||
|
||
#### Контекстное логирование
|
||
|
||
```python
|
||
# Создание контекста
|
||
context = get_logging_context(__name__)
|
||
context.add_context("user_id", user_id)
|
||
context.add_context("question_id", question_id)
|
||
context.log_info("Пользователь ответил на вопрос")
|
||
|
||
# Специальные функции для бизнес-событий
|
||
log_question_created(logger, question_id, from_user_id, to_user_id)
|
||
log_user_created(logger, user_id, username)
|
||
log_user_blocked(logger, user_id, reason)
|
||
```
|
||
|
||
#### Традиционное логирование
|
||
|
||
```python
|
||
# Информационные сообщения
|
||
logger.info("🚀 Запуск бота в режиме polling")
|
||
logger.info(f"👤 Создание пользователя: {user.telegram_id} ({user.first_name})")
|
||
|
||
# Предупреждения
|
||
logger.warning("⚠️ Список администраторов пуст")
|
||
logger.warning(f"⚠️ Не удалось отправить уведомление админу {admin_id}: {e}")
|
||
|
||
# Ошибки
|
||
logger.error(f"💥 Ошибка при запуске бота: {e}")
|
||
logger.error(f"💥 Ошибка в обработчике /start: {e}")
|
||
```
|
||
|
||
### Docker интеграция
|
||
|
||
Логи настроены для вывода в stderr, что обеспечивает корректную работу в Docker:
|
||
|
||
```dockerfile
|
||
# В Dockerfile
|
||
CMD ["python", "main.py"]
|
||
```
|
||
|
||
```bash
|
||
# Просмотр логов в Docker
|
||
docker logs <container_name>
|
||
```
|
||
|
||
### Файловое логирование (DEBUG режим)
|
||
|
||
В режиме отладки логи также сохраняются в файлы:
|
||
|
||
- **logs/bot.log** - основные логи
|
||
- Ротация: 10 MB
|
||
- Хранение: 7 дней
|
||
- Сжатие: zip
|
||
|
||
### ⚡ Производительность и оптимизация
|
||
|
||
#### Тихие декораторы
|
||
|
||
Для предотвращения избыточного логирования используются тихие декораторы:
|
||
|
||
- **`@log_middleware`** - для middleware (логирует только ошибки)
|
||
- **`@log_utility`** - для служебных функций (логирует только ошибки)
|
||
- **`quiet=True`** - параметр для полного отключения логирования входа/выхода
|
||
|
||
#### Уровни логирования
|
||
|
||
- **INFO** - бизнес-события и важные операции
|
||
- **DEBUG** - детальная информация (только в DEBUG режиме)
|
||
- **WARNING** - предупреждения
|
||
- **ERROR** - ошибки (всегда логируются)
|
||
|
||
#### Контекстная информация
|
||
|
||
Декораторы автоматически извлекают контекстную информацию:
|
||
- `user_id` - ID пользователя
|
||
- `question_id` - ID вопроса
|
||
- `page` - номер страницы
|
||
- `status` - статус операции
|
||
|
||
#### Примеры оптимизированного логирования
|
||
|
||
```python
|
||
# Middleware - тихое логирование
|
||
@log_middleware(log_params=True, log_result=False)
|
||
async def __call__(self, handler, event, data):
|
||
# Логирует только ошибки, вход/выход в DEBUG режиме
|
||
pass
|
||
|
||
# Служебные функции - только ошибки
|
||
@log_utility
|
||
def _has_attachments(message: Message) -> bool:
|
||
# Логирует только ошибки
|
||
pass
|
||
|
||
# Бизнес-функции - полное логирование
|
||
@log_business_event("create_question", log_params=True, log_result=True)
|
||
async def create_question(self, ...):
|
||
# Полное логирование для важных операций
|
||
pass
|
||
```
|
||
|
||
### Мониторинг
|
||
|
||
#### Ключевые метрики для мониторинга
|
||
|
||
1. **Запуск/остановка бота**
|
||
- `🚀 Запуск бота в режиме polling`
|
||
- `🛑 Бот остановлен`
|
||
|
||
2. **Пользователи**
|
||
- `👤 Создание пользователя`
|
||
- `👤 Обновление существующего пользователя`
|
||
|
||
3. **Вопросы и ответы**
|
||
- `❓ Создание вопроса`
|
||
- `📝 Обновление вопроса`
|
||
- `📤 Отправка ответа`
|
||
|
||
4. **Ошибки**
|
||
- `💥 Ошибка в обработчике`
|
||
- `⚠️ Предупреждения`
|
||
|
||
#### Алерты
|
||
|
||
Рекомендуется настроить алерты на:
|
||
- ERROR уровень логов
|
||
- Отсутствие логов более 5 минут
|
||
- Частые ошибки в обработчиках
|
||
|
||
### Настройка уровней
|
||
|
||
Уровень логирования настраивается через переменную окружения:
|
||
|
||
```bash
|
||
# В .env файле
|
||
DEBUG=true # DEBUG уровень
|
||
DEBUG=false # INFO уровень (по умолчанию)
|
||
```
|
||
|
||
### Производительность
|
||
|
||
- Логирование асинхронное
|
||
- Минимальное влияние на производительность
|
||
- Эмодзи в логах для быстрого визуального поиска
|
||
- Структурированный формат для парсинга
|
||
|
||
## 🤝 Вклад в проект
|
||
|
||
1. Fork репозитория
|
||
2. Создайте feature branch
|
||
3. Внесите изменения
|
||
4. Создайте Pull Request
|
||
|
||
## 📄 Лицензия
|
||
|
||
MIT License
|
||
|
||
## 🆘 Поддержка
|
||
|
||
Если у вас возникли проблемы:
|
||
|
||
1. Проверьте логи бота
|
||
2. Убедитесь в правильности настройки `.env`
|
||
3. Проверьте права доступа к файлам
|
||
4. Обратитесь к администратору
|
||
|
||
### Дополнительная документация:
|
||
- [Руководство по суперпользователям](SUPERUSER_DISPLAY_FEATURE.md) - подробное описание системы ролей и функционала для суперпользователей
|
||
- [Настройка Dependency Injection](DI_SETUP.md) - подробное руководство по системе инъекции зависимостей
|
||
- [Примеры использования DI](examples/dependency_injection_example.py) - практические примеры использования системы инъекции зависимостей
|
||
|
||
## 📈 Prometheus метрики
|
||
|
||
AnonBot поддерживает экспорт метрик в формате Prometheus для мониторинга и анализа производительности.
|
||
|
||
### Эндпоинты
|
||
|
||
- **http://localhost:8081/metrics** - экспорт метрик Prometheus
|
||
- **http://localhost:8081/health** - проверка здоровья бота
|
||
- **http://localhost:8081/ready** - готовность к работе (readiness probe)
|
||
- **http://localhost:8081/status** - информация о процессе (PID, uptime, использование ресурсов)
|
||
- **http://localhost:8081/** - информация о сервисе
|
||
|
||
### Доступные метрики
|
||
|
||
#### Информационные метрики
|
||
- `anon_bot_info` - Информация о боте (версия, сервис)
|
||
|
||
#### Счетчики сообщений
|
||
- `anon_bot_messages_total` - Общее количество обработанных сообщений
|
||
- Метки: `message_type`, `status`
|
||
|
||
#### Счетчики вопросов
|
||
- `anon_bot_questions_total` - Общее количество вопросов
|
||
- Метки: `status` (created, rejected, deleted)
|
||
|
||
#### Счетчики ответов
|
||
- `anon_bot_answers_total` - Общее количество ответов
|
||
- Метки: `status` (sent, edited, delivered, delivery_failed)
|
||
|
||
#### Счетчики пользователей
|
||
- `anon_bot_users_total` - Общее количество пользователей
|
||
- Метки: `action` (created, updated)
|
||
|
||
#### Счетчики ошибок
|
||
- `anon_bot_errors_total` - Общее количество ошибок
|
||
- Метки: `error_type`, `component`
|
||
|
||
#### HTTP метрики
|
||
- `anon_bot_http_requests_total` - Общее количество HTTP запросов
|
||
- Метки: `method`, `endpoint`, `status_code`
|
||
- `anon_bot_http_request_duration_seconds` - Время обработки HTTP запросов
|
||
- Метки: `method`, `endpoint`
|
||
|
||
#### Время обработки
|
||
- `anon_bot_message_processing_seconds` - Время обработки сообщений
|
||
- Метки: `message_type`
|
||
- `anon_bot_question_processing_seconds` - Время обработки вопросов
|
||
- `anon_bot_answer_processing_seconds` - Время обработки ответов
|
||
|
||
#### Gauge метрики
|
||
- `anon_bot_active_users` - Количество активных пользователей
|
||
- `anon_bot_active_questions` - Количество активных вопросов
|
||
|
||
### Настройка Prometheus
|
||
|
||
Добавьте в конфигурацию Prometheus (`prometheus.yml`):
|
||
|
||
```yaml
|
||
scrape_configs:
|
||
- job_name: 'anon-bot'
|
||
static_configs:
|
||
- targets: ['localhost:8081']
|
||
metrics_path: '/metrics'
|
||
scrape_interval: 30s
|
||
```
|
||
|
||
### Примеры запросов PromQL
|
||
|
||
#### Количество сообщений в секунду
|
||
```promql
|
||
rate(anon_bot_messages_total[5m])
|
||
```
|
||
|
||
#### Количество ошибок в секунду
|
||
```promql
|
||
rate(anon_bot_errors_total[5m])
|
||
```
|
||
|
||
#### Время обработки сообщений (95-й процентиль)
|
||
```promql
|
||
histogram_quantile(0.95, rate(anon_bot_message_processing_seconds_bucket[5m]))
|
||
```
|
||
|
||
#### Количество активных пользователей
|
||
```promql
|
||
anon_bot_active_users
|
||
```
|
||
|
||
### Мониторинг в Docker
|
||
|
||
При запуске в Docker убедитесь, что порт 8081 доступен:
|
||
|
||
```yaml
|
||
ports:
|
||
- "8081:8081"
|
||
```
|
||
|
||
### Алерты
|
||
|
||
Рекомендуется настроить алерты на:
|
||
|
||
1. **Высокий уровень ошибок**
|
||
```promql
|
||
rate(anon_bot_errors_total[5m]) > 0.1
|
||
```
|
||
|
||
2. **Медленную обработку**
|
||
```promql
|
||
histogram_quantile(0.95, rate(anon_bot_message_processing_seconds_bucket[5m])) > 5
|
||
```
|
||
|
||
3. **Недоступность сервиса**
|
||
```promql
|
||
up{job="anon-bot"} == 0
|
||
```
|
||
|
||
### Безопасность
|
||
|
||
Эндпоинты метрик не требуют аутентификации. В продакшене рекомендуется:
|
||
- Ограничить доступ к эндпоинтам через firewall
|
||
- Использовать reverse proxy с аутентификацией
|
||
- Настроить TLS для HTTPS
|
||
|
||
## 🐳 Docker
|
||
|
||
### Сборка образа
|
||
|
||
```bash
|
||
docker build -t anon-bot .
|
||
```
|
||
|
||
### Запуск контейнера
|
||
|
||
```bash
|
||
docker run -d \
|
||
--name anon-bot \
|
||
--restart unless-stopped \
|
||
-p 8081:8081 \
|
||
-v $(pwd)/database:/app/database \
|
||
-v $(pwd)/logs:/app/logs \
|
||
-e BOT_TOKEN=your_bot_token_here \
|
||
-e ADMINS=123456789,987654321 \
|
||
-e DEBUG=false \
|
||
anon-bot
|
||
```
|
||
|
||
### Управление контейнером
|
||
|
||
```bash
|
||
# Просмотр логов
|
||
docker logs anon-bot
|
||
|
||
# Остановка контейнера
|
||
docker stop anon-bot
|
||
|
||
# Запуск контейнера
|
||
docker start anon-bot
|
||
|
||
# Удаление контейнера
|
||
docker rm anon-bot
|
||
|
||
# Перезапуск контейнера
|
||
docker restart anon-bot
|
||
```
|
||
|
||
### Переменные окружения
|
||
|
||
Все переменные окружения из `.env_example` можно передать через `-e`:
|
||
|
||
```bash
|
||
docker run -d \
|
||
--name anon-bot \
|
||
-p 8081:8081 \
|
||
-e BOT_TOKEN=your_token \
|
||
-e ADMINS=123456789 \
|
||
-e DEBUG=false \
|
||
-e MAX_QUESTION_LENGTH=1000 \
|
||
-e MAX_ANSWER_LENGTH=2000 \
|
||
anon-bot
|
||
```
|
||
|
||
### Volumes
|
||
|
||
- `./database:/app/database` - персистентное хранение базы данных
|
||
- `./logs:/app/logs` - персистентное хранение логов
|
||
|
||
### Порты
|
||
|
||
- `8081:8081` - порт для метрик и health check
|
||
|
||
### Health Check
|
||
|
||
Контейнер включает встроенный health check:
|
||
|
||
```bash
|
||
# Проверка статуса
|
||
docker inspect --format='{{.State.Health.Status}}' anon-bot
|
||
|
||
# Просмотр деталей health check
|
||
docker inspect anon-bot | jq '.[0].State.Health'
|
||
```
|
||
|
||
**Параметры health check:**
|
||
- **Интервал проверки**: 30 секунд
|
||
- **Таймаут**: 10 секунд
|
||
- **Период запуска**: 60 секунд (время на инициализацию)
|
||
- **Количество попыток**: 3
|
||
- **Команда проверки**: `curl -f http://localhost:8081/health`
|
||
|
||
**Статусы health check:**
|
||
- `starting` - контейнер запускается
|
||
- `healthy` - контейнер здоров
|
||
- `unhealthy` - контейнер нездоров
|
||
|
||
## 🚦 Rate Limiting
|
||
|
||
AnonBot включает комплексную систему rate limiting для предотвращения ошибок Flood Control в Telegram Bot API. Система автоматически ограничивает скорость отправки сообщений и обрабатывает ошибки с повторными попытками.
|
||
|
||
### Рекомендуемые настройки на основе лимитов Telegram API
|
||
|
||
Система настроена с учетом официальных лимитов Telegram Bot API:
|
||
- **1 сообщение в секунду** в личных чатах
|
||
- **20 сообщений в минуту** в групповых чатах (0.33 в секунду)
|
||
- **30 запросов в секунду** глобально
|
||
|
||
Настройки по умолчанию используют консервативные значения (50% от лимитов) для обеспечения стабильной работы.
|
||
|
||
### Компоненты системы
|
||
|
||
#### 1. Конфигурация (`services/rate_limit_config.py`)
|
||
- **RateLimitSettings**: Основные настройки rate limiting
|
||
- **Конфигурации для разных окружений**: development, production, strict
|
||
- **Адаптивная конфигурация**: Автоматическая настройка на основе уровня ошибок
|
||
|
||
#### 2. Rate Limiter (`services/rate_limiter.py`)
|
||
- **ChatRateLimiter**: Ограничения для конкретного чата
|
||
- **GlobalRateLimiter**: Глобальные ограничения для всех чатов
|
||
- **RetryHandler**: Обработка повторных попыток с экспоненциальной задержкой
|
||
- **TelegramRateLimiter**: Основной класс, объединяющий все компоненты
|
||
|
||
#### 3. Сервис (`services/rate_limit_service.py`)
|
||
- **RateLimitService**: Высокоуровневый сервис для управления rate limiting
|
||
- **Статистика**: Отслеживание успешных/неудачных запросов
|
||
- **Адаптация**: Автоматическая настройка конфигурации
|
||
|
||
#### 4. Middleware (`middlewares/rate_limit_middleware.py`)
|
||
- **RateLimitMiddleware**: Автоматическое применение rate limiting ко всем сообщениям
|
||
- **Прозрачная интеграция**: Минимальные изменения в существующем коде
|
||
|
||
#### 5. Validation Middleware (`middlewares/validation_middleware.py`)
|
||
- **ValidationMiddleware**: Автоматическая валидация всех входящих данных
|
||
- **Безопасность**: Защита от некорректных данных и атак
|
||
- **Логирование**: Оптимизированное логирование с тихими декораторами для middleware
|
||
|
||
### Настройка
|
||
|
||
#### Переменные окружения
|
||
|
||
Добавьте следующие переменные в ваш `.env` файл:
|
||
|
||
```env
|
||
# Окружение для rate limiting (development/production/strict)
|
||
RATE_LIMIT_ENV=production
|
||
|
||
# Основные настройки rate limiting
|
||
RATE_LIMIT_MESSAGES_PER_SECOND=0.5
|
||
RATE_LIMIT_BURST_LIMIT=2
|
||
RATE_LIMIT_RETRY_MULTIPLIER=1.5
|
||
RATE_LIMIT_MAX_RETRY_DELAY=30.0
|
||
RATE_LIMIT_MAX_RETRIES=3
|
||
|
||
# Задержки для разных типов сообщений
|
||
RATE_LIMIT_VOICE_DELAY=2.0
|
||
RATE_LIMIT_MEDIA_DELAY=1.5
|
||
RATE_LIMIT_TEXT_DELAY=1.0
|
||
|
||
# Множители для разных типов чатов
|
||
RATE_LIMIT_PRIVATE_MULTIPLIER=1.0
|
||
RATE_LIMIT_GROUP_MULTIPLIER=0.8
|
||
RATE_LIMIT_CHANNEL_MULTIPLIER=0.6
|
||
|
||
# Глобальные ограничения
|
||
RATE_LIMIT_GLOBAL_MESSAGES_PER_SECOND=10.0
|
||
RATE_LIMIT_GLOBAL_BURST_LIMIT=20
|
||
```
|
||
|
||
#### Конфигурации по умолчанию
|
||
|
||
**Production (по умолчанию)**
|
||
- 0.5 сообщений в секунду на чат (50% от лимита Telegram API)
|
||
- Максимум 2 сообщения подряд
|
||
- 3 повторные попытки при ошибках
|
||
- Максимальная задержка 30 секунд
|
||
- 20 глобальных запросов в секунду (из 30 доступных)
|
||
|
||
**Development**
|
||
- 0.8 сообщений в секунду на чат (80% от лимита для тестирования)
|
||
- Максимум 3 сообщения подряд
|
||
- 2 повторные попытки при ошибках
|
||
- Максимальная задержка 15 секунд
|
||
|
||
**Strict**
|
||
- 0.3 сообщений в секунду на чат (30% от лимита для максимальной стабильности)
|
||
- Максимум 1 сообщение подряд
|
||
- 5 повторных попыток при ошибках
|
||
- Максимальная задержка 60 секунд
|
||
- 10 глобальных запросов в секунду (консервативные настройки)
|
||
|
||
### Использование
|
||
|
||
#### Автоматическое использование (рекомендуется)
|
||
|
||
Rate limiting и валидация автоматически применяются ко всем сообщениям через middleware. Никаких изменений в коде не требуется.
|
||
|
||
**ValidationMiddleware** автоматически валидирует:
|
||
- Все callback queries
|
||
- Все сообщения
|
||
- Telegram ID пользователей
|
||
- Username (если есть)
|
||
- Chat ID
|
||
|
||
#### Ручное использование
|
||
|
||
```python
|
||
from services.rate_limiting.rate_limit_service import RateLimitService
|
||
from dependencies import get_rate_limit_service
|
||
|
||
# Получение сервиса через DI
|
||
rate_limit_service = get_rate_limit_service()
|
||
|
||
# Отправка сообщения с rate limiting
|
||
result = await rate_limit_service.send_with_rate_limit(
|
||
bot.send_message,
|
||
chat_id=user_id,
|
||
text="Привет!"
|
||
)
|
||
```
|
||
|
||
#### Прямое использование rate limiter
|
||
|
||
```python
|
||
from services.rate_limiting.rate_limiter import send_with_rate_limit
|
||
|
||
# Отправка с автоматическим rate limiting
|
||
result = await send_with_rate_limit(
|
||
bot.send_message,
|
||
chat_id=user_id,
|
||
text="Привет!"
|
||
)
|
||
```
|
||
|
||
#### Ручное использование валидатора
|
||
|
||
```python
|
||
from services.validation import InputValidator
|
||
from dependencies import get_validator
|
||
|
||
# Получение валидатора через DI
|
||
validator = get_validator()
|
||
|
||
# Валидация текста вопроса
|
||
result = validator.validate_question_text(text, max_length=1000)
|
||
if not result:
|
||
print(f"Ошибка: {result.error_message}")
|
||
else:
|
||
sanitized_text = result.sanitized_value
|
||
|
||
# Валидация callback data
|
||
result = validator.validate_callback_data(callback_data)
|
||
if not result:
|
||
await callback.answer("❌ Неверные данные", show_alert=True)
|
||
return
|
||
```
|
||
|
||
### Административные функции
|
||
|
||
Доступ к управлению rate limiting осуществляется через админскую панель:
|
||
|
||
1. **Перейти в админку**: `/admin`
|
||
2. **Нажать кнопку "🚦 Rate Limiting"**
|
||
3. **Выбрать нужное действие**:
|
||
- **📊 Статистика Rate Limiting** - показывает подробную статистику:
|
||
- Общее количество запросов
|
||
- Процент успеха/ошибок
|
||
- Количество RetryAfter ошибок
|
||
- Среднее время ожидания
|
||
- **🔄 Сбросить статистику** - сбрасывает всю статистику rate limiting
|
||
- **⚙️ Адаптировать конфигурацию** - адаптирует настройки на основе текущей производительности
|
||
|
||
**Важно**: Доступ к функциям rate limiting имеют только администраторы бота (не суперпользователи).
|
||
|
||
### Мониторинг
|
||
|
||
#### Логирование
|
||
|
||
Система логирует:
|
||
- Rate limiting события (DEBUG уровень)
|
||
- RetryAfter ошибки (WARNING уровень)
|
||
- Критические ошибки (ERROR уровень)
|
||
|
||
#### Статистика
|
||
|
||
RateLimitService отслеживает:
|
||
- Общее количество запросов
|
||
- Успешные/неудачные запросы
|
||
- Типы ошибок (RetryAfter, API ошибки)
|
||
- Время ожидания
|
||
|
||
#### Адаптивная конфигурация
|
||
|
||
Система автоматически адаптирует настройки:
|
||
- При >10% ошибок: ужесточает ограничения
|
||
- При <1% ошибок: ослабляет ограничения
|
||
- Требует минимум 100 запросов для адаптации
|
||
|
||
### Интеграция с DI
|
||
|
||
Rate limiting полностью интегрирован в систему инъекции зависимостей:
|
||
|
||
```python
|
||
# В обработчиках
|
||
async def some_handler(
|
||
message: Message,
|
||
rate_limit_service: RateLimitService
|
||
):
|
||
# rate_limit_service автоматически инжектируется
|
||
pass
|
||
```
|
||
|
||
### Архитектурные особенности
|
||
|
||
#### Соблюдение принципов
|
||
|
||
1. **Single Responsibility**: Каждый компонент отвечает за свою задачу
|
||
2. **Open/Closed**: Легко расширяется новыми типами ограничений
|
||
3. **Dependency Inversion**: Зависит от абстракций, а не от конкретных реализаций
|
||
|
||
#### Производительность
|
||
|
||
- Минимальные накладные расходы
|
||
- Эффективное управление памятью
|
||
- Асинхронная обработка
|
||
|
||
#### Надежность
|
||
|
||
- Обработка всех типов ошибок Telegram API
|
||
- Экспоненциальная задержка при повторных попытках
|
||
- Автоматическое восстановление после ошибок
|
||
|
||
### Устранение неполадок
|
||
|
||
#### Высокий процент ошибок
|
||
|
||
1. Проверьте статистику: `/ratelimit_stats`
|
||
2. Адаптируйте конфигурацию: `/adapt_ratelimit`
|
||
3. При необходимости ужесточите настройки в `.env`
|
||
|
||
#### Медленная отправка сообщений
|
||
|
||
1. Проверьте настройки `RATE_LIMIT_MESSAGES_PER_SECOND`
|
||
2. Увеличьте значение для более быстрой отправки
|
||
3. Убедитесь, что не превышаете лимиты Telegram API
|
||
|
||
#### Проблемы с интеграцией
|
||
|
||
1. Убедитесь, что middleware зарегистрирован в `loader.py`
|
||
|
||
2. **Ошибка `TypeError: got an unexpected keyword argument 'dispatcher'`**:
|
||
- **Причина**: aiogram 3.x передает `dispatcher` во все обработчики сообщений, но `@inject_all` не обрабатывает его правильно
|
||
- **Решение**: Используйте специализированные декораторы вместо `@inject_all`:
|
||
```python
|
||
# ❌ Неправильно
|
||
@inject_all
|
||
async def process_question(message: Message, dispatcher, ...):
|
||
|
||
# ✅ Правильно
|
||
@inject_question_services
|
||
async def process_question(message: Message, question_service: QuestionService, ...):
|
||
```
|
||
- **Преимущества**: Нет проблем с `dispatcher`, меньше зависимостей, лучшая производительность
|
||
|
||
3. Проверьте, что все зависимости корректно импортированы
|
||
4. Проверьте логи на наличие ошибок инициализации
|
||
|
||
## 🔮 Планы развития
|
||
|
||
- [x] **Система суперпользователей** - расширенные права для модерации с отображением информации об авторах
|
||
- [x] **Система инъекции зависимостей** - современная архитектура с DI для лучшей тестируемости
|
||
- [x] **Система разрешений** - гибкая система разрешений с соблюдением принципа OCP
|
||
- [x] **Prometheus метрики** - мониторинг производительности и ошибок
|
||
- [x] **Rate limiting** - защита от спама и DDoS с автоматической адаптацией
|
||
- [x] **Реорганизация структуры проекта** - улучшенная архитектура с логической группировкой сервисов
|
||
- [x] **Локальная нумерация вопросов** - каждый пользователь видит свои вопросы с номерами #1, #2, #3... вместо глобальных ID
|
||
- [ ] **Unit-тесты** - полное покрытие тестами с использованием DI
|
||
- [ ] **Кэширование Redis** - оптимизация производительности
|
||
- [ ] Рассылка сообщений
|
||
- [ ] Экспорт данных
|
||
- [ ] Веб-интерфейс для админов
|
||
- [ ] Поддержка медиафайлов в вопросах
|
||
- [ ] Категории вопросов
|
||
- [ ] Модерация контента
|
||
- [ ] Аналитика и отчеты
|
||
|
||
## 📝 Changelog
|
||
|
||
### v1.7.0 - Продвинутая система логирования (2025-01-27)
|
||
|
||
#### ✨ Новые возможности
|
||
- **Автоматические декораторы логирования** - `@log_function_call`, `@log_business_event`, `@log_fsm_transition`
|
||
- **Контекстное логирование** - `LoggingContext` с дополнительной информацией
|
||
- **Оптимизированные декораторы** - `@log_middleware`, `@log_utility` для предотвращения избыточного логирования
|
||
- **FSM отслеживание** - автоматическое логирование переходов между состояниями
|
||
- **Специальные функции** - `log_question_created`, `log_user_created`, `log_user_blocked`
|
||
- **Тихие декораторы** - предотвращение рекурсивного логирования в middleware
|
||
|
||
#### 🔧 Улучшения
|
||
- **100% покрытие логированием** - все функции теперь логируются автоматически
|
||
- **Улучшенная производительность** - оптимизированное логирование для middleware
|
||
- **Детальная диагностика** - полная трассировка выполнения функций
|
||
- **Контекстная информация** - автоматическое извлечение user_id, question_id и других параметров
|
||
|
||
#### 📁 Новые файлы
|
||
- `services/infrastructure/logging_decorators.py` - декораторы для автоматического логирования
|
||
- `services/infrastructure/logging_utils.py` - утилиты для контекстного логирования
|
||
|
||
### v1.6.0 - Локальная нумерация вопросов (2025-01-27)
|
||
|
||
#### ✨ Новые возможности
|
||
- **Локальная нумерация вопросов**: Каждый пользователь теперь видит свои вопросы с номерами #1, #2, #3... вместо глобальных ID
|
||
- **Автоматическая нумерация**: Триггеры БД автоматически присваивают и пересчитывают номера вопросов
|
||
- **Умная нумерация**: Удаленные вопросы не участвуют в нумерации, номера автоматически пересчитываются при удалении
|
||
|
||
#### 🔧 Технические изменения
|
||
- **Новое поле БД**: `user_question_number` в таблице `questions`
|
||
- **Триггеры БД**:
|
||
- `calculate_user_question_number` - автоматическое вычисление номера при создании
|
||
- `recalculate_user_question_numbers_on_delete` - пересчет номеров при удалении
|
||
- **Индексы**: Оптимизированные индексы для быстрого поиска по локальным номерам
|
||
- **Модель Question**: Новый метод `get_display_number()` для получения номера для отображения
|
||
|
||
#### 🐛 Исправления
|
||
- **Отображение номеров**: Исправлена проблема с несоответствием номеров в тексте сообщений и на кнопках
|
||
- **Пагинация**: Обновлена для работы с локальными номерами
|
||
- **CRUD операции**: Обновлены для поддержки автоматической нумерации
|
||
|
||
#### 📊 Улучшения UX
|
||
- **Интуитивная нумерация**: Пользователи видят последовательные номера своих вопросов
|
||
- **Консистентность**: Номера в тексте и на кнопках всегда совпадают
|
||
- **Автоматизация**: Никаких ручных действий для поддержания нумерации
|