Добавление всех наработок за период практики.
@@ -0,0 +1,64 @@
|
||||
# ADR-001: разделение приложения на сайт, Telegram-бот и базу данных
|
||||
|
||||
**Статус:** Принято
|
||||
**Дата:** 2026-07-23
|
||||
|
||||
## Контекст
|
||||
|
||||
Приложение включает страницу привязки пользователя Битрикс24, Telegram-интерфейс
|
||||
менеджера и хранилище интеграционных данных. HTTP-сайт обрабатывает короткие
|
||||
входящие запросы, тогда как Telegram-бот выполняет длительный polling и
|
||||
параллельные REST-операции.
|
||||
|
||||
## Решение
|
||||
|
||||
Архитектура разделена на три основные структурные единицы: сайт привязки,
|
||||
Telegram-бот и база данных, которая обслуживает остальные компоненты. Зона
|
||||
ответственности каждого модуля определена отдельно. Сайт и бот не делят общий
|
||||
код, а их права на уровне базы данных ограничены.
|
||||
|
||||
HTTP-приложение `site` обслуживает только инициацию привязки и взаимодействует с
|
||||
REST API и OAuth Битрикс24. Telegram-приложение `bot` обрабатывает команды
|
||||
менеджера и взаимодействует как с REST API и OAuth Битрикс24, так и с Telegram
|
||||
Bot API. PostgreSQL предоставляет обоим процессам устойчивый версионированный
|
||||
контракт в виде `SECURITY DEFINER`-функций и поддерживает применение миграций.
|
||||
|
||||
Программное решение регистрируется администратором портала как локальное
|
||||
приложение с указанием ссылки на страницу привязки. Локальное приложение
|
||||
отправляет на HTTPS-адрес `/bitrix/bind` идентификационные данные пользователя и
|
||||
refresh-токен. Сайт обменивает его на новую пару OAuth-токенов, извлекает
|
||||
доверенные `member_id`, `user_id` и `client_endpoint` из ответа Битрикс24,
|
||||
проверяет пользователя методом `user.current` и только после этого выпускает
|
||||
одноразовую ссылку Telegram.
|
||||
|
||||

|
||||
|
||||
*Рисунок ADR-001/1. UML-диаграмма компонентов программного решения*
|
||||
|
||||
На схеме также обозначен сервис `migrate`. Он не является постоянно запущенным
|
||||
модулем, но отвечает за миграции схемы БД. При перезапуске Docker Compose сервис
|
||||
последовательно выполняет необходимые SQL-скрипты. Сайт имеет право выполнять
|
||||
только функцию выпуска ссылки `binding.issue_v1`. Бот погашает ссылку, получает
|
||||
привязку и обращается к OAuth-функциям. Прямые операции `SELECT`, `INSERT` и
|
||||
`UPDATE` над таблицами для ролей приложений запрещены, поэтому граница базы
|
||||
данных одновременно является границей доступа.
|
||||
|
||||
| Компонент | Ответственность | Внешний интерфейс |
|
||||
|------------|----------------------------------------------------------|------------------------------------------|
|
||||
| nginx | Завершение TLS и проксирование только к сайту | HTTPS → 127.0.0.1:8000 |
|
||||
| apps.site | OAuth-проверка пользователя и выпуск одноразовой ссылки | POST /bitrix/bind, GET /health |
|
||||
| apps.bot | Команды Telegram, карточки и операции со сделками | Telegram Bot API, Битрикс REST |
|
||||
| PostgreSQL | Привязки, токены, транзакционная синхронизация | binding.\* и oauth.\* |
|
||||
| migrate | Однократное применение SQL-миграций до старта приложений | db/migrations/\*.sql |
|
||||
| Битрикс24 | Источник CRM-данных и OAuth-контекста | oauth/token, user.current, crm.\* |
|
||||
| Telegram | Пользовательский канал и доставка callback-событий | getUpdates, sendMessage, editMessageText |
|
||||
|
||||
*Таблица ADR-001/1. Ответственность компонентов архитектуры*
|
||||
|
||||
## Последствия
|
||||
|
||||
Разделение уменьшает связанность и позволяет перезапускать или масштабировать
|
||||
процессы независимо. Для коротких входящих запросов используется синхронный
|
||||
Flask/Gunicorn, а для длительного polling и параллельных REST-операций —
|
||||
asyncio/aiogram. Связь приложений формализована версионированными функциями
|
||||
PostgreSQL вместо общего программного модуля.
|
||||
@@ -0,0 +1,46 @@
|
||||
# ADR-002: привязка пользователей Битрикс24 и Telegram
|
||||
|
||||
**Статус:** Принято
|
||||
**Дата:** 2026-07-23
|
||||
|
||||
## Контекст
|
||||
|
||||
Локальное приложение CRM отправляет на HTTPS-адрес `/bitrix/bind`
|
||||
идентификационные данные пользователя и refresh-токен. Идентификаторам портала и
|
||||
пользователя из входной формы доверять нельзя: контекст должен быть получен от
|
||||
OAuth-сервера Битрикс24 и подтверждён методом `user.current`.
|
||||
|
||||
## Решение
|
||||
|
||||
Сайт использует refresh-токен для получения новой OAuth-пары, доверенных
|
||||
`member_id`, `user_id` и `client_endpoint`. После этого `BitrixClient` сверяет
|
||||
`user_id` с результатом `user.current`.
|
||||
|
||||
Процесс привязки учётных записей представлен на диаграмме последовательности.
|
||||
|
||||

|
||||
|
||||
*Рисунок ADR-002/1. Диаграмма последовательности привязки Битрикс24 к Telegram*
|
||||
|
||||
После проверки пользователя функцией `secrets.token_urlsafe(32)` формируется
|
||||
одноразовый токен привязки. В БД записывается только SHA-256-хеш, поэтому
|
||||
компрометация базы не позволяет восстановить действующую ссылку. Срок жизни
|
||||
задаётся переменной окружения `BINDING_TOKEN_TTL_SECONDS`, ограничен диапазоном
|
||||
от 60 до 3600 секунд и по умолчанию равен 600 секундам. При повторном выпуске
|
||||
прежние непогашенные токены того же пользователя отзываются.
|
||||
|
||||
Пользователь переходит по одноразовой ссылке в чат с Telegram-ботом. Бот
|
||||
повторно вычисляет SHA-256-хеш и сверяет его с активными токенами. Если токен
|
||||
существует, не истёк, не отозван и ещё не погашен, он помечается использованным,
|
||||
а в таблице привязок создаётся или обновляется связь пользователя Битрикс24 с
|
||||
аккаунтом Telegram.
|
||||
|
||||
Погашение выполняется только в личном чате. Проверка токена и изменение привязки
|
||||
выполняются функцией `binding.consume_v1` в одной транзакции.
|
||||
|
||||
## Последствия
|
||||
|
||||
Привязка не использует идентификаторы пользователя из недоверенной входной
|
||||
формы. В базе хранится только хеш одноразового токена, а повторный выпуск ссылки
|
||||
отзывает предыдущие непогашенные токены. Атомарное погашение не позволяет двум
|
||||
запросам одновременно использовать одну ссылку.
|
||||
@@ -0,0 +1,53 @@
|
||||
# ADR-003: защита и обновление OAuth-токенов
|
||||
|
||||
**Статус:** Принято
|
||||
**Дата:** 2026-07-23
|
||||
|
||||
## Контекст
|
||||
|
||||
Для выполнения REST-запросов приложение хранит `access_token` и `refresh_token`.
|
||||
Битрикс24 возвращает новую пару токенов при каждом обновлении, поэтому
|
||||
одновременное использование одного refresh-токена несколькими воркерами может
|
||||
привести к потере актуальной пары.
|
||||
|
||||
## Решение
|
||||
|
||||
До передачи в PostgreSQL `access_token` и `refresh_token` шифруются алгоритмом
|
||||
Fernet. Общий `TOKEN_ENCRYPTION_KEY` передаётся контейнерам `site` и `bot` через
|
||||
переменные окружения, но не записывается в базу. Бот расшифровывает access-токен
|
||||
непосредственно перед REST-запросом и не включает OAuth-параметры в тексты
|
||||
ошибок.
|
||||
|
||||
Принятые меры защиты сведены в таблицу.
|
||||
|
||||
| Риск | Реализованная мера | Остаточный контроль |
|
||||
|---------------------------------|--------------------------------------------|--------------------------------------|
|
||||
| Утечка одноразовой ссылки из БД | Хранение SHA-256-хеша | Короткий TTL и однократное погашение |
|
||||
| Чтение OAuth-токенов из БД | Fernet-шифрование до INSERT/UPDATE | Секретный ключ вне БД |
|
||||
| Подмена пользователя | member_id/user_id из OAuth + user.current | Проверка HTTPS endpoint |
|
||||
| Гонка refresh token | Версия и аренда refresh_locked_until | Повторное чтение до версии N+1 |
|
||||
| Избыточные права приложений | Разные роли и EXECUTE только на функции | REVOKE для PUBLIC |
|
||||
| Долгая транзакция | Сетевые запросы выполняются вне транзакции | Короткие контексты Psycopg |
|
||||
|
||||
*Таблица ADR-003/1. Риски и меры защиты от них*
|
||||
|
||||
При получении `expired_token`, `invalid_token` или `no_auth_found` клиент
|
||||
пытается обновить пару токенов. Поле `version` реализует оптимистическую
|
||||
проверку, а `refresh_locked_until` — короткую аренду продолжительностью 30
|
||||
секунд. Это предотвращает одновременное использование одного refresh-токена
|
||||
несколькими воркерами.
|
||||
|
||||
После успешного обновления токенов воркер освобождает аренду. Если она занята,
|
||||
другой воркер ожидает обновления, после чего повторяет обращение к API с новой
|
||||
парой токенов.
|
||||
|
||||

|
||||
|
||||
*Рисунок ADR-003/1. Диаграмма последовательности обновления OAuth-токена*
|
||||
|
||||
## Последствия
|
||||
|
||||
Сетевой запрос к OAuth выполняется вне транзакции PostgreSQL. Версия и аренда
|
||||
координируют обновление между воркерами, а повторное чтение позволяет продолжить
|
||||
работу с версией `N+1`. Дальнейшее развитие механизма защиты предусматривает
|
||||
ротацию ключей шифрования.
|
||||
@@ -0,0 +1,61 @@
|
||||
# ADR-004: слоистая организация Telegram-бота
|
||||
|
||||
**Статус:** Принято
|
||||
**Дата:** 2026-07-23
|
||||
|
||||
## Контекст
|
||||
|
||||
Telegram-бот принимает команды и callback-запросы, проверяет привязку
|
||||
пользователя, обращается к PostgreSQL и REST API Битрикс24, а затем формирует
|
||||
HTML-сообщения и inline-клавиатуры.
|
||||
|
||||
## Решение
|
||||
|
||||
Бот организован по слоям: обработчики принимают события Telegram, middleware
|
||||
добавляет проверенную привязку в контекст, сервисы реализуют прикладные
|
||||
сценарии, `BitrixClient` отвечает за OAuth и HTTP, а классы представления
|
||||
формируют HTML-тексты и inline-клавиатуры.
|
||||
|
||||

|
||||
|
||||
*Рисунок ADR-004/1. UML-диаграмма основных классов решения*
|
||||
|
||||
Классы `Binding`, `OAuthCredentials`, `ClientInfo`, `DealStageFilter` и
|
||||
`DealPage` являются dataclass-моделями передачи данных. Они отделяют словари
|
||||
REST-ответов и строки БД от интерфейсов сервисов. `DealPage` дополнительно
|
||||
вычисляет признак `has_next`, который используется при построении кнопок
|
||||
пагинации.
|
||||
|
||||
Основные команды и callback-действия Telegram-бота приведены в таблице.
|
||||
|
||||
| Ввод | Обработчик | Результат |
|
||||
|-----------------------------------------|---------------------------------|--------------------------------------------|
|
||||
| /start, /help | StartBotHandlers.start | Справка или состояние привязки |
|
||||
| /start bind_<token> | StartBotHandlers.bind | Погашение одноразовой ссылки в личном чате |
|
||||
| /deals, /leads | DealBotHandlers.deals | Первая страница сделок начальной стадии |
|
||||
| /deal ID, /lead ID | DealBotHandlers.deal_by_command | Карточка сделки по идентификатору |
|
||||
| deals:page:<stage>:<page> | deals_page | Фильтрация по стадии и пагинация |
|
||||
| deal:view:<id> | deal_by_button | Карточка выбранной сделки |
|
||||
| deal:assign:<id>:<expected> | assign_responsible | Назначение текущего Битрикс-пользователя |
|
||||
| deal:remind:<id> | remind_to_call | Создание дела на звонок через час |
|
||||
| deal:history:<id> | show_history | Пять последних переходов по стадиям |
|
||||
|
||||
*Таблица ADR-004/1. Пользовательские команды и callback-действия*
|
||||
|
||||
Навигация по страницам списка, переход к карточке и возврат выполняются кнопками
|
||||
клавиатуры с редактированием исходного сообщения бота. Список сделок также
|
||||
поддерживает фильтрацию, которая задаётся дополнительными кнопками на странице
|
||||
просмотра списка.
|
||||
|
||||
Перед выполнением CRM-команд `BindingRequiredMiddleware` ищет привязку по
|
||||
Telegram user id. При успешной проверке объект `Binding` помещается в словарь
|
||||
`data` и передаётся именованным параметром обработчика. Если привязки нет,
|
||||
цепочка прерывается до REST-запроса, а пользователь получает инструкцию открыть
|
||||
приложение в Битрикс24.
|
||||
|
||||
## Последствия
|
||||
|
||||
Конструкторы принимают зависимости явно, поэтому сервисы можно тестировать с
|
||||
имитационными репозиториями и REST-клиентами. Проверка middleware ограждает
|
||||
пользователя от ошибочного поведения и гарантирует наличие привязки перед
|
||||
обращением к CRM.
|
||||
@@ -0,0 +1,83 @@
|
||||
# ADR-005: получение списка и карточки сделки из Битрикс24
|
||||
|
||||
**Статус:** Принято
|
||||
**Дата:** 2026-07-23
|
||||
|
||||
## Контекст
|
||||
|
||||
Приложение не копирует CRM-данные в локальную базу. Сведения о сделках,
|
||||
контактах, компаниях, стадиях и истории запрашиваются через REST API
|
||||
непосредственно в момент действия пользователя. PostgreSQL хранит только данные,
|
||||
необходимые для идентификации пользователя и выполнения авторизованных запросов.
|
||||
|
||||
Реализация работает с сущностью сделки и методами `crm.deal.*`. Команды `/leads`
|
||||
и `/lead` используются как пользовательские псевдонимы `/deals` и `/deal`.
|
||||
|
||||
## Решение
|
||||
|
||||
Для получения и изменения данных применяются следующие методы REST API
|
||||
Битрикс24.
|
||||
|
||||
| Метод | Назначение | Ключевые параметры |
|
||||
|-----------------------|-------------------------------|----------------------------------------|
|
||||
| crm.deal.list | Список и пагинация | filter, select, order, start |
|
||||
| crm.deal.get | Карточка и контроль состояния | id |
|
||||
| crm.deal.update | Ответственный и стадия | id, fields, REGISTER_HISTORY_EVENT |
|
||||
| crm.status.list | Стадии воронки и источники | ENTITY_ID, STATUS_ID |
|
||||
| crm.contact.get | ФИО и телефон контакта | id |
|
||||
| crm.company.get | Название и телефон компании | id |
|
||||
| crm.stagehistory.list | История переходов | entityTypeId=2, OWNER_ID |
|
||||
| crm.activity.todo.add | Отложенный звонок | ownerTypeId=2, deadline, responsibleId |
|
||||
|
||||
*Таблица ADR-005/1. Используемые методы REST API Битрикс24*
|
||||
|
||||
### Формирование списка
|
||||
|
||||
Названия стадий не зашиты в интерфейсе. Метод `crm.status.list` получает
|
||||
актуальную конфигурацию воронки, после чего первая стадия трактуется как
|
||||
псевдофильтр `new`. Карта стадий кэшируется в памяти на 300 секунд отдельно для
|
||||
портала, пользователя и категории. Дополнительно добавляется фильтр «Все», не
|
||||
передающий `STAGE_ID` в Битрикс24.
|
||||
|
||||
Размер страницы Telegram равен пяти сделкам, тогда как Битрикс24 может
|
||||
возвращать другое количество элементов за запрос. `DealService` собирает
|
||||
REST-страницы по полю `next` до тех пор, пока не сможет выделить диапазон
|
||||
`[page * limit; page * limit + limit)`. Значение `total` используется для
|
||||
расчёта общего числа страниц. Список сделок запрашивается методом
|
||||
`crm.deal.list` с параметрами фильтрации.
|
||||
|
||||

|
||||
|
||||
*Рисунок ADR-005/1. Список сделок в интерфейсе Telegram*
|
||||
|
||||
### Формирование карточки
|
||||
|
||||
Получение карточки сделки продолжает сценарий работы со списком.
|
||||
|
||||

|
||||
|
||||
*Рисунок ADR-005/2. Диаграмма последовательности просмотра списка и карточки
|
||||
сделки*
|
||||
|
||||
Карточка загружается методом `crm.deal.get`, затем обогащается данными связанных
|
||||
сущностей. Для контакта составляется ФИО и выбирается первый телефон; при
|
||||
отсутствии телефона контакта проверяется компания. Идентификаторы источника и
|
||||
стадии преобразуются в человекочитаемые названия. В итоговое сообщение
|
||||
включаются сумма, валюта, ответственный, дата создания и комментарий.
|
||||
|
||||
Все динамические строки перед включением в HTML-ответ Telegram проходят
|
||||
`html.escape`. Длина карточки ограничена 3900 символами, что оставляет запас до
|
||||
ограничения Telegram и предотвращает ошибку отправки из-за длинного комментария.
|
||||
Кнопка назначения отображается только для новой сделки, если текущий
|
||||
пользователь ещё не является ответственным.
|
||||
|
||||

|
||||
|
||||
*Рисунок ADR-005/3. Карточка сделки в интерфейсе Telegram*
|
||||
|
||||
## Последствия
|
||||
|
||||
Битрикс24 остаётся источником актуальных CRM-данных, а локальная база не требует
|
||||
синхронизации сделок и связанных сущностей. В качестве дальнейшего развития
|
||||
предусмотрен переход с устаревающих методов `crm.deal.*` на универсальные методы
|
||||
`crm.item.*`.
|
||||
@@ -0,0 +1,60 @@
|
||||
# ADR-006: изменение сделки с проверкой актуального состояния
|
||||
|
||||
**Статус:** Принято
|
||||
**Дата:** 2026-07-23
|
||||
|
||||
## Контекст
|
||||
|
||||
Состояние сделки может измениться в Битрикс24 после формирования карточки в
|
||||
Telegram, но до нажатия callback-кнопки. Используемый метод `crm.deal.update` не
|
||||
предоставляет условный `UPDATE`, поэтому перед изменением требуется проверить,
|
||||
что показанное пользователю состояние остаётся актуальным.
|
||||
|
||||
## Решение
|
||||
|
||||
### Назначение ответственного и изменение стадии
|
||||
|
||||
Callback-кнопка назначения содержит не только id сделки, но и `ASSIGNED_BY_ID`,
|
||||
который был показан пользователю:
|
||||
`deal:assign:<deal_id>:<expected_responsible_id>`. Перед изменением сервис
|
||||
повторно загружает сделку и сравнивает фактического ответственного с ожидаемым.
|
||||
Если карточка устарела, REST-обновление не выполняется.
|
||||
|
||||
Внутри одного процесса операции по паре `(member_id, deal_id)` последовательно
|
||||
выполняются под `asyncio.Lock`. После `crm.deal.update` сервис повторно читает
|
||||
сделку и убеждается, что ответственным стал `bitrix_user_id` привязанного
|
||||
пользователя.
|
||||
|
||||

|
||||
|
||||
*Рисунок ADR-006/1. Диаграмма последовательности взятия сделки в работу*
|
||||
|
||||
При обновлении одновременно передаются `ASSIGNED_BY_ID` связанного пользователя,
|
||||
рабочая `STAGE_ID` и параметр `REGISTER_HISTORY_EVENT=Y`. После REST-запроса
|
||||
выполняется контрольное чтение сделки.
|
||||
|
||||
### Планирование звонка и просмотр истории
|
||||
|
||||
Действие «Позвонить позже» создаёт в Битрикс24 дело типа `todo` с крайним сроком
|
||||
через один час. Владельцем является сделка (`ownerTypeId=2`), а ответственным —
|
||||
связанный пользователь Битрикс24. Массив `pingOffsets=[0]` включает напоминание
|
||||
в момент наступления срока.
|
||||
|
||||
История загружается методом `crm.stagehistory.list` с фильтром `OWNER_ID` и
|
||||
сортировкой по убыванию идентификатора. В интерфейс выводятся первые пять
|
||||
событий. Для каждого события идентификатор стадии преобразуется в название с
|
||||
учётом `CATEGORY_ID`, после чего `DealFormatter` формирует защищённый HTML-текст
|
||||
и клавиатуру возврата к карточке или списку.
|
||||
|
||||

|
||||
|
||||
*Рисунок ADR-006/2. Диаграмма последовательности планирования звонка и просмотра
|
||||
истории*
|
||||
|
||||
## Последствия
|
||||
|
||||
Локальный `asyncio.Lock` защищает только один процесс `bot`. При горизонтальном
|
||||
масштабировании на несколько контейнеров потребуется распределённая блокировка
|
||||
либо серверная условная операция. Повторная проверка REST-результата сохраняет
|
||||
защиту от внешних изменений, но не делает два удалённых вызова одной транзакцией
|
||||
Битрикс24.
|
||||
@@ -0,0 +1,46 @@
|
||||
# ADR-007: развёртывание приложения с помощью Docker Compose
|
||||
|
||||
**Статус:** Принято
|
||||
**Дата:** 2026-07-23
|
||||
|
||||
## Контекст
|
||||
|
||||
Программное решение состоит из PostgreSQL, сервиса миграций, Telegram-бота и
|
||||
Flask-сайта. База данных должна быть готова до запуска приложений, а
|
||||
SQL-миграции должны выполняться последовательно с сохранением истории
|
||||
применения.
|
||||
|
||||
## Решение
|
||||
|
||||
Для развёртывания используется Docker Compose из четырёх основных сервисов:
|
||||
`db`, `site`, `bot` и `migrate`. Сервисы базы данных и миграций используют
|
||||
готовые образы на основе Alpine Linux. Модули приложения собираются с помощью
|
||||
Dockerfile на базе среды выполнения Python 3.13 и включают необходимые
|
||||
библиотеки.
|
||||
|
||||
Первым запускается контейнер `db` с PostgreSQL, который также импортирует
|
||||
первичные настройки ролей. Контейнер PostgreSQL не публикует порт на хост и
|
||||
остаётся доступным только внутри сети Compose.
|
||||
|
||||
Затем запускается контейнер `migrate`. Он последовательно применяет SQL-скрипты
|
||||
из `db/migrations` и сохраняет историю их применения в таблице
|
||||
`public.schema_migrations`. Для каждой миграции хранится контрольная сумма. Если
|
||||
уже применённый файл был изменён, выполнение завершается с ошибкой.
|
||||
|
||||
После успешного завершения миграций запускаются Telegram-бот и Flask-сайт.
|
||||
Условиями их запуска являются нормальное состояние контейнера базы данных и
|
||||
успешное завершение контейнера `migrate`.
|
||||
|
||||
Сайт доступен только через loopback-адрес `127.0.0.1`. Внешний nginx принимает
|
||||
HTTPS-трафик и передаёт заголовки `X-Forwarded-*`. Бот не имеет входящего порта
|
||||
и получает обновления методом long polling.
|
||||
|
||||
Контейнеры приложения выполняются от системного пользователя `runtime`, а не от
|
||||
`root`.
|
||||
|
||||
## Последствия
|
||||
|
||||
Миграции выполняются до запуска прикладных процессов. PostgreSQL не публикуется
|
||||
наружу, сайт доступен извне только через HTTPS-прокси, а Telegram-бот не требует
|
||||
входящего сетевого порта. Изменение уже применённой миграции обнаруживается по
|
||||
несовпадению контрольной суммы.
|
||||
@@ -0,0 +1,70 @@
|
||||
# ADR-008: хранение интеграционных данных и разграничение доступа
|
||||
|
||||
**Статус:** Принято
|
||||
**Дата:** 2026-07-23
|
||||
|
||||
## Контекст
|
||||
|
||||
Основное назначение базы данных — хранение авторизационных данных, токенов
|
||||
привязки, OAuth-токенов и связей между пользователями Битрикс24 и Telegram.
|
||||
Сделки, контакты, компании, стадии и история остаются в Битрикс24 и в локальной
|
||||
базе не дублируются.
|
||||
|
||||
## Решение
|
||||
|
||||
Модель хранения нормализована вокруг портала Битрикс24.
|
||||
|
||||

|
||||
|
||||
*Рисунок ADR-008/1. Схема базы данных*
|
||||
|
||||
| Таблица | Ключевые данные | Назначение и ограничения |
|
||||
|------------------------|-------------------------------------------------|-------------------------------------------------------|
|
||||
| binding.portals | member_id, domain | Справочник порталов; member_id уникален |
|
||||
| binding.tokens | token_hash, expires_at, consumed_at, revoked_at | Одноразовые ссылки; токен хранится только как хеш |
|
||||
| binding.user_bindings | portal_id, bitrix_user_id, telegram_user_id | Однозначная привязка пользователей в пределах портала |
|
||||
| oauth.user_credentials | access_token, refresh_token, version, lock | Зашифрованные OAuth-данные и координация обновления |
|
||||
|
||||
*Таблица ADR-008/1. Назначение таблиц базы данных*
|
||||
|
||||
Поле `member_id` является устойчивым внешним идентификатором портала, а числовой
|
||||
`bitrix_user_id` имеет смысл только вместе с `portal_id`. Связи с `portals`
|
||||
используют `ON DELETE CASCADE`: удаление портала автоматически удаляет его
|
||||
ссылки, привязки и OAuth-данные. Составной первичный ключ
|
||||
`oauth.user_credentials(portal_id, bitrix_user_id)` исключает две конкурирующие
|
||||
записи учётных данных одного пользователя. Поля `consumed_at` и `revoked_at`
|
||||
разделяют два независимых основания недействительности одноразовой ссылки.
|
||||
|
||||
Для работы с данными в PostgreSQL созданы две роли: `site_role` и `bot_role`.
|
||||
Они имеют разные права доступа к хранимым функциям и не могут редактировать
|
||||
таблицы напрямую.
|
||||
|
||||
| Функция | Вызывающая роль | Назначение |
|
||||
|-----------------------------|-----------------|-------------------------------------------------------------------------------------|
|
||||
| binding.issue_v1 | site_role | Сохранить OAuth-данные, отозвать старые ссылки и выпустить новую в одной транзакции |
|
||||
| binding.consume_v1 | bot_role | Однократно погасить ссылку и создать привязку |
|
||||
| binding.find_by_telegram_v1 | bot_role | Получить актуальную привязку Telegram |
|
||||
| oauth.get_credentials_v1 | bot_role | Получить учётные данные связанного пользователя |
|
||||
| oauth.claim_refresh_v1 | bot_role | Получить короткую аренду на обновление токена |
|
||||
| oauth.finish_refresh_v1 | bot_role | Атомарно записать новую пару при совпадении версии |
|
||||
| oauth.release_refresh_v1 | bot_role | Освободить аренду после ошибки |
|
||||
|
||||
*Таблица ADR-008/2. Контракт хранимых функций PostgreSQL*
|
||||
|
||||
Сайт использует роль `site_role` и может вызывать только функцию привязки.
|
||||
Telegram-бот использует `bot_role` и может вызывать функции погашения токенов,
|
||||
получения привязки и работы с OAuth. Роль `site_role` не имеет доступа к схеме
|
||||
`oauth` и не может читать сохранённые токены; роль `bot_role` не может выпускать
|
||||
новые ссылки от имени сайта.
|
||||
|
||||
Все функции объявлены `SECURITY DEFINER` и фиксируют `search_path` в
|
||||
`pg_catalog`, что уменьшает риск подмены объектов. Права `PUBLIC` на таблицы и
|
||||
функции отозваны.
|
||||
|
||||
## Последствия
|
||||
|
||||
Конкурирующие вызовы `binding.consume_v1` для одного хеша не смогут одновременно
|
||||
пройти условие `consumed_at IS NULL`: `UPDATE` блокирует строку, а после
|
||||
завершения первой транзакции второй вызов видит уже установленное время
|
||||
погашения. Проверка и изменение не разделены между приложением и базой, поэтому
|
||||
отсутствует окно гонки между `SELECT` и `UPDATE`.
|
||||
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 58 KiB |
|
After Width: | Height: | Size: 55 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 102 KiB |
|
After Width: | Height: | Size: 43 KiB |
|
After Width: | Height: | Size: 213 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 32 KiB |
@@ -1,13 +1,43 @@
|
||||
# Архитектурные решения (ADR)
|
||||
# Архитектурные решения BitrixDealsBot
|
||||
|
||||
## Введение
|
||||
В ходе производственной практики разработано серверное приложение
|
||||
BitrixDealsBot, которое предоставляет менеджеру по продажам интерфейс Telegram
|
||||
для работы со сделками CRM Битрикс24. Разработанное решение не копирует
|
||||
CRM-данные в локальную базу: все сведения о сделках, контактах, компаниях,
|
||||
стадиях и истории запрашиваются через REST API непосредственно в момент действия
|
||||
пользователя. PostgreSQL хранит только данные, необходимые для идентификации
|
||||
пользователя Битрикс24 и его привязки к пользователю Telegram-бота, включая
|
||||
OAuth-токены.
|
||||
|
||||
Данный раздел содержит архитектурные решения (ADR) для проекта.
|
||||
Архитектурные решения описывают ключевые решения, принятые в процессе разработки
|
||||
системы, включая выбор технологий, подходов и структурных решений.
|
||||
В индивидуальном задании используется термин «лид», однако реализация работает с
|
||||
сущностью сделки и методами `crm.deal.*`. Команды `/leads` и `/lead` сохранены
|
||||
как пользовательские псевдонимы `/deals` и `/deal`, поэтому интерфейс остаётся
|
||||
совместимым с формулировкой задания, а в документации используется технически
|
||||
точное понятие «сделка».
|
||||
|
||||
## Оглавление
|
||||
## Технологический стек
|
||||
|
||||
**В данном разделе представлены следующие архитектурные решения:**
|
||||
| Уровень | Технология | Назначение |
|
||||
|---------------|---------------------------|--------------------------------------------------------------------------|
|
||||
| Язык | Python 3.13 | Серверная логика сайта и Telegram-бота |
|
||||
| Telegram | aiogram 3 | Асинхронная маршрутизация команд, callback-запросов и опроса сервера |
|
||||
| HTTP-сервер | Flask 3 + Gunicorn | Страница привязки пользователя Битрикс24 к конкретному аккаунту Telegram |
|
||||
| Запросы к API | httpx | Синхронные и асинхронные запросы к OAuth и REST API Битрикс24 |
|
||||
| Хранилище | PostgreSQL 17 + Psycopg 3 | Транзакции, хранимые функции и пулы соединений |
|
||||
| Защита | Fernet + SHA-256 | Шифрование OAuth-токенов и хеширование одноразовых ссылок |
|
||||
| Развёртывание | Docker Compose + nginx | Изоляция процессов, миграции, HTTPS и обратное проксирование |
|
||||
|
||||
_(В процессе разработки будут добавляться новые решения)_
|
||||
*Таблица 1. Технологический стек решения*
|
||||
|
||||
## Состав группы ADR
|
||||
|
||||
| ADR | Архитектурное решение | Статус |
|
||||
|----------------------------------------------|-----------------------------------------------------------|---------|
|
||||
| [ADR-001](001-apps-and-database.md) | Разделение приложения на сайт, Telegram-бот и базу данных | Принято |
|
||||
| [ADR-002](002-oauth-telegram-binding.md) | Привязка пользователей Битрикс24 и Telegram | Принято |
|
||||
| [ADR-003](003-oauth-credential-lifecycle.md) | Защита и обновление OAuth-токенов | Принято |
|
||||
| [ADR-004](004-bot-layers.md) | Слоистая организация Telegram-бота | Принято |
|
||||
| [ADR-005](005-bitrix-deal-read-model.md) | Получение списка и карточки сделки из Битрикс24 | Принято |
|
||||
| [ADR-006](006-guarded-deal-mutations.md) | Изменение сделки с проверкой актуального состояния | Принято |
|
||||
| [ADR-007](007-container-deployment.md) | Развёртывание приложения с помощью Docker Compose | Принято |
|
||||
| [ADR-008](008-data-storage-and-access.md) | Хранение интеграционных данных и разграничение доступа | Принято |
|
||||
|
||||