JavaRush /בלוג Java /Random-HE /בוט טלגרם - תזכורת דרך webHook ב-Java או אמור לא ללוח השנ...
Vladimir Popov
רָמָה

בוט טלגרם - תזכורת דרך webHook ב-Java או אמור לא ללוח השנה של גוגל! חלק 2

פורסם בקבוצה
החלק השני של הפרויקט - הנה קישור לראשון: וכך מחלקת BotState : כדי שהבוט שלנו יבין מה מצופה ממנו בנקודת זמן מסוימת, למשל, מחיקת תזכורת, אנחנו צריכים איכשהו תודיע לבוט שלנו שיש להתייחס למספרים המוזנים ונשלחים עכשיו כמזהה תזכורת מהרשימה ויש למחוק אותם. לכן, לאחר לחיצה על כפתור "מחק" , הבוט עובר למצב 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,>
מפה רגילה עם מזהה משתמש והסטטוס שלה. השדה int adminId מיועד עבורי) לאחר מכן, ההיגיון של שיטת handleUpdate יבדוק באיזה סוג הודעה מדובר? שאילתת התקשרות או סתם טקסט? אם זה טקסט רגיל, אז אנחנו עוברים לשיטת handleInputMessage , שם אנחנו מעבדים את כפתורי התפריט הראשי, ואם הם נלחצו, אז אנחנו מגדירים את המצב הרצוי, אבל אם לא לחצו עליהם וזה טקסט לא מוכר אז אנחנו הגדר את המצב מהמטמון, אם הוא לא שם, אז אנחנו מגדירים את מצב ההתחלה. ואז הטקסט נכנס לעיבוד שיטת הידית עם המצב שאנחנו צריכים. כעת אנו מציגים את ההיגיון של מחלקת 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);
        }
    }
}
בשיטת handle, אנו בודקים את סטטוס ההודעה שקיבלנו ושולחים אותה ל-event handler – המחלקה 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,>
ובכן, כלומר. כאשר אנו יוצרים אירוע, נוצר אובייקט אירוע חדש במטמון -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 , רק שאנו מעבדים שם הודעות Callbackquery. אין טעם לנתח לחלוטין את ההיגיון בעבודה עם אירועים - 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 - אפשר עבודה מתוזמנת באביב. @Scheduled(cron = "0 0 0 * * *") – אנו מגדירים את השיטה לפעול בשעה 0:00 כל יום calendar.setTime(new Date()); - הגדר זמן שרת. אנו מקבלים רשימה של תזכורות להיום, דרך קסם הנחלים והלמבדה. אנחנו עוברים על הרשימה שהתקבלה, מגדירים את זמן השליחה הנכון של קלנדרUserTime ו... כאן החלטתי להתחמק ולהפעיל את התהליכים באיחור בזמן. כיתת Time ב-Java אחראית לכך . 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 דקות ולשלוח הודעות, אבל כתבתי על זה הכל ממש בהתחלה)) כאן אנו נתקלים גם בסבל של הרוקו מס' 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 - הוא פועל לאחר כל הפעלה של אפליקציה. הוא קולט את כל האירועים הלא מעובדים ממסד הנתונים ומחזיר אותם לזיכרון. הנה הרוקו מגעיל בשבילך!
@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 לחץ על צור אפליקציה חדשה , הזן את שם האפליקציה שלך, בחר אירופה, צור אפליקציה . זהו, המקום ליישום מוכן. אם תלחץ על פתח אפליקציה, הדפדפן יפנה אותך לכתובת האפליקציה שלך, זוהי כתובת ה-webhook שלך - https://your_name.herokuapp.com/ רשום אותה בטלגרם, ובהגדרות של application.propertie שנה את telegrambot. webHookPath=https: //telegrambotsimpl.herokuapp.com/ לשרת.port=5000 שלך ניתן למחוק או להגיב. עכשיו בואו נחבר את מסד הנתונים. עבור ללשונית משאבים ב-Heroku, לחץ על: בוט טלגרם - תזכורת דרך webHook ב-Java או אמור לא ללוח השנה של גוגל!  חלק 2: - 1 מצא את Heroku Postgres שם , לחץ על התקן : אתה תופנה לדף חשבון מסד הנתונים שלך. מצא אותו שם בהגדרות/ בוט טלגרם - תזכורת דרך webHook ב-Java או אמור לא ללוח השנה של גוגל!  חלק 2: - 2 יהיו כל הנתונים הדרושים ממסד הנתונים שלך. ב- application.properties הכל אמור כעת להיות כך:
#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=org.sslfactory יש להחליף את postgresql.ssl .NonValidatingFactory באותיות מודגשות בנתונים המתאימים מהחשבון (מארח, מסד נתונים). לא קשה לנחש את שדות שם המשתמש, הסיסמה. עכשיו אנחנו צריכים ליצור טבלאות במסד הנתונים, עשיתי את זה מ-IDEA. הסקריפט שלנו יהיה שימושי ליצירת מסד נתונים. אנו מוסיפים את מסד הנתונים כפי שנכתב למעלה: אנו לוקחים מהחשבון בוט טלגרם - תזכורת דרך webHook ב-Java או אמור לא ללוח השנה של גוגל!  חלק 2: - 3 את השדה מארח, משתמש, סיסמה, מסד נתונים . השדה URl הוא שדה spring.datasource.url שלנו עד לסימן השאלה. אנחנו עוברים ללשונית מתקדם , זה צריך להיות כך: בוט טלגרם - תזכורת דרך webHook ב-Java או אמור לא ללוח השנה של גוגל!  חלק 2: - 4 אם עשית הכל נכון, אז לאחר לחיצה על בדיקה, יהיה סימן ביקורת ירוק. לחץ על אישור. לחץ לחיצה ימנית על מסד הנתונים שלנו ובחר קפוץ למסוף השאילתות . העתק את הסקריפט שלנו לשם ולחץ על בצע . יש ליצור את מסד הנתונים. 10,000 קווים זמינים עבורך בחינם! הכל מוכן לפריסה. עבור אל האפליקציה שלנו ב-Heroku בקטע 'פריסה'. בחר את הקטע של Github שם: בוט טלגרם - תזכורת דרך webHook ב-Java או אמור לא ללוח השנה של גוגל!  חלק 2: - 5 קשר את המאגר שלך להרוקו. כעת הענפים שלך יהיו גלויים. אל תשכח לדחוף את השינויים האחרונים שלך ל-.properties. למטה, בחר את הסניף שיורד ולחץ על הפעל סניף . אם הכל נעשה כהלכה, תקבל הודעה שהאפליקציה נפרסה בהצלחה. אל תשכח להפעיל פריסה אוטומטית מ .. כך שהיישום שלך יופעל אוטומטית. אגב, כשאתה דוחף שינויים ל-GitHub, Heroku יפעיל מחדש את האפליקציה באופן אוטומטי. היזהר בעניין זה, צור שרשור נפרד לבריונות, והשתמש בראשי רק עבור היישום הפועל. עכשיו זולות מס' 2! זהו החיסרון הידוע של התוכנית החינמית עבור Heroku. אם אין הודעות נכנסות, האפליקציה נכנסת למצב המתנה ולאחר קבלת הודעה ייקח די הרבה זמן להתחיל, וזה לא נעים. יש פתרון פשוט לזה - https://uptimerobot.com/ ולא, גאדג'טים של גוגל פינג לא יעזרו, אני אפילו לא יודע מאיפה המידע הזה הגיע, חיפשתי את השאלה הזו בגוגל, וכבר כעשר שנים שהנושא הזה לא עבד בוודאות, אם הוא עבד בכלל. אפליקציה זו תשלח בקשות 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-specific-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