JavaRush /Java Blog /Random-KO /텔레그램 봇 - Java의 webHook을 통해 알림을 보내거나 Google 캘린더를 거부하세요! 2 ...
Vladimir Popov
레벨 41

텔레그램 봇 - Java의 webHook을 통해 알림을 보내거나 Google 캘린더를 거부하세요! 2 부

Random-KO 그룹에 게시되었습니다
프로젝트의 두 번째 부분 - 첫 번째 부분에 대한 링크는 다음과 같습니다. BotState 클래스 : 봇이 특정 시점에 예상되는 작업(예: 알림 삭제)을 이해하려면 다음을 수행해야 합니다. 지금 입력되어 전송되는 숫자는 목록에서 미리 알림 ID로 처리되어 삭제되어야 함을 어떻게든 우리 봇에게 알려주세요. 따라서 "삭제" 버튼을 클릭하면 봇은 BotState.ENTERNUMBEREVENT 상태로 전환됩니다 . 이는 봇 상태가 포함된 특별히 생성된 Enum 클래스입니다.
public enum BotState {
    ENTERDESCRIPTION,//the bot will wait for the description to be entered.
    START,
    MYEVENTS, //the bot show to user list events.
    ENTERNUMBEREVENT,//the bot will wait for the number of event to be entered.
    ENTERDATE, //the bot will wait for the date to be entered
    CREATE, //the bot run created event
    ENTERNUMBERFOREDIT, //the bot will wait for the number of event to be entered
    EDITDATE, //the bot will wait for the date to be entered
    EDITDESCRIPTION,//the bot will wait for the description to be entered
    EDITFREQ,//the bot will wait callbackquery
    ALLUSERS, // show all users
    ALLEVENTS, //show all events
    ENTERNUMBERUSER,//the bot will wait for the number of user to be entered.
    ENTERTIME,//the bot will wait for the hour to be entered.
    ONEVENT // state toggle
}
이제 숫자를 입력해야 합니다. " 목록에서 알림 번호를 입력하세요 ." 입력 후 원하는 처리 방법으로 이동합니다. 상태 스위치는 다음과 같습니다.
public class BotStateCash {
    private final Map<long, botstate=""> botStateMap = new HashMap<>();

    public void saveBotState(long userId, BotState botState) {
        botStateMap.put(userId, botState);
    }
}

</long,>
사용자 ID와 해당 상태가 포함된 일반 지도입니다. int adminId 필드는 나를 위한 것입니다.) 다음으로, handlerUpdate 메소드 의 로직은 이것이 어떤 종류의 메시지인지 확인합니다. 콜백 쿼리인가요 , 아니면 그냥 문자인가요? 이것이 일반 텍스트라면, main 메뉴 버튼을 처리하는 handlerInputMessage 메소드 로 이동하고, 버튼이 클릭되면 원하는 상태를 설정하지만, 클릭되지 않았고 이것이 익숙하지 않은 텍스트라면, 캐시에서 상태를 설정하고, 캐시가 없으면 시작 상태를 설정합니다. 그런 다음 텍스트는 필요한 상태로 핸들 메소드를 처리합니다. 이제 봇의 상태에 따라 메시지 처리를 담당하는 MessageHandler 클래스 의 논리를 제시합니다 .
public class MessageHandler {

    private final UserDAO userDAO;
    private final MenuService menuService;
    private final EventHandler eventHandler;
    private final BotStateCash botStateCash;
    private final EventCash eventCash;

    public MessageHandler(UserDAO userDAO, MenuService menuService, EventHandler eventHandler, BotStateCash botStateCash, EventCash eventCash) {
        this.userDAO = userDAO;
        this.menuService = menuService;
        this.eventHandler = eventHandler;
        this.botStateCash = botStateCash;
        this.eventCash = eventCash;
    }

    public BotApiMethod<!--?--> handle(Message message, BotState botState) {
        long userId = message.getFrom().getId();
        long chatId = message.getChatId();
        SendMessage sendMessage = new SendMessage();
        sendMessage.setChatId(String.valueOf(chatId));
        //if new user
        if (!userDAO.isExist(userId)) {
            return eventHandler.saveNewUser(message, userId, sendMessage);
        }
        //save state in to cache
        botStateCash.saveBotState(userId, botState);
        //if state =...
        switch (botState.name()) {
            case ("START"):
                return menuService.getMainMenuMessage(message.getChatId(),
                        "Воспользуйтесь главным меню", userId);
            case ("ENTERTIME"):
                //set time zone user. for correct sent event
                return eventHandler.enterLocalTimeUser(message);
            case ("MYEVENTS"):
                //list events of user
                return eventHandler.myEventHandler(userId);
            case ("ENTERNUMBEREVENT"):
                //remove event
                return eventHandler.removeEventHandler(message, userId);
            case ("ENTERDESCRIPTION"):
                //enter description for create event
                return eventHandler.enterDescriptionHandler(message, userId);
            case ("ENTERDATE"):
                //enter date for create event
                return eventHandler.enterDateHandler(message, userId);
            case ("CREATE"):
                //start create event, set state to next step
                botStateCash.saveBotState(userId, BotState.ENTERDESCRIPTION);
                //set new event to cache
                eventCash.saveEventCash(userId, new Event());
                sendMessage.setText("Введите описание события");
                return sendMessage;
            case ("ENTERNUMBERFOREDIT"):
                //show to user selected event
                return eventHandler.editHandler(message, userId);
            case ("EDITDESCRIPTION"):
                //save new description in database
                return eventHandler.editDescription(message);
            case ("EDITDATE"):
                //save new date in database
                return eventHandler.editDate(message);
            case ("ALLEVENTS"):
                //only admin
                return eventHandler.allEvents(userId);
            case ("ALLUSERS"):
                //only admin
                return eventHandler.allUsers(userId);
            case ("ONEVENT"):
                // on/off notification
                return eventHandler.onEvent(message);
            case ("ENTERNUMBERUSER"):
                //only admin
                return eventHandler.removeUserHandler(message, userId);
            default:
                throw new IllegalStateException("Unexpected value: " + botState);
        }
    }
}
핸들 메소드에서는 수신한 메시지의 상태를 확인하고 이를 이벤트 핸들러인 EventHandler 클래스로 보냅니다. 여기에는 MenuService와 EventCash라는 두 개의 새로운 클래스가 있습니다 . MenuService - 여기에서 모든 메뉴를 생성합니다. EventCash - BotStateCash 와 유사하며 입력 후 이벤트의 일부를 저장하고 입력이 완료되면 이벤트를 데이터베이스에 저장합니다.
@Service
@Setter
@Getter
// used to save entered event data per session
public class EventCash {

    private final Map<long, event=""> eventMap = new HashMap<>();

    public void saveEventCash(long userId, Event event) {
        eventMap.put(userId, event);
    }
}
</long,>
글쎄요. 이벤트를 생성하면 캐시에 새 Event 객체가 생성됩니다. -eventCash.saveEventCash(userId, new Event()); 그런 다음 이벤트에 대한 설명을 입력하고 이를 캐시에 추가합니다.
Event event = eventCash.getEventMap().get(userId);
event.setDescription(description);
//save to cache
eventCash.saveEventCash(userId, event);
그런 다음 숫자를 입력하십시오.
Event event = eventCash.getEventMap().get(userId);
event.setDate(date);
//save data to cache
eventCash.saveEventCash(userId, event);
CallbackQueryHandler 클래스는 MessageHandler 와 유사하지만 여기서는 콜백 쿼리 메시지만 처리합니다. 이벤트 작업 논리를 완전히 분석하는 것은 의미가 없습니다. EventHandler , 이미 너무 많은 문자가 있으며 코드의 메서드 이름과 주석에서 분명합니다. 그리고 300줄이 넘는 내용을 텍스트로 완전히 나열하는 것이 의미가 없다고 생각합니다. 다음은 Github 의 수업 링크입니다 . 메뉴를 생성하는 MenuService 클래스 도 마찬가지입니다 . 이에 대한 자세한 내용은 텔레그램 라이브러리 제조업체 웹사이트(https://github.com/rubenlagus/TelegramBots/blob/master/TelegramBots.wiki/FAQ.md) 또는 텔레그램 참고서(https://)에서 읽을 수 있습니다. tlgrm.ru/docs/bots /api 이제 가장 맛있는 부분이 남았습니다. EventService 메시지를 처리하기 위한 클래스입니다 .
@EnableScheduling
@Service
public class EventService {
    private final EventDAO eventDAO;
    private final EventCashDAO eventCashDAO;

    @Autowired
    public EventService(EventDAO eventDAO, EventCashDAO eventCashDAO) {
        this.eventDAO = eventDAO;
        this.eventCashDAO = eventCashDAO;
    }

    //start service in 0:00 every day
    @Scheduled(cron = "0 0 0 * * *")
    // @Scheduled(fixedRateString = "${eventservice.period}")
    private void eventService() {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(new Date());
        int day = calendar.get(Calendar.DAY_OF_MONTH);
        int month = calendar.get(Calendar.MONTH);
        int year = calendar.get(Calendar.YEAR);

        //get event list is now date
        List<event> list = eventDAO.findAllEvent().stream().filter(event -> {
            if (event.getUser().isOn()) {
                EventFreq eventFreq = event.getFreq();

                //set user event time
                Calendar calendarUserTime = getDateUserTimeZone(event);

                int day1 = calendarUserTime.get(Calendar.DAY_OF_MONTH);
                int month1 = calendarUserTime.get(Calendar.MONTH);
                int year1 = calendarUserTime.get(Calendar.YEAR);
                switch (eventFreq.name()) {
                    case "TIME": //if one time - remove event
                        if (day == day1 && month == month1 && year == year1) {
                            eventDAO.remove(event);
                            return true;
                        }
                    case "EVERYDAY":
                        return true;
                    case "MONTH":
                        if (day == day1) return true;
                    case "YEAR":
                        if (day == day1 && month == month1) return true;
                    default: return false;
                }
            } else return false;
        }).collect(Collectors.toList());

        for (Event event : list) {
            //set user event time
            Calendar calendarUserTime = getDateUserTimeZone(event);
            int hour1 = calendarUserTime.get(Calendar.HOUR_OF_DAY);
            calendarUserTime.set(year, month, day, hour1, 0, 0);

            String description = event.getDescription();
            String userId = String.valueOf(event.getUser().getId());

            //save the event to the database in case the server reboots.
            EventCashEntity eventCashEntity = EventCashEntity.eventTo(calendarUserTime.getTime(), event.getDescription(), event.getUser().getId());
            eventCashDAO.save(eventCashEntity);

            //create a thread for the upcoming event with the launch at a specific time
            SendEvent sendEvent = new SendEvent();
            sendEvent.setSendMessage(new SendMessage(userId, description));
            sendEvent.setEventCashId(eventCashEntity.getId());

            new Timer().schedule(new SimpleTask(sendEvent), calendarUserTime.getTime());
        }
    }

    private Calendar getDateUserTimeZone(Event event) {
        Calendar calendarUserTime = Calendar.getInstance();
        calendarUserTime.setTime(event.getDate());
        int timeZone = event.getUser().getTimeZone();

        //set correct event time with user timezone
        calendarUserTime.add(Calendar.HOUR_OF_DAY, -timeZone);
        return calendarUserTime;
    }
}

</event>
@EnableScheduling – Spring에서 예약된 작업을 활성화합니다. @Scheduled(cron = "0 0 0 * * *") – 매일 0시에 실행되도록 메서드를 구성합니다 . Calendar.setTime(new Date()); - 서버 시간을 설정합니다. 우리는 스트림과 람다의 마법을 통해 오늘의 알림 목록을 얻습니다. 우리는 수신된 목록을 살펴보고 올바른 전송 시간을 설정합니다. CalendarUserTime 그리고... 여기에서 시간이 지연된 프로세스를 피하고 시작하기로 결정했습니다. Java의 Time 클래스가 이를 담당합니다 . new Timer().schedule(new SimpleTask(sendEvent), CalendarUserTime.getTime()); 이를 위해 우리는 스레드를 생성해야 합니다:
public class SendEvent extends Thread {


    private long eventCashId;
    private SendMessage sendMessage;

    public SendEvent() {
    }

    @SneakyThrows
    @Override
    public void run() {
        TelegramBot telegramBot = ApplicationContextProvider.getApplicationContext().getBean(TelegramBot.class);
        EventCashDAO eventCashDAO = ApplicationContextProvider.getApplicationContext().getBean(EventCashDAO.class);
        telegramBot.execute(sendMessage);
        //if event it worked, need to remove it from the database of unresolved events
        eventCashDAO.delete(eventCashId);
    }
}
TimerTask 구현
public class SimpleTask extends TimerTask {
    private final SendEvent sendEvent;

    public SimpleTask(SendEvent sendEvent) {
        this.sendEvent = sendEvent;
    }

    @Override
    public void run() {
        sendEvent.start();
    }
}
예, 20분마다 데이터베이스를 살펴보고 메시지를 보낼 수 있다는 것을 잘 알고 있지만 처음에 이에 대한 모든 것을 썼습니다.)) 여기서 우리는 Heroku No. 1의 비참함도 접하게 됩니다. 무료 플랜에서는 약 550디노가 제공되는데, 이는 한 달에 애플리케이션이 작동하는 시간과 비슷합니다. 이는 애플리케이션을 한 달 동안 운영하기에는 충분하지 않지만 카드를 연결하면 눈에 충분한 450 디노가 추가로 제공됩니다. 카드가 걱정된다면 빈 카드를 연결해도 되는데, 카드에 0.6달러가 들어 있는지 확인하세요... 이것은 확인 금액이므로 계좌에 있어야 합니다. 관세를 직접 변경하지 않는 한 숨겨진 비용은 없습니다. 무료 계획에는 작은 문제가 하나 더 있습니다. 1a라고 부르겠습니다. 그들은 지속적으로 서버를 재부팅하거나 단순히 응용 프로그램을 다시 시작하라는 명령을 보냅니다. 일반적으로 모스크바 시간 자정에 매일 재부팅되며 때로는 때때로 재부팅됩니다. 다른 때에는. 이를 통해 메모리의 모든 프로세스가 삭제됩니다. 이 문제를 해결하기 위해 EventCash 테이블을 생각해 냈습니다. 전송하기 전에 이벤트는 별도의 테이블에 저장됩니다.
EventCashEntity eventCashEntity = EventCashEntity.eventTo(calendarUserTime.getTime(), event.getDescription(), event.getUser().getId());
eventCashDAO.save(eventCashEntity);
전송 후 다음 내용이 삭제됩니다.
@Override
public void run() {
    TelegramBot telegramBot = ApplicationContextProvider.getApplicationContext().getBean(TelegramBot.class);
    EventCashDAO eventCashDAO = ApplicationContextProvider.getApplicationContext().getBean(EventCashDAO.class);
    telegramBot.execute(sendMessage);
    //if event it worked, need to remove it from the database of unresolved events
    eventCashDAO.delete(eventCashId);
}
ApplicationContextProvider 는 즉석에서 컨텍스트를 가져오기 위한 특수 클래스입니다.
@Component
//wrapper to receive Beans
public class ApplicationContextProvider implements ApplicationContextAware {

    private static ApplicationContext context;

    public static ApplicationContext getApplicationContext() {
        return context;
    }

    @Override
    public void setApplicationContext(ApplicationContext ac)
            throws BeansException {
        context = ac;
    }
}
처리되지 않은 이벤트를 확인하기 위해 @PostConstruct 라고 표시된 메서드가 있는 특수 서비스를 만들었습니다. 이 서비스는 각 애플리케이션 시작 후에 실행됩니다. 데이터베이스에서 처리되지 않은 모든 이벤트를 선택하여 메모리로 반환합니다. 여기 당신을 위한 불쾌한 Heroku가 있습니다!
@Component
public class SendEventFromCache {

    private final EventCashDAO eventCashDAO;
    private final TelegramBot telegramBot;

    @Value("${telegrambot.adminId}")
    private int admin_id;

    @Autowired
    public SendEventFromCache(EventCashDAO eventCashDAO, TelegramBot telegramBot) {
        this.eventCashDAO = eventCashDAO;
        this.telegramBot = telegramBot;
    }

    @PostConstruct
    @SneakyThrows
    //after every restart app  - check unspent events
    private void afterStart() {
        List<eventcashentity> list = eventCashDAO.findAllEventCash();

        SendMessage sendMessage = new SendMessage();
        sendMessage.setChatId(String.valueOf(admin_id));
        sendMessage.setText("Произошла перезагрузка!");
        telegramBot.execute(sendMessage);

        if (!list.isEmpty()) {
            for (EventCashEntity eventCashEntity : list) {
                Calendar calendar = Calendar.getInstance();
                calendar.setTime(eventCashEntity.getDate());
                SendEvent sendEvent = new SendEvent();
                sendEvent.setSendMessage(new SendMessage(String.valueOf(eventCashEntity.getUserId()), eventCashEntity.getDescription()));
                sendEvent.setEventCashId(eventCashEntity.getId());
                new Timer().schedule(new SimpleTask(sendEvent), calendar.getTime());
            }
        }
    }
}
</eventcashentity>
애플리케이션이 준비되었습니다. 이제 애플리케이션과 데이터베이스에 대한 Heroku 주소를 가져와야 합니다. 귀하의 신청서는 Github에 게시되어야 합니다!!! Heroku.com으로 이동하여 새 앱 만들기를 클릭 하고 애플리케이션 이름을 입력한 후 유럽을 선택하고 앱을 만듭니다 . 이제 신청 장소가 준비되었습니다. 앱 열기를 클릭하면 브라우저가 애플리케이션 주소로 리디렉션됩니다. 이는 웹훅 주소입니다. https://your_name.herokuapp.com/ 텔레그램에 등록하고 application.propertie 설정에서 telegrambot을 변경하세요 . webHookPath=https: //telegrambotsimpl.herokuapp.com/ 을 server.port=5000 으로 삭제하거나 주석 처리할 수 있습니다. 이제 데이터베이스를 연결해보자. Heroku의 리소스 탭 으로 이동하여 Heroku Postgres텔레그램 봇 - Java의 webHook을 통해 알림을 보내거나 Google 캘린더를 거부하세요!  파트 2: - 1 찾기를 클릭하고 설치를 클릭 하면 데이터베이스 계정 페이지로 리디렉션됩니다. 설정에서 찾으세요. 데이터베이스에 필요한 모든 데이터가 있을 것입니다. application.properties 의 모든 내용은 이제 다음과 같아야 합니다. 텔레그램 봇 - Java의 webHook을 통해 알림을 보내거나 Google 캘린더를 거부하세요!  파트 2: - 2
#server.port=5000

telegrambot.userName=@calendar_event_bot
telegrambot.botToken=1731265488:AAFDjUSk3vu5SFfgdfh556gOOFmuml7SqEjwrmnEF5Ak
telegrambot.webHookPath=https://telegrambotsimpl.herokuapp.com/
#telegrambot.webHookPath=https://f5d6beeb7b93.ngrok.io


telegrambot.adminId=39376213

eventservice.period =600000

#spring.datasource.driver-class-name=org.postgresql.Driver
#spring.datasource.url=jdbc:postgresql://localhost:5432/telegramUsers
#spring.datasource.username=postgres
#spring.datasource.password=password

spring.datasource.url=jdbc:postgresql:ec2-54-247-158-179.eu-west-1.compute.amazonaws.com:5432/d2um26le5notq?ssl=true&sslmode=require&sslfactory=org.postgresql.ssl.NonValidatingFactory
spring.datasource.username=ulmbeymwyvsxa
spring.datasource.password=4c7646c69dbgeacbk98fa96e8daa6d9b1bl4894e67f3f3ddd6a27fe7b0537fd
귀하의 계정 데이터를 귀하의 데이터로 바꾸십시오. 필드 jdbc:postgresql:ec2-54-247-158-179.eu-west-1.compute.amazonaws.com:5432/d2um26le5notq?ssl=true&sslmode=require&sslfactory=org. postgresql.ssl .NonValidatingFactory는 굵게 표시된 계정(호스트, 데이터베이스)의 해당 데이터로 바꿔야 하며 사용자 이름, 비밀번호 필드는 추측하기 어렵지 않습니다. 이제 데이터베이스에 테이블을 생성해야 합니다. 저는 IDEA에서 이 작업을 수행했습니다. 우리의 스크립트는 데이터베이스를 생성하는 데 유용합니다. 위에 작성된 대로 데이터베이스를 추가합니다. 계정에서 텔레그램 봇 - Java의 webHook을 통해 알림을 보내거나 Google 캘린더를 거부하세요!  파트 2: - 3 호스트 , 사용자, 비밀번호, 데이터베이스 필드를 가져옵니다. URl 필드는 물음표까지의 spring.datasource.url 필드입니다. 고급 탭 으로 이동하면 다음과 같아야 합니다. 텔레그램 봇 - Java의 webHook을 통해 알림을 보내거나 Google 캘린더를 거부하세요!  2부: - 4 모든 작업을 올바르게 수행한 경우 테스트를 클릭하면 녹색 확인 표시가 나타납니다. 확인을 클릭하세요. 데이터베이스를 마우스 오른쪽 버튼으로 클릭하고 Jump to Query Console 을 선택합니다 . 거기에 스크립트를 복사하고 실행을 클릭하세요 . 데이터베이스가 생성되어야 합니다. 10,000개의 라인을 무료로 사용할 수 있습니다! 배포를 위한 모든 준비가 완료되었습니다. 배포 섹션에서 Heroku의 애플리케이션으로 이동하세요. Github 섹션을 선택하세요. 텔레그램 봇 - Java의 webHook을 통해 알림을 보내거나 Google 캘린더를 거부하세요!  2부: - 5 저장소를 Heroku에 연결하세요. 이제 가지가 표시됩니다. 최신 변경 사항을 .properties에 푸시하는 것을 잊지 마세요. 아래에서 다운로드할 브랜치를 선택하고 브랜치 배포를 클릭합니다 . 모든 작업이 올바르게 완료되면 애플리케이션이 성공적으로 배포되었다는 알림을 받게 됩니다. 애플리케이션이 자동으로 시작되도록 하려면 .. 에서 자동 배포를 활성화하는 것을 잊지 마세요 . 그런데 GitHub에 변경 사항을 푸시하면 Heroku가 자동으로 애플리케이션을 다시 시작합니다. 이에 주의하고, 괴롭힘을 위한 별도의 스레드를 생성하고, 기본 스레드는 작동 중인 애플리케이션에만 사용하세요. 이제 저렴함 #2! 이것이 Heroku 무료 플랜의 잘 알려진 단점입니다. 들어오는 메시지가 없으면 응용 프로그램은 대기 모드로 들어가고 메시지를 받은 후 시작하는 데 꽤 오랜 시간이 걸리므로 기분이 좋지 않습니다. 이에 대한 간단한 해결책이 있습니다 - https://uptimerobot.com/ 그리고 아니요, Google 핑 가젯은 도움이 되지 않습니다. 이 정보가 어디서 왔는지도 모르고 이 질문을 Google에서 검색했는데 약 10년 동안 이 주제가 전혀 효과가 있었다 해도 확실하게 효과가 없었습니다. 이 애플리케이션은 귀하가 설정한 시간 동안 귀하가 지정한 주소로 HEAD 요청을 보내고, 응답하지 않으면 이메일로 메시지를 보냅니다. 알아내는 것은 어렵지 않을 것입니다. 혼란스러울 만큼 버튼이 충분하지 않습니다.)) 축하합니다!! 내가 잊어버린 것이 없고 여러분이 주의를 기울였다면 무료로 작동하고 충돌이 발생하지 않는 자신만의 애플리케이션을 갖게 되는 것입니다. 괴롭힘과 실험의 기회가 당신 앞에 열립니다. 어쨌든 나는 질문에 답하고 어떤 비판도 받아들일 준비가 되어 있습니다! 코드: https://github.com/papoff8295/webHookBotForHabr 사용된 자료: https://tlgrm.ru/docs/bots/api - 봇에 대해. https://en.wikibooks.org/wiki/Java_Persistence - 데이터베이스의 관계에 대해. https://stackoverflow.com/questions/11432498/how-to-call-a-thread-to-run-on-특이적-time-in-java - 시간 클래스 및 TimerTask https://www.youtube.com/ watch?v=CUDgSbaYGx4 - Github https://github.com/rubenlagus/TelegramBots에 코드를 게시하는 방법 - 텔레그램 라이브러리 및 이에 대한 많은 유용한 정보.
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION