JavaRush /وبلاگ جاوا /Random-FA /ربات تلگرام - یادآوری از طریق وب هوک در جاوا یا نه گفتن ب...
Vladimir Popov
مرحله

ربات تلگرام - یادآوری از طریق وب هوک در جاوا یا نه گفتن به تقویم گوگل! قسمت 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 می رویم ، جایی که دکمه های منوی اصلی را پردازش می کنیم، و اگر روی آنها کلیک شده بود، وضعیت مورد نظر را تنظیم می کنیم، اما اگر کلیک نشده بود و این متن ناآشنا است، پس ما حالت را از کش تنظیم کنید، اگر آنجا نباشد، وضعیت شروع را تنظیم می کنیم. سپس متن وارد پردازش متد handle با حالت مورد نیاز ما می شود. اکنون منطق کلاس 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، وضعیت پیامی را که دریافت کرده‌ایم بررسی می‌کنیم و آن را به رویداد 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()); - تنظیم زمان سرور از طریق جادوی استریم‌ها و لامبدا، فهرستی از یادآوری‌ها را برای امروز دریافت می‌کنیم. لیست دریافتی را مرور می کنیم، زمان ارسال صحیح را تعیین می کنیم . کلاس 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 بروید روی ایجاد برنامه جدید کلیک کنید ، نام برنامه خود را وارد کنید، اروپا را انتخاب کنید، برنامه ایجاد کنید . تمام است، مکان برنامه آماده است. اگر روی Open App کلیک کنید، مرورگر شما را به آدرس برنامه شما هدایت می کند، این آدرس وب هوک شما است - https://your_name.herokuapp.com/ آن را در تلگرام ثبت کنید و در تنظیمات application.propertie ربات تلگرام را تغییر دهید . webHookPath=https: //telegrambotsimpl.herokuapp.com/ به server.port=5000 شما قابل حذف یا نظر دادن است. حالا بیایید پایگاه داده را به هم وصل کنیم. به برگه منابع در Heroku بروید، روی: Heroku Postgres راربات تلگرام - یادآوری از طریق وب هوک در جاوا یا نه گفتن به تقویم گوگل!  قسمت 2: - 1 در آنجا پیدا کنید ، روی install کلیک کنید : به صفحه حساب پایگاه داده خود هدایت خواهید شد. آن را در تنظیمات پیدا کنید/ تمام داده های لازم از پایگاه داده شما وجود خواهد داشت. اکنون در application.properties همه چیز باید به این صورت باشد:ربات تلگرام - یادآوری از طریق وب هوک در جاوا یا نه گفتن به تقویم گوگل!  قسمت 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.org. postgresql.ssl .NonValidatingFactory باید با داده های مربوطه از حساب (میزبان، پایگاه داده) به صورت پررنگ جایگزین شود. حدس زدن فیلدهای نام کاربری و رمز عبور دشوار نیست. اکنون باید جداول را در پایگاه داده ایجاد کنیم، من این کار را از IDEA انجام دادم. اسکریپت ما برای ایجاد یک پایگاه داده مفید خواهد بود. پایگاه داده را همانطور که در بالا نوشته شد اضافه می کنیم: ربات تلگرام - یادآوری از طریق وب هوک در جاوا یا نه گفتن به تقویم گوگل!  قسمت 2: - 3 فیلد Host, User, Password, Database را از حساب کاربری می گیریم. فیلد URl فیلد spring.datasource.url ما تا علامت سوال است. ما به تب Advanced می رویم ، باید به این صورت باشد: ربات تلگرام - یادآوری از طریق وب هوک در جاوا یا نه گفتن به تقویم گوگل!  قسمت 2: - 4 اگر همه چیز را به درستی انجام دادید، پس از کلیک روی تست، یک علامت سبز وجود خواهد داشت. روی OK کلیک کنید. روی پایگاه داده ما کلیک راست کرده و Jump to query console را انتخاب کنید . اسکریپت ما را در آنجا کپی کنید و روی execute کلیک کنید . پایگاه داده باید ایجاد شود. 10000 خط به صورت رایگان در دسترس شماست! همه چیز برای Deploy آماده است. به برنامه ما در Heroku در بخش Deploy بروید. بخش Github را در آنجا انتخاب کنید: ربات تلگرام - یادآوری از طریق وب هوک در جاوا یا نه گفتن به تقویم گوگل!  قسمت 2: - 5 مخزن خود را به Heroku پیوند دهید. اکنون شاخه های شما قابل مشاهده خواهند بود. فراموش نکنید که آخرین تغییرات خود را به .properties فشار دهید. در زیر، شاخه ای را که دانلود می شود انتخاب کنید و روی Deploy branch کلیک کنید . اگر همه چیز به درستی انجام شود، به شما اطلاع داده می شود که برنامه با موفقیت اجرا شده است. فراموش نکنید که Automatic Deploys را از .. فعال کنید تا برنامه شما به طور خودکار شروع شود. به هر حال، وقتی تغییرات را در GitHub فشار دهید، Heroku به طور خودکار برنامه را راه اندازی مجدد می کند. در این مورد مراقب باشید، یک موضوع جداگانه برای قلدری ایجاد کنید و از اصلی فقط برای برنامه کاربردی استفاده کنید. اکنون ارزانی شماره 2! این نقطه ضعف شناخته شده طرح رایگان برای Heroku است. اگر پیام های دریافتی وجود نداشته باشد، برنامه به حالت آماده به کار می رود و پس از دریافت پیام زمان زیادی طول می کشد تا شروع شود، که خوشایند نیست. یک راه حل ساده برای این وجود دارد - https://uptimerobot.com/ و نه، ابزارهای پینگ گوگل کمکی نمی کنند، من حتی نمی دانم این اطلاعات از کجا آمده است، من این سوال را در گوگل جستجو کردم و حدود 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-specific-time-in-java - Time class and 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