JavaRush /Java блог /Random UA /Додаємо клієнта до статей - "Java-проект від А до Я"
Roman Beekeeper
35 рівень

Додаємо клієнта до статей - "Java-проект від А до Я"

Стаття з групи Random UA
Всім привіт, дорогі друзі. Крок за кроком ми ближче до нашої мети — до виходу в MVP нашого проекту — JavaRush Telegram Bot. Як я вже говорив у минулій статті, залишилося лише 5 завдань. Сьогодні ми покриємо дві із них. "Java-проект від А до Я": Додаємо клієнта до статей - 1Хочу повторити, що проект на цьому не скінчиться. У мене є ще темрява ідей та видінь того, як має розвиватися цей проект, що в ньому можна додати нове, що зробити краще. Перед MVP зробимо ще окрему статтю на тему рефакторингу – тобто про покращення якості коду без зміни його функціональності. До того моменту буде видно весь проект і буде зрозуміло, що і де можна поліпшити. У нашому випадку ми будемо максимально захищені від того, щоби не зламати функціонал, тому що написано багато тестів. Також напишемо ретроспективу на тему того, що ми хотіли та що отримали у результаті. Це дуже корисна річ: подивимося, наскільки правильно бачився весь півроку тому. Мені, принаймні, дуже це цікаво. Якщо у когось буде бажання спробувати себе у ролі мануального тестувальника – пишіть, співпрацюємо. Зробимо цей проект краще разом! Отже, ось вони: два завдання, описані ще півроку тому:JRTB-8 та JRTB-9 . Почав я дивитися, що потрібно реалізувати за цими завданнями, і зрозумів, що у плані запуску команд уже все готове. Ось, можна подивитися в StartCommand , метод execute :
@Override
public void execute(Update update) {
   String chatId = update.getMessage().getChatId().toString();

   telegramUserService.findByChatId(chatId).ifPresentOrElse(
           user -> {
               user.setActive(true);
               telegramUserService.save(user);
           },
           () -> {
               TelegramUser telegramUser = new TelegramUser();
               telegramUser.setActive(true);
               telegramUser.setChatId(chatId);
               telegramUserService.save(telegramUser);
           });

   sendBotMessageService.sendMessage(chatId, START_MESSAGE);
}
Тут працює логіка, якщо в нашій базі вже є такий користувач по chatId, ми йому просто ставимо поле active = true. А якщо немає такого користувача – створюємо нового. Так само і для команди / stop у StopCommand :
@Override
public void execute(Update update) {
   telegramUserService.findByChatId(update.getMessage().getChatId().toString())
           .ifPresent(it -> {
               it.setActive(false);
               telegramUserService.save(it);
           });
   sendBotMessageService.sendMessage(update.getMessage().getChatId().toString(), STOP_MESSAGE);
}
Видно, що під час виклику цієї команди для користувача ставлять лише поле active = false. І все: його підписки житимуть і чекатимуть свого часу, коли користувач знову вирішить активувати чат з ботом. І здавалося б, завдання вже зроблено і його можна закрити. Але не тут було. Найголовніше завдання — створити оповіщення про нові статті у передплаті. Ось саме там і буде повністю оновлено та зроблено ці завдання. Тобто поки ми не реалізували сповіщення про нові статті, не можна закрити. Тому займемося завданням JRTB-4 — створенням перевірки кожні 20 хвабон та оповіщення про нові статті. Друзі! Хочете одразу дізнаватися, коли вийде новий код проекту? Коли виходить нова стаття? Приєднуйтесь до мого каналу . Там збираю свою статтю, свої думки, свою open-source розробку воєдино.

Реалізуємо JRTB-4

Що нам потрібно зробити в рамках цього завдання:
  1. Створити джобу, яка буде періодично ходити до всіх груп, підписки на які є у нас БД, сортувати статті за датою публікації та перевіряти, чи збігається ID останньої публікації зі значенням у GroupSub. Якщо не збігається, то треба зрозуміти, скільки саме статей вийшло з останнього разу. Оновлюємо last_article_id у GroupSub7 до актуального стану.

  2. Коли знайшли список статей, що вийшли, знаходимо всіх АКТИВНИХ користувачів для цих груп і пересилаємо їм повідомлення про нові статті.

Щоб це зробити, скористаємося такою річчю як Spring Scheduler. Це механізм у Spring Framework, з ним можна створювати завдання, які виконуватимуться у певний час. Чи то кожні 15-20-40 хвабон, чи щочетверга о 15:30 чи якийсь інший варіант. Їх ще називають калькою з англійської – джоба. Поки ми робитимемо це завдання, я навмисне залишаю один дефект у пошуку нових статей. Він досить рідкісний і виявився лише в ситуації, коли я тестував мануально роботу цього завдання. Щоб це зробити, потрібно написати клієнт з пошуку статей. Для цього скористаємося вже знайомим нам Swagger API . Там є post-controller. Нас цікавить лише пошук колекції статей за певними фільтрами:
/api/1.0/rest/posts Get posts by filters
Працюватимемо з цим запитом. Що нам у ньому потрібно? Отримати список статей, які належать до певної групи, при цьому вони мають бути відсортовані за датою публікації. Таким чином ми можемо взяти останні 15 статей і перевірити, чи не вийшли нові публікації, судячи з останньогоArticleId з нашої бази даних. Якщо є, ми їх передамо далі для обробки та відправки вже користувачеві. Тому нам потрібно написати CodeGymPostClient .

Пишемо CodeGymPostClient

Тут не намагатимемося охопити всі запити, які нам передали в API і створимо лише той, який нам потрібний. Роблячи це ми досягаємо відразу дві мети:
  1. Прискорюємо процес написання нашої програми.

  2. Залишаємо таку роботу для тих, хто захоче допомогти нашій спільноті та вирішить спробувати себе у ролі розробника. Я зроблю для цього завдання, які можна буде виконати після MVP.

Тож на чому. Для запиту по секції Models у Swagger UI створимо наступні DTO:"Java-проект від А до Я": Додаємо клієнта до статей - 2

BaseUserInfo:

package com.github.codegymcommunity.jrtb.codegymclient.dto;

import lombok.Data;

/**
* DTO, which represents base user information.
*/
@Data
public class BaseUserInfo {
   private String city;
   private String country;
   private String displayName;
   private Integer id;
   private String job;
   private String key;
   private Integer level;
   private String pictureUrl;
   private String position;
   private UserPublicStatus publicStatus;
   private String publicStatusMessage;
   private Integer rating;
   private Integer userId;
}

Language:

package com.github.codegymcommunity.jrtb.codegymclient.dto;

/**
* DTO, which represents languages.
*/
public enum Language {
   UNKNOWN,
   ENGLISH,
   GERMAN,
   SPANISH,
   HINDI,
   FRENCH,
   PORTUGUESE,
   POLISH,
   BENGALI,
   PUNJABI,
   CHINESE,
   ITALIAN,
   INDONESIAN,
   MARATHI,
   TAMIL,
   TELUGU,
   JAPANESE,
   KOREAN,
   URDU,
   TAIWANESE,
   NETHERLANDS,
   RUSSIAN,
   UKRAINIAN
}

LikesInfo:

package com.github.codegymcommunity.jrtb.codegymclient.dto;

/**
* DTO, which represents like's information.
*/
public class LikesInfo {

   private Integer count;
   private LikeStatus status;
}

LikeStatus:

package com.github.codegymcommunity.jrtb.codegymclient.dto;

/**
* DTO, which represents like's status.
*/
public enum LikeStatus {

   UNKNOWN,
   LIKE,
   HOT,
   FOLLOW,
   FAVORITE,
   SOLUTION,
   HELPFUL,
   ARTICLE,
   OSCAR,
   DISLIKE,
   WRONG,
   SPAM,
   ABUSE,
   FOUL,
   TROLLING,
   OFFTOPIC,
   DUPLICATE,
   DIRTY,
   OUTDATED,
   BORING,
   UNCLEAR,
   HARD,
   EASY,
   FAKE,
   SHAM,
   AWFUL
}

PostType:

package com.github.codegymcommunity.jrtb.codegymclient.dto;

/**
* DTO, which represents post types.
*/
public enum PostType {
   UNKNOWN, USUAL, INNER_LINK, OUTER_LINK
}

UserPublicStatus:

package com.github.codegymcommunity.jrtb.codegymclient.dto;

/**
* DTO, which represents user public status.
*/
public enum UserPublicStatus {
   UNKNOWN,
   BEGINNER,
   ACTIVE,
   STRONG,
   GRADUATED,
   INTERNSHIP_IN_PROGRESS,
   INTERNSHIP_COMPLETED,
   RESUME_COMPLETED,
   LOOKING_FOR_JOB,
   HAVE_JOB;
}

VisibilityStatus:
package com.github.codegymcommunity.jrtb.codegymclient.dto;

/**
* DTO, which represents visibility status.
*/
public enum VisibilityStatus {
   UNKNOWN,
   RESTRICTED,
   PUBLIC,
   PROTECTED,
   PRIVATE,
   DISABLED,
   DELETED
}
На основі всіх цих DTO напишемо основний клас для отримання статей:

PostInfo:

package com.github.codegymcommunity.jrtb.codegymclient.dto;

import lombok.Data;

/**
* DTO, which represents post information.
*/
@Data
public class PostInfo {

   private BaseUserInfo authorInfo;
   private Integer commentsCount;
   private String content;
   private Long createdTime;
   private String description;
   private GroupInfo groupInfo;
   private Integer id;
   private String key;
   private Language language;
   private LikesInfo likesInfo;
   private GroupInfo originalGroupInfo;
   private String pictureUrl;
   private Double rating;
   private Integer ratingCount;
   private String title;
   private PostType type;
   private Long updatedTime;
   private UserDiscussionInfo userDiscussionInfo;
   private Integer views;
   private VisibilityStatus visibilityStatus;

}
Тепер створимо інтерфейс для роботи та його реалізацію. Нам потрібен лише один метод для роботи зі статтями:

CodeGymPostClient:

package com.github.codegymcommunity.jrtb.codegymclient;

import com.github.codegymcommunity.jrtb.codegymclient.dto.PostInfo;

import java.util.List;

/**
* Client for Javarush Open API corresponds to Posts.
*/
public interface CodeGymPostClient {

   /**
    * Find new posts since lastPostId in provided group.
    *
    * @param groupId provided group ID.
    * @param lastPostId provided last post ID.
    * @return the collection of the new {@link PostInfo}.
    */
   List<PostInfo> findNewPosts(Integer groupId, Integer lastPostId);
}
findNewPosts приймає два аргументи: ID групи та останній ID статті, яку бот вже надсилав. Тому передадуться всі ті статті, які вийшли пізніше за статтю з lastPostId . І його реалізація:
package com.github.codegymcommunity.jrtb.codegymclient;

import com.github.codegymcommunity.jrtb.codegymclient.dto.PostInfo;
import kong.unirest.GenericType;
import kong.unirest.Unirest;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;

@Component
public class CodeGymPostClientImpl implements CodeGymPostClient {

   private final String codegymApiPostPath;

   public CodeGymPostClientImpl(@Value("${codegym.api.path}") String codegymApi) {
       this.codegymApiPostPath = codegymApi + "/posts";
   }

   @Override
   public List<PostInfo> findNewPosts(Integer groupId, Integer lastPostId) {
       List<PostInfo> lastPostsByGroup = Unirest.get(codegymApiPostPath)
               .queryString("order", "NEW")
               .queryString("groupKid", groupId)
               .queryString("limit", 15)
               .asObject(new GenericType<List<PostInfo>>() {
               }).getBody();
       List<PostInfo> newPosts = new ArrayList<>();
       for (PostInfo post : lastPostsByGroup) {
           if (lastPostId.equals(post.getId())) {
               return newPosts;
           }
           newPosts.add(post);
       }
       return newPosts;
   }
}
У запиті додаємо кілька фільтрів:
  • order = NEW - щоб у списку були спочатку нові;
  • groupKid = groupId - пошук тільки за певними групами;
  • limit = 15 – обмежуємо кількість статей на запит. У нас періодичність 15-20 хвабон і ми очікуємо, що за цей час не буде написано більше, ніж 15(!).
Далі, коли ми знайшли статті, пробігаємо за списком та шукаємо нові. Алгоритм простий та наочний. Якщо є бажання його покращити – пишіть). Напишемо простий тест для цього клієнта:
package com.github.codegymcommunity.jrtb.codegymclient;

import com.github.codegymcommunity.jrtb.codegymclient.dto.PostInfo;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import java.util.List;

import static com.github.codegymcommunity.jrtb.codegymclient.CodeGymGroupClientTest.CODEGYM_API_PATH;

@DisplayName("Integration-level testing for CodeGymPostClient")
class CodeGymPostClientTest {

   private final CodeGymPostClient postClient = new CodeGymPostClientImpl(CODEGYM_API_PATH);

   @Test
   public void shouldProperlyGetNew15Posts() {
       //when
       List<PostInfo> newPosts = postClient.findNewPosts(30, 2935);

       //then
       Assertions.assertEquals(15, newPosts.size());
   }
}
Це дуже простий тест, який перевіряє, чи взагалі є зв'язок з клієнтом чи ні. Він знаходить 15 нових статей у групі Java-проекти, тому що я йому передаю ID першої у цій групі статті, а їх уже більше 15… Їх уже 22! Я навіть не думав, що їх буде багато. Як я швидко це дізнався? Думаєте, пішов рахувати їх? Не-а) Я скористався свагером і подивився кількість статей з певної групи. До речі, так можна подивитися і в інших... А скільки всього статей у групі RANDOM?... зараз скажу: їх 1062! Серйозна кількість.

Закінчення першої частини

Тут ми додали роботу із клієнтом за статтями. Все вже робабо, цього разу, я думаю, все має пройти просто та швидко. У наступній статті будемо додавати Spring Scheduler і писати FindNewArticleService . Ну і як звичайно лайк - передплата - дзвіночок , став зірку нашому проекту , пиши коментарі та оцінюй статтю! Дякую всім за прочитання — до зустрічі!

Список всіх матеріалів серії на початку цієї статті.

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ