Всем привет, мои дорогие друзья.
Шаг за шагом мы все ближе к нашей цели — к выходу в MVP нашего проекта — JavaRush Telegram Bot. Как я уже говорил в прошлой статье, осталось всего 5 задач. Сегодня мы покроем две из них.
Хочу повторить, что проект на этом не закончится. У меня есть еще тьма идей и видений того, как должен развиваться этот проект, что в нем можно добавить новое, что сделать лучше.
Перед 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
Что нам нужно сделать в рамках этой задачи:Создать джобу, которая будет периодически ходить во все группы, подписки на которые есть у нас БД, сортировать статьи по дате публикации и проверять, совпадает ли ID последней публикации со значением в GroupSub. Если не совпадает, значит, нужно понять, сколько именно статей вышло с последнего раза. Обновляем last_article_id в GroupSub7 до актуального состояния.
Когда нашли список вышедших статей, находим всех АКТИВНЫХ пользователей для этих групп и пересылаем им уведомления о новых статьях.
/api/1.0/rest/posts Get posts by filters
Будем работать с этим запросом. Что нам в нем нужно? Получить список статей, которые относятся к определенной группе, при этом они должны быть отсортированы по дате публикации. Таким образом мы можем взять последние 15 статей и проверить, не вышли ли новые публикации судя по lastArticleId из нашей базы данных.
Если таки есть, мы их передадим далее для обработки и отправки уже пользователю. Поэтому нам нужно написать JavaRushPostClient.Пишем JavaRushPostClient
Здесь не будем стараться охватить все запросы, которые нам передали в API и создадим только тот, который нам нужен. Делая это, мы достигаем сразу две цели:Ускоряем процесс написания нашего приложения.
Оставляем такую работу для тех, кто захочет помочь нашему сообществу и решит попробовать себя в роли разработчика. Я сделаю для этого задачи, которые можно будет выполнить уже после MVP.
BaseUserInfo:
package com.github.javarushcommunity.jrtb.javarushclient.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.javarushcommunity.jrtb.javarushclient.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.javarushcommunity.jrtb.javarushclient.dto;
/**
* DTO, which represents like's information.
*/
public class LikesInfo {
private Integer count;
private LikeStatus status;
}
LikeStatus:
package com.github.javarushcommunity.jrtb.javarushclient.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.javarushcommunity.jrtb.javarushclient.dto;
/**
* DTO, which represents post types.
*/
public enum PostType {
UNKNOWN, USUAL, INNER_LINK, OUTER_LINK
}
UserPublicStatus:
package com.github.javarushcommunity.jrtb.javarushclient.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.javarushcommunity.jrtb.javarushclient.dto;
/**
* DTO, which represents visibility status.
*/
public enum VisibilityStatus {
UNKNOWN,
RESTRICTED,
PUBLIC,
PROTECTED,
PRIVATE,
DISABLED,
DELETED
}
Основываясь на всех этих DTO, напишем основной класс для получения статей:PostInfo:
package com.github.javarushcommunity.jrtb.javarushclient.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;
}
Теперь создадим интерфейс для работы и его реализацию.
Нам нужен будет только один метод для работы со статьями:JavaRushPostClient:
package com.github.javarushcommunity.jrtb.javarushclient;
import com.github.javarushcommunity.jrtb.javarushclient.dto.PostInfo;
import java.util.List;
/**
* Client for Javarush Open API corresponds to Posts.
*/
public interface JavaRushPostClient {
/**
* 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.javarushcommunity.jrtb.javarushclient;
import com.github.javarushcommunity.jrtb.javarushclient.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 JavaRushPostClientImpl implements JavaRushPostClient {
private final String javarushApiPostPath;
public JavaRushPostClientImpl(@Value("${javarush.api.path}") String javarushApi) {
this.javarushApiPostPath = javarushApi + "/posts";
}
@Override
public List<PostInfo> findNewPosts(Integer groupId, Integer lastPostId) {
List<PostInfo> lastPostsByGroup = Unirest.get(javarushApiPostPath)
.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.javarushcommunity.jrtb.javarushclient;
import com.github.javarushcommunity.jrtb.javarushclient.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.javarushcommunity.jrtb.javarushclient.JavaRushGroupClientTest.JAVARUSH_API_PATH;
@DisplayName("Integration-level testing for JavaRushPostClient")
class JavaRushPostClientTest {
private final JavaRushPostClient postClient = new JavaRushPostClientImpl(JAVARUSH_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! Серьезное количество.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ