JavaRush /Blog Java /Random-VI /Chúng tôi xóa đăng ký các bài viết khỏi nhóm - "Dự án Jav...
Roman Beekeeper
Mức độ

Chúng tôi xóa đăng ký các bài viết khỏi nhóm - "Dự án Java từ A đến Z"

Xuất bản trong nhóm
Xin chào mọi người, những người bạn thân yêu của tôi, những Kỹ sư phần mềm cao cấp trong tương lai. Chúng tôi tiếp tục phát triển bot telegram. Trong bước này của dự án, chúng ta sẽ xem xét ba nhiệm vụ có giá trị rõ ràng hơn giá trị chương trình. Chúng ta cần tìm hiểu cách xóa đăng ký các bài viết mới khỏi một nhóm cụ thể: sử dụng lệnh /stop để tắt bot và sử dụng lệnh /start để kích hoạt nó. Hơn nữa, tất cả các yêu cầu và cập nhật chỉ liên quan đến người dùng đang hoạt động của bot. Như thường lệ, chúng tôi sẽ cập nhật nhánh chính để nhận tất cả các thay đổi và tạo một nhánh mới: STEP_7_JRTB-7. Trong phần này, chúng ta sẽ nói về việc xóa đăng ký và xem xét 5 tùy chọn cho các sự kiện - điều này sẽ rất thú vị.

JRTB-7: xóa đăng ký các bài viết mới khỏi một nhóm

Rõ ràng là tất cả người dùng sẽ muốn xóa đăng ký của mình để không nhận được thông báo về các bài viết mới. Logic của nó sẽ rất giống với logic của việc thêm đăng ký. Nếu chúng tôi chỉ gửi một lệnh, để phản hồi, chúng tôi sẽ nhận được danh sách các nhóm và ID của họ mà người dùng đã đăng ký, để chúng tôi có thể hiểu chính xác những gì cần xóa. Và nếu người dùng gửi ID nhóm cùng với nhóm, chúng tôi sẽ xóa đăng ký. Vì vậy, hãy cùng phát triển lệnh này từ phía bot telegram.
  1. Hãy thêm tên của lệnh mới - /deleteGroupSub và trong CommandName - dòng:

    DELETE_GROUP_SUB("/deleteGroupSub")

  2. Tiếp theo, hãy tạo lệnh 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));
       }
    }

Để làm điều này, chúng tôi phải thêm hai phương thức nữa để làm việc với thực thể GroupSub - truy xuất từ ​​cơ sở dữ liệu bằng ID và lưu chính thực thể đó. Tất cả các phương thức này chỉ đơn giản gọi các phương thức kho lưu trữ được tạo sẵn. Tôi sẽ nói riêng với bạn về việc xóa đăng ký. Trong lược đồ cơ sở dữ liệu, đây là bảng chịu trách nhiệm cho quá trình nhiều-nhiều và để xóa mối quan hệ này, bạn cần xóa bản ghi trong đó. Đây là nếu chúng ta sử dụng sự hiểu biết chung về phần cơ sở dữ liệu. Nhưng chúng tôi sử dụng Spring Data và có Hibernate theo mặc định, có thể thực hiện việc này theo cách khác. Chúng tôi nhận được thực thể GroupSub mà tất cả người dùng được liên kết với nó sẽ được rút ra. Từ bộ sưu tập người dùng này, chúng tôi sẽ xóa người dùng chúng tôi cần và lưu groupSub trở lại cơ sở dữ liệu nhưng không có người dùng này. Bằng cách này Spring Data sẽ hiểu những gì chúng ta muốn và xóa bản ghi. "Dự án Java từ A đến Z": Xóa đăng ký các bài viết khỏi nhóm - 1"Dự án Java từ A đến Z": Xóa đăng ký các bài viết khỏi nhóm - 2Để nhanh chóng xóa người dùng, tôi đã thêm chú thích EqualsAndHashCode vào TelegramUser, loại trừ danh sách GroupSub để không gặp vấn đề gì. Và gọi là phương thức xóa trên tập hợp người dùng với người dùng mà chúng ta cần. Đây là giao diện của 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;
}
Kết quả là mọi thứ đã diễn ra đúng như chúng tôi mong muốn. Có một số sự kiện có thể xảy ra trong một nhóm, vì vậy viết một bài kiểm tra hay cho từng sự kiện đó là một ý tưởng tuyệt vời. Nói về các bài kiểm tra: trong khi viết chúng, tôi đã tìm thấy một khiếm khuyết trong logic và sửa nó trước khi nó được đưa vào sản xuất. Nếu không có xét nghiệm thì không rõ nó sẽ được phát hiện nhanh đến mức nào. XóaGroupSubCommandTest:
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);
   }
}
Ở đây, mỗi bài kiểm tra sẽ kiểm tra một kịch bản riêng biệt và để tôi nhắc bạn, chỉ có năm kịch bản trong số đó:
  • khi bạn chỉ truyền lệnh /deleteGroupSub và không có đăng ký nhóm nào;
  • khi bạn chỉ cần truyền lệnh /deleteGroupSub và có đăng ký vào nhóm;
  • khi ID nhóm không hợp lệ được chuyển, ví dụ: /deleteGroupSub abc ;
  • một kịch bản trong đó mọi thứ đều bị xóa một cách chính xác như mong đợi;
  • một kịch bản khi ID nhóm hợp lệ, nhưng nhóm như vậy không có trong cơ sở dữ liệu.
Như bạn có thể thấy, tất cả các tình huống này cần phải được kiểm thử. Trong khi viết, tôi nhận ra rằng để viết bài kiểm tra tốt hơn, cần tham gia một số khóa học kiểm tra. Tôi nghĩ điều này sẽ giúp tìm kiếm các lựa chọn khác nhau một cách chính xác. Đúng vậy, những suy nghĩ cho tương lai. Tiếp theo, bạn cần thêm mô tả vào lệnh /help để bây giờ bạn có thể xóa đăng ký. Hãy đặt nó trong phần làm việc với đăng ký. "Dự án Java từ A đến Z": Xóa đăng ký các bài viết khỏi nhóm - 3Tất nhiên, để lệnh này hoạt động, bạn cần thêm phần khởi tạo của nó vào CommandContainer :
.put(DELETE_GROUP_SUB.getCommandName(),
       new DeleteGroupSubCommand(sendBotMessageService, groupSubService, telegramUserService))
Bây giờ bạn có thể kiểm tra chức năng trên bot thử nghiệm. Chúng tôi khởi chạy cơ sở dữ liệu của mình bằng docker-compose-test.yml: docker-compose -f docker-compose-test.yml up Và khởi chạy SpringBoot thông qua IDEA. Tôi sẽ xóa hoàn toàn thư từ với bot và bắt đầu lại. Tôi sẽ xem xét tất cả các tùy chọn có thể phát sinh khi làm việc với nhóm này. "Dự án Java từ A đến Z": Xóa đăng ký các bài viết khỏi nhóm - 4Như bạn có thể thấy từ ảnh chụp màn hình, tất cả các tùy chọn đều được thực hiện và thành công.
Bạn! Bạn có muốn biết ngay khi nào mã mới cho dự án được phát hành không? Khi nào có bài viết mới? Tham gia kênh Telegram của tôi . Ở đó tôi thu thập các bài viết, suy nghĩ của mình và quá trình phát triển nguồn mở cùng nhau.
Chúng tôi cập nhật phiên bản dự án của mình lên 0.6.0-SNAPSHOT Chúng tôi cập nhật RELEASE_NOTES.md, thêm mô tả về những gì đã được thực hiện trong phiên bản mới:
## 0.6.0-SNAPSHOT * JRTB-7: thêm khả năng xóa đăng ký nhóm.
Mã hoạt động, các bài kiểm tra đã được viết cho nó: đã đến lúc đẩy tác vụ vào kho lưu trữ và tạo yêu cầu kéo."Dự án Java từ A đến Z": Xóa đăng ký các bài viết khỏi nhóm - 5

Thay vì kết thúc

Chúng tôi đã lâu không xem lại bảng dự án của mình nhưng đã có những thay đổi lớn: "Dự án Java từ A đến Z": Xóa đăng ký các bài viết khỏi nhóm - 6Chỉ còn 5 nhiệm vụ. Tức là bạn và tôi đã ở cuối con đường. Còn lại một chút. Điều đặc biệt thú vị cần lưu ý là loạt bài này đã được đăng từ giữa tháng 9, tức là được 7 tháng!!! Khi nảy ra ý tưởng này, tôi không ngờ nó lại mất nhiều thời gian đến vậy. Đồng thời, tôi hài lòng hơn với kết quả này! Các bạn ơi, nếu chưa rõ điều gì đang xảy ra trong bài viết, hãy đặt câu hỏi trong phần bình luận. Bằng cách này, tôi sẽ biết rằng điều gì đó cần được mô tả rõ hơn và điều gì đó cần được giải thích thêm. Vâng, như thường lệ, thích - đăng ký - rung chuông, cho dự án của chúng tôi một ngôi sao , viết bình luận và đánh giá bài viết! Cảm ơn tất cả. Cho đến phần tiếp theo. Chúng ta sẽ sớm nói về cách thêm tính năng vô hiệu hóa và kích hoạt bot thông qua các lệnh /stop & /start cũng như cách sử dụng chúng tốt nhất. Hẹn gặp lại!

Danh sách tất cả các tài liệu trong loạt bài này nằm ở đầu bài viết này.

Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION