Вдохновившись различными проектами по переделке получения тележных сообщений в более привычный 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-ов, различных типов команд и прочего. Но это уже совсем другая история.
Итого у нас есть легковесный стартер написанный на основе популярной, а главное живой библиотеке.
Автор: broadcastination