Это мой первый 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"
