JavaRush /Java Blogu /Random-AZ /Telegram botu - Java-da webHook vasitəsilə xatırlatma və ...
Vladimir Popov
Səviyyə

Telegram botu - Java-da webHook vasitəsilə xatırlatma və ya Google təqviminə yox deyin! 2-ci hissə

Qrupda dərc edilmişdir
Layihənin ikinci hissəsi - burada birinciyə keçid var: Beləliklə, BotState sinfi : Botumuzun müəyyən bir anda ondan nə gözlədiyini başa düşməsi üçün, məsələn, bir xatırlatma silmək, birtəhər botumuza bildirin ki, nömrələr indi daxil edilib göndərilib, siyahıdan xatırlatma ID kimi qəbul olunmalı və silinməlidir. Buna görə də, “Sil” düyməsini kliklədikdən sonra bot BotState.ENTERNUMBEREVENT vəziyyətinə keçir , bu, bot vəziyyətləri ilə xüsusi yaradılmış Enum sinfidir.
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
}
İndi nömrələri daxil etməyimiz gözlənilir - " Siyahıdan xatırlatma nömrəsini daxil edin ." Daxil etdikdən sonra emal üçün istədiyiniz üsula keçəcəklər. Budur dövlət keçidimiz:
public class BotStateCash {
    private final Map<long, botstate=""> botStateMap = new HashMap<>();

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

</long,>
İstifadəçi ID-si və statusu olan adi xəritə. int adminId sahəsi mənim üçündür) Sonra handleUpdate metodunun məntiqi bunun hansı mesaj olduğunu yoxlayacaq? Zəng sorğusu yoxsa sadəcə mətn? Bu adi mətndirsə, onda biz handleInputMessage metoduna gedirik , burada əsas menyu düymələrini emal edirik və əgər onlar kliklənibsə, onda biz istədiyiniz vəziyyəti təyin edirik, lakin onlar kliklənməyibsə və bu tanış olmayan mətndirsə, onda biz vəziyyəti keşdən təyin edirik, əgər orada deyilsə, başlanğıc vəziyyətini təyin edirik. Sonra mətn bizə lazım olan vəziyyətlə tutacaq metodunun işlənməsinə keçir. İndi biz botun vəziyyətindən asılı olaraq mesajların işlənməsi üçün cavabdeh olan MessageHandler sinifinin məntiqini təqdim edirik :
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);
        }
    }
}
Dəstək metodunda biz aldığımız mesajın statusunu yoxlayırıq və onu hadisə idarəedicisinə - EventHandler sinfinə göndəririk. Burada iki yeni sinifimiz var, MenuService və EventCash . MenuService – burada biz bütün menyularımızı yaradırıq. EventCash - BotStateCash -a bənzər , daxil edildikdən sonra tədbirimizin hissələrini saxlayacaq və daxiletmə tamamlandıqda, biz hadisəni verilənlər bazasında saxlayacağıq.
@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,>
Yaxşı, yəni. biz hadisə yaratdıqda keşdə yeni Hadisə obyekti yaradılır -eventCash.saveEventCash(userId, new Event()); Sonra hadisənin təsvirini daxil edirik və onu önbelleğe əlavə edirik:
Event event = eventCash.getEventMap().get(userId);
event.setDescription(description);
//save to cache
eventCash.saveEventCash(userId, event);
Sonra nömrəni daxil edin:
Event event = eventCash.getEventMap().get(userId);
event.setDate(date);
//save data to cache
eventCash.saveEventCash(userId, event);
CallbackQueryHandler sinfi MessageHandler sinfinə bənzəyir , yalnız biz orada geri çağırış sorğusu mesajlarını emal edirik. Hadisələrlə işləmək məntiqini tam təhlil etmək mənasızdır - EventHandler , artıq çoxlu hərflər var, koddakı metodların və şərhlərin adlarından aydın olur. Mən bunu tam mətndə yazmağın mənasını görmürəm, 300-dən çox sətir var. Budur Github -da sinifə keçid . Eyni şey menyularımızı yaratdığımız MenuService sinfinə də aiddir . Onlar haqqında ətraflı şəkildə teleqram kitabxanası istehsalçısının saytında oxuya bilərsiniz - https://github.com/rubenlagus/TelegramBots/blob/master/TelegramBots.wiki/FAQ.md Və ya Telegram istinad kitabında - https:// tlgrm.ru/docs/bots /api İndi bizə ən dadlı hissə qalıb. Bu, EventService mesajlarını idarə etmək üçün sinifdir :
@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 – Baharda planlaşdırılmış işi aktivləşdirin. @Scheduled(cron = "0 0 0 * * *") – metodu hər gün saat 0:00-da işləmək üçün konfiqurasiya edirik calendar.setTime(yeni Tarix()); - server vaxtını təyin edin. Biz axınların və lambdanın sehri vasitəsilə bu gün üçün xatırlatmaların siyahısını alırıq. Alınan siyahıdan keçirik, düzgün göndərmə vaxtını təyin edirik təqvimUserTime və... Bu, vaxtında gecikmiş proseslərdən yayınmaq və işə salmaq qərarına gəldiyim yerdir. Javadakı Time sinfi buna cavabdehdir . new Timer().schedule(yeni SimpleTask(sendEvent), calendarUserTime.getTime()); Bunun üçün bir ip yaratmalıyıq:
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- ın həyata keçirilməsi
public class SimpleTask extends TimerTask {
    private final SendEvent sendEvent;

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

    @Override
    public void run() {
        sendEvent.start();
    }
}
Hə, çox yaxşı başa düşürəm ki, hər 20 dəqiqədən bir məlumat bazasından keçib mesaj göndərə bilərsiniz, amma bu barədə hər şeyi əvvəldən yazmışam)) Burada da 1 nömrəli Heroku bədbəxtliyi ilə qarşılaşırıq. Pulsuz planda sizə təxminən 550 dino verilir ki, bu da ərizənizin ayda işlədiyi saatlara bənzəyir. Tətbiqin tam bir ayı işləməsi üçün bu kifayət deyil, ancaq bir kartı bağlasanız, sizə gözləriniz üçün kifayət edən başqa 450 dino verilir. Kartdan narahatsınızsa, boş olanı əlaqələndirə bilərsiniz, lakin onun tərkibində $0,6 olduğuna əmin olun... Bu, doğrulama məbləğidir, sadəcə hesabda olmalıdır. Tarifi özünüz dəyişdirməsəniz, heç bir gizli ödəniş yoxdur. Pulsuz planda daha bir kiçik problem var, 1a deyək.. Onlar daim serverləri yenidən yükləyirlər və ya sadəcə tətbiqi yenidən başlatmaq üçün əmr göndərirlər, ümumiyyətlə, hər gün Moskva vaxtı ilə gecə yarısı hardasa yenidən başlayır. başqa vaxtlarda. Bundan yaddaşdakı bütün proseslərimiz silinir. Bu problemi həll etmək üçün EventCash cədvəli ilə gəldim. Göndərmədən əvvəl hadisələr ayrıca cədvəldə saxlanılır:
EventCashEntity eventCashEntity = EventCashEntity.eventTo(calendarUserTime.getTime(), event.getDescription(), event.getUser().getId());
eventCashDAO.save(eventCashEntity);
Və göndərildikdən sonra aşağıdakılar silinir:
@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 tez kontekst əldə etmək üçün xüsusi sinifdir:
@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;
    }
}
İşlənməmiş hadisələri yoxlamaq üçün @PostConstruct ilə işarələnmiş metodu olan xüsusi bir xidmət yaratdım - o, hər proqram başladıqdan sonra işləyir. O, verilənlər bazasından bütün işlənməmiş hadisələri götürür və yaddaşa qaytarır. Budur sizin üçün iyrənc 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>
Tətbiqimiz hazırdır və ərizə və verilənlər bazası üçün Heroku ünvanını əldə etməyin vaxtı gəldi. Müraciətiniz Github-da dərc edilməlidir!!! Heroku.com saytına gedin Yeni Proqram Yarat klikləyin , tətbiqinizin adını daxil edin, Avropa seçin, proqram yaradın . Budur, müraciət üçün yer hazırdır. Açıq Tətbiqi klikləsəniz , brauzer sizi ərizənizin ünvanına yönləndirəcək, bu sizin webhook ünvanınızdır - https://your_name.herokuapp.com/ Onu teleqramda qeydiyyatdan keçirin və application.propertie nin parametrlərində telegrambot dəyişin . webHookPath=https: //telegrambotsimpl.herokuapp.com/ server.port=5000 ünvanınıza silinə və ya şərh edilə bilər. İndi verilənlər bazasını birləşdirək. Heroku-da Resurslar sekmesine keçin , klikləyin: Orada Heroku Postgres-iTelegram botu - Java-da webHook vasitəsilə xatırlatma və ya Google təqviminə yox deyin!  2-ci hissə: - 1 tapın , quraşdırma düyməsini basın : Siz verilənlər bazası hesabı səhifəsinə yönləndiriləcəksiniz. Orada onu Parametrlərdə tapın/ Verilənlər bazanızdan bütün lazımi məlumatlar olacaq. application.properties -də hər şey indi belə olmalıdır: Telegram botu - Java-da webHook vasitəsilə xatırlatma və ya Google təqviminə yox deyin!  2-ci hissə: - 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
Hesabınızdakı məlumatları özünüzlə əvəz edin: jdbc:postgresql:ec2-54-247-158-179.eu-west-1.compute.amazonaws.com:5432/d2um26le5notq?ssl=true&sslmode=requitory&sslf sahəsində postgresql.ssl .NonValidatingFactory qalın hərflərlə hesabdan (Host, Database) müvafiq məlumatlar ilə əvəz edilməlidir.İstifadəçi adı, parol sahələrini təxmin etmək çətin deyil. İndi verilənlər bazasında cədvəllər yaratmalıyıq, mən bunu IDEA-dan etdim. Skriptimiz verilənlər bazası yaratmaq üçün faydalı olacaq. Verilənlər bazasını yuxarıda yazıldığı kimi əlavə edirik: Hesabdan Telegram botu - Java-da webHook vasitəsilə xatırlatma və ya Google təqviminə yox deyin!  2-ci hissə: - 3 Host , User, Password, Database sahələrini götürürük. URl sahəsi sual işarəsinə qədər olan spring.datasource.url sahəmizdir. Biz gedirik Qabaqcıl nişanı , bu belə olmalıdır: Telegram botu - Java-da webHook vasitəsilə xatırlatma və ya Google təqviminə yox deyin!  2-ci hissə: - 4 Hər şeyi düzgün etdinizsə, testi tıkladıqdan sonra yaşıl bir onay işareti olacaq. OK düyməsini basın. Verilənlər bazamıza sağ klikləyin və sorğu konsoluna keç seçin . Skriptimizi oraya kopyalayın və icra düyməsini basın . Məlumat bazası yaradılmalıdır. 10.000 xətt pulsuzdur! Deploy üçün hər şey hazırdır. Yerləşdirmə bölməsində Heroku-da tətbiqimizə keçin. Orada Github bölməsini seçin: Telegram botu - Java-da webHook vasitəsilə xatırlatma və ya Google təqviminə yox deyin!  2-ci hissə: - 5 Anbarınızı Heroku ilə əlaqələndirin. İndi filiallarınız görünəcək. Ən son dəyişikliklərinizi .properties-ə köçürməyi unutmayın. Aşağıda, endiriləcək filialı seçin və filialı yerləşdirməyə klikləyin . Hər şey düzgün aparılıbsa, tətbiqin uğurla yerləşdirildiyi barədə sizə məlumat veriləcək. Tətbiqinizin avtomatik başlaması üçün ..- dan Avtomatik yerləşdirmələri aktiv etməyi unutmayın . Yeri gəlmişkən, dəyişiklikləri GitHub-a basdığınız zaman Heroku proqramı avtomatik olaraq yenidən işə salacaq. Bu barədə diqqətli olun, zorakılıq üçün ayrıca bir mövzu yaradın və əsası yalnız işləyən tətbiq üçün istifadə edin. İndi Ucuzluq #2! Bu, Heroku üçün pulsuz planın məlum dezavantajıdır. Daxil olan mesajlar yoxdursa, proqram gözləmə rejiminə keçir və mesaj aldıqdan sonra işə başlaması kifayət qədər uzun çəkəcək, bu xoşagəlməzdir. Bunun üçün sadə bir həll var - https://uptimerobot.com/ Xeyr, Google ping gadget'ları kömək etməyəcək, mən bu məlumatın haradan gəldiyini belə bilmirəm, mən bu sualı Google-da axtardım və təxminən 10 ildir ki, bu mövzu heç işləmirsə, dəqiq işləmir. Bu proqram təyin etdiyiniz vaxt üçün göstərdiyiniz ünvana HEAD sorğuları göndərəcək və cavab vermədikdə e-poçt vasitəsilə mesaj göndərəcək. Bunu başa düşmək sizin üçün çətin olmayacaq, çaşqın olmaq üçün kifayət qədər düymələr yoxdur)) Təbriklər!! Əgər heç nəyi unutmamışamsa və diqqətli olmusunuzsa, deməli pulsuz işləyən və heç vaxt qəzaya uğramayan öz tətbiqiniz var. Qarşınızda zorakılıq və təcrübə fürsəti açılır. İstənilən halda suallara cavab verməyə və istənilən tənqidi qəbul etməyə hazıram! Kod: https://github.com/papoff8295/webHookBotForHabr İstifadə olunan materiallar: https://tlgrm.ru/docs/bots/api - botlar haqqında. https://en.wikibooks.org/wiki/Java_Persistence - verilənlər bazalarında əlaqələr haqqında. https://stackoverflow.com/questions/11432498/how-to-call-a-thread-to-run-on-specific-time-in-java - Zaman sinfi və TimerTask https://www.youtube.com/ watch?v=CUDgSbaYGx4 – kodu Github-da necə yerləşdirmək olar https://github.com/rubenlagus/TelegramBots - teleqram kitabxanası və bu haqda çoxlu faydalı məlumatlar.
Şərhlər
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION