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