2024. 2. 27. 15:30ㆍ스프링
회사 내부 프로젝트에서 결재 알림을 위해 텔레그램 메시지를 활용하기로 했다.
사실은 웹푸시로 개발하려 준비하고 있었지만 토큰 관리와 모바일 기기 푸시 문제로 텔레그램 메시지로 선회하였다.
우선 telegram 같은 경우 longPolling과 webhook을 모두 지원하는데
간략하게 설명을 하자면
롱폴링같은 경우 지속적으로 메시지 요청을 보내고 계속해서 확인하는 방법이다.
계속해서 연결을 유지하며 이벤트가 메시지를 감지해서 data를 전달해주는 방법인데 실제론 몇시간동안 연결이 유지되는 건 아니고 일정시간 간격으로 다시 요청하는 방식이다.
웹훅같은 경우는 데이터를 주는 곳에 직접 접근할 수 있는 url을 제공하는 것이다.
롱폴링의 경우 데이터를 받는 서버에서 지속적으로 요청을 한다면 웹훅은 데이터를 주는 곳이 주도건을 잡고 직접 만든 url에 보내주는 것이다.
웹훅에 비해 롱폴은 더 간단한 편이다. 도메인이나 공개할 url이 필요하지 않고 ssl인증서 설정을 조작할 필요가 없다.
웹훅은 특히 https에서만 작동하게 된다. 그러나 웹훅은 롱폴링에 비해 서비스 부하가 덜하다.
spring으로 두 방식 모두 구현을 해봤고 최종적으로는 longPolling으로 가져가기로 했다.
Long Polling
@Component
@ConfigurationProperties("telegram")
@RequiredArgsConstructor
@Slf4j
public class CetusTelegramBot extends TelegramLongPollingBot {
@Setter
@Getter
private String botUsername;
@Setter
@Getter
private String botToken;
private final CetusUserService cetusUserService;
public void sendMessage(String userId, String userNm, String message) {
TelegramUser sendUser = cetusUserService.getTelegramIdAndUserNm(new TelegramUser(userId))
.filter(user -> user.getChatId() != null)
.orElseThrow(() -> new IllegalArgumentException("chatId를 가지고 있지 않습니다"));
resMessage(sendUser.getChatId(), userNm, message);
}
@Override
public void onUpdateReceived(Update update) {
if (update.hasMessage() && update.getMessage().hasText()) {
String receivedText = update.getMessage().getText();
String chatId = String.valueOf(update.getMessage().getChatId());
if ("/myChatId".equals(receivedText)) {
resMessage(chatId, "chatId: " + chatId);
}
}
}
private void resMessage(String chatId, String text) {
SendMessage responseMessage = new SendMessage();
responseMessage.setChatId(chatId);
responseMessage.setText(text);
try {
execute(responseMessage);
} catch (Exception e) {
e.printStackTrace(); // 전송 중 예외 발생 시 예외를 출력
}
}
private void resMessage(String chatId, String userNm, String text) {
SendMessage responseMessage = new SendMessage();
responseMessage.setChatId(chatId);
responseMessage.setText(userNm + text);
try {
execute(responseMessage);
} catch (Exception e) {
e.printStackTrace(); // 전송 중 예외 발생 시 예외를 출력
}
}
정말 간단하게 텔레그램 bot을 만들고 username과 token을 가져와서
빈 등록을 해주면 onUpdateReceived 메소드에서 텔레그램 메시지 정보들을 읽어와서 가공할 수 있다.
저 클래스를 의존성 주입해서 sendMessage() 메소드를 호출하면 개별 유저에게 메시지를 보낼 수도 있다.
지속적인 연결이 필요한 롱폴링 방식이므로 하나의 봇은 하나의 서버만 붙을 수 있다.
Webhook
@Getter
@Setter
@Configuration
@ConfigurationProperties(prefix = "telegram")
public class BotConfig {
private String botUsername;
private String botToken;
@Bean
public SetWebhook setWebhookInstance() {
return SetWebhook.builder().url("webhook url").build();
}
@Bean
public CetusTelegramBot cetusTelegramBot() throws TelegramApiException {
CetusTelegramBot cetusTelegramBot = new CetusTelegramBot();
cetusTelegramBot.setBotUsername(botUsername);
cetusTelegramBot.setBotToken(botToken);
cetusTelegramBot.setWebhook(setWebhookInstance());
try {
TelegramBotsApi telegramBotsApi = new TelegramBotsApi(DefaultBotSession.class);
telegramBotsApi.registerBot(cetusTelegramBot, setWebhookInstance());
} catch (TelegramApiException e) {
e.printStackTrace();
}
return cetusTelegramBot;
}
}
webhook url을 통해서 텔레그램에 webhook을 설정해준다. https만 지원하므로 로컬에선 ngrok을 써야한다.
로컬에서 테스트를 하려면 ngrok에서 주소를 받아서 기입해준다.
@Slf4j
@Getter
@Setter
public class CetusTelegramBot extends TelegramWebhookBot {
private String botUsername;
private String botToken;
private String botPath;
@Override
public BotApiMethod<?> onWebhookUpdateReceived(Update update) {
final String chatId = update.getMessage().getChatId().toString();
final Long userId = update.getMessage().getFrom().getId();
String msg = update.getMessage().getText();
log.info("CallbackQuery received from userId: {}, chatId:{} message: {}", userId, chatId, msg);
resMessage(chatId, "chatId: " + userId);
return null;
}
private void resMessage(String chatId, String text) {
SendMessage responseMessage = new SendMessage();
responseMessage.setChatId(chatId);
responseMessage.setText(text);
try {
execute(responseMessage);
} catch (Exception e) {
e.printStackTrace(); // 전송 중 예외 발생 시 예외를 출력
}
}
private void resMessage(String chatId, String userNm, String text) {
SendMessage responseMessage = new SendMessage();
responseMessage.setChatId(chatId);
responseMessage.setText(userNm + text);
try {
execute(responseMessage);
} catch (Exception e) {
e.printStackTrace(); // 전송 중 예외 발생 시 예외를 출력
}
}
}
위에 롱폴링 방식과 다르게 CetusTelegramBot 자체를 빈에 등록하지 않았고 일반 클래스로 정의 한 뒤에
BotConfig에서 가져와서 빈 등록을 해주었다. webhook을 설정하기 위함이다.
@PostMapping("/webhook-response/callback")
public BotApiMethod<?> webhookResponse(@RequestBody Update update) {
log.info("Update received id: {}", update.getUpdateId());
return bot.onWebhookUpdateReceived(update);
}
메시지를 보내면 이 콜백 url을 타서 onWebhookUpdateReceived 메소드에 리턴 해주고 롱폴링 방식과 같이 메시지를 수신할 수 있게 된다.
'스프링' 카테고리의 다른 글
spring filterChain 동작과정 (1) | 2023.11.15 |
---|---|
스프링부트 개발 가이드 (1) | 2023.11.08 |
springboot redis 연동 (0) | 2023.10.11 |
네이버 클라우드 sens 알림톡 발송 (0) | 2023.09.26 |
yml 파일 암호화 (1) | 2023.08.29 |