JavaRush /Java 博客 /Random-ZH /我们删除了“Java 项目从 A 到 Z”组中的文章订阅
Roman Beekeeper
第 35 级

我们删除了“Java 项目从 A 到 Z”组中的文章订阅

已在 Random-ZH 群组中发布
大家好,亲爱的朋友们,未来的高级软件工程师。我们继续开发电报机器人。在我们项目的这一步中,我们将考虑比计划价值具有更明显价值的三个任务。我们需要了解如何从特定组中删除对新文章的订阅:使用/stop命令停用机器人,并使用/start命令激活它。此外,所有请求和更新仅涉及机器人的活跃用户。像往常一样,我们将更新分支以获取所有更改并创建一个新分支:STEP_7_JRTB-7。在这一部分中,我们将讨论删除订阅并考虑 5 个事件选项 - 这会很有趣。

JRTB-7:从组中删除对新文章的订阅

显然,所有用户都希望能够删除他们的订阅,以免收到有关新文章的通知。它的逻辑与添加订阅的逻辑非常相似。如果我们只发送一个命令,作为响应,我们将收到用户已订阅的组及其 ID 的列表,以便我们了解到底需要删除哪些内容。如果用户将组 ID 与团队一起发送,我们将删除订阅。因此,让我们从电报机器人端开发这个命令。
  1. 让我们添加新命令的名称 - /deleteGroupSub,并在CommandName中添加以下行:

    DELETE_GROUP_SUB("/deleteGroupSub")

  2. 接下来,让我们创建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 = "Пока нет подписок на группы. Whatбы добавить подписку напиши /addGroupSub";
           } else {
               message = "Whatбы удалить подписку на группу - передай комадну вместе с 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));
       }
    }

为此,我们必须添加另外两个方法来处理 GroupSub 实体 - 通过 ID 从数据库中检索并保存实体本身。所有这些方法都只是调用现成的存储库方法。关于删除订阅,我会单独告诉你。在数据库模式中,这是负责多对多过程的表,要删除这种关系,需要删除其中的记录。这是如果我们用一般的理解的话对数据库的部分。但我们使用 Spring Data,并且默认有 Hibernate,它可以以不同的方式执行此操作。我们获得 GroupSub 实体,与其关联的所有用户都将被吸引到该实体。从这个用户集合中,我们将删除我们需要的用户并将 groupSub 保存回数据库,但不包含该用户。这样Spring Data就会理解我们想要什么并删除记录。《Java 项目从头到尾》:删除群组中文章的订阅 - 1《Java 项目从头到尾》:删除群组中文章的订阅 - 2为了快速删除用户,我在 TelegramUser 中添加了 EqualsAndHashCode 注释,排除了 GroupSub 列表,这样就不会出现问题。并在用户集合上调用了我们需要的用户的remove方法。TelegramUser 的外观如下:
@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;
}
结果,一切都如我们所愿。团队中有几个可能的事件,因此为每个事件编写一个好的测试是一个好主意。说到测试:当我编写测试时,我发现了逻辑上的缺陷,并在发布到生产环境之前纠正了它。如果没有进行测试,目前还不清楚它会多快被发现。删除组子命令测试:
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 = "Пока нет подписок на группы. Whatбы добавить подписку напиши /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 = "Whatбы удалить подписку на группу - передай комадну вместе с 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);
   }
}
在这里,每个测试都会检查一个单独的场景,让我提醒您,只有其中五个:
  • 当您只是传递/deleteGroupSub命令并且没有组订阅时;
  • 当您简单地传递/deleteGroupSub命令并且有组订阅时;
  • 当传递无效组 ID 时,例如/deleteGroupSub abc
  • 正如预期的那样,所有内容都被正确删除的场景;
  • 组 ID 有效,但数据库中不存在这样的组的情况。
正如您所看到的,所有这些场景都需要通过测试来覆盖。当我写作时,我意识到为了编写更好的测试,值得参加一些测试课程。我认为这将有助于正确寻找不同的选择。没错,就是对未来的想法。接下来,您需要向/help命令添加说明,表明您现在可以删除订阅。让我们将其放置在用于处理订阅的部分中。《Java 项目从头到尾》:删除群组中文章的订阅 - 3当然,要使该命令正常工作,您需要将其初始化添加到CommandContainer中:
.put(DELETE_GROUP_SUB.getCommandName(),
       new DeleteGroupSubCommand(sendBotMessageService, groupSubService, telegramUserService))
现在您可以在测试机器人上测试功能。我们使用 docker-compose-test.yml 启动数据库: docker-compose -f docker-compose-test.yml up 并通过 IDEA 启动 SpringBoot。我将彻底清除与机器人的通信并重新开始。我将详细介绍与该团队合作时可能出现的所有选项。《Java 项目从头到尾》:删除群组中文章的订阅 - 4从屏幕截图中可以看出,所有选项均已通过并且成功。
朋友们!您想立即知道项目的新代码何时发布吗?新文章什么时候出来?加入我的Telegram 频道。我在那里收集我的文章、想法和开源开发。
我们将项目的版本更新为 0.6.0-SNAPSHOT 我们更新 RELEASE_NOTES.md,添加新版本中所做操作的描述:
## 0.6.0-SNAPSHOT * JRTB-7:添加了删除群组订阅的功能。
代码可以工作,已经为其编写了测试:是时候将任务推送到存储库并创建拉取请求了。《Java 项目从头到尾》:删除组中文章的订阅 - 5

而不是结束

我们已经很久没有查看我们的项目板了,但是已经发生了很大的变化:《Java 项目从头到尾》:从组中删除文章订阅 - 6只剩下 5 个任务了。也就是说,你我已经走到了路的尽头。留下了一点。特别有趣的是,这个系列文章从9月中旬开始,也就是7个月了!!!当我想到这个想法时,我没想到会花这么长时间。同时,我对结果非常满意!朋友们,如果不清楚文章中发生的事情,请在评论中提问。这样我就会知道有些东西需要更好地描述,有些东西需要进一步解释。 好吧,像往常一样,喜欢 - 订阅 - 按响铃,我们的项目一颗星,写评论并评价文章! 谢谢大家。直到下一部分。我们很快就会讨论如何通过/stop/start命令添加机器人停用和激活以及如何最好地使用它们。回头见!

该系列所有材料的列表位于本文开头。

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