JavaRush /Blog Java /Random-PL /Dodajemy możliwość subskrypcji grupy artykułów. (Część 2)...
Roman Beekeeper
Poziom 35

Dodajemy możliwość subskrypcji grupy artykułów. (Część 2) - „Projekt Java od A do Z”

Opublikowano w grupie Random-PL
Cześć wszystkim! Kontynuujemy pracę nad zadaniem rozpoczętym w zeszłym tygodniu .„Projekt Java od A do Z”: Dodanie możliwości subskrypcji grupy artykułów.  Część 2 - 1

Wdrażamy JRTB-5

Teraz musimy dodać polecenie, abyśmy mogli subskrybować jakąś grupę artykułów z JavaRush. Jak to zrobić? Zastosujemy najprostszy scenariusz, jaki wymyśliłem. Ponieważ mamy dostęp według identyfikatora grupy, potrzebujemy, aby użytkownik go przekazał. W tym celu użytkownik wprowadzi komendę /addGroupSub GROUP_ID, która zadziała na jeden z dwóch sposobów: jeśli przyjdzie samo polecenie: /addGroupSub , w odpowiedzi zostanie wysłana lista wszystkich grup wraz z ich identyfikatorami. Następnie użytkownik będzie mógł wybrać potrzebny mu identyfikator grupy i utworzyć drugą wersję żądania w poleceniu: /addGroupSub GROUP_ID - i wówczas zostanie zapisany zapis tej grupy z tym użytkownikiem. Myślę, że w przyszłości możemy spisać się lepiej. Naszym celem jest pokazanie rozwoju, a nie super fajnego doświadczenia użytkownika (wstydzę się to mówić, ale nie znam rosyjskiego terminu, który by to oznaczał). Aby prawidłowo dodać funkcjonalność przechodzącą przez całą aplikację (w naszym przypadku od klienta bota telegramu do bazy danych), trzeba zacząć od czegoś. Zrobimy to od strony bazy danych.

Dodanie nowej migracji do bazy danych

Pierwszą rzeczą, którą należy zrobić, to dodać nową migrację bazy danych i możliwość zapisywania danych subskrypcji grup użytkowników w JR. Aby pamiętać jak powinno być, wróć do artykułu „ Planowanie projektu: mierz siedem razy ”. Tam na drugim zdjęciu znajduje się przybliżony schemat bazy danych. Musimy dodać tabelę do przechowywania informacji o grupie:
  • Identyfikator grupy w JavaRush będzie także naszym identyfikatorem. Ufamy im i wierzymy, że te identyfikatory są unikalne;
  • tytuł - na naszych zdjęciach była to nazwa - nieformalna nazwa grupy; czyli to, co widzimy na stronie JavaRush;
  • last_article_id - i to jest ciekawe pole. Będzie przechowywać ostatni identyfikator artykułu w tej grupie, który bot wysłał już do swoich subskrybentów. Przy wykorzystaniu tego pola będzie działał mechanizm wyszukiwania nowych artykułów. Nowi subskrybenci nie będą otrzymywać artykułów opublikowanych przed subskrypcją użytkownika: tylko te, które zostały opublikowane po zapisaniu się do grupy.
Będziemy mieć także relację wiele do wielu pomiędzy grupami i tabelą użytkowników, ponieważ każdy użytkownik może mieć wiele subskrypcji grupowych (jeden do wielu), a każda subskrypcja grupowa może mieć wielu użytkowników (jeden do wielu, tylko z drugiej strony). Okazuje się, że będzie to nasze wiele do wielu. Jeśli masz pytania, przejrzyj artykuły w bazie danych. Tak, planuję wkrótce stworzyć post na kanale Telegram, w którym będę zestawiał wszystkie artykuły z bazy. Tak będzie wyglądać nasza druga migracja bazy danych.
V00002__created_groupsub_many_to_many.sql:

-- add PRIMARY KEY FOR tg_user
ALTER TABLE tg_user ADD PRIMARY KEY (chat_id);

-- ensure that the tables with these names are removed before creating a new one.
DROP TABLE IF EXISTS group_sub;
DROP TABLE IF EXISTS group_x_user;

CREATE TABLE group_sub (
   id INT,
   title VARCHAR(100),
   last_article_id INT,
   PRIMARY KEY (id)
);

CREATE TABLE group_x_user (
   group_sub_id INT NOT NULL,
   user_id VARCHAR(100) NOT NULL,
   FOREIGN KEY (user_id) REFERENCES tg_user(chat_id),
   FOREIGN KEY (group_sub_id) REFERENCES group_sub(id),
   UNIQUE(user_id, group_sub_id)
);
Warto zaznaczyć, że najpierw zmieniam starą tabelę - dodaję do niej klucz podstawowy. Jakoś mi to wtedy umknęło, ale teraz MySQL nie dał mi możliwości dodania KLUCZ OBCY dla tabeli gorup_x_user i w ramach tej migracji zaktualizowałem bazę danych. Proszę zwrócić uwagę na ważny aspekt. Zmianę bazy danych należy przeprowadzić dokładnie w ten sposób - wszystko, co potrzebne, znajduje się w nowej migracji, a nie poprzez aktualizację już wydanej migracji. Tak, w naszym przypadku nic by się nie stało, ponieważ jest to projekt testowy i wiemy, że jest wdrożony tylko w jednym miejscu, ale byłoby to błędne podejście. Ale chcemy, żeby wszystko było w porządku. Następnie następuje usuwanie tabel przed ich utworzeniem. Dlaczego to? Tak aby, gdyby jakimś cudem w bazie znalazły się tabele o takich nazwach, migracja nie zakończyła się niepowodzeniem i przebiegła dokładnie tak, jak oczekiwano. A następnie dodajemy dwie tabele. Wszystko było tak jak chcieliśmy. Teraz musimy uruchomić naszą aplikację. Jeśli wszystko się uruchomi i nie ulegnie awarii, migracja zostanie zarejestrowana. Aby to jeszcze raz sprawdzić, odwiedzamy bazę danych i upewniamy się, że: a) takie tabele się pojawiły; b) pojawił się nowy wpis w tabeli technicznej drogi przelotu. To kończy prace migracyjne, przejdźmy do repozytoriów.

Dodanie warstwy repozytorium

Dzięki Spring Boot Data wszystko jest tutaj bardzo proste: musimy dodać encję GroupSub, lekko zaktualizować TelegramUser i dodać prawie puste GroupSubRepository: Dodajemy encję GroupSub do tego samego pakietu co TelegramUser:
package com.github.javarushcommunity.jrtb.repository.entity;

import lombok.Data;
import lombok.EqualsAndHashCode;

import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;

import static java.util.Objects.isNull;

@Data
@Entity
@Table(name = "group_sub")
@EqualsAndHashCode
public class GroupSub {

   @Id
   private Integer id;

   @Column(name = "title")
   private String title;

   @Column(name = "last_article_id")
   private Integer lastArticleId;

   @ManyToMany(fetch = FetchType.EAGER)
   @JoinTable(
           name = "group_x_user",
           joinColumns = @JoinColumn(name = "group_sub_id"),
           inverseJoinColumns = @JoinColumn(name = "user_id")
   )
   private List<TelegramUser> users;

   public void addUser(TelegramUser telegramUser) {
       if (isNull(users)) {
           users = new ArrayList<>();
       }
       users.add(telegramUser);
   }
}
Warto zauważyć, że mamy dodatkowe pole użytkowników, które będzie zawierało zbiór wszystkich użytkowników zapisanych do grupy. A dwie adnotacje – ManyToMany i JoinTable – są dokładnie tym, czego potrzebujemy. To samo pole należy dodać dla TelegramUser:
@ManyToMany(mappedBy = "users", fetch = FetchType.EAGER)
private List<GroupSub> groupSubs;
W tym polu używane są złączenia zapisane w encji GroupSub. W rzeczywistości nasza klasa repozytorium dla GroupSub to GroupSubRepository :
package com.github.javarushcommunity.jrtb.repository;

import com.github.javarushcommunity.jrtb.repository.entity.GroupSub;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

/**
* {@link Repository} for {@link GroupSub} entity.
*/
@Repository
public interface GroupSubRepository extends JpaRepository<GroupSub, Integer> {
}
Na tym etapie nie potrzebujemy dodatkowych metod: wystarczą nam te zaimplementowane w przodku JpaRepository. Napiszmy w TelegramUserRepositoryIT test, który sprawdzi, czy działa nasze wiele do wielu. Ideą testu jest to, że dodamy do bazy 5 grup subskrypcji na użytkownika za pomocą skryptu sql, pobierzemy tego użytkownika po jego ID i sprawdzimy, czy otrzymaliśmy dokładnie te grupy i z dokładnie tymi samymi wartościami. Jak to zrobić? Możesz osadzić licznik w danych, które następnie możemy przejrzeć i sprawdzić. Oto skrypt fiveGroupSubsForUser.sql:
INSERT INTO tg_user VALUES (1, 1);

INSERT INTO group_sub VALUES
(1, 'g1', 1),
(2, 'g2', 2),
(3, 'g3', 3),
(4, 'g4', 4),
(5, 'g5', 5);

INSERT INTO group_x_user VALUES
(1, 1),
(2, 1),
(3, 1),
(4, 1),
(5, 1);
I sam test:
@Sql(scripts = {"/sql/clearDbs.sql", "/sql/fiveGroupSubsForUser.sql"})
@Test
public void shouldProperlyGetAllGroupSubsForUser() {
   //when
   Optional<TelegramUser> userFromDB = telegramUserRepository.findById("1");

   //then
   Assertions.assertTrue(userFromDB.isPresent());
   List<GroupSub> groupSubs = userFromDB.get().getGroupSubs();
   for (int i = 0; i < groupSubs.size(); i++) {
       Assertions.assertEquals(String.format("g%s", (i + 1)), groupSubs.get(i).getTitle());
       Assertions.assertEquals(i + 1, groupSubs.get(i).getId());
       Assertions.assertEquals(i + 1, groupSubs.get(i).getLastArticleId());
   }
}
Dodajmy teraz test o tym samym znaczeniu dla encji GroupSub. Aby to zrobić, utwórzmy klasę testową groupSubRepositoryIT w tym samym pakiecie co groupSubRepositoryIT :
package com.github.javarushcommunity.jrtb.repository;

import com.github.javarushcommunity.jrtb.repository.entity.GroupSub;
import com.github.javarushcommunity.jrtb.repository.entity.TelegramUser;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.jdbc.Sql;

import java.util.List;
import java.util.Optional;

import static org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase.Replace.NONE;

/**
* Integration-level testing for {@link GroupSubRepository}.
*/
@ActiveProfiles("test")
@DataJpaTest
@AutoConfigureTestDatabase(replace = NONE)
public class GroupSubRepositoryIT {

   @Autowired
   private GroupSubRepository groupSubRepository;

   @Sql(scripts = {"/sql/clearDbs.sql", "/sql/fiveUsersForGroupSub.sql"})
   @Test
   public void shouldProperlyGetAllUsersForGroupSub() {
       //when
       Optional<GroupSub> groupSubFromDB = groupSubRepository.findById(1);

       //then
       Assertions.assertTrue(groupSubFromDB.isPresent());
       Assertions.assertEquals(1, groupSubFromDB.get().getId());
       List<TelegramUser> users = groupSubFromDB.get().getUsers();
       for(int i=0; i<users.size(); i++) {
           Assertions.assertEquals(String.valueOf(i + 1), users.get(i).getChatId());
           Assertions.assertTrue(users.get(i).isActive());
       }
   }
}
Oraz brakujący skrypt fiveUsersForGroupSub.sql:
INSERT INTO tg_user VALUES
(1, 1),
(2, 1),
(3, 1),
(4, 1),
(5, 1);

INSERT INTO group_sub VALUES (1, 'g1', 1);

INSERT INTO group_x_user VALUES
(1, 1),
(1, 2),
(1, 3),
(1, 4),
(1, 5);
W tym momencie część pracy z repozytorium można uznać za zakończoną. Teraz napiszmy warstwę usług.

Piszemy GroupSubService

Na tym etapie do pracy z grupami subskrypcji wystarczy nam tylko możliwość ich zapisania, więc nie ma problemu: tworzymy usługę GroupSubService i jej implementację GroupSubServiceImpl w pakiecie zawierającym inne usługi - usługa:
package com.github.javarushcommunity.jrtb.service;

import com.github.javarushcommunity.jrtb.javarushclient.dto.GroupDiscussionInfo;
import com.github.javarushcommunity.jrtb.repository.entity.GroupSub;

/**
* Service for manipulating with {@link GroupSub}.
*/
public interface GroupSubService {

   GroupSub save(String chatId, GroupDiscussionInfo groupDiscussionInfo);
}
I jego realizacja:
package com.github.javarushcommunity.jrtb.service;

import com.github.javarushcommunity.jrtb.javarushclient.dto.GroupDiscussionInfo;
import com.github.javarushcommunity.jrtb.repository.GroupSubRepository;
import com.github.javarushcommunity.jrtb.repository.entity.GroupSub;
import com.github.javarushcommunity.jrtb.repository.entity.TelegramUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.ws.rs.NotFoundException;
import java.util.Optional;

@Service
public class GroupSubServiceImpl implements GroupSubService {

   private final GroupSubRepository groupSubRepository;
   private final TelegramUserService telegramUserService;

   @Autowired
   public GroupSubServiceImpl(GroupSubRepository groupSubRepository, TelegramUserService telegramUserService) {
       this.groupSubRepository = groupSubRepository;
       this.telegramUserService = telegramUserService;
   }

   @Override
   public GroupSub save(String chatId, GroupDiscussionInfo groupDiscussionInfo) {
       TelegramUser telegramUser = telegramUserService.findByChatId(chatId).orElseThrow(NotFoundException::new);
       //TODO add exception handling
       GroupSub groupSub;
       Optional<GroupSub> groupSubFromDB = groupSubRepository.findById(groupDiscussionInfo.getId());
       if(groupSubFromDB.isPresent()) {
           groupSub = groupSubFromDB.get();
           Optional<TelegramUser> first = groupSub.getUsers().stream()
                   .filter(it -> it.getChatId().equalsIgnoreCase(chatId))
                   .findFirst();
           if(first.isEmpty()) {
               groupSub.addUser(telegramUser);
           }
       } else {
           groupSub = new GroupSub();
           groupSub.addUser(telegramUser);
           groupSub.setId(groupDiscussionInfo.getId());
           groupSub.setTitle(groupDiscussionInfo.getTitle());
       }
       return groupSubRepository.save(groupSub);
   }
}
Aby Spring Data działała poprawnie i powstał rekord wiele do wielu, musimy pobrać użytkownika z naszej bazy danych dla tworzonej przez nas grupy subskrypcji i dodać go do obiektu GroupSub. Zatem gdy przekażemy tę subskrypcję do zapisania, połączenie zostanie utworzone również poprzez tabelę group_x_user. Może się zdarzyć, że taka grupa subskrypcyjna została już utworzona i wystarczy dodać do niej kolejnego użytkownika. W tym celu najpierw pobieramy z bazy ID grupy i jeżeli taki rekord istnieje to pracujemy z nim, jeśli nie to tworzymy nowy. Należy zauważyć, że do pracy z TelegramUser używamy TelegramUserService, aby przestrzegać ostatniej z zasad SOLID. Na chwilę obecną jeśli nie znajdziemy rekordu po ID to po prostu rzucam wyjątek. Nie jest to teraz w żaden sposób przetwarzane: zrobimy to na samym końcu, przed MVP. Napiszmy dwa testy jednostkowe dla klasy GroupSubServiceTest . Które z nich są nam potrzebne? Chcę mieć pewność, że w GroupSubRepository zostanie wywołana metoda save i do GroupSub zostanie przekazana encja z jednym użytkownikiem - ta, która zwróci nam TelegramUserService korzystając z podanego ID. I druga opcja, gdy w bazie znajduje się już grupa o tym samym ID i ta grupa ma już jednego użytkownika i trzeba sprawdzić, czy do tej grupy zostanie dodany kolejny użytkownik i ten obiekt zostanie zapisany. Oto implementacja:
package com.github.javarushcommunity.jrtb.service;

import com.github.javarushcommunity.jrtb.javarushclient.dto.GroupDiscussionInfo;
import com.github.javarushcommunity.jrtb.repository.GroupSubRepository;
import com.github.javarushcommunity.jrtb.repository.entity.GroupSub;
import com.github.javarushcommunity.jrtb.repository.entity.TelegramUser;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;

import java.util.Optional;

@DisplayName("Unit-level testing for GroupSubService")
public class GroupSubServiceTest {

   private GroupSubService groupSubService;
   private GroupSubRepository groupSubRepository;
   private TelegramUser newUser;

   private final static String CHAT_ID = "1";

   @BeforeEach
   public void init() {
       TelegramUserService telegramUserService = Mockito.mock(TelegramUserService.class);
       groupSubRepository = Mockito.mock(GroupSubRepository.class);
       groupSubService = new GroupSubServiceImpl(groupSubRepository, telegramUserService);

       newUser = new TelegramUser();
       newUser.setActive(true);
       newUser.setChatId(CHAT_ID);

       Mockito.when(telegramUserService.findByChatId(CHAT_ID)).thenReturn(Optional.of(newUser));
   }

   @Test
   public void shouldProperlySaveGroup() {
       //given

       GroupDiscussionInfo groupDiscussionInfo = new GroupDiscussionInfo();
       groupDiscussionInfo.setId(1);
       groupDiscussionInfo.setTitle("g1");

       GroupSub expectedGroupSub = new GroupSub();
       expectedGroupSub.setId(groupDiscussionInfo.getId());
       expectedGroupSub.setTitle(groupDiscussionInfo.getTitle());
       expectedGroupSub.addUser(newUser);

       //when
       groupSubService.save(CHAT_ID, groupDiscussionInfo);

       //then
       Mockito.verify(groupSubRepository).save(expectedGroupSub);
   }

   @Test
   public void shouldProperlyAddUserToExistingGroup() {
       //given
       TelegramUser oldTelegramUser = new TelegramUser();
       oldTelegramUser.setChatId("2");
       oldTelegramUser.setActive(true);

       GroupDiscussionInfo groupDiscussionInfo = new GroupDiscussionInfo();
       groupDiscussionInfo.setId(1);
       groupDiscussionInfo.setTitle("g1");

       GroupSub groupFromDB = new GroupSub();
       groupFromDB.setId(groupDiscussionInfo.getId());
       groupFromDB.setTitle(groupDiscussionInfo.getTitle());
       groupFromDB.addUser(oldTelegramUser);

       Mockito.when(groupSubRepository.findById(groupDiscussionInfo.getId())).thenReturn(Optional.of(groupFromDB));

       GroupSub expectedGroupSub = new GroupSub();
       expectedGroupSub.setId(groupDiscussionInfo.getId());
       expectedGroupSub.setTitle(groupDiscussionInfo.getTitle());
       expectedGroupSub.addUser(oldTelegramUser);
       expectedGroupSub.addUser(newUser);

       //when
       groupSubService.save(CHAT_ID, groupDiscussionInfo);

       //then
       Mockito.verify(groupSubRepository).findById(groupDiscussionInfo.getId());
       Mockito.verify(groupSubRepository).save(expectedGroupSub);
   }

}
Dodałem także metodę init() z adnotacją BeforeEach. W ten sposób zwykle tworzysz metodę, która będzie wykonywana przed uruchomieniem każdego testu, i możesz umieścić w niej wspólną logikę dla wszystkich testów. W naszym przypadku musimy zablokować TelegramUserService w ten sam sposób dla wszystkich testów tej klasy, dlatego warto przenieść tę logikę na wspólną metodę. Wykorzystano tutaj dwa projekty mokito:
  • Mockito.when(o1.m1(a1)).thenReturn(o2) - mówimy w nim, że gdy na obiekcie o1 zostanie wywołana metoda m1 z argumentem a1 , metoda zwróci obiekt o2 . To niemal najważniejsza funkcjonalność mockito – wymuszenie na próbnym obiekcie zwrócenia dokładnie tego, czego potrzebujemy;

  • Mockito.verify(o1).m1(a1) - sprawdza, czy na obiekcie o1 z argumentem a1 została wywołana metoda m1 . Można było oczywiście wykorzystać zwrócony obiekt metody save, ale postanowiłem nieco skomplikować sprawę, pokazując inną możliwą metodę. Kiedy może się przydać? W przypadkach, gdy metody klas próbnych zwracają wartość void. Wtedy bez Mockito.verify nie będzie pracy)))

Nadal pozostajemy wierni idei, że testy należy pisać, a wiele z nich trzeba napisać. Kolejnym etapem jest współpraca z zespołem bota telegramu.

Utwórz polecenie /addGroupSub

Tutaj musimy zastosować się do następującej logiki: jeśli otrzymamy samo polecenie, bez żadnego kontekstu, pomagamy użytkownikowi i przekazujemy mu listę wszystkich grup wraz z ich identyfikatorami, aby mógł przekazać botowi niezbędne informacje. A jeśli użytkownik wyśle ​​botowi polecenie zawierające inne słowa - znajdź grupę o tym ID lub napisz, że takiej grupy nie ma. Dodajmy nową wartość do naszej nazwy - CommandName:
ADD_GROUP_SUB("/addgroupsub")
Przejdźmy dalej od bazy danych do bota telegramu - utwórz klasę AddGroupSubCommand w pakiecie poleceń:
package com.github.javarushcommunity.jrtb.command;

import com.github.javarushcommunity.jrtb.javarushclient.JavaRushGroupClient;
import com.github.javarushcommunity.jrtb.javarushclient.dto.GroupDiscussionInfo;
import com.github.javarushcommunity.jrtb.javarushclient.dto.GroupRequestArgs;
import com.github.javarushcommunity.jrtb.repository.entity.GroupSub;
import com.github.javarushcommunity.jrtb.service.GroupSubService;
import com.github.javarushcommunity.jrtb.service.SendBotMessageService;
import org.telegram.telegrambots.meta.api.objects.Update;

import java.util.stream.Collectors;

import static com.github.javarushcommunity.jrtb.command.CommandName.ADD_GROUP_SUB;
import static com.github.javarushcommunity.jrtb.command.CommandUtils.getChatId;
import static com.github.javarushcommunity.jrtb.command.CommandUtils.getMessage;
import static java.util.Objects.isNull;
import static org.apache.commons.lang3.StringUtils.SPACE;
import static org.apache.commons.lang3.StringUtils.isNumeric;

/**
* Add Group subscription {@link Command}.
*/
public class AddGroupSubCommand implements Command {

   private final SendBotMessageService sendBotMessageService;
   private final JavaRushGroupClient javaRushGroupClient;
   private final GroupSubService groupSubService;

   public AddGroupSubCommand(SendBotMessageService sendBotMessageService, JavaRushGroupClient javaRushGroupClient,
                             GroupSubService groupSubService) {
       this.sendBotMessageService = sendBotMessageService;
       this.javaRushGroupClient = javaRushGroupClient;
       this.groupSubService = groupSubService;
   }

   @Override
   public void execute(Update update) {
       if (getMessage(update).equalsIgnoreCase(ADD_GROUP_SUB.getCommandName())) {
           sendGroupIdList(getChatId(update));
           return;
       }
       String groupId = getMessage(update).split(SPACE)[1];
       String chatId = getChatId(update);
       if (isNumeric(groupId)) {
           GroupDiscussionInfo groupById = javaRushGroupClient.getGroupById(Integer.parseInt(groupId));
           if (isNull(groupById.getId())) {
               sendGroupNotFound(chatId, groupId);
           }
           GroupSub savedGroupSub = groupSubService.save(chatId, groupById);
           sendBotMessageService.sendMessage(chatId, "Подписал на группу " + savedGroupSub.getTitle());
       } else {
           sendGroupNotFound(chatId, groupId);
       }
   }

   private void sendGroupNotFound(String chatId, String groupId) {
       String groupNotFoundMessage = "Нет группы с ID = \"%s\"";
       sendBotMessageService.sendMessage(chatId, String.format(groupNotFoundMessage, groupId));
   }

   private void sendGroupIdList(String chatId) {
       String groupIds = javaRushGroupClient.getGroupList(GroupRequestArgs.builder().build()).stream()
               .map(group -> String.format("%s - %s \n", group.getTitle(), group.getId()))
               .collect(Collectors.joining());

       String message = "Coбы подписаться на группу - передай комадну вместе с ID группы. \n" +
               "Например: /addGroupSub 16. \n\n" +
               "я подготовил список всех групп - выберай Jakую хочешь :) \n\n" +
               "Nazwa группы - ID группы \n\n" +
               "%s";

       sendBotMessageService.sendMessage(chatId, String.format(message, groupIds));
   }
}
Klasa ta korzysta z metody isNumeric z biblioteki Apache-commons, więc dodajmy ją do naszej pamięci:
<dependency>
  <groupId>org.apache.commons</groupId>
  <artifactId>commons-lang3</artifactId>
  <version>${apache.commons.version}</version>
</dependency>
A w bloku właściwości:
<apache.commons.version>3.11</apache.commons.version>
Cała ta logika jest w klasie. Przeczytaj uważnie. Jeśli masz jakieś pytania/sugestie, napisz je w komentarzach. Następnie musimy dodać polecenie do CommandContainer w naszej mapie poleceń:
.put(ADD_GROUP_SUB.getCommandName(), new AddGroupSubCommand(sendBotMessageService, javaRushGroupClient, groupSubService))
I wszystko dla tej drużyny. Chciałbym jakoś przetestować tę funkcjonalność, ale na razie tak naprawdę mogę tylko przyjrzeć się jej w bazie danych. W części trzeciej dodam zmiany z JRTB-6, abyśmy mogli zobaczyć listę grup, do których subskrybuje użytkownik. Teraz dobrze byłoby to wszystko sprawdzić. Aby to zrobić, wykonamy wszystkie czynności w Telegramie i sprawdzimy w bazie danych. Ponieważ napisaliśmy testy, wszystko powinno być w porządku. Artykuł jest już dość długi, dlatego później napiszemy test dla AddGroupSubCommand, a w kodzie dodamy TODO, żeby nie zapomnieć.

wnioski

W tym artykule przyjrzeliśmy się pracy nad dodawaniem funkcjonalności przez całą aplikację, zaczynając od bazy danych, a kończąc na pracy z klientem korzystającym z bota. Zwykle takie zadania pomagają zrozumieć projekt i zrozumieć jego istotę. Zrozum, jak to działa. W dzisiejszych czasach tematyka nie jest łatwa, więc nie wstydź się: napisz swoje pytania w komentarzach, a ja postaram się na nie odpowiedzieć. Czy podoba Ci się projekt? Daj mu gwiazdkę na Githubie : w ten sposób będzie jasne, że są zainteresowani projektem, a ja będę szczęśliwy. Jak mówią, mistrz zawsze jest zadowolony, gdy jego praca jest doceniana. Kod będzie zawierał wszystkie trzy części STEP_6 i będzie dostępny przed tym artykułem. Jak się o tym dowiedzieć? To proste – dołącz do kanału telegramu , na którym publikuję wszystkie informacje o moich artykułach o bocie telegramowym. Dziękuje za przeczytanie! Część 3 już jest .

Lista wszystkich materiałów wchodzących w skład serii znajduje się na początku artykułu.

Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION