Вдохновившись различными проектами по переделке получения тележных сообщений в более привычный Spring MVC-ый формат, решил написать про легковесный стартер. Оглядевшись вокруг, быстро понял, что все текущие реализации, к сожалению, заброшены, за исключением, наверное, самой популярной либы - TelegramBots. Её и решил взять за основу.
Начнем с аннотаций.
@Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface BotRequestMapping { @AliasFor("value") String[] command() default {}; @AliasFor("command") String[] value() default {}; }
Теперь научим эту аннотацию получать сообщения. Если посмотреть на реализацию регистрации бота, там всего 2 бина, которые нужно переопределить - TelegramBotInitializer и TelegramBotsLongPollingApplication. С них и начнём.
@Bean @ConditionalOnMissingBean(TelegramBotsLongPollingApplication.class) public TelegramBotsLongPollingApplication telegramBotsApplication() { return new TelegramBotsLongPollingApplication(); }
@Bean @ConditionalOnMissingBean public TelegramBotInitializer telegramBotInitializer(TelegramBotsLongPollingApplication telegramBotsApplication, ObjectProvider<List<SpringLongPollingBot>> longPollingBots) { return new TelegramBotInitializer(telegramBotsApplication, longPollingBots.getIfAvailable(Collections::emptyList)); }
Отлично, теперь нам нужно зарегистрировать своего бота. Добавим ещё один бин.
@Bean public SpringLongPollingBot springLongPollingBot(TelegramBotProperties properties, BotRequestProcessor botRequestProcessor) { return new SpringLongPollingBot() { @Override public String getBotToken() { return properties.getToken(); } @Override public LongPollingUpdateConsumer getUpdatesConsumer() { return updates -> updates.forEach(botRequestProcessor::handleUpdate); } }; }
Создаем реализацию SpringLongPollingBot и реализуем два метода - отдаём токен и получаем апдейты. Далее, уже привычно, отдаём апдейты на съедение BeanPostProcessor-у.
@Slf4j @Component public class BotRequestProcessor implements BeanPostProcessor { private final Map<String, BotHandlerMethod> handlers = new HashMap<>(); @Override public Object postProcessAfterInitialization(Object bean, @NotNull String beanName) { for (Method method : bean.getClass().getDeclaredMethods()) { // Ищем нашу аннотцию BotRequestMapping annotation = AnnotationUtils.findAnnotation(method, BotRequestMapping.class); if (annotation != null) { for (String command : annotation.value()) { // Берем все команды из аннотации handlers.put(command, new BotHandlerMethod(bean, method)); // И сохраняем log.debug("✅ Registered command handler '{}': {}.{}", command, bean.getClass().getSimpleName(), method.getName()); } } } return bean; } public void handleUpdate(Update update) { if (update.hasMessage() && update.getMessage().hasText()) { // Проверяем, что сообщение текстовое String text = update.getMessage().getText().trim(); BotHandlerMethod handlerMethod = handlers.get(text); // Ищем обработчик команды if (handlerMethod != null) { try { handlerMethod.invoke(update); // Вызываем нужный метод } catch (Exception e) { log.error("Error on calling handler '{}': {}", text, e.getMessage(), e); } } else { log.warn("No handler found for: {}", text); } } } private record BotHandlerMethod(Object bean, Method method) { void invoke(Update update) throws Exception { method.invoke(bean, update); // Вызываем метод и передаем в него update } } }
Уверен многие написали уже не один спринговый бин-пост-процессор, но всё же оставил комментарии для самых маленьких.
Теперь сделаем из этого стартер. Создадим папку в сорсах META-INF/spring и положим туда путь до наших конфигураций.
com.flux.spring.boot.telegram.mapping.config.TelegramBotAutoConfiguration
Стартер готов. Как пользоваться?
Добавляем зависимость.
<dependency> <groupId>com.flux</groupId> <artifactId>spring-boot-telegram-mapping</artifactId> </dependency>
И прописываем токен бота в application.properties/yml.
telegram: bot: token: "secret"
Теперь вы счастливый обладатель маппинга в стиле Srping MVC.
@BotRequestMapping("/start") public void startCommand(Update update) { log.info("/start {}", update.getMessage().getFrom().getUserName()); }
Конечно, можно пойти дальше. Добавить функционал для отправки сообщений, поддержку Webhook-ов, различных типов команд и прочего. Но это уже совсем другая история.
Итого у нас есть легковесный стартер написанный на основе популярной, а главное живой библиотеке.
