코딩과 매일매일♥/Seoulvival

230829 - 알림기능 구현하기(알림 리스트)

코딩러버 2023. 8. 29. 12:43
728x90

알림 리스트 만들기


참고로 지금은 sse를 연결하지 않았다. 알림 기능을 처음 만들어 보는 거라서 굉장히 어렵게 느껴졌는데 sse는 말 그대로 구독한 알림에 대해서 띠링~~~ 하고 알려주는 것뿐 알림 리스트는 구독과 상관없이 보이는 것이었다!

 

우선 알림 리스트는 활동 알림과 해시태그 알림으로 나뉜다. 

활동 알림은 누군가 내 게시물이나 댓글에 좋아요를 누르거나 댓글을 달았을 때,

해시태그 알림은 내가 구독한 해시태그가 포함된 게시물이 새로 올라왔을 때 리스트로 보인다.


Alarm Entity

@Entity
@Getter
@Setter
@Table(name = "notification")
@NoArgsConstructor
public class Alarm {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "notification_id")
    private Long id;

    // 알람을 받은 사람
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id")
    private User user;

    // 알람이 발생한 post
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "post_id")
    private Post post;

    @Enumerated(EnumType.STRING)
    private AlarmEventType alarmEventType;

    private String notificationMessage;

    @Column(nullable = false)
    private Boolean isRead;

    private LocalDateTime registeredAt;

    private String hashtagName;
    private String userImg;

    public Alarm(Post post, User user, AlarmEventType alarmEventType, Boolean isRead, String notificationMessage, LocalDateTime registeredAt, String userImg) {
        this.post = post;
        this.user = user;
        this.alarmEventType = alarmEventType;
        this.notificationMessage = notificationMessage;
        this.isRead = isRead;
        this.registeredAt = registeredAt;
        this.userImg = userImg;
    }
}

alarm은 알람 notification은 알림인데 둘 중에서 고민을 하다가 일단은 단어 자체가 짧아 나에게는 가독성이 좋게 느껴지는 alarm을 기능 구현할 때 쓰고 DB에 저장할 때는 테이블 이름을 notification으로 정해주었다. 지금 드는 생각은 그냥 통일성 있게 한 단어로만 쓸걸 싶다 ^^

알림은 알람을 발생시킨 사람이 아닌 알람을 받은 사람에게 보여야 하니 
1. 알림을 받은 사람에 대한 Id

알림이 어디서 발생되었는지 알아야 하니
2. 알림이 발생한 post에 대한 Id

어떠한 타입의 알림이 발생했는지 (좋아요에 대한 것인지, 댓글에 대한 것인지 등)
3. AlarmEventtype

알림 리스트에 보일 메시지
4. notificationMessage

알림을 읽었는지 안 읽었는지 리스트의 색깔을 다르게 나타내기 위해서 추가한
5. Boolean 타입의 isRead 알림을 최신순으로 내림차순 정렬하기 위해 
6. 언제 알림이 발생되었는지(저장되었는지) 기록하는 registeredAt

7. 다음 활동 알림에는 알림을 발생시킨 사람의 userImg
8. 해시태그 알림에는 어떤 해시태그가 포함된 글인지 hashtag

📌 활동 및 해시태그 알림 List 조회 Api

 

AlarmController 

@RestController
@RequestMapping("/alarm")
@RequiredArgsConstructor
public class AlarmController {
    private final AlarmService alarmService;
    //활동 및 해시태그 알림 조회
    @GetMapping("/activity")
    public AlarmListResponse getNotification(@RequestParam int page,
                                             @RequestParam int size,
                                             @RequestParam String alarmCategory,
                                             @AuthenticationPrincipal UserDetailsImpl userDetails){
        User user = userDetails.getUser();
        return alarmService.getNotification(page-1, size, alarmCategory,  user);
    }

@GetMapping("/activity") 이 부분은 처음에 활동 알림 리스트와 해시태그 알림 리스트를 따로 만들어야 하나? 하는 나의 깜찍한 고민 속에서 나온 작은 실수... 프런트 분을 아조 초큼 당황스럽게 혼란스럽게 만들었지만 프로젝트 기간을 얼마 안 남았고 일단 끝내야 하니 이대로 가기로 하였다.

 

param 형식으로 alarmCategory를 받아 거기에 따라 활동 알림 리스트나 해시태그 알림 리스트를 보여줄 것이다.

AlarmService

@Service
@RequiredArgsConstructor
public class AlarmService {
    private final AlarmRepository alarmRepository;
    private final UserRepository userRepository;
    private final SubscribeHashtagRepository subscribeHashtagRepository;

    //알림 조회
    public AlarmListResponse getNotification(int page, int size, String alarmCategory, User user) {
        Pageable pageable = PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "registeredAt"));
        Page<Alarm> alarmPages = alarmRepository.findAllByUser(user, pageable);

        List<AlarmResponse> alarmResponses;

        if (alarmCategory.equals("activity")) {
            // activity일 때 필터링
            alarmResponses = alarmPages.stream()
                    .filter(alarm -> alarm.getAlarmEventType().getAlarmType().equals(AlarmType.LIKE)
                            || alarm.getAlarmEventType().getAlarmType().equals(AlarmType.COMMENT))
                    .map(alarm -> new AlarmResponse(
                            alarm.getId(),
                            alarm.getAlarmEventType(),
                            alarm.getNotificationMessage(),
                            alarm.getIsRead(),
                            alarm.getRegisteredAt(),
                            alarm.getUserImg()
                    ))
                    .collect(Collectors.toList());
        } else if (alarmCategory.equals("hashtag")) {
            // hashtag일 때 필터링
            alarmResponses = alarmPages.stream()
                    .filter(alarm -> alarm.getAlarmEventType().getAlarmType().equals(AlarmType.HASHTAG))
                    .map(alarm -> new AlarmResponse(
                            alarm.getId(),
                            alarm.getAlarmEventType(),
                            alarm.getNotificationMessage(),
                            alarm.getIsRead(),
                            alarm.getRegisteredAt(),
                            alarm.getUserImg(),
                            alarm.getHashtagName()
                    ))
                    .collect(Collectors.toList());
        } else {
            throw new IllegalArgumentException("알림 타입이 존재하지 않습니다.");
        }

        return new AlarmListResponse("조회 성공", alarmPages.getTotalPages(), alarmPages.getTotalElements(), size, alarmResponses);
    }

 

 

다음으로 각각 조회할 때 보이는 게 다르게 설정하였다. activity 일 때는 hashtag는 필요 없으므로!

hashtag 쪽은 userImg가 필요 없긴 하지만 혹시나 달라질 수도 있으니 넣어보았다.

AlarmResponse

@Getter
public class AlarmResponse {
    private Long id;
    private AlarmEventType alarmEventType;
    //알림의 내용
    private String text;
    private Boolean isRead;
    private LocalDateTime registeredAt;
    private String userImg;
    private String hashTagName;

    public AlarmResponse(Long id, AlarmEventType alarmEventType, String text, Boolean isRead, LocalDateTime registeredAt, String userImg) {
        this.id = id;
        this.alarmEventType = alarmEventType;
        this.text = text;
        this.isRead = isRead;
        this.registeredAt = registeredAt;
        this.userImg = userImg;
    }

    public AlarmResponse(Long id, AlarmEventType alarmEventType, String text, Boolean isRead, LocalDateTime registeredAt, String userImg, String hashTagName) {
        this.id = id;
        this.alarmEventType = alarmEventType;
        this.text = text;
        this.isRead = isRead;
        this.registeredAt = registeredAt;
        this.userImg = userImg;
        this.hashTagName = hashTagName;
    }
}
사실 hashtag 쪽도 userImg가 없게 해서 보내려 했는데 hashtag와 userImg가 똑같이 String타입이기 때문에 매개변수의 수가 같고 매개변수의 타입까지 모두 같아져서 보내려면 새로운 Response를 만들어야 했기에...ㅎㅎㅎ

 

commentService

// 댓글 생성
    public CommentResponseDto createComment(Long postId, CommentRequestDto requestDto, User user) {
        Post post = getPostById(postId);
        Comment comment = new Comment(requestDto, user.getNickname(), post, user);
        Comment newComment = commentRepository.save(comment);

        AlarmEventType eventType = AlarmEventType.NEW_COMMENT_ON_POST; // 댓글에 대한 알림 타입 설정
        Boolean isRead = false; // 초기값으로 미읽음 상태 설정
        String notificationMessage = "<b>" + user.getNickname() + "</b>" + "님이 [" + post.getContent() + "] 글에 [" + comment.getComment() + "] 댓글을 달았어요!"; // 알림 메시지 설정
        LocalDateTime registeredAt = LocalDateTime.now(); // 알림 생성 시간 설정
        String userImg = user.getProfileImageUrl();

        if (!post.getUser().getId().equals(user.getId())) {
            Alarm commentNotification = new Alarm(post ,post.getUser(), eventType, isRead, notificationMessage, registeredAt,userImg);
            alarmRepository.save(commentNotification);
        }
        return new CommentResponseDto(newComment); // ReCommentResponseDto로 변경
    }

 

 

postService   

    //게시물 생성 : 게시물을 업로드하고 이미지를 저장
    public MessageResponseDto upload(PostRequestDto requestDto, User user, List<MultipartFile> photos) throws IOException {
        requestDto.validateCategory();

        //본문에서 해시태그 추출
        List<String> hashtags = extractHashtagsFromContent(requestDto);
        // [#맛집, #좋아요]

        List<PostImg> postImgList = uploadPhotosToS3AndCreatePostImages(photos);

        Post post = new Post(requestDto, user);
        postRepository.save(post);

        associatePostImagesWithPost(post, postImgList);

        // 게시물에 연결된 해시태그를 구독하는 사용자 찾기
        for (String hashtag : hashtags) {
            List<User> subscribers = findSubscribersForHashtag(hashtag);

            for (User subscriber : subscribers) {
                // 로그인한 사용자와 게시물을 올린 사용자가 다를 때만 알림 생성
                if (!subscriber.getId().equals(user.getId())) {
                    AlarmEventType eventType = AlarmEventType.NEW_POST_WITH_HASHTAG; // 댓글에 대한 알림 타입 설정
                    Boolean isRead = false; // 초기값으로 미읽음 상태 설정
                    String notificationMessage = post.getContent(); // 알림 메시지 설정
                    LocalDateTime registeredAt = LocalDateTime.now(); // 알림 생성 시간 설정
                    String hashtagName = hashtag;

                    Alarm alarm = new Alarm(post, subscriber, eventType, isRead, notificationMessage, registeredAt, hashtagName);
                    alarmRepository.save(alarm);
                }
            }
        }
        return new MessageResponseDto("파일 저장 성공");
    }

두 개정도만 예시로 넣어놨는데 이렇게 알림이 저장되는 부분들까지 잘 수정해 준다!


📌 결과

 

저렇게 null 값으로 나오는 게 조금 거슬린다... 다음에 코드 리팩토링 할 때 이 부분 잘 고쳐봐야겠다.