JavaRush /Java Blog /Random-TL /Telegram bot - paalala sa pamamagitan ng webHook sa Java ...

Telegram bot - paalala sa pamamagitan ng webHook sa Java o tumanggi sa Google calendar! Bahagi 2

Nai-publish sa grupo
Ang ikalawang bahagi ng proyekto - narito ang isang link sa una: At kaya ang klase ng BotState : Upang maunawaan ng aming bot kung ano ang inaasahan dito sa isang tiyak na punto ng oras, halimbawa, pagtanggal ng isang paalala, kailangan naming kahit papaano ay ipaalam sa aming bot na ang mga numero ay ipinasok at ipinadala ngayon ay dapat ituring bilang isang reminder ID mula sa listahan at dapat na tanggalin. Samakatuwid, pagkatapos mag-click sa button na “Tanggalin” , mapupunta ang bot sa estado ng BotState.ENTERNUMBEREVENT , isa itong espesyal na nilikhang klase ng Enum na may mga estado ng bot.
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
}
At ngayon ay inaasahang maglalagay kami ng mga numero - " Ipasok ang numero ng paalala mula sa listahan ." Matapos ipasok kung saan pupunta sila sa nais na paraan para sa pagproseso. Narito ang aming switch ng estado:
public class BotStateCash {
    private final Map<long, botstate=""> botStateMap = new HashMap<>();

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

</long,>
Isang regular na mapa na may user ID at katayuan nito. Ang patlang ng int adminId ay para sa akin) Susunod, susuriin ng lohika ng paraan ng handleUpdate kung anong uri ng mensahe ito? Callbackquery o text lang? Kung ito ay regular na teksto, pagkatapos ay pumunta kami sa paraan ng handleInputMessage , kung saan pinoproseso namin ang mga pindutan ng pangunahing menu, at kung na-click ang mga ito, pagkatapos ay itinakda namin ang nais na estado, ngunit kung hindi sila na-click at ito ay hindi pamilyar na teksto, kung gayon kami itakda ang estado mula sa cache, kung wala ito, pagkatapos ay itinakda namin ang panimulang estado. Pagkatapos ang teksto ay napupunta sa pagproseso ng paraan ng paghawak sa estado na kailangan namin. Ngayon ay ipinakita namin ang lohika ng klase ng MessageHandler , na responsable para sa pagproseso ng mga mensahe depende sa estado ng bot:
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);
        }
    }
}
sa paraan ng paghawak, sinusuri namin ang katayuan ng mensaheng natanggap namin at ipinapadala ito sa tagapangasiwa ng kaganapan - ang klase ng EventHandler. Narito mayroon kaming dalawang bagong klase, ang MenuService at EventCash . MenuService – dito ginagawa namin ang lahat ng aming menu. EventCash - katulad ng BotStateCash, ise-save nito ang mga bahagi ng aming event pagkatapos ng input at kapag nakumpleto na ang input, ise-save namin ang event sa database.
@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,>
Well, iyon ay. kapag lumikha kami ng isang kaganapan, isang bagong bagay na Kaganapan ay nilikha sa cache -eventCash.saveEventCash(userId, bagong Kaganapan()); Pagkatapos ay nagpasok kami ng isang paglalarawan ng kaganapan at idagdag ito sa cache:
Event event = eventCash.getEventMap().get(userId);
event.setDescription(description);
//save to cache
eventCash.saveEventCash(userId, event);
Pagkatapos ay ipasok ang numero:
Event event = eventCash.getEventMap().get(userId);
event.setDate(date);
//save data to cache
eventCash.saveEventCash(userId, event);
Ang klase ng CallbackQueryHandler ay katulad ng MessageHandler , kami lang ang nagpoproseso ng mga mensahe ng callbackquery doon. Walang saysay na ganap na pag-aralan ang lohika ng pagtatrabaho sa mga kaganapan - EventHandler , mayroon nang masyadong maraming mga titik, ito ay malinaw mula sa mga pangalan ng mga pamamaraan at komento sa code. At hindi ko nakikita ang punto ng paglalatag nito nang buo sa teksto, mayroong higit sa 300 mga linya. Narito ang isang link sa klase sa Github . Ang parehong napupunta para sa klase ng MenuService , kung saan ginagawa namin ang aming mga menu. Maaari mong basahin ang tungkol sa mga ito nang detalyado sa website ng tagagawa ng telegram library - https://github.com/rubenlagus/TelegramBots/blob/master/TelegramBots.wiki/FAQ.md O sa Telegram reference book - https:// tlgrm.ru/docs/bots /api Ngayon natitira na tayo sa pinakamasarap na bahagi. Ito ang klase para sa paghawak ng mga mensahe ng 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 – paganahin ang nakaiskedyul na trabaho sa Spring. @Scheduled(cron = "0 0 0 * * *") – kino-configure namin ang paraan para tumakbo sa 0:00 araw-araw calendar.setTime(new Date()); - itakda ang oras ng server. Nakakuha kami ng listahan ng mga paalala para sa araw na ito, sa pamamagitan ng magic ng mga stream at lambda. Dumaan kami sa natanggap na listahan, itakda ang tamang oras ng pagpapadala ng kalendaryoUserTime at... Dito ako nagpasya na umiwas at ilunsad ang mga prosesong naantala sa oras. Ang klase ng Oras sa java ay responsable para dito . bagong Timer().schedule(new SimpleTask(sendEvent), calendarUserTime.getTime()); Para dito kailangan naming lumikha ng isang thread:
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);
    }
}
at pagpapatupad ng TimerTask
public class SimpleTask extends TimerTask {
    private final SendEvent sendEvent;

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

    @Override
    public void run() {
        sendEvent.start();
    }
}
Oo, lubos kong nauunawaan na maaari kang dumaan sa database tuwing 20 minuto at magpadala ng mga mensahe, ngunit isinulat ko ang lahat tungkol dito sa simula pa lang)) Dito rin natin nakatagpo ang paghihirap ng Heroku No. Sa libreng plano, bibigyan ka ng 550 dino, na katulad ng mga oras ng operasyon ng iyong aplikasyon kada buwan. Ito ay hindi sapat para sa isang buong buwan ng pagpapatakbo ng application, ngunit kung mag-link ka ng isang card, bibigyan ka ng isa pang 450 dino, na sapat para sa iyong mga mata. Kung nag-aalala ka tungkol sa card, maaari kang mag-link ng isang walang laman, ngunit siguraduhing naglalaman ito ng $0.6... Isa itong halaga ng pag-verify, kailangan lang na nasa account ito. Walang mga nakatagong singil maliban kung ikaw mismo ang magpalit ng taripa. Sa libreng plano, mayroong isa pang maliit na problema, tawagan natin itong No. 1a.. Patuloy nilang i-reboot ang mga server, o magpadala lamang ng isang utos upang i-restart ang application, sa pangkalahatan ay nag-reboot ito araw-araw sa isang lugar sa hatinggabi na oras ng Moscow, at kung minsan sa ibang pagkakataon. Mula dito, ang lahat ng aming mga proseso sa memorya ay tinanggal. Upang malutas ang problemang ito, nakaisip ako ng talahanayan ng EventCash. Bago ipadala, ang mga kaganapan ay nai-save sa isang hiwalay na talahanayan:
EventCashEntity eventCashEntity = EventCashEntity.eventTo(calendarUserTime.getTime(), event.getDescription(), event.getUser().getId());
eventCashDAO.save(eventCashEntity);
At pagkatapos ipadala, ang mga sumusunod ay tinanggal:
@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);
}
Ang ApplicationContextProvider ay isang espesyal na klase para sa pagkuha ng konteksto sa mabilisang:
@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;
    }
}
Upang suriin ang mga hindi naprosesong kaganapan, gumawa ako ng isang espesyal na serbisyo na may isang paraan na may markang @PostConstruct - ito ay tumatakbo pagkatapos ng bawat pagsisimula ng application. Kinukuha nito ang lahat ng hindi naprosesong kaganapan mula sa database at ibinabalik ang mga ito sa memorya. Narito ang isang masamang Heroku para sa iyo!
@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>
Ang aming aplikasyon ay handa na, at oras na para makuha namin ang Heroku address para sa aplikasyon at database. Ang iyong aplikasyon ay dapat na mai-publish sa Github!!! Pumunta sa Heroku.com I-click ang Lumikha ng Bagong App , ilagay ang pangalan ng iyong application, piliin ang Europe, lumikha ng app . Iyon lang, handa na ang lugar para sa aplikasyon. Kung iki-click mo ang buksan ang App, ire-redirect ka ng browser sa address ng iyong application, ito ang iyong webhook address - https://your_name.herokuapp.com/ Irehistro ito sa telegram, at sa application.propertie s ay baguhin ang mga setting ng telegrambot. webHookPath=https: //telegrambotsimpl.herokuapp.com/ sa iyong server.port=5000 ay maaaring tanggalin o magkomento. Ngayon ikonekta natin ang database. Pumunta sa tab na Mga Mapagkukunan sa Heroku, i-click ang: Telegram bot - paalala sa pamamagitan ng webHook sa Java o tumanggi sa Google calendar!  Bahagi 2: - 1 Hanapin ang Heroku Postgres doon , i-click ang i-install : Ire-redirect ka sa pahina ng iyong database account. Hanapin ito doon sa Mga Setting/ Telegram bot - paalala sa pamamagitan ng webHook sa Java o tumanggi sa Google calendar!  Bahagi 2: - 2 Magkakaroon ng lahat ng kinakailangang data mula sa iyong database. Sa application.properties ang lahat ay dapat na ngayon ay ganito:
#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
Palitan ang data mula sa iyong account ng sa iyo: Sa field jdbc:postgresql:ec2-54-247-158-179.eu-west-1.compute.amazonaws.com:5432/d2um26le5notq?ssl=true&sslmode=require&sslfactory=org. postgresql.ssl .NonValidatingFactory ay kailangang mapalitan ng naka-bold ng kaukulang data mula sa account (Host, Database). Ang username, password field ay hindi mahirap hulaan. Ngayon kailangan nating lumikha ng mga talahanayan sa database, ginawa ko ito mula sa IDEA. Ang aming script ay magiging kapaki-pakinabang para sa paglikha ng isang database. Idinaragdag namin ang database tulad ng nakasulat sa itaas: Kinukuha namin Telegram bot - paalala sa pamamagitan ng webHook sa Java o tumanggi sa Google calendar!  Bahagi 2: - 3 ang field ng Host, User, Password, Database mula sa account. Ang URl field ay ang aming spring.datasource.url field hanggang sa tandang pananong. Pumunta kami sa tab na Advanced , dapat ay ganito: Telegram bot - paalala sa pamamagitan ng webHook sa Java o tumanggi sa Google calendar!  Bahagi 2: - 4 Kung ginawa mo ang lahat ng tama, pagkatapos ay pagkatapos mag-click sa pagsubok, magkakaroon ng berdeng checkmark. I-click ang OK. Mag-right-click sa aming database at piliin ang Tumalon sa query console . Kopyahin ang aming script doon at i-click ang execute . Ang database ay dapat gawin. 10,000 linya ang magagamit mo nang libre! Handa na ang lahat para sa Deploy. Pumunta sa aming application sa Heroku sa seksyong Deploy. Piliin ang seksyong Github doon: Telegram bot - paalala sa pamamagitan ng webHook sa Java o tumanggi sa Google calendar!  Bahagi 2: - 5 I-link ang iyong repository sa Heroku. Ngayon ang iyong mga sangay ay makikita. Huwag kalimutang itulak ang iyong mga pinakabagong pagbabago sa .properties. Sa ibaba, piliin ang sangay na mada-download at i-click ang I-deploy ang sangay . Kung nagawa nang tama ang lahat, aabisuhan ka na ang application ay matagumpay na nai-deploy. Huwag kalimutang paganahin ang Awtomatikong pag-deploy mula sa .. Upang awtomatikong magsimula ang iyong application. Sa pamamagitan ng paraan, kapag nag-push ka ng mga pagbabago sa GitHub, awtomatikong ire-restart ni Heroku ang application. Mag-ingat tungkol dito, lumikha ng isang hiwalay na thread para sa pananakot, at gamitin ang pangunahing isa para lamang sa gumaganang application. Ngayon ang Murang #2! Ito ang kilalang kawalan ng libreng plano para kay Heroku. Kung walang mga papasok na mensahe, ang application ay napupunta sa standby mode, at pagkatapos matanggap ang isang mensahe ay aabutin ng mahabang panahon upang magsimula, na hindi kaaya-aya. Mayroong isang simpleng solusyon para dito - https://uptimerobot.com/ At hindi, hindi makakatulong ang mga ping gadget ng Google, hindi ko alam kung saan nanggaling ang impormasyong ito, I-Google ko ang tanong na ito, at sa loob ng humigit-kumulang 10 taon na ang paksang ito ay hindi gumagana para sigurado, kung ito ay gumana sa lahat. Ang application na ito ay magpapadala ng mga kahilingan sa HEAD sa address na iyong tinukoy para sa oras na iyong itinakda at, kung hindi ito tumugon, magpadala ng mensahe sa pamamagitan ng email. Hindi mahirap para sa iyo na malaman ito, walang sapat na mga pindutan upang malito)) Congratulations!! Kung wala akong nakalimutan at naging matulungin ka, mayroon kang sariling application na gumagana nang libre at hindi nag-crash. Ang pagkakataon para sa pananakot at pag-eeksperimento ay nagbubukas sa harap mo. Sa anumang kaso, handa akong sagutin ang mga tanong at tanggapin ang anumang pagpuna! Code: https://github.com/papoff8295/webHookBotForHabr Mga materyales na ginamit: https://tlgrm.ru/docs/bots/api - tungkol sa mga bot. https://en.wikibooks.org/wiki/Java_Persistence - tungkol sa mga relasyon sa mga database. https://stackoverflow.com/questions/11432498/how-to-call-a-thread-to-run-on-specific-time-in-java - Time class at TimerTask https://www.youtube.com/ panoorin?v=CUDgSbaYGx4 – paano mag-post ng code sa Github https://github.com/rubenlagus/TelegramBots - telegram library at maraming kapaki-pakinabang na impormasyon tungkol dito.
Mga komento
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION