JavaRush /Java-Blog /Random-DE /Wir entfernen das Abonnement für Artikel aus der Gruppe -...

Wir entfernen das Abonnement für Artikel aus der Gruppe - „Java-Projekt von A bis Z“

Veröffentlicht in der Gruppe Random-DE
Hallo zusammen, meine lieben Freunde, zukünftige Senior Software Engineers. Wir entwickeln weiterhin einen Telegram-Bot. In diesem Schritt unseres Projekts werden wir drei Aufgaben betrachten, die mehr sichtbaren Wert als Programmwert haben. Wir müssen lernen, wie man ein Abonnement für neue Artikel aus einer bestimmten Gruppe entfernt: Verwenden Sie den Befehl /stop , um den Bot zu deaktivieren, und den Befehl /start , um ihn zu aktivieren. Darüber hinaus betreffen alle Anfragen und Aktualisierungen nur aktive Benutzer des Bots. Wie üblich aktualisieren wir den Hauptzweig , um alle Änderungen zu erhalten, und erstellen einen neuen: STEP_7_JRTB-7. In diesem Teil sprechen wir über das Löschen eines Abonnements und betrachten 5 Optionen für Veranstaltungen – das wird interessant sein.

JRTB-7: Entfernen eines Abonnements für neue Artikel aus einer Gruppe

Es ist klar, dass alle Benutzer ihr Abonnement löschen möchten, um keine Benachrichtigungen über neue Artikel zu erhalten. Seine Logik wird der Logik des Hinzufügens eines Abonnements sehr ähnlich sein. Wenn wir nur einen Befehl senden, erhalten wir als Antwort eine Liste der Gruppen und deren IDs, bei denen der Benutzer bereits abonniert ist, damit wir verstehen können, was genau gelöscht werden muss. Und wenn der Benutzer die Gruppen-ID zusammen mit dem Team sendet, löschen wir das Abonnement. Lassen Sie uns daher diesen Befehl von der Telegram-Bot-Seite aus entwickeln.
  1. Fügen wir den Namen des neuen Befehls hinzu – /deleteGroupSub , und in CommandName – die Zeile:

    DELETE_GROUP_SUB("/deleteGroupSub")

  2. Als nächstes erstellen wir den Befehl „DeleteGroupSubCommand“ :

    package com.github.javarushcommunity.jrtb.command;
    
    import com.github.javarushcommunity.jrtb.repository.entity.GroupSub;
    import com.github.javarushcommunity.jrtb.repository.entity.TelegramUser;
    import com.github.javarushcommunity.jrtb.service.GroupSubService;
    import com.github.javarushcommunity.jrtb.service.SendBotMessageService;
    import com.github.javarushcommunity.jrtb.service.TelegramUserService;
    import org.springframework.util.CollectionUtils;
    import org.telegram.telegrambots.meta.api.objects.Update;
    
    import javax.ws.rs.NotFoundException;
    import java.util.List;
    import java.util.Optional;
    import java.util.stream.Collectors;
    
    import static com.github.javarushcommunity.jrtb.command.CommandName.DELETE_GROUP_SUB;
    import static com.github.javarushcommunity.jrtb.command.CommandUtils.getChatId;
    import static com.github.javarushcommunity.jrtb.command.CommandUtils.getMessage;
    import static java.lang.String.format;
    import static org.apache.commons.lang3.StringUtils.SPACE;
    import static org.apache.commons.lang3.StringUtils.isNumeric;
    
    /**
    * Delete Group subscription {@link Command}.
    */
    public class DeleteGroupSubCommand implements Command {
    
       private final SendBotMessageService sendBotMessageService;
       private final TelegramUserService telegramUserService;
       private final GroupSubService groupSubService;
    
       public DeleteGroupSubCommand(SendBotMessageService sendBotMessageService, GroupSubService groupSubService,
                                    TelegramUserService telegramUserService) {
           this.sendBotMessageService = sendBotMessageService;
           this.groupSubService = groupSubService;
           this.telegramUserService = telegramUserService;
       }
    
       @Override
       public void execute(Update update) {
           if (getMessage(update).equalsIgnoreCase(DELETE_GROUP_SUB.getCommandName())) {
               sendGroupIdList(getChatId(update));
               return;
           }
           String groupId = getMessage(update).split(SPACE)[1];
           String chatId = getChatId(update);
           if (isNumeric(groupId)) {
               Optional<GroupSub> optionalGroupSub = groupSubService.findById(Integer.valueOf(groupId));
               if (optionalGroupSub.isPresent()) {
                   GroupSub groupSub = optionalGroupSub.get();
                   TelegramUser telegramUser = telegramUserService.findByChatId(chatId).orElseThrow(NotFoundException::new);
                   groupSub.getUsers().remove(telegramUser);
                   groupSubService.save(groupSub);
                   sendBotMessageService.sendMessage(chatId, format("Удалил подписку на группу: %s", groupSub.getTitle()));
               } else {
                   sendBotMessageService.sendMessage(chatId, "Не нашел такой группы =/");
               }
           } else {
               sendBotMessageService.sendMessage(chatId, "неправильный формат ID группы.\n " +
                       "ID должно быть целым положительным числом");
           }
       }
    
       private void sendGroupIdList(String chatId) {
           String message;
           List<GroupSub> groupSubs = telegramUserService.findByChatId(chatId)
                   .orElseThrow(NotFoundException::new)
                   .getGroupSubs();
           if (CollectionUtils.isEmpty(groupSubs)) {
               message = "Пока нет подписок на группы. Wasбы добавить подписку напиши /addGroupSub";
           } else {
               message = "Wasбы удалить подписку на группу - передай комадну вместе с ID группы. \n" +
                       "Например: /deleteGroupSub 16 \n\n" +
                       "я подготовил список всех групп, на которые ты подписан) \n\n" +
                       "Name группы - ID группы \n\n" +
                       "%s";
    
           }
           String userGroupSubData = groupSubs.stream()
                   .map(group -> format("%s - %s \n", group.getTitle(), group.getId()))
                   .collect(Collectors.joining());
    
           sendBotMessageService.sendMessage(chatId, format(message, userGroupSubData));
       }
    }

Dazu mussten wir zwei weitere Methoden für die Arbeit mit der GroupSub-Entität hinzufügen – das Abrufen aus der Datenbank anhand der ID und das Speichern der Entität selbst. Alle diese Methoden rufen einfach vorgefertigte Repository-Methoden auf. Über das Löschen eines Abonnements erzähle ich Ihnen gesondert. Im Datenbankschema ist dies die Tabelle, die für den Viele-zu-Viele-Prozess verantwortlich ist. Um diese Beziehung zu löschen, müssen Sie den darin enthaltenen Datensatz löschen. Dies ist der Fall, wenn wir das allgemeine Verständnis der Datenbank nutzen. Aber wir nutzen Spring Data und es gibt standardmäßig Hibernate, was das anders machen kann. Wir erhalten die GroupSub-Entität, zu der alle damit verbundenen Benutzer gezogen werden. Aus dieser Benutzersammlung entfernen wir den benötigten Benutzer und speichern groupSub wieder in der Datenbank, jedoch ohne diesen Benutzer. Auf diese Weise versteht Spring Data, was wir wollten, und löscht den Datensatz. „Java-Projekt von A bis Z“: Entfernen des Abonnements für Artikel aus der Gruppe – 1„Java-Projekt von A bis Z“: Entfernen des Abonnements für Artikel aus der Gruppe – 2Um einen Benutzer schnell zu entfernen, habe ich die EqualsAndHashCode-Annotation für TelegramUser hinzugefügt und die GroupSub-Liste ausgeschlossen, damit es keine Probleme gibt. Und rufen Sie die Remove-Methode für die Sammlung von Benutzern mit dem von uns benötigten Benutzer auf. So sieht es für TelegramUser aus:
@Data
@Entity
@Table(name = "tg_user")
@EqualsAndHashCode(exclude = "groupSubs")
public class TelegramUser {

   @Id
   @Column(name = "chat_id")
   private String chatId;

   @Column(name = "active")
   private boolean active;

   @ManyToMany(mappedBy = "users", fetch = FetchType.EAGER)
   private List<GroupSub> groupSubs;
}
Dadurch verlief alles genau so, wie wir es wollten. Es gibt mehrere mögliche Ereignisse in einem Team, daher ist es eine gute Idee, für jedes davon einen guten Test zu schreiben. Apropos Tests: Während ich sie schrieb, entdeckte ich einen Fehler in der Logik und korrigierte ihn, bevor er in die Produktion ging. Hätte es keinen Test gegeben, ist unklar, wie schnell er entdeckt worden wäre. DeleteGroupSubCommandTest:
package com.github.javarushcommunity.jrtb.command;

import com.github.javarushcommunity.jrtb.repository.entity.GroupSub;
import com.github.javarushcommunity.jrtb.repository.entity.TelegramUser;
import com.github.javarushcommunity.jrtb.service.GroupSubService;
import com.github.javarushcommunity.jrtb.service.SendBotMessageService;
import com.github.javarushcommunity.jrtb.service.TelegramUserService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.telegram.telegrambots.meta.api.objects.Update;

import java.util.ArrayList;
import java.util.Optional;

import static com.github.javarushcommunity.jrtb.command.AbstractCommandTest.prepareUpdate;
import static com.github.javarushcommunity.jrtb.command.CommandName.DELETE_GROUP_SUB;
import static java.util.Collections.singletonList;

@DisplayName("Unit-level testing for DeleteGroupSubCommand")
class DeleteGroupSubCommandTest {

   private Command command;
   private SendBotMessageService sendBotMessageService;
   GroupSubService groupSubService;
   TelegramUserService telegramUserService;


   @BeforeEach
   public void init() {
       sendBotMessageService = Mockito.mock(SendBotMessageService.class);
       groupSubService = Mockito.mock(GroupSubService.class);
       telegramUserService = Mockito.mock(TelegramUserService.class);

       command = new DeleteGroupSubCommand(sendBotMessageService, groupSubService, telegramUserService);
   }

   @Test
   public void shouldProperlyReturnEmptySubscriptionList() {
       //given
       Long chatId = 23456L;
       Update update = prepareUpdate(chatId, DELETE_GROUP_SUB.getCommandName());

       Mockito.when(telegramUserService.findByChatId(String.valueOf(chatId)))
               .thenReturn(Optional.of(new TelegramUser()));

       String expectedMessage = "Пока нет подписок на группы. Wasбы добавить подписку напиши /addGroupSub";

       //when
       command.execute(update);

       //then
       Mockito.verify(sendBotMessageService).sendMessage(chatId.toString(), expectedMessage);
   }

   @Test
   public void shouldProperlyReturnSubscriptionLit() {
       //given
       Long chatId = 23456L;
       Update update = prepareUpdate(chatId, DELETE_GROUP_SUB.getCommandName());
       TelegramUser telegramUser = new TelegramUser();
       GroupSub gs1 = new GroupSub();
       gs1.setId(123);
       gs1.setTitle("GS1 Title");
       telegramUser.setGroupSubs(singletonList(gs1));
       Mockito.when(telegramUserService.findByChatId(String.valueOf(chatId)))
               .thenReturn(Optional.of(telegramUser));

       String expectedMessage = "Wasбы удалить подписку на группу - передай комадну вместе с ID группы. \n" +
               "Например: /deleteGroupSub 16 \n\n" +
               "я подготовил список всех групп, на которые ты подписан) \n\n" +
               "Name группы - ID группы \n\n" +
               "GS1 Title - 123 \n";

       //when
       command.execute(update);

       //then
       Mockito.verify(sendBotMessageService).sendMessage(chatId.toString(), expectedMessage);
   }

   @Test
   public void shouldRejectByInvalidGroupId() {
       //given
       Long chatId = 23456L;
       Update update = prepareUpdate(chatId, String.format("%s %s", DELETE_GROUP_SUB.getCommandName(), "groupSubId"));
       TelegramUser telegramUser = new TelegramUser();
       GroupSub gs1 = new GroupSub();
       gs1.setId(123);
       gs1.setTitle("GS1 Title");
       telegramUser.setGroupSubs(singletonList(gs1));
       Mockito.when(telegramUserService.findByChatId(String.valueOf(chatId)))
               .thenReturn(Optional.of(telegramUser));

       String expectedMessage = "неправильный формат ID группы.\n " +
               "ID должно быть целым положительным числом";

       //when
       command.execute(update);

       //then
       Mockito.verify(sendBotMessageService).sendMessage(chatId.toString(), expectedMessage);
   }

   @Test
   public void shouldProperlyDeleteByGroupId() {
       //given

       /// prepare update object
       Long chatId = 23456L;
       Integer groupId = 1234;
       Update update = prepareUpdate(chatId, String.format("%s %s", DELETE_GROUP_SUB.getCommandName(), groupId));


       GroupSub gs1 = new GroupSub();
       gs1.setId(123);
       gs1.setTitle("GS1 Title");
       TelegramUser telegramUser = new TelegramUser();
       telegramUser.setChatId(chatId.toString());
       telegramUser.setGroupSubs(singletonList(gs1));
       ArrayList<TelegramUser> users = new ArrayList<>();
       users.add(telegramUser);
       gs1.setUsers(users);
       Mockito.when(groupSubService.findById(groupId)).thenReturn(Optional.of(gs1));
       Mockito.when(telegramUserService.findByChatId(String.valueOf(chatId)))
               .thenReturn(Optional.of(telegramUser));

       String expectedMessage = "Удалил подписку на группу: GS1 Title";

       //when
       command.execute(update);

       //then
       users.remove(telegramUser);
       Mockito.verify(groupSubService).save(gs1);
       Mockito.verify(sendBotMessageService).sendMessage(chatId.toString(), expectedMessage);
   }

   @Test
   public void shouldDoesNotExistByGroupId() {
       //given
       Long chatId = 23456L;
       Integer groupId = 1234;
       Update update = prepareUpdate(chatId, String.format("%s %s", DELETE_GROUP_SUB.getCommandName(), groupId));


       Mockito.when(groupSubService.findById(groupId)).thenReturn(Optional.empty());

       String expectedMessage = "Не нашел такой группы =/";

       //when
       command.execute(update);

       //then
       Mockito.verify(groupSubService).findById(groupId);
       Mockito.verify(sendBotMessageService).sendMessage(chatId.toString(), expectedMessage);
   }
}
Hier prüft jeder Test ein separates Szenario, und ich möchte Sie daran erinnern, dass es nur fünf davon gibt:
  • wenn Sie einfach den Befehl /deleteGroupSub übergeben haben und keine Gruppenabonnements vorhanden sind;
  • wenn Sie einfach den Befehl /deleteGroupSub übergeben haben und Abonnements für Gruppen vorhanden sind;
  • wenn eine ungültige Gruppen-ID übergeben wurde, zum Beispiel /deleteGroupSub abc ;
  • ein Szenario, in dem erwartungsgemäß alles korrekt gelöscht wird;
  • Ein Szenario, in dem die Gruppen-ID gültig ist, eine solche Gruppe jedoch nicht in der Datenbank vorhanden ist.
Wie Sie sehen, müssen alle diese Szenarien mit Tests abgedeckt werden. Während ich schrieb, wurde mir klar, dass es sich lohnt, einige Testkurse zu belegen, um bessere Tests zu schreiben. Ich denke, das wird dabei helfen, richtig nach verschiedenen Optionen zu suchen. Genau, Gedanken für die Zukunft. Als Nächstes müssen Sie dem Befehl /help eine Beschreibung hinzufügen , damit Sie das Abonnement nun löschen können. Platzieren wir es im Abschnitt zum Arbeiten mit Abonnements. „Java-Projekt von A bis Z“: Entfernen des Abonnements für Artikel aus der Gruppe – 3Damit dieser Befehl funktioniert, müssen Sie natürlich seine Initialisierung zum CommandContainer hinzufügen :
.put(DELETE_GROUP_SUB.getCommandName(),
       new DeleteGroupSubCommand(sendBotMessageService, groupSubService, telegramUserService))
Jetzt können Sie die Funktionalität auf einem Testbot testen. Wir starten unsere Datenbank mit docker-compose-test.yml: docker-compose -f docker-compose-test.yml up Und starten SpringBoot über IDEA. Ich werde die Korrespondenz mit dem Bot vollständig löschen und von vorne beginnen. Ich werde alle Optionen durchgehen, die sich bei der Zusammenarbeit mit diesem Team ergeben können. „Java-Projekt von A bis Z“: Entfernen des Abonnements für Artikel aus der Gruppe – 4Wie Sie dem Screenshot entnehmen können, wurden alle Optionen durchgegangen und waren erfolgreich.
Freunde! Möchten Sie sofort wissen, wann neuer Code für ein Projekt veröffentlicht wird? Wann erscheint ein neuer Artikel? Treten Sie meinem Telegram-Kanal bei . Dort sammle ich meine Artikel, Gedanken und die Open-Source-Entwicklung zusammen.
Wir aktualisieren die Version unseres Projekts auf 0.6.0-SNAPSHOT. Wir aktualisieren RELEASE_NOTES.md und fügen eine Beschreibung dessen hinzu, was in der neuen Version getan wurde:
## 0.6.0-SNAPSHOT * JRTB-7: Möglichkeit hinzugefügt, Gruppenabonnements zu löschen.
Der Code funktioniert, es wurden Tests dafür geschrieben: Es ist Zeit, die Aufgabe in das Repository zu verschieben und eine Pull-Anfrage zu erstellen.„Java-Projekt von A bis Z“: Entfernen des Abonnements für Artikel aus der Gruppe – 5

Anstatt zu enden

Wir haben schon lange nicht mehr auf unser Projektboard geschaut, aber es gibt große Veränderungen: „Java-Projekt von A bis Z“: Entfernen eines Abonnements für Artikel aus der Gruppe – 6Es sind nur noch 5 Aufgaben übrig. Das heißt, Sie und ich sind bereits am Ende des Weges. Etwas übrig geblieben. Besonders interessant ist, dass diese Artikelserie seit Mitte September, also seit 7 Monaten, läuft!!! Als ich auf diese Idee kam, hatte ich nicht damit gerechnet, dass es sooo lange dauern würde. Gleichzeitig bin ich mit dem Ergebnis mehr als zufrieden! Freunde, wenn nicht klar ist, was in dem Artikel passiert, stellen Sie Fragen in den Kommentaren. Auf diese Weise weiß ich, dass etwas besser beschrieben werden muss und dass etwas einer weiteren Erklärung bedarf. Nun, wie immer, liken – abonnieren – klingeln, unserem Projekt einen Stern geben , Kommentare schreiben und den Artikel bewerten! Danke an alle. Bis zum nächsten Teil. Wir werden bald darüber sprechen, wie man die Bot-Deaktivierung und -Aktivierung über die Befehle /stop und /start hinzufügt und wie man sie am besten nutzt. Bis bald!

Eine Liste aller Materialien der Serie finden Sie am Anfang dieses Artikels.

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