From 204955aa228878e91228930c940a75c1299ae0b3 Mon Sep 17 00:00:00 2001 From: runtime-zer0 Date: Mon, 5 Aug 2024 00:01:24 +0900 Subject: [PATCH 1/4] =?UTF-8?q?Feat:=20=EA=B2=8C=EC=8B=9C=EA=B8=80=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1=EC=9C=84=EC=B9=98=20=EC=A3=BC=EB=B3=80=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9=EC=9E=90=20=EC=95=8C=EB=A6=BC=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - GatherArticleResponse.LocationInfoDTO.java 구현 - 모집글 작성 위치 정보를 위한 DTO 구현 - GatherArticleRepositoryCustom.java - 모집글 작성 위치 정보를 조회하기 위한 메서드 선언 - findLocationInfoDTOById() 메서드 선언 - GatherArticleRepositoryCustomImpl.java - 모집글 작성 위치 정보를 조회하기 위한 메서드 구현 - findLocationInfoDTOById() 메서드 구현 - MemberRepositoryCustom.java - 작성된 모집글의 주변에 위치한 사용자의 아이디를 반환하는 메서드를 선언 - findUsernamesWithGatherArticleInRange() 메서드 선언 - MemberRepositoryCustomImpl.java - 작성된 모집글의 주변에 위치한 사용자의 아이디를 반환하는 메서드를 구현 - findUsernamesWithGatherArticleInRange() 메서드 구현 - NotificationService.java - notifyGatherArticle() 메서드: 모집글이 작성되면 해당 모집글 주변에 위치한 사용자에게 알림 - GatherArticleController.java - createGatherArticle() 메서드 - 모집글 작성시 작성된 모집글 주변에 위치한 사용자에게 알림을 보내기위하여, notifyGatherArticle() 호출 --- .../controller/GatherArticleController.java | 5 ++ .../boardbuddy/dto/GatherArticleResponse.java | 22 ++++- .../GatherArticleRepositoryCustom.java | 2 + .../GatherArticleRepositoryCustomImpl.java | 13 +++ .../member/MemberRepositoryCustom.java | 8 +- .../member/MemberRepositoryCustomImpl.java | 36 ++++++++- .../service/NotificationService.java | 80 +++++++++++++------ 7 files changed, 131 insertions(+), 35 deletions(-) diff --git a/src/main/java/sumcoda/boardbuddy/controller/GatherArticleController.java b/src/main/java/sumcoda/boardbuddy/controller/GatherArticleController.java index 6a172bf8..6fa48cdb 100644 --- a/src/main/java/sumcoda/boardbuddy/controller/GatherArticleController.java +++ b/src/main/java/sumcoda/boardbuddy/controller/GatherArticleController.java @@ -13,6 +13,7 @@ import sumcoda.boardbuddy.service.ChatMessageService; import sumcoda.boardbuddy.service.ChatRoomService; import sumcoda.boardbuddy.service.GatherArticleService; +import sumcoda.boardbuddy.service.NotificationService; import java.util.List; import java.util.Map; @@ -31,6 +32,8 @@ public class GatherArticleController { private final ChatMessageService chatMessageService; + private final NotificationService notificationService; + /** * 모집글 작성 컨트롤러 * @@ -56,6 +59,8 @@ public ResponseEntity>> // 채팅방 입장 메세지 전송 chatMessageService.publishEnterOrExitChatMessage(chatRoomAndNicknamePair, MessageType.ENTER); + notificationService.notifyGatherArticle(gatherArticleId, username); + return buildSuccessResponseWithPairKeyData("post", createResponse, "모집글이 업로드 되었습니다", HttpStatus.CREATED); } diff --git a/src/main/java/sumcoda/boardbuddy/dto/GatherArticleResponse.java b/src/main/java/sumcoda/boardbuddy/dto/GatherArticleResponse.java index 94f5d6f9..ea0aab88 100644 --- a/src/main/java/sumcoda/boardbuddy/dto/GatherArticleResponse.java +++ b/src/main/java/sumcoda/boardbuddy/dto/GatherArticleResponse.java @@ -279,9 +279,6 @@ public SimpleInfoDTO(Long gatherArticleId, String title, String meetingLocation, } } - - - @Getter @NoArgsConstructor public static class TitleDTO { @@ -292,4 +289,23 @@ public TitleDTO(String title) { this.title = title; } } + + @Getter + @NoArgsConstructor + public static class LocationInfoDTO { + private String sido; + + private String sgg; + + private String emd; + + @Builder + public LocationInfoDTO(String sido, String sgg, String emd) { + this.sido = sido; + this.sgg = sgg; + this.emd = emd; + } + } + + } diff --git a/src/main/java/sumcoda/boardbuddy/repository/gatherArticle/GatherArticleRepositoryCustom.java b/src/main/java/sumcoda/boardbuddy/repository/gatherArticle/GatherArticleRepositoryCustom.java index 5631b702..29b49ec9 100644 --- a/src/main/java/sumcoda/boardbuddy/repository/gatherArticle/GatherArticleRepositoryCustom.java +++ b/src/main/java/sumcoda/boardbuddy/repository/gatherArticle/GatherArticleRepositoryCustom.java @@ -29,4 +29,6 @@ Slice findReadSliceDTOByLocationAndStatusAnd GatherArticleResponse.ReadDTO findGatherArticleReadDTOByGatherArticleId(Long gatherArticleId, Long memberId); Optional findTitleDTOById(Long gatherArticleId); + + Optional findLocationInfoDTOById(Long gatherArticleId); } \ No newline at end of file diff --git a/src/main/java/sumcoda/boardbuddy/repository/gatherArticle/GatherArticleRepositoryCustomImpl.java b/src/main/java/sumcoda/boardbuddy/repository/gatherArticle/GatherArticleRepositoryCustomImpl.java index 4ca8208f..0b72d485 100644 --- a/src/main/java/sumcoda/boardbuddy/repository/gatherArticle/GatherArticleRepositoryCustomImpl.java +++ b/src/main/java/sumcoda/boardbuddy/repository/gatherArticle/GatherArticleRepositoryCustomImpl.java @@ -253,4 +253,17 @@ public Optional findTitleDTOById(Long gatherArti .where(gatherArticle.id.eq(gatherArticleId)) .fetchOne()); } + + @Override + public Optional findLocationInfoDTOById(Long gatherArticleId) { + return Optional.ofNullable(jpaQueryFactory + .select(Projections.fields(GatherArticleResponse.LocationInfoDTO.class, + gatherArticle.sido, + gatherArticle.sgg, + gatherArticle.emd + )) + .from(gatherArticle) + .where(gatherArticle.id.eq(gatherArticleId)) + .fetchOne()); + } } diff --git a/src/main/java/sumcoda/boardbuddy/repository/member/MemberRepositoryCustom.java b/src/main/java/sumcoda/boardbuddy/repository/member/MemberRepositoryCustom.java index 6c9acd52..8bd4909a 100644 --- a/src/main/java/sumcoda/boardbuddy/repository/member/MemberRepositoryCustom.java +++ b/src/main/java/sumcoda/boardbuddy/repository/member/MemberRepositoryCustom.java @@ -21,11 +21,15 @@ public interface MemberRepositoryCustom { Optional findLocationWithRadiusDTOByUsername(String username); - Optional findUserNameDTOByUsername(String username); + Optional findUserNameDTOByUsername(String username); Optional findIdDTOByUsername(String username); - Optional findUsernameDTOByNickname(String nickname); + Optional findUsernameDTOByNickname(String nickname); Optional findNicknameDTOByUsername(String username); + + List findUsernamesWithGatherArticleInRange(String username, String sido, String sgg, String emd); + + } diff --git a/src/main/java/sumcoda/boardbuddy/repository/member/MemberRepositoryCustomImpl.java b/src/main/java/sumcoda/boardbuddy/repository/member/MemberRepositoryCustomImpl.java index 349eaa83..5733fa40 100644 --- a/src/main/java/sumcoda/boardbuddy/repository/member/MemberRepositoryCustomImpl.java +++ b/src/main/java/sumcoda/boardbuddy/repository/member/MemberRepositoryCustomImpl.java @@ -1,6 +1,7 @@ package sumcoda.boardbuddy.repository.member; import com.querydsl.core.types.Projections; +import com.querydsl.jpa.JPAExpressions; import com.querydsl.jpa.impl.JPAQueryFactory; import lombok.RequiredArgsConstructor; import sumcoda.boardbuddy.dto.AuthResponse; @@ -12,7 +13,9 @@ import static sumcoda.boardbuddy.entity.QBadgeImage.badgeImage; import static sumcoda.boardbuddy.entity.QMember.*; +import static sumcoda.boardbuddy.entity.QNearPublicDistrict.*; import static sumcoda.boardbuddy.entity.QProfileImage.*; +import static sumcoda.boardbuddy.entity.QPublicDistrict.*; @RequiredArgsConstructor public class MemberRepositoryCustomImpl implements MemberRepositoryCustom { @@ -112,9 +115,9 @@ public Optional findLocationWithRadiusDTOB } @Override - public Optional findUserNameDTOByUsername(String username) { + public Optional findUserNameDTOByUsername(String username) { return Optional.ofNullable(jpaQueryFactory - .select(Projections.fields(MemberResponse.UserNameDTO.class, + .select(Projections.fields(MemberResponse.UsernameDTO.class, member.username)) .from(member) .where(member.username.eq(username)) @@ -132,9 +135,9 @@ public Optional findIdDTOByUsername(String username) { } @Override - public Optional findUsernameDTOByNickname(String nickname) { + public Optional findUsernameDTOByNickname(String nickname) { return Optional.ofNullable(jpaQueryFactory - .select(Projections.fields(MemberResponse.UserNameDTO.class, + .select(Projections.fields(MemberResponse.UsernameDTO.class, member.username)) .from(member) .where(member.nickname.eq(nickname)) @@ -150,4 +153,29 @@ public Optional findNicknameDTOByUsername(String use .where(member.username.eq(username)) .fetchOne()); } + + @Override + public List findUsernamesWithGatherArticleInRange(String username, String sido, String sgg, String emd) { + return jpaQueryFactory + .select(member.username) + .from(member) + .where( + member.username.ne(username) + .and( + JPAExpressions + .selectOne() + .from(publicDistrict) + .join(nearPublicDistrict).on(publicDistrict.id.eq(nearPublicDistrict.publicDistrict.id)) + .where( + nearPublicDistrict.sido.eq(sido) + .and(nearPublicDistrict.sgg.eq(sgg)) + .and(nearPublicDistrict.emd.eq(emd)) + .and(member.radius.goe(nearPublicDistrict.radius)) // 반경 조건 + ) + .exists() + ) + ) + .fetch(); + } + } diff --git a/src/main/java/sumcoda/boardbuddy/service/NotificationService.java b/src/main/java/sumcoda/boardbuddy/service/NotificationService.java index ccd5c170..d84e68ab 100644 --- a/src/main/java/sumcoda/boardbuddy/service/NotificationService.java +++ b/src/main/java/sumcoda/boardbuddy/service/NotificationService.java @@ -11,6 +11,7 @@ import sumcoda.boardbuddy.entity.Member; import sumcoda.boardbuddy.entity.Notification; import sumcoda.boardbuddy.exception.gatherArticle.GatherArticleNotFoundException; +import sumcoda.boardbuddy.exception.gatherArticle.GatherArticleRetrievalException; import sumcoda.boardbuddy.exception.member.MemberNotFoundException; import sumcoda.boardbuddy.exception.member.MemberRetrievalException; import sumcoda.boardbuddy.exception.sseEmitter.SseEmitterSendErrorException; @@ -20,6 +21,7 @@ import sumcoda.boardbuddy.repository.notification.NotificationRepository; import sumcoda.boardbuddy.repository.gatherArticle.GatherArticleRepository; import sumcoda.boardbuddy.repository.memberGatherArticle.MemberGatherArticleRepository; +import sumcoda.boardbuddy.repository.publicDistrict.PublicDistrictRepository; import sumcoda.boardbuddy.repository.sseEmitter.SseEmitterRepository; import java.io.IOException; @@ -46,6 +48,8 @@ public class NotificationService { private final CommentRepository commentRepository; + private final PublicDistrictRepository publicDistrictRepository; + // SSE Emitter와 이벤트 캐시를 위한 저장소 private static final Map emitters = new ConcurrentHashMap<>(); @@ -54,21 +58,22 @@ public class NotificationService { /** * 유저 로그인 시 SSE Emitter 구독 요청 캐치 * - * @param username 로그인 사용자 아이디 + * @param nickname 알람 구독 요청 사용자 닉네임 **/ @Transactional - public SseEmitter subscribe(String username) { + public SseEmitter subscribe(String nickname) { + + MemberResponse.UsernameDTO usernameDTO = memberRepository.findUsernameDTOByNickname(nickname) + .orElseThrow(() -> new MemberNotFoundException("존재하지 않는 유저입니다.")); + + String username = usernameDTO.getUsername(); + // 매 연결마다 고유 Id 부여 String emitterId = username + "_" + System.currentTimeMillis(); // SseEmitter 인스턴스 생성 SseEmitter emitter = new SseEmitter(DEFAULT_TIMEOUT); - // 유저 검증 로직 - boolean memberExist = memberRepository.existsByUsername(username); - if (!memberExist) { - throw new MemberNotFoundException("존재하지 않는 유저입니다."); - } // 503 Service Unavailable 방지용 dummy 이벤트 전송 try { @@ -96,7 +101,7 @@ public SseEmitter subscribe(String username) { @Transactional public void notifyApplyParticipation(Long gatherArticleId, String appliedUsername) { // 모집글 작성자의 유저 아이디를 조회 - MemberResponse.UserNameDTO authorUsernameDTO = memberGatherArticleRepository.findAuthorUsernameByGatherArticleId(gatherArticleId) + MemberResponse.UsernameDTO authorUsernameDTO = memberGatherArticleRepository.findAuthorUsernameByGatherArticleId(gatherArticleId) .orElseThrow(() -> new MemberRetrievalException("서버 문제로 해당 모집글의 작성자를 찾을 수 없습니다. 관리자에게 문의하세요.")); String authorUsername = authorUsernameDTO.getUsername(); @@ -150,7 +155,7 @@ public void notifyRejectParticipation(String appliedNickname, Long gatherArticle @Transactional public void notifyCancelParticipation(Long gatherArticleId, String canceledUsername) { // 모집글 작성자의 유저 아이디를 조회 - MemberResponse.UserNameDTO authorUsernameDTO = memberGatherArticleRepository.findAuthorUsernameByGatherArticleId(gatherArticleId) + MemberResponse.UsernameDTO authorUsernameDTO = memberGatherArticleRepository.findAuthorUsernameByGatherArticleId(gatherArticleId) .orElseThrow(() -> new MemberRetrievalException("서버 문제로 해당 모집글의 작성자를 찾을 수 없습니다. 관리자에게 문의하세요.")); String authorUsername = authorUsernameDTO.getUsername(); @@ -172,7 +177,7 @@ public void notifyReviewRequest(Long gatherArticleId) { String message = String.format("모집글 '%s'에 대한 리뷰를 남겨주세요!", formattingTitle(gatherArticleId)); // 모든 참가자들의 아이디 조회 - List participants = memberGatherArticleRepository.findParticipantsByGatherArticleId(gatherArticleId); + List participants = memberGatherArticleRepository.findParticipantsByGatherArticleId(gatherArticleId); // 모든 참가자에게 알림 전송 participants.forEach(userNameDTO -> saveNotification(userNameDTO.getUsername(), message, "reviewRequest")); @@ -188,7 +193,7 @@ public void notifyWriteComment(Long gatherArticleId, Long parentId, String writt if (parentId == null) { // 모집글 작성자의 유저 아이디를 조회 - MemberResponse.UserNameDTO authorUsernameDTO = memberGatherArticleRepository.findAuthorUsernameByGatherArticleId(gatherArticleId) + MemberResponse.UsernameDTO authorUsernameDTO = memberGatherArticleRepository.findAuthorUsernameByGatherArticleId(gatherArticleId) .orElseThrow(() -> new MemberRetrievalException("서버 문제로 해당 모집글의 작성자를 찾을 수 없습니다. 관리자에게 문의하세요.")); String authorUsername = authorUsernameDTO.getUsername(); @@ -199,7 +204,7 @@ public void notifyWriteComment(Long gatherArticleId, Long parentId, String writt saveNotification(authorUsername, message, "writeComment"); } else { // 원댓글 작성자의 유저 아이디를 조회 - MemberResponse.UserNameDTO authorUsernameDTO = commentRepository.findCommentAuthorByCommentId(parentId); + MemberResponse.UsernameDTO authorUsernameDTO = commentRepository.findCommentAuthorByCommentId(parentId); String authorUsername = authorUsernameDTO.getUsername(); @@ -210,26 +215,50 @@ public void notifyWriteComment(Long gatherArticleId, Long parentId, String writt } } + /** + * 모집글이 작성되면 해당 모집글 주변에 위치한 사용자에게 알림 + * + * @param gatherArticleId 해당 모집글 Id + **/ + @Transactional + public void notifyGatherArticle(Long gatherArticleId, String writtenUsername) { + + GatherArticleResponse.LocationInfoDTO locationInfoDTO = gatherArticleRepository.findLocationInfoDTOById(gatherArticleId) + .orElseThrow(() -> new GatherArticleRetrievalException("서버 문제로 해당 모집글의 정보를 찾을 수 없습니다. 관리자에게 문의하세요.")); + + String sido = locationInfoDTO.getSido(); + String sgg = locationInfoDTO.getSgg(); + String emd = locationInfoDTO.getEmd(); + + List usernamesWithGatherArticleInRange = memberRepository.findUsernamesWithGatherArticleInRange(writtenUsername, sido, sgg, emd); + + for (String username : usernamesWithGatherArticleInRange) { + // 리뷰 요청 메시지를 포맷하여 생성 + String message = String.format("%s님의 주변에 '%s' 모집글이 작성되었습니다.", getNickname(username), formattingTitle(gatherArticleId)); + + log.info(message); + + saveNotification(username, message, "writeComment"); + } + + } + /** * 유저의 알림을 조회하여 최신순으로 반환하는 메서드 * - * @param username 알림을 조회할 유저의 아이디 + * @param nickname 알람 구독 요청 사용자 닉네임 * @return NotificationResponse 알림 응답 DTO **/ - public List getNotifications(String username) { - // 유저 검증 - if (Boolean.FALSE.equals(memberRepository.existsByUsername(username))) { - throw new MemberNotFoundException("해당 유저를 찾을 수 없습니다."); - } + public List getNotifications(String nickname) { + + MemberResponse.UsernameDTO usernameDTO = memberRepository.findUsernameDTOByNickname(nickname) + .orElseThrow(() -> new MemberNotFoundException("존재하지 않는 유저입니다.")); + + String username = usernameDTO.getUsername(); //DB에서 해당 유저의 알림을 최신순으로 조회 - return notificationRepository.findNotificationByMemberUsername(username).stream() - .map(notification -> NotificationResponse.NotificationDTO.builder() - .message(notification.getMessage()) - .createdAt(notification.getCreatedAt()) - .build()) - .toList(); + return notificationRepository.findNotificationByMemberUsername(username); } /** @@ -239,7 +268,6 @@ public List getNotifications(String userna * @param message 알림 메세지 * @param eventName 알림 이벤트 이름 **/ - @Transactional public void saveNotification(String username, String message, String eventName) { Member member = memberRepository.findByUsername(username) .orElseThrow(() -> new MemberRetrievalException("유저를 찾을 수 없습니다. 관리자에게 문의하세요.")); @@ -322,7 +350,7 @@ private String getNickname(String username) { private String getUsername(String nickname) { // 유저 닉네임으로 유저 아이디 조회 - MemberResponse.UserNameDTO userNameDTO = memberRepository.findUsernameDTOByNickname(nickname) + MemberResponse.UsernameDTO userNameDTO = memberRepository.findUsernameDTOByNickname(nickname) .orElseThrow(() -> new MemberRetrievalException("서버 문제로 해당 유저를 찾을 수 없습니다. 관리자에게 문의하세요.")); return userNameDTO.getUsername(); From 024880d5c17f7bf4f87571e535e379091b3639fb Mon Sep 17 00:00:00 2001 From: runtime-zer0 Date: Mon, 5 Aug 2024 00:03:44 +0900 Subject: [PATCH 2/4] =?UTF-8?q?Refactor:=20MemberResponse.UserNameDTO=20?= =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=20=EC=9D=B4=EB=A6=84=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=EC=9C=BC=EB=A1=9C=20=EC=9D=B8=ED=95=9C=20=EB=A6=AC?= =?UTF-8?q?=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - MemberResponse.UserNameDTO -> MemberResponse.UsernameDTO 로 클래스 이름 수정으로 인한 리팩토링 --- src/main/java/sumcoda/boardbuddy/dto/MemberResponse.java | 4 ++-- .../repository/comment/CommentRepositoryCustom.java | 2 +- .../repository/comment/CommentRepositoryCustomImpl.java | 4 ++-- .../MemberGatherArticleRepositoryCustom.java | 4 ++-- .../MemberGatherArticleRepositoryCustomImpl.java | 8 ++++---- .../java/sumcoda/boardbuddy/service/CommentService.java | 4 ++-- .../service/ParticipationApplicationService.java | 2 +- 7 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/main/java/sumcoda/boardbuddy/dto/MemberResponse.java b/src/main/java/sumcoda/boardbuddy/dto/MemberResponse.java index 2dca4aec..d4cc8bb6 100644 --- a/src/main/java/sumcoda/boardbuddy/dto/MemberResponse.java +++ b/src/main/java/sumcoda/boardbuddy/dto/MemberResponse.java @@ -106,11 +106,11 @@ public LocationWithRadiusDTO(String sido, String sgg, String emd, Integer radius @Getter @NoArgsConstructor - public static class UserNameDTO { + public static class UsernameDTO { private String username; @Builder - public UserNameDTO(String username) { + public UsernameDTO(String username) { this.username = username; } } diff --git a/src/main/java/sumcoda/boardbuddy/repository/comment/CommentRepositoryCustom.java b/src/main/java/sumcoda/boardbuddy/repository/comment/CommentRepositoryCustom.java index 6fe6044f..d30bf6f9 100644 --- a/src/main/java/sumcoda/boardbuddy/repository/comment/CommentRepositoryCustom.java +++ b/src/main/java/sumcoda/boardbuddy/repository/comment/CommentRepositoryCustom.java @@ -17,5 +17,5 @@ public interface CommentRepositoryCustom { Optional findCommentByCommentId(Long commentId); - MemberResponse.UserNameDTO findCommentAuthorByCommentId(Long commentId); + MemberResponse.UsernameDTO findCommentAuthorByCommentId(Long commentId); } \ No newline at end of file diff --git a/src/main/java/sumcoda/boardbuddy/repository/comment/CommentRepositoryCustomImpl.java b/src/main/java/sumcoda/boardbuddy/repository/comment/CommentRepositoryCustomImpl.java index c26cbf40..b14ec1ec 100644 --- a/src/main/java/sumcoda/boardbuddy/repository/comment/CommentRepositoryCustomImpl.java +++ b/src/main/java/sumcoda/boardbuddy/repository/comment/CommentRepositoryCustomImpl.java @@ -60,9 +60,9 @@ public Optional findCommentByCommentId(Long commentId) { } @Override - public MemberResponse.UserNameDTO findCommentAuthorByCommentId(Long commentId) { + public MemberResponse.UsernameDTO findCommentAuthorByCommentId(Long commentId) { return jpaQueryFactory. - select(Projections.fields(MemberResponse.UserNameDTO.class, + select(Projections.fields(MemberResponse.UsernameDTO.class, member.username)) .from(comment) .join(comment.member, member) diff --git a/src/main/java/sumcoda/boardbuddy/repository/memberGatherArticle/MemberGatherArticleRepositoryCustom.java b/src/main/java/sumcoda/boardbuddy/repository/memberGatherArticle/MemberGatherArticleRepositoryCustom.java index 0e2840e7..ad093157 100644 --- a/src/main/java/sumcoda/boardbuddy/repository/memberGatherArticle/MemberGatherArticleRepositoryCustom.java +++ b/src/main/java/sumcoda/boardbuddy/repository/memberGatherArticle/MemberGatherArticleRepositoryCustom.java @@ -12,7 +12,7 @@ public interface MemberGatherArticleRepositoryCustom { boolean isHasRole(Long gatherArticleId, String username); - Optional findAuthorUsernameByGatherArticleId(Long gatherArticleId); + Optional findAuthorUsernameByGatherArticleId(Long gatherArticleId); - List findParticipantsByGatherArticleId(Long gatherArticleId); + List findParticipantsByGatherArticleId(Long gatherArticleId); } diff --git a/src/main/java/sumcoda/boardbuddy/repository/memberGatherArticle/MemberGatherArticleRepositoryCustomImpl.java b/src/main/java/sumcoda/boardbuddy/repository/memberGatherArticle/MemberGatherArticleRepositoryCustomImpl.java index 5d71eb55..cd1a8e81 100644 --- a/src/main/java/sumcoda/boardbuddy/repository/memberGatherArticle/MemberGatherArticleRepositoryCustomImpl.java +++ b/src/main/java/sumcoda/boardbuddy/repository/memberGatherArticle/MemberGatherArticleRepositoryCustomImpl.java @@ -49,9 +49,9 @@ public boolean isHasRole(Long gatherArticleId, String username) { // 모집글의 작성자를 찾는 메서드 @Override - public Optional findAuthorUsernameByGatherArticleId(Long gatherArticleId) { + public Optional findAuthorUsernameByGatherArticleId(Long gatherArticleId) { return Optional.ofNullable(jpaQueryFactory - .select(Projections.fields(MemberResponse.UserNameDTO.class, + .select(Projections.fields(MemberResponse.UsernameDTO.class, member.username)) .from(memberGatherArticle) .join(memberGatherArticle.member, member) @@ -62,9 +62,9 @@ public Optional findAuthorUsernameByGatherArticleId( //모집글의 모든 참가자를 찾는 메서드 @Override - public List findParticipantsByGatherArticleId(Long gatherArticleId) { + public List findParticipantsByGatherArticleId(Long gatherArticleId) { return jpaQueryFactory - .select(Projections.fields(MemberResponse.UserNameDTO.class, + .select(Projections.fields(MemberResponse.UsernameDTO.class, member.username)) .from(memberGatherArticle) .join(memberGatherArticle.member, member) diff --git a/src/main/java/sumcoda/boardbuddy/service/CommentService.java b/src/main/java/sumcoda/boardbuddy/service/CommentService.java index be4e5718..4e36bb34 100644 --- a/src/main/java/sumcoda/boardbuddy/service/CommentService.java +++ b/src/main/java/sumcoda/boardbuddy/service/CommentService.java @@ -114,7 +114,7 @@ public List getComments(Long gatherArticleId, String us public void updateComment(Long gatherArticleId, Long commentId, CommentRequest.UpdateDTO updateDTO, String username) { // 사용자 검증 - MemberResponse.UserNameDTO userNameDTO = memberRepository.findUserNameDTOByUsername(username) + MemberResponse.UsernameDTO userNameDTO = memberRepository.findUserNameDTOByUsername(username) .orElseThrow(() -> new MemberRetrievalException("유효하지 않은 사용자입니다.")); // 모집글 검증 @@ -158,7 +158,7 @@ public void updateComment(Long gatherArticleId, Long commentId, CommentRequest.U public void deleteComment(Long gatherArticleId, Long commentId, String username) { // 사용자 검증 - MemberResponse.UserNameDTO userNameDTO = memberRepository.findUserNameDTOByUsername(username) + MemberResponse.UsernameDTO userNameDTO = memberRepository.findUserNameDTOByUsername(username) .orElseThrow(() -> new MemberRetrievalException("유효하지 않은 사용자입니다.")); // 모집글 검증 diff --git a/src/main/java/sumcoda/boardbuddy/service/ParticipationApplicationService.java b/src/main/java/sumcoda/boardbuddy/service/ParticipationApplicationService.java index 88178c05..e955fa70 100644 --- a/src/main/java/sumcoda/boardbuddy/service/ParticipationApplicationService.java +++ b/src/main/java/sumcoda/boardbuddy/service/ParticipationApplicationService.java @@ -174,7 +174,7 @@ public String approveParticipationApplication(Long gatherArticleId, Long partici // 모집글 상태 확인, 업데이트 updateGatherArticleStatusBasedOnParticipants(gatherArticle, newParticipantsCount); - MemberResponse.UserNameDTO userNameDTO = memberRepository.findUsernameDTOByNickname(applicantNickname).orElseThrow(() -> new MemberNotFoundException("참가 승인할 사용자의 정보를 찾을 수 없습니다.")); + MemberResponse.UsernameDTO userNameDTO = memberRepository.findUsernameDTOByNickname(applicantNickname).orElseThrow(() -> new MemberNotFoundException("참가 승인할 사용자의 정보를 찾을 수 없습니다.")); return userNameDTO.getUsername(); } From b0632b6017f00f71592bf93c5c84a6957b48f5ba Mon Sep 17 00:00:00 2001 From: runtime-zer0 Date: Mon, 5 Aug 2024 00:13:54 +0900 Subject: [PATCH 3/4] =?UTF-8?q?Refactor:=20=EC=95=8C=EB=A6=BC=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - NotificationRepositoryCustom.java - 사용자별 알림 목록 단순 조회 로직에서 엔티티를 직접 레포지토리에서 조회하고 있으므로 DTO 를 활용하여 조회하도록 리턴값 수정 - findNotificationByMemberUsername() 메서드 리턴값 수정 - NotificationRepositoryCustomImpl.java - 사용자별 알림 목록 단순 조회 로직에서 엔티티를 직접 레포지토리에서 조회하고 있으므로 DTO 를 활용하도록 로직 수정 - findNotificationByMemberUsername() 메서드 로직 수정 --- .../notification/NotificationRepositoryCustom.java | 4 ++-- .../notification/NotificationRepositoryCustomImpl.java | 9 ++++++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/main/java/sumcoda/boardbuddy/repository/notification/NotificationRepositoryCustom.java b/src/main/java/sumcoda/boardbuddy/repository/notification/NotificationRepositoryCustom.java index 697e4d80..1043e863 100644 --- a/src/main/java/sumcoda/boardbuddy/repository/notification/NotificationRepositoryCustom.java +++ b/src/main/java/sumcoda/boardbuddy/repository/notification/NotificationRepositoryCustom.java @@ -1,10 +1,10 @@ package sumcoda.boardbuddy.repository.notification; -import sumcoda.boardbuddy.entity.Notification; +import sumcoda.boardbuddy.dto.NotificationResponse; import java.util.List; public interface NotificationRepositoryCustom { - List findNotificationByMemberUsername(String username); + List< NotificationResponse.NotificationDTO> findNotificationByMemberUsername(String username); } diff --git a/src/main/java/sumcoda/boardbuddy/repository/notification/NotificationRepositoryCustomImpl.java b/src/main/java/sumcoda/boardbuddy/repository/notification/NotificationRepositoryCustomImpl.java index 172b44f9..69a3ebd6 100644 --- a/src/main/java/sumcoda/boardbuddy/repository/notification/NotificationRepositoryCustomImpl.java +++ b/src/main/java/sumcoda/boardbuddy/repository/notification/NotificationRepositoryCustomImpl.java @@ -1,8 +1,9 @@ package sumcoda.boardbuddy.repository.notification; +import com.querydsl.core.types.Projections; import com.querydsl.jpa.impl.JPAQueryFactory; import lombok.RequiredArgsConstructor; -import sumcoda.boardbuddy.entity.Notification; +import sumcoda.boardbuddy.dto.NotificationResponse; import java.util.List; @@ -22,8 +23,10 @@ public class NotificationRepositoryCustomImpl implements NotificationRepositoryC * @return 최신순으로 정렬된 알림 내역 **/ @Override - public List findNotificationByMemberUsername(String username) { - return jpaQueryFactory.selectFrom(notification) + public List< NotificationResponse.NotificationDTO> findNotificationByMemberUsername(String username) { + return jpaQueryFactory.select(Projections.fields(NotificationResponse.NotificationDTO.class, + notification.message, + notification.createdAt)) .join(notification.member, member) .where(member.username.eq(username)) .orderBy(notification.createdAt.desc()) From ecae673376232fd75ce31dc6f1fb8173d4c4935f Mon Sep 17 00:00:00 2001 From: runtime-zer0 Date: Mon, 5 Aug 2024 00:19:02 +0900 Subject: [PATCH 4/4] =?UTF-8?q?Refactor:=20=EB=B0=B0=ED=8F=AC=ED=99=98?= =?UTF-8?q?=EA=B2=BD=EC=97=90=EC=84=9C=EC=9D=98=20=EC=9D=B4=EC=8A=88=20?= =?UTF-8?q?=ED=95=B4=EA=B2=B0=EC=9D=84=20=EC=9C=84=ED=95=9C=20=EC=95=8C?= =?UTF-8?q?=EB=A6=BC=20=EA=B8=B0=EB=8A=A5=20=EB=A6=AC=ED=8C=A9=ED=86=A0?= =?UTF-8?q?=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - NotificationController.java - subscribe() 메서드 - 알림 관련 요청을 효율적으로 proxy 하기위하여 URI 수정 - SSE 방식은 표준 HTTP 통신과 다른 방식이므로 Spring에서 제공하는 Authentication 객체를 활용하여 사용자 인증을 할 수 없으므로, 파라미터 수정 - SSE 방식은 캐싱과 버퍼 기능을 활용하지 않아야 통신 가능하므로 응답 헤더 추가 - 알림 구독 관련 로직 수정으로 인한 리턴값 수정 - getNotifications() 메서드 - SSE 방식은 표준 HTTP 통신과 다른 방식이므로 Spring 에서 제공하는 Authentication 객체를 활용하여 사용자 인증을 할 수 없으므로, 파라미터 수정 - nginx.conf - 배포 환경에서의 알림 기능 관련 이슈 해결을 위하여 알림 기능 관련 nginx 코드 추가 - WebMvcConfig.java - 알림 기능은 SSE 방식은 표준 HTTP 통신과 다른 방식이므로 Spring 에서 제공하는 Authentication 객체를 활용하여 사용자 인증을 할 수 없으므로, 알림 기능 관련한 요청은 excludePathPatterns() 메서드를 활용하여 인증 인터셉터가 동작하지 않도록 수정된 URI 등록 --- nginx.conf | 13 ++++++++++ .../boardbuddy/config/WebMvcConfig.java | 7 ++---- .../controller/NotificationController.java | 24 ++++++++++++------- 3 files changed, 30 insertions(+), 14 deletions(-) diff --git a/nginx.conf b/nginx.conf index b0b49bcb..49536fb2 100644 --- a/nginx.conf +++ b/nginx.conf @@ -48,4 +48,17 @@ server { proxy_set_header Connection "Upgrade"; proxy_set_header Origin ""; } + + location /api/notifications { + proxy_pass http://127.0.0.1:8080; + proxy_http_version 1.1; + proxy_read_timeout 120s; + proxy_pass_request_headers on; + proxy_set_header Connection ""; + proxy_set_header Cache-Control "no-cache"; + proxy_set_header X-Accel-Buffering "no"; + proxy_set_header Content-Type "text/event-stream"; + proxy_buffering off; + chunked_transfer_encoding on; + } } \ No newline at end of file diff --git a/src/main/java/sumcoda/boardbuddy/config/WebMvcConfig.java b/src/main/java/sumcoda/boardbuddy/config/WebMvcConfig.java index 874735f5..1b3be8c4 100644 --- a/src/main/java/sumcoda/boardbuddy/config/WebMvcConfig.java +++ b/src/main/java/sumcoda/boardbuddy/config/WebMvcConfig.java @@ -30,12 +30,9 @@ public void addInterceptors(InterceptorRegistry registry) { "/api/login/oauth2/code/**", "/api/rankings", "/api/auth/locations/search", -// "/api/ws-stomp/connection", -// "/api/ws-stomp/reception" "/api/ws-stomp/**", - "/api/subscribe", - "/api/notifications" - )); + "/api/notifications/**" + )); WebMvcConfigurer.super.addInterceptors(registry); } diff --git a/src/main/java/sumcoda/boardbuddy/controller/NotificationController.java b/src/main/java/sumcoda/boardbuddy/controller/NotificationController.java index f1c09ba5..397c560c 100644 --- a/src/main/java/sumcoda/boardbuddy/controller/NotificationController.java +++ b/src/main/java/sumcoda/boardbuddy/controller/NotificationController.java @@ -2,11 +2,12 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestAttribute; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; import sumcoda.boardbuddy.dto.NotificationResponse; @@ -28,15 +29,20 @@ public class NotificationController { /** * SSE Emitter 구독 요청 * -// * @param username 유저 아이디 + * @param nickname 알람 구독 요청 사용자 닉네임 **/ - @GetMapping(value = "/api/subscribe", produces = MediaType.TEXT_EVENT_STREAM_VALUE) - public SseEmitter subscribe( -// @RequestAttribute String username + @GetMapping(value = "/api/notifications/subscribe", produces = MediaType.TEXT_EVENT_STREAM_VALUE) + public ResponseEntity subscribe( + @RequestParam String nickname ) { - log.info("User {} subscribed for notifications", "test"); + log.info("User {} subscribed for notifications", nickname); + SseEmitter sseEmitter = notificationService.subscribe(nickname); + + HttpHeaders headers = new HttpHeaders(); + headers.add("Cache-Control", "no-cache"); + headers.add("X-Accel-Buffering", "no"); - return notificationService.subscribe("test"); + return new ResponseEntity<>(sseEmitter, headers, HttpStatus.OK); } /** @@ -47,10 +53,10 @@ public SseEmitter subscribe( **/ @GetMapping(value = "/api/notifications") public ResponseEntity>>> getNotifications( -// @RequestAttribute String username + @RequestParam String nickname ) { - List notificationDTOs = notificationService.getNotifications("test"); + List notificationDTOs = notificationService.getNotifications(nickname); return buildSuccessResponseWithPairKeyData("notifications", notificationDTOs, "알림이 조회되었습니다.", HttpStatus.OK); }