Архитектура telegram-бота. На горутинах и каналах

—

от автора

Это мой первый telegram-бот и моя первая разработка на go. В качестве базы данных используются google-таблицы, благодаря чему большая часть логики находиться в них. Но к сути это отношения имеет мало. Основной причиной написания этой статьи стало то, что проект который я разрабатывал, уже технически реализованный, отложили что бы маркетингом «навести суету» по схемам десятилетней давности. Полагаю проект всё равно будет заброшен, по крайней мере у меня уже нет сил его «продавливать». Хоть я и не могу опубликовать исходный код (полностью), но могу поделиться той схемой к которой пришёл. Возможно это кому то пригодиться.

Архитектура

Есть основная структура в которую упаковываются запросы и ответы:

type tgRequest struct { // Структура для хранения сообщения и ответов на него MessageText                string                        // Текст сообщения KeyData                    string                        // Нажатая кнопка TextRequest                string                        // При заполнении структуры, этому полю присваивается значение дублирующее текст сообщения или нажатую кнопку ClientUserName             string                        // Имя пользователя сделавшего запрос ChatID                     int64                         // Идентификатор чата с ним MessageReplyID             int                           // Идентификатор сообщения на которое нужно ответить TimeRequest                float64                       // Время регистрации запроса. Предназначено для того что бы уведомлять пользователя о задержке, и предотвращать повторный запрос. Responses                  []string                      // Массив ответов на запрос. request.Responses = append(request.Responses, "Ещё один ответ на запрос.") tgKeyboard                 tgbotapi.InlineKeyboardMarkup // Кнопки телеграмма для ответа CloseRequest               bool                          // Присвоение True означает что запрос следует удалить из листа ожидания. StopGo                     bool                          // Для остановки горутины TimeUNIXtoWaitNotification int64                         // Время UNIX после которого произойдёт оповещение пользователя о том что его запрос задерживается DisableNotification        bool                          // Отключение уведомления DialogChan                 chan<- tgRequest              // Возвращаемый канал для диалога  IncomingFileURL string // вложенный файл, URL для загрузки SaveFilePath    string // Имя файла который был сохранён SaveFileName    string // Имя файла  DownloadPathFile    string // Путь к файлу на локальном накопителе, который нужно отправить пользователю RenameDownlodFile   string // Нужно ли переименовать файл. Пока что не используется. DownloadFileIsMedia bool   // Означает что файл надо отправить не как "документ", а как например фото. }

Заполняется эта структура разбором var updates tgbotapi.UpdatesChannel в секции select.

После чего запросы распределяются между горутинами. Есть несколько горутин.

  • Одна по очереди принимает и обрабатывает те запросы которые нельзя обрабатывать одновременно.

  • Для всех запросов которые не входят в ограниченный список для которых запрещена параллельная обработка, запускаются отдельные горутины.

  • Все запросы посланные в предыдущие две, дублируются в горутину для уведомлений, она регистрирует запросы и если через неё в течении нескольких секунд не проходит ответ, то она посылает пользователю уведомление «подождите».

  • Есть горутина для приёма отправляемых файлов.

Ещё есть глобальная карта, в которой ключом является ChatID, а значением указатель на канал. Есть в карте есть значение !=nil то запросы направляются в этот канал, а не в упомянутые выше горутины.

// Создание каналов для общения с потоками syncRequestChan := make(chan tgRequest, 1000)    // Канал для синхронных запросов waitQueueList := make(chan tgRequest, 1000)      // Канал для листа ожидания. Смысл горутины его использующей, в том что бы всем пользователям ожидающим больше некоторого времени, посылать успокаивающее сообщение notificationLogStop := make(chan tgRequest, 10)  // Канал для остановки уведомлений администраторов о журналах answersChan := make(chan tgRequest, 1000)        // Канал для ответов loadFilesChan := make(chan tgRequest, 1000)      // Канал для загрузки файлов stopNotificationOp := make(chan tgRequest, 1000) // Канал для остановки уведомлений операторов  // Запуск потоков go waitQueueListRequest(waitQueueList, answersChan)  // Запуск потока который будет оповещать пользователей когда выполнение из запросов задерживается. go processResponseSync(syncRequestChan, answersChan) // Запуск потока который обрабатывает запросы пользователей последовательно, то есть по очереди go processNotificationLog(notificationLogStop, answersChan) // processResponseAsync(requestChan chan<- tgRequest, Request tgRequest) будет запускаться для каждого асинхронного запроса отдельно go processLoadFiles(loadFilesChan, answersChan) // Процесс для загрузки файлов go processNotificationOrders(stopNotificationOp, answersChan)

Схема:

Основная функция отслеживает ответы горутин приходящие через канал answersChan и направляет их пользователям.

Приблизительная схема основных этапов

Приблизительная схема основных этапов

Сначала через API телеграмм приходит сообщение или нажатие на кнопку. Оно конвертируется в структуру типа tgRequest и распределяется между горутиной для последовательной обработки запросов, например для финансовых операций. И параллельным выполнением в отдельной горутине. Распределение происходит по списку команд.

var SyncСommandPrefixes = map[string]bool{ // Перечисленные в этом массиве команды выполняются синхронно, остальные асинхронно "/зачисление": true, "/depositing": true, // Административная (бухгалтерская) команда для зачисление средств на счёт пользователя. Синтаксис /depositing UserName +1000 "/резервирование": true, // Резервирование товара. Если введено число выше доступных остатков или доступных средств, то происходит меньшее резервирование. "/reservation":    true, // Пользовательская команда для резервирования определённого числа товара. Синтаксис /reservation артикул +10 // Если в параметре отрицательное число, то происходит снятие с резерва. "/order": true, "/заказ": true, //  Команда для совершения заказа из резерва }

Так же сообщение дублируется в горутину уведомителя о задержках.

Все горутины обрабатывающие запросы отправляют ответы в единый канал answersChan. Всё что в него приходит отправляется пользователям как ответы и дублируется в уведомитель о задержках.

Уведомитель о задержках, посылает снижающие суету сообщения, если в него пришёл запрос и в течении нескольких секунд не пришёл ответ.

Для упрощения в схеме не обозначена горутина принимающая файлы, отсылающая уведомления операторам о ошибках. А так же перенаправление всей обработки в «диалоги», то есть горутины отключающие обычную обработку команд для отдельных пользователей. И многие другие функции.

Используется библиотека tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"


ссылка на оригинал статьи https://habr.com/ru/articles/807759/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *