JavaRush /وبلاگ جاوا /Random-FA /ما اشتراک مقالات را از گروه حذف می کنیم - "پروژه جاوا از ...
Roman Beekeeper
مرحله

ما اشتراک مقالات را از گروه حذف می کنیم - "پروژه جاوا از A تا Z"

در گروه منتشر شد
سلام به همه دوستان عزیزم، مهندسان ارشد نرم افزار آینده. ما به توسعه ربات تلگرام ادامه می دهیم. در این مرحله از پروژه خود، سه وظیفه را در نظر خواهیم گرفت که ارزش قابل مشاهده بیشتری نسبت به مقدار برنامه دارند. ما باید یاد بگیریم که چگونه اشتراک مقالات جدید را از یک گروه خاص حذف کنیم: از دستور /stop برای غیرفعال کردن ربات و استفاده از دستور / برای فعال کردن آن استفاده کنید. علاوه بر این، تمام درخواست ها و به روز رسانی ها فقط مربوط به کاربران فعال ربات است. طبق معمول، شعبه اصلی را به‌روزرسانی می‌کنیم تا همه تغییرات را دریافت کنیم و یک شاخه جدید ایجاد کنیم: STEP_7_JRTB-7. در این بخش، ما در مورد حذف اشتراک صحبت خواهیم کرد و 5 گزینه را برای رویدادها در نظر می گیریم - جالب خواهد بود.

JRTB-7: حذف اشتراک مقالات جدید از یک گروه

واضح است که همه کاربران می خواهند بتوانند اشتراک خود را حذف کنند تا در مورد مقالات جدید اعلان دریافت نکنند. منطق آن بسیار شبیه به منطق اضافه کردن اشتراک خواهد بود. اگر فقط یک دستور ارسال کنیم، در پاسخ لیستی از گروه‌ها و شناسه‌های آن‌ها را دریافت می‌کنیم که کاربر قبلاً در آن عضو شده است، تا بتوانیم بفهمیم دقیقاً چه چیزی باید حذف شود. و اگر کاربر شناسه گروه را به همراه تیم ارسال کند، اشتراک را حذف می کنیم. بنابراین، اجازه دهید این دستور را از سمت ربات تلگرام توسعه دهیم.
  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 آنچه را که ما می‌خواستیم می‌فهمد و رکورد را حذف می‌کند. "پروژه جاوا از A تا Z": حذف اشتراک مقالات از گروه - 1"پروژه جاوا از A تا Z": حذف اشتراک مقالات از گروه - 2برای حذف سریع یک کاربر، حاشیه نویسی EqualsAndHashCode را برای TelegramUser اضافه کردم و لیست GroupSub را حذف کردم تا مشکلی پیش نیاید. و روش حذف را روی مجموعه کاربران با کاربر مورد نیاز خود نامید. این چیزی است که برای 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;
}
در نتیجه همه چیز همانطور که ما می خواستیم شروع شد. چندین رویداد ممکن در یک تیم وجود دارد، بنابراین نوشتن یک تست خوب برای هر یک از آنها ایده خوبی است. صحبت از تست ها: در حین نوشتن آنها، نقصی در منطق پیدا کردم و قبل از عرضه به تولید آن را اصلاح کردم. اگر آزمایشی وجود نداشت، مشخص نیست که چقدر سریع تشخیص داده می شد. 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 = "Пока нет подписок на группы. 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 را ارسال کردید و اشتراک هایی در گروه ها وجود داشت.
  • هنگامی که یک شناسه گروه نامعتبر ارسال شد، به عنوان مثال، /deleteGroupSub abc ;
  • سناریویی که در آن همه چیز به درستی حذف می شود، همانطور که انتظار می رود.
  • سناریویی زمانی که شناسه گروه معتبر است، اما چنین گروهی در پایگاه داده نیست.
همانطور که می بینید، تمام این سناریوها باید با آزمایش پوشانده شوند. در حین نوشتن، متوجه شدم که برای نوشتن تست های بهتر، ارزش شرکت در چند دوره تست را دارد. من فکر می کنم این کمک خواهد کرد که به درستی گزینه های مختلف را جستجو کنید. درست است، افکاری برای آینده. در مرحله بعد، باید توضیحاتی را به دستور /help اضافه کنید که اکنون می توانید اشتراک را حذف کنید. بیایید آن را در بخش کار با اشتراک قرار دهیم. "پروژه جاوا از A تا Z": حذف اشتراک مقالات از گروه - 3البته برای اینکه این دستور کار کند، باید مقدار دهی اولیه آن را به CommandContainer اضافه کنید :
.put(DELETE_GROUP_SUB.getCommandName(),
       new DeleteGroupSubCommand(sendBotMessageService, groupSubService, telegramUserService))
اکنون می توانید عملکرد را روی یک ربات آزمایشی آزمایش کنید. ما پایگاه داده خود را با استفاده از docker-compose-test.yml: docker-compose -f docker-compose-test.yml راه اندازی می کنیم و SpringBoot را از طریق IDEA راه اندازی می کنیم. مکاتبات با ربات را کاملا پاک می کنم و دوباره شروع می کنم. من تمام گزینه هایی را که ممکن است هنگام کار با این تیم پیش بیاید، بررسی خواهم کرد. "پروژه جاوا از A تا Z": حذف اشتراک مقالات از گروه - 4همانطور که از اسکرین شات می بینید، همه گزینه ها از بین رفته و موفقیت آمیز بوده اند.
دوستان! آیا می خواهید بدانید چه زمانی کد جدید برای یک پروژه منتشر می شود؟ چه زمانی مقاله جدید منتشر می شود؟ به کانال تلگرام من بپیوندید . در آنجا مقالات، افکار و توسعه متن باز خود را با هم جمع می کنم.
ما نسخه پروژه خود را به 0.6.0-SNAPSHOT به روز می کنیم. RELEASE_NOTES.md را به روز می کنیم و شرح کارهایی را که در نسخه جدید انجام شده است اضافه می کنیم:
## 0.6.0-SNAPSHOT * JRTB-7: قابلیت حذف اشتراک گروه را اضافه کرد.
کد کار می کند، آزمایش هایی برای آن نوشته شده است: وقت آن است که وظیفه را به مخزن فشار دهید و یک درخواست کشش ایجاد کنید."پروژه جاوا از A تا Z": حذف اشتراک مقالات از گروه - 5

به جای تمام شدن

ما مدت زیادی است که به تابلوی پروژه خود نگاه نکرده ایم، اما تغییرات بزرگی رخ داده است: "پروژه جاوا از A تا Z": حذف اشتراک مقالات از گروه - 6فقط 5 کار باقی مانده است. یعنی من و شما در انتهای راه هستیم. کمی ترک کرد. جالب است بدانید که این سری مقالات از اواسط شهریور یعنی به مدت 7 ماه در حال اجرا هستند!!! وقتی به این ایده رسیدم، انتظار نداشتم اینقدر طول بکشد. در عین حال، من از نتیجه راضی هستم! دوستان اگر مشخص نیست در مقاله چه اتفاقی می افتد، سوالات خود را در نظرات مطرح کنید. به این ترتیب من می دانم که چیزی نیاز به توضیح بهتر دارد و چیزی نیاز به توضیح بیشتر دارد. خوب، طبق معمول، مانند - مشترک شوید - زنگ را بزنید، به پروژه ما ستاره بدهید ، نظرات خود را بنویسید و به مقاله امتیاز دهید! با تشکر از همه. تا قسمت بعدی. به زودی در مورد نحوه اضافه کردن غیرفعال سازی و فعال سازی ربات از طریق دستورات /stop & / start و نحوه بهترین استفاده از آنها صحبت خواهیم کرد. بعدا میبینمت!

فهرستی از تمام مواد این مجموعه در ابتدای این مقاله است.

نظرات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION