-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #192 from 100-hours-a-week/feat-notification
댓글 알림 구현(알림 구독, 알림 전송만)
- Loading branch information
Showing
8 changed files
with
258 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
32 changes: 32 additions & 0 deletions
32
src/main/java/connectripbe/connectrip_be/notification/dto/NotificationCommentResponse.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
package connectripbe.connectrip_be.notification.dto; | ||
|
||
import connectripbe.connectrip_be.comment.entity.AccompanyCommentEntity; | ||
import java.time.LocalDateTime; | ||
import lombok.Builder; | ||
import lombok.Getter; | ||
|
||
@Getter | ||
@Builder | ||
public class NotificationCommentResponse { | ||
|
||
private Long userId; // 댓글을 남긴 사용자 ID | ||
private String userNickname; // 댓글을 남긴 사용자 닉네임 | ||
private String userProfilePath; // 댓글을 남긴 사용자 프로필 이미지 경로 | ||
private Long postId; // 댓글이 달린 게시물 ID | ||
private String content; // 댓글 내용 (글자 제한 적용) | ||
private LocalDateTime notificationTime; // 알림 생성 시간 | ||
private boolean isRead; // 읽음 여부 | ||
|
||
|
||
public static NotificationCommentResponse fromEntity(AccompanyCommentEntity comment, String content) { | ||
return NotificationCommentResponse.builder() | ||
.userId(comment.getMemberEntity().getId()) | ||
.userNickname(comment.getMemberEntity().getNickname()) | ||
.userProfilePath(comment.getMemberEntity().getProfileImagePath()) | ||
.postId(comment.getAccompanyPostEntity().getId()) | ||
.content(content) | ||
.notificationTime(LocalDateTime.now()) | ||
.isRead(false) | ||
.build(); | ||
} | ||
} |
47 changes: 47 additions & 0 deletions
47
src/main/java/connectripbe/connectrip_be/notification/entity/NotificationEntity.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
package connectripbe.connectrip_be.notification.entity; | ||
|
||
import connectripbe.connectrip_be.global.entity.BaseEntity; | ||
import connectripbe.connectrip_be.member.entity.MemberEntity; | ||
import jakarta.persistence.Column; | ||
import jakarta.persistence.Entity; | ||
import jakarta.persistence.FetchType; | ||
import jakarta.persistence.GeneratedValue; | ||
import jakarta.persistence.GenerationType; | ||
import jakarta.persistence.Id; | ||
import jakarta.persistence.JoinColumn; | ||
import jakarta.persistence.ManyToOne; | ||
import jakarta.persistence.Table; | ||
import java.time.LocalDateTime; | ||
import java.time.ZoneId; | ||
import java.time.ZonedDateTime; | ||
import lombok.AllArgsConstructor; | ||
import lombok.Builder; | ||
import lombok.Getter; | ||
import lombok.NoArgsConstructor; | ||
|
||
@Entity | ||
@Table(name = "notifications") | ||
@Getter | ||
@NoArgsConstructor | ||
@AllArgsConstructor | ||
@Builder | ||
public class NotificationEntity extends BaseEntity { | ||
|
||
@Id | ||
@GeneratedValue(strategy = GenerationType.IDENTITY) | ||
private Long id; | ||
|
||
@ManyToOne(fetch = FetchType.LAZY) | ||
@JoinColumn(name = "member_id", nullable = false) | ||
private MemberEntity member; // 알림을 받을 사용자 | ||
|
||
@Column(nullable = false, length = 256) | ||
private String message; // 알림 내용 | ||
|
||
@Column(name = "read_at") | ||
private LocalDateTime readAt; // 읽은 시간 | ||
|
||
public void markAsRead() { | ||
this.readAt = ZonedDateTime.now(ZoneId.of("UTC")).toLocalDateTime(); | ||
} | ||
} |
12 changes: 12 additions & 0 deletions
12
src/main/java/connectripbe/connectrip_be/notification/repository/NotificationRepository.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package connectripbe.connectrip_be.notification.repository; | ||
|
||
import connectripbe.connectrip_be.notification.entity.NotificationEntity; | ||
import java.util.List; | ||
import org.springframework.data.jpa.repository.JpaRepository; | ||
import org.springframework.stereotype.Repository; | ||
|
||
@Repository | ||
public interface NotificationRepository extends JpaRepository<NotificationEntity, Long> { | ||
List<NotificationEntity> findByMemberIdAndReadAtIsNull(Long memberId); | ||
} | ||
|
15 changes: 15 additions & 0 deletions
15
src/main/java/connectripbe/connectrip_be/notification/service/NotificationService.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package connectripbe.connectrip_be.notification.service; | ||
|
||
import connectripbe.connectrip_be.notification.dto.NotificationCommentResponse; | ||
import connectripbe.connectrip_be.notification.entity.NotificationEntity; | ||
import java.util.List; | ||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; | ||
|
||
public interface NotificationService { | ||
|
||
SseEmitter subscribe(Long memberId); // SSE 연결 구독 | ||
|
||
void sendNotification(Long memberId, NotificationCommentResponse notificationResponse); // 알림 전송 | ||
|
||
List<NotificationEntity> getUnreadNotifications(Long memberId); // 읽지 않은 알림 조회 | ||
} |
89 changes: 89 additions & 0 deletions
89
...in/java/connectripbe/connectrip_be/notification/service/impl/NotificationServiceImpl.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
package connectripbe.connectrip_be.notification.service.impl; | ||
|
||
import connectripbe.connectrip_be.global.exception.GlobalException; | ||
import connectripbe.connectrip_be.global.exception.type.ErrorCode; | ||
import connectripbe.connectrip_be.member.entity.MemberEntity; | ||
import connectripbe.connectrip_be.member.repository.MemberJpaRepository; | ||
import connectripbe.connectrip_be.notification.dto.NotificationCommentResponse; | ||
import connectripbe.connectrip_be.notification.entity.NotificationEntity; | ||
import connectripbe.connectrip_be.notification.repository.NotificationRepository; | ||
import connectripbe.connectrip_be.notification.service.NotificationService; | ||
import java.io.IOException; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.concurrent.ConcurrentHashMap; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.stereotype.Service; | ||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; | ||
|
||
@Service | ||
@RequiredArgsConstructor | ||
public class NotificationServiceImpl implements NotificationService { | ||
|
||
private final NotificationRepository notificationRepository; | ||
private final MemberJpaRepository memberJpaRepository; | ||
private final Map<Long, SseEmitter> emitters = new ConcurrentHashMap<>(); | ||
|
||
/** | ||
* 사용자가 알림을 구독할 수 있도록 SSE 연결을 설정하는 메서드. SSE 연결을 통해 실시간 알림을 구독하며, 연결이 완료되면 "알림 연결 완료" 메시지를 전송합니다. | ||
* | ||
* @param memberId 알림을 구독할 사용자의 ID | ||
* @return SSE Emitter 객체 (알림 구독을 위한 연결) | ||
*/ | ||
@Override | ||
public SseEmitter subscribe(Long memberId) { | ||
SseEmitter emitter = new SseEmitter(Long.MAX_VALUE); | ||
emitters.put(memberId, emitter); | ||
|
||
try { | ||
emitter.send(SseEmitter.event().name("CONNECTED").data("알림 연결 완료")); | ||
} catch (IOException e) { | ||
emitters.remove(memberId); | ||
} | ||
|
||
emitter.onCompletion(() -> emitters.remove(memberId)); | ||
emitter.onTimeout(() -> emitters.remove(memberId)); | ||
|
||
return emitter; | ||
} | ||
|
||
@Override | ||
public void sendNotification(Long memberId, NotificationCommentResponse notificationResponse) { | ||
|
||
// 사용자 ID로 MemberEntity 조회 | ||
MemberEntity member = memberJpaRepository.findById(memberId) | ||
.orElseThrow(() -> new GlobalException(ErrorCode.USER_NOT_FOUND)); | ||
|
||
// NotificationEntity를 빌더 패턴으로 생성 | ||
NotificationEntity notification = NotificationEntity.builder() | ||
.member(member) | ||
.message(notificationResponse.getContent()) | ||
.build(); | ||
|
||
notificationRepository.save(notification); | ||
|
||
// 알림을 받을 사용자에게 실시간 알림 전송 | ||
SseEmitter emitter = emitters.get(memberId); | ||
if (emitter != null) { | ||
try { | ||
emitter.send(SseEmitter.event() | ||
.name("COMMENT_ADDED") | ||
.data(notificationResponse)); // NotificationCommentResponse 객체 전송 | ||
} catch (IOException e) { | ||
emitters.remove(memberId); | ||
} | ||
} | ||
} | ||
|
||
|
||
/** | ||
* 특정 사용자의 읽지 않은 알림 목록을 조회하는 메서드. 읽지 않은 알림(읽음 시간이 설정되지 않은 알림)을 데이터베이스에서 조회하여 반환합니다. | ||
* | ||
* @param memberId 알림을 조회할 사용자의 ID | ||
* @return 읽지 않은 알림 목록 | ||
*/ | ||
@Override | ||
public List<NotificationEntity> getUnreadNotifications(Long memberId) { | ||
return notificationRepository.findByMemberIdAndReadAtIsNull(memberId); | ||
} | ||
} |
31 changes: 31 additions & 0 deletions
31
src/main/java/connectripbe/connectrip_be/notification/web/NotificationController.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
package connectripbe.connectrip_be.notification.web; | ||
|
||
import connectripbe.connectrip_be.notification.entity.NotificationEntity; | ||
import connectripbe.connectrip_be.notification.service.NotificationService; | ||
import java.util.List; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.http.ResponseEntity; | ||
import org.springframework.web.bind.annotation.GetMapping; | ||
import org.springframework.web.bind.annotation.PathVariable; | ||
import org.springframework.web.bind.annotation.RequestMapping; | ||
import org.springframework.web.bind.annotation.RestController; | ||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; | ||
|
||
@RestController | ||
@RequestMapping("/api/v1/comment-notifications") | ||
@RequiredArgsConstructor | ||
public class NotificationController { | ||
|
||
private final NotificationService notificationService; | ||
|
||
@GetMapping("/subscribe/{memberId}") | ||
public SseEmitter subscribe(@PathVariable Long memberId) { | ||
return notificationService.subscribe(memberId); | ||
} | ||
|
||
@GetMapping("/unread/{memberId}") | ||
public ResponseEntity<List<NotificationEntity>> getUnreadNotifications(@PathVariable Long memberId) { | ||
List<NotificationEntity> notifications = notificationService.getUnreadNotifications(memberId); | ||
return ResponseEntity.ok(notifications); | ||
} | ||
} |