JavaRush /Java Blog /Random-IT /Bot di Telegram: promemoria tramite webHook in Java o dì ...
Vladimir Popov
Livello 41

Bot di Telegram: promemoria tramite webHook in Java o dì no al calendario di Google! Parte 2

Pubblicato nel gruppo Random-IT
La seconda parte del progetto - ecco un collegamento alla prima: E quindi la classe BotState : Affinché il nostro bot possa capire cosa ci si aspetta da lui in un determinato momento, ad esempio, eliminando un promemoria, dobbiamo in qualche modo fai sapere al nostro bot che i numeri inseriti e inviati ora dovrebbero essere trattati come ID promemoria dall'elenco e dovrebbero essere eliminati. Pertanto, dopo aver cliccato sul pulsante “Elimina” , il bot entra nello stato BotState.ENTERNUMBEREVENT , questa è una classe Enum appositamente creata con stati 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
}
E ora dobbiamo inserire i numeri: " Inserisci il numero del promemoria dall'elenco ". Dopo aver inserito il quale andranno al metodo desiderato per l'elaborazione. Ecco il nostro cambio di stato:
public class BotStateCash {
    private final Map<long, botstate=""> botStateMap = new HashMap<>();

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

</long,>
Una mappa normale con un ID utente e il suo stato. Il campo int adminId è per me) Successivamente, la logica del metodo handleUpdate controllerà di che tipo di messaggio si tratta? Richiesta di richiamata o solo SMS? Se si tratta di testo normale, andiamo al metodo handleInputMessage , dove elaboriamo i pulsanti del menu principale e, se sono stati cliccati, impostiamo lo stato desiderato, ma se non sono stati cliccati e si tratta di testo sconosciuto, allora noi impostiamo lo stato dalla cache, se non è presente, impostiamo lo stato iniziale. Quindi il testo entra nell'elaborazione del metodo handle con lo stato di cui abbiamo bisogno. Ora presentiamo la logica della classe MessageHandler , che è responsabile dell'elaborazione dei messaggi in base allo stato del 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);
        }
    }
}
nel metodo handle, controlliamo lo stato del messaggio che abbiamo ricevuto e lo inviamo al gestore di eventi, la classe EventHandler. Qui abbiamo due nuove classi, MenuService e EventCash . MenuService – qui creiamo tutti i nostri menu. EventCash - simile a BotStateCash, salverà parti del nostro evento dopo l'input e una volta completato l'input, salveremo l'evento nel 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,>
Bene, questo è. quando creiamo un evento, un nuovo oggetto Evento viene creato nella cache -eventCash.saveEventCash(userId, new Event()); Quindi inseriamo una descrizione dell'evento e la aggiungiamo alla cache:
Event event = eventCash.getEventMap().get(userId);
event.setDescription(description);
//save to cache
eventCash.saveEventCash(userId, event);
Quindi inserisci il numero:
Event event = eventCash.getEventMap().get(userId);
event.setDate(date);
//save data to cache
eventCash.saveEventCash(userId, event);
La classe CallbackQueryHandler è simile a MessageHandler , solo che lì elaboriamo i messaggi callbackquery. Non ha senso analizzare completamente la logica del lavoro con gli eventi - EventHandler , ci sono già troppe lettere, è chiaro dai nomi dei metodi e dai commenti nel codice. E non vedo il motivo di disporlo completamente nel testo, ci sono più di 300 righe. Ecco un collegamento alla lezione su Github . Lo stesso vale per la classe MenuService , dove creiamo i nostri menu. Puoi leggerli in dettaglio sul sito web del produttore della libreria di Telegram - https://github.com/rubenlagus/TelegramBots/blob/master/TelegramBots.wiki/FAQ.md O nel libro di riferimento di Telegram - https:// tlgrm.ru/docs/bots /api Ora ci rimane la parte più deliziosa. Questa è la classe per la gestione dei messaggi 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 : abilita il lavoro programmato in primavera. @Scheduled(cron = "0 0 0 * * *") – configuriamo il metodo per essere eseguito alle 0:00 ogni giorno calendar.setTime(new Date()); - imposta l'ora del server. Otteniamo un elenco di promemoria per oggi, attraverso la magia di stream e lambda. Esaminiamo l'elenco dei ricevuti, impostiamo l'orario di invio corretto calendarUserTime e... È qui che ho deciso di schivare e avviare i processi ritardati nel tempo. La classe Time in Java è responsabile di ciò . new Timer().schedule(new SimpleTask(sendEvent), calendarUserTime.getTime()); Per questo dobbiamo creare un 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);
    }
}
e implementazione di TimerTask
public class SimpleTask extends TimerTask {
    private final SendEvent sendEvent;

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

    @Override
    public void run() {
        sendEvent.start();
    }
}
Sì, capisco perfettamente che puoi consultare il database ogni 20 minuti e inviare messaggi, ma ho scritto tutto al riguardo all'inizio)) Qui incontriamo anche la miseria di Heroku n. 1. Nel piano gratuito ti vengono dati circa 550 dino, che sono qualcosa come le ore di funzionamento della tua applicazione al mese. Questo non è sufficiente per un mese intero di funzionamento dell'applicazione, ma se colleghi una carta, ti verranno dati altri 450 dino, che sono sufficienti per i tuoi occhi. Se sei preoccupato per la carta, puoi collegarne una vuota, ma assicurati che contenga $ 0,6... Questo è un importo di verifica, deve solo essere sul conto. Non ci sono costi nascosti a meno che tu non modifichi tu stesso la tariffa. Nel piano gratuito c'è un altro piccolo problema, chiamiamolo n. 1a.. Riavviano costantemente i server o semplicemente inviano un comando per riavviare l'applicazione, in generale si riavvia ogni giorno da qualche parte a mezzanotte, ora di Mosca, e talvolta in altri tempi. Da questo, tutti i nostri processi in memoria vengono cancellati. Per risolvere questo problema, ho ideato la tabella EventCash. Prima dell'invio, gli eventi vengono salvati in una tabella separata:
EventCashEntity eventCashEntity = EventCashEntity.eventTo(calendarUserTime.getTime(), event.getDescription(), event.getUser().getId());
eventCashDAO.save(eventCashEntity);
E dopo l'invio, vengono eliminati:
@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 è una classe speciale per ottenere il contesto al volo:
@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;
    }
}
Per verificare la presenza di eventi non elaborati, ho creato un servizio speciale che ha un metodo contrassegnato con @PostConstruct : viene eseguito dopo l'avvio di ogni applicazione. Raccoglie tutti gli eventi non elaborati dal database e li restituisce in memoria. Ecco un brutto Heroku per te!
@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>
La nostra applicazione è pronta ed è ora di ottenere l'indirizzo Heroku per l'applicazione e il database. La tua candidatura deve essere pubblicata su Github!!! Vai su Heroku.com Fai clic su Crea nuova app , inserisci il nome della tua applicazione, seleziona Europa, crea app . Questo è tutto, il posto per la domanda è pronto. Se fai clic su Apri app, il browser ti reindirizzerà all'indirizzo della tua applicazione, questo è il tuo indirizzo webhook - https://tuo_nome.herokuapp.com/ Registralo in telegram e nelle impostazioni di application.property cambia telegrambot. webHookPath=https: //telegrambotsimpl.herokuapp.com/ al tuo server.port=5000 può essere eliminato o commentato. Ora colleghiamo il database. Vai alla scheda Risorse su Heroku, fai clic su: Bot di Telegram: promemoria tramite webHook in Java o dì no al calendario di Google!  Parte 2: - 1 Trova Heroku Postgres lì , fai clic su Installa : verrai reindirizzato alla pagina del tuo account database. Lo trovi lì in Impostazioni/ Bot di Telegram: promemoria tramite webHook in Java o dì no al calendario di Google!  Parte 2: - 2 Ci saranno tutti i dati necessari dal tuo database. In application.properties ora tutto dovrebbe essere così:
#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
Sostituisci i dati del tuo account con i tuoi: Nel campo jdbc:postgresql:ec2-54-247-158-179.eu-west-1.compute.amazonaws.com:5432/d2um26le5notq?ssl=true&sslmode=require&sslfactory=org. postgresql.ssl .NonValidatingFactory deve essere sostituito in grassetto con i dati corrispondenti dell'account (Host, Database).I campi nome utente e password non sono difficili da indovinare. Ora dobbiamo creare le tabelle nel database, l'ho fatto da IDEA. Il nostro script sarà utile per creare un database. Aggiungiamo il database come scritto sopra: prendiamo dall'account Bot di Telegram: promemoria tramite webHook in Java o dì no al calendario di Google!  Parte 2: - 3 il campo Host, Utente, Password, Database . Il campo URL è il nostro campo spring.datasource.url fino al punto interrogativo. Andiamo alla scheda Avanzate , dovrebbe essere così: Bot di Telegram: promemoria tramite webHook in Java o dì no al calendario di Google!  Parte 2: - 4 Se hai fatto tutto correttamente, dopo aver fatto clic su test, ci sarà un segno di spunta verde. Fare clic su OK. Fare clic con il tasto destro sul nostro database e selezionare Passa alla console delle query . Copia lì il nostro script e fai clic su esegui . Il database dovrebbe essere creato. 10.000 linee sono a tua disposizione gratuitamente! Tutto è pronto per la distribuzione. Vai alla nostra applicazione su Heroku nella sezione Distribuisci. Seleziona la sezione Github lì: Bot di Telegram: promemoria tramite webHook in Java o dì no al calendario di Google!  Parte 2: - 5 collega il tuo repository a Heroku. Ora i tuoi rami saranno visibili. Non dimenticare di inviare le ultime modifiche a .properties. Di seguito, seleziona il ramo che verrà scaricato e fai clic su Distribuisci ramo . Se tutto è stato eseguito correttamente, ti verrà notificato che l'applicazione è stata distribuita con successo. Non dimenticare di abilitare Distribuzioni automatiche da .. In modo che l'applicazione venga avviata automaticamente. A proposito, quando invii modifiche a GitHub, Heroku riavvierà automaticamente l'applicazione. Fai attenzione, crea un thread separato per il bullismo e utilizza quello principale solo per l'applicazione funzionante. Ora Economicità #2! Questo è il noto svantaggio del piano gratuito di Heroku. Se non ci sono messaggi in arrivo, l'applicazione entra in modalità standby e dopo aver ricevuto un messaggio ci vorrà molto tempo per avviarsi, il che non è piacevole. Esiste una soluzione semplice per questo: https://uptimerobot.com/ E no, i gadget ping di Google non aiutano, non so nemmeno da dove provengano queste informazioni, ho cercato su Google questa domanda e da circa 10 anni questo argomento non ha funzionato di sicuro, se ha funzionato del tutto. Questa applicazione invierà richieste HEAD all'indirizzo specificato per il tempo impostato e, se non risponde, invierà un messaggio via email. Non sarà difficile capirlo, non ci sono abbastanza pulsanti per confondersi)) Congratulazioni!! Se non ho dimenticato nulla e sei stato attento, allora hai la tua applicazione che funziona gratuitamente e non si blocca mai. L’opportunità di bullismo e sperimentazione si apre davanti a te. In ogni caso sono pronto a rispondere alle domande e ad accettare qualsiasi critica! Codice: https://github.com/papoff8295/webHookBotForHabr Materiali utilizzati: https://tlgrm.ru/docs/bots/api - sui bot. https://en.wikibooks.org/wiki/Java_Persistence - sulle relazioni nei database. https://stackoverflow.com/questions/11432498/how-to-call-a-thread-to-run-on-specific-time-in-java - Classe temporale e TimerTask https://www.youtube.com/ watch?v=CUDgSbaYGx4 – come pubblicare codice su Github https://github.com/rubenlagus/TelegramBots - libreria di telegrammi e molte informazioni utili a riguardo.
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION