From c31533744994162f1d9d73f84ab1f91c6cf67639 Mon Sep 17 00:00:00 2001 From: lyouxsun Date: Thu, 6 Mar 2025 00:54:48 +0900 Subject: [PATCH 01/21] =?UTF-8?q?fix:=20=ED=8E=98=EC=9D=B4=EC=A7=95=20?= =?UTF-8?q?=EC=B2=98=EB=A6=AC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 쿼리 파라미터를 통해 page와 size 지정 가능하도록 추가 --- .../block/controller/BlockController.java | 8 ++++---- .../chat/controller/ChatController.java | 19 ++++++++++--------- .../domain/chat/service/ChatService.java | 6 ------ .../friend/controller/FriendController.java | 19 ++++++++++--------- .../message/repository/MessageRepository.java | 7 +++---- .../message/service/MessageService.java | 11 +++++++++++ 6 files changed, 38 insertions(+), 32 deletions(-) diff --git a/src/main/java/kuchat/server/domain/block/controller/BlockController.java b/src/main/java/kuchat/server/domain/block/controller/BlockController.java index 02f8ba3..a68c330 100644 --- a/src/main/java/kuchat/server/domain/block/controller/BlockController.java +++ b/src/main/java/kuchat/server/domain/block/controller/BlockController.java @@ -9,6 +9,7 @@ import kuchat.server.domain.member.Member; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.data.web.PageableDefault; @@ -45,11 +46,10 @@ public ResponseEntity release(@Auth Member member, @Operation(summary = "내가 차단한 사용자 목록 조회") @GetMapping public ResponseEntity blockMemberList(@Auth Member member, - @PageableDefault(size = 20, - sort = "createdDate", - direction = Sort.Direction.DESC) - Pageable pageable) { + @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "20") int size) { log.info("[blockMemberList] {} 가 차단한 사용자들 목록 조회", member.getId()); + Pageable pageable = PageRequest.of(page, size, Sort.Direction.DESC, "createdDate"); return blockService.getblocks(member, pageable); } } diff --git a/src/main/java/kuchat/server/domain/chat/controller/ChatController.java b/src/main/java/kuchat/server/domain/chat/controller/ChatController.java index ba34e98..79617b6 100644 --- a/src/main/java/kuchat/server/domain/chat/controller/ChatController.java +++ b/src/main/java/kuchat/server/domain/chat/controller/ChatController.java @@ -2,19 +2,20 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; -import kuchat.server.domain.auth.argumentResolver.Auth; import kuchat.server.common.response.BaseResponse; import kuchat.server.domain.Validator; +import kuchat.server.domain.auth.argumentResolver.Auth; import kuchat.server.domain.chat.Chat; import kuchat.server.domain.chat.dto.CreateChatRequest; import kuchat.server.domain.chat.service.ChatService; import kuchat.server.domain.member.Member; import kuchat.server.domain.message.dto.RecentMessageResponses; +import kuchat.server.domain.message.service.MessageService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; -import org.springframework.data.web.PageableDefault; import org.springframework.http.ResponseEntity; import org.springframework.validation.BindingResult; import org.springframework.validation.annotation.Validated; @@ -28,6 +29,7 @@ public class ChatController { private final ChatService chatService; + private final MessageService messageService; @Operation(summary = "채팅방 생성 (1:1 채팅, 그룹 채팅 모두 해당)") @PostMapping @@ -41,14 +43,13 @@ public ResponseEntity create(@Auth Member member, @RequestBody @Va @Operation(summary = "채팅방 화면으로 들어가기 (채팅방 메시지 조회하기)") @GetMapping("/{chatId}") public ResponseEntity enter(@Auth Member member, - @PathVariable("chatId") Long chatId, - @PageableDefault( - size = 50, - sort = "createdDate", - direction = Sort.Direction.DESC - ) Pageable pageable) { + @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "30") int size, + @PathVariable("chatId") Long chatId) { + log.info("[enter] member id = {} 가 chat id = {} 채팅방 조회", member.getId(), chatId); + Pageable pageable = PageRequest.of(page, size, Sort.Direction.DESC, "createdDate"); Chat chat = chatService.validateEnter(member, chatId); - RecentMessageResponses responses = chatService.getRecentMessages(chat, pageable); + RecentMessageResponses responses = messageService.getRecentMessages(chat, pageable); return ResponseEntity.ok(responses); } diff --git a/src/main/java/kuchat/server/domain/chat/service/ChatService.java b/src/main/java/kuchat/server/domain/chat/service/ChatService.java index 5661871..bd29b8e 100644 --- a/src/main/java/kuchat/server/domain/chat/service/ChatService.java +++ b/src/main/java/kuchat/server/domain/chat/service/ChatService.java @@ -64,10 +64,4 @@ private List getParticipants(Member creator, CreateChatRequest request) members.add(creator); return members; } - - public RecentMessageResponses getRecentMessages(Chat chat, Pageable pageable) { - // TODO. 채팅방에 새로운 메시지 읽음 처리 - - return null; - } } diff --git a/src/main/java/kuchat/server/domain/friend/controller/FriendController.java b/src/main/java/kuchat/server/domain/friend/controller/FriendController.java index fd24f7a..c8fdaeb 100644 --- a/src/main/java/kuchat/server/domain/friend/controller/FriendController.java +++ b/src/main/java/kuchat/server/domain/friend/controller/FriendController.java @@ -1,17 +1,17 @@ package kuchat.server.domain.friend.controller; import io.swagger.v3.oas.annotations.Operation; -import kuchat.server.domain.auth.argumentResolver.Auth; import kuchat.server.common.response.BaseResponse; +import kuchat.server.domain.auth.argumentResolver.Auth; import kuchat.server.domain.friend.dto.FriendApplyResponses; import kuchat.server.domain.friend.dto.FriendResponses; import kuchat.server.domain.friend.service.FriendService; import kuchat.server.domain.member.Member; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; -import org.springframework.data.web.PageableDefault; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @@ -42,18 +42,17 @@ public ResponseEntity acceptFriendApply(@Auth Member member, @Operation(summary = "내가 받은 친구신청 목록 조회") @GetMapping("/apply") public ResponseEntity getFriendApplyList(@Auth Member member, - @PageableDefault(size = 20, - sort = "createdDate", - direction = Sort.Direction.DESC) - Pageable pageable) { + @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "20") int size) { log.info("[getFriendApplyList] id={} , name={} 가 받은 친구 신청 목록 조회", member.getId(), member.getName()); + Pageable pageable = PageRequest.of(page, size, Sort.Direction.DESC, "createdDate"); return friendService.getFriendApplyList(member, pageable); } @Operation(summary = "친구신청 삭제") @DeleteMapping("/apply/{memberId}") public ResponseEntity deleteFriendApply(@Auth Member member, - @PathVariable("memberId") Long friendMemberId) { + @PathVariable("memberId") Long friendMemberId) { log.info("[deleteFriend] member id = {} 인 사용자가 보낸 친구신청 삭제", friendMemberId); return friendService.delete(member, friendMemberId, false); } @@ -69,9 +68,11 @@ public ResponseEntity deleteFriend(@Auth Member member, @Operation(summary = "친구 목록 조회 (이름 검색)") @GetMapping public ResponseEntity getFriendList(@Auth Member member, - @RequestParam(value = "name", required = false) String friendName, - @PageableDefault(size = 20, sort = "receiver.profile.name") Pageable pageable) { + @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "20") int size, + @RequestParam(value = "name", required = false) String friendName) { log.info("[getFriendList] 친구 목록 검색 및 조회. 검색 문자열 = '{}'", friendName); + Pageable pageable = PageRequest.of(page, size, Sort.Direction.ASC, "receiver.profile.name"); return friendService.getFriendList(member, friendName, pageable); } } diff --git a/src/main/java/kuchat/server/domain/message/repository/MessageRepository.java b/src/main/java/kuchat/server/domain/message/repository/MessageRepository.java index 106ff28..c6ef792 100644 --- a/src/main/java/kuchat/server/domain/message/repository/MessageRepository.java +++ b/src/main/java/kuchat/server/domain/message/repository/MessageRepository.java @@ -2,17 +2,16 @@ import kuchat.server.domain.chat.Chat; import kuchat.server.domain.message.Message; +import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; -import java.util.List; - @Repository public interface MessageRepository extends JpaRepository { - @Query("select m from Message m where m.chat = :chatroom order by m.createdDate DESC") - List findRecent20MessagesByChatroomId(@Param("chatroom") Chat chatroom, Pageable pageable); + @Query("select m from Message m where m.chat = :chat") + Page findRecent30MessagesByChat(@Param("chat") Chat chat, Pageable pageable); } diff --git a/src/main/java/kuchat/server/domain/message/service/MessageService.java b/src/main/java/kuchat/server/domain/message/service/MessageService.java index 4625d4e..b0b03dd 100644 --- a/src/main/java/kuchat/server/domain/message/service/MessageService.java +++ b/src/main/java/kuchat/server/domain/message/service/MessageService.java @@ -2,8 +2,14 @@ import kuchat.server.domain.chat.Chat; import kuchat.server.domain.chat.dto.CreateChatResponse; +import kuchat.server.domain.message.Message; +import kuchat.server.domain.message.dto.RecentMessageResponses; +import kuchat.server.domain.message.repository.MessageRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; import org.springframework.messaging.simp.SimpMessagingTemplate; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -20,6 +26,7 @@ public class MessageService { private final SimpMessagingTemplate simpMessagingTemplate; + private final MessageRepository messageRepository; public void notifyNewChat(Chat chat, List memberIds) { if (chat.isGroup()) { @@ -37,4 +44,8 @@ public void notifyNewChat(Chat chat, List memberIds) { } } + public RecentMessageResponses getRecentMessages(Chat chat, Pageable pageable) { + Page recentMessages = messageRepository.findRecent30MessagesByChat(chat, pageable); + return null; + } } From 7295e2ec31e47f17e6a295691d0d69a5c1160b96 Mon Sep 17 00:00:00 2001 From: lyouxsun Date: Thu, 6 Mar 2025 01:53:24 +0900 Subject: [PATCH 02/21] =?UTF-8?q?feat:=20=EC=B1=84=ED=8C=85=EB=B0=A9=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 해당 사용자가 조회하고자 하는 채팅방의 멤버인지 확인 (인가) - 해당 채팅방의 최근 30개 메시지 조회 - 채팅방의 모든 멤버들 프로필 정보와 30개 메시지를 함께 응답 --- .../chat/controller/ChatController.java | 21 ++++++----- .../domain/chat/dto/ChatMemberResponse.java | 26 ++++++++++++++ .../domain/chat/dto/ViewChatResponse.java | 36 +++++++++++++++++++ .../chat/repository/ChatMemberRepository.java | 5 ++- .../domain/chat/service/ChatService.java | 24 +++++++++---- .../message/dto/RecentMessageResponse.java | 29 +++++++++++++++ .../message/dto/RecentMessageResponses.java | 13 ------- .../message/repository/MessageRepository.java | 5 +-- .../message/service/MessageService.java | 11 +++--- 9 files changed, 134 insertions(+), 36 deletions(-) create mode 100644 src/main/java/kuchat/server/domain/chat/dto/ChatMemberResponse.java create mode 100644 src/main/java/kuchat/server/domain/chat/dto/ViewChatResponse.java create mode 100644 src/main/java/kuchat/server/domain/message/dto/RecentMessageResponse.java delete mode 100644 src/main/java/kuchat/server/domain/message/dto/RecentMessageResponses.java diff --git a/src/main/java/kuchat/server/domain/chat/controller/ChatController.java b/src/main/java/kuchat/server/domain/chat/controller/ChatController.java index 79617b6..f6c3474 100644 --- a/src/main/java/kuchat/server/domain/chat/controller/ChatController.java +++ b/src/main/java/kuchat/server/domain/chat/controller/ChatController.java @@ -5,11 +5,11 @@ import kuchat.server.common.response.BaseResponse; import kuchat.server.domain.Validator; import kuchat.server.domain.auth.argumentResolver.Auth; -import kuchat.server.domain.chat.Chat; import kuchat.server.domain.chat.dto.CreateChatRequest; +import kuchat.server.domain.chat.dto.ViewChatResponse; import kuchat.server.domain.chat.service.ChatService; import kuchat.server.domain.member.Member; -import kuchat.server.domain.message.dto.RecentMessageResponses; +import kuchat.server.domain.message.dto.RecentMessageResponse; import kuchat.server.domain.message.service.MessageService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -21,6 +21,8 @@ import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; +import java.util.List; + @Slf4j @Tag(name = "Chat", description = "1:1 채팅방 + 단체 채팅방 모두에 대한 요청을 받음") @RequiredArgsConstructor @@ -42,15 +44,16 @@ public ResponseEntity create(@Auth Member member, @RequestBody @Va @Operation(summary = "채팅방 화면으로 들어가기 (채팅방 메시지 조회하기)") @GetMapping("/{chatId}") - public ResponseEntity enter(@Auth Member member, - @RequestParam(defaultValue = "0") int page, - @RequestParam(defaultValue = "30") int size, - @PathVariable("chatId") Long chatId) { + public ResponseEntity view(@Auth Member member, + @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "30") int size, + @PathVariable("chatId") Long chatId) { log.info("[enter] member id = {} 가 chat id = {} 채팅방 조회", member.getId(), chatId); Pageable pageable = PageRequest.of(page, size, Sort.Direction.DESC, "createdDate"); - Chat chat = chatService.validateEnter(member, chatId); - RecentMessageResponses responses = messageService.getRecentMessages(chat, pageable); - return ResponseEntity.ok(responses); + ViewChatResponse response = chatService.validateEnter(member, chatId); + List recentMessages = messageService.getRecentMessages(response.getChatId(), pageable); + response.setRecentMessages(recentMessages); + return ResponseEntity.ok(response); } } diff --git a/src/main/java/kuchat/server/domain/chat/dto/ChatMemberResponse.java b/src/main/java/kuchat/server/domain/chat/dto/ChatMemberResponse.java new file mode 100644 index 0000000..e8a0293 --- /dev/null +++ b/src/main/java/kuchat/server/domain/chat/dto/ChatMemberResponse.java @@ -0,0 +1,26 @@ +package kuchat.server.domain.chat.dto; + +import kuchat.server.domain.chat.ChatMember; +import lombok.*; +import lombok.extern.slf4j.Slf4j; + +/** + * 채팅방 화면을 조회하는 API의 응답인 ViewChatResponse 내에서 사용하기 때문에 BaseResponse를 상속받지 않는다. + */ +@Getter +@Setter +@ToString +@Slf4j +@NoArgsConstructor +@AllArgsConstructor +public class ChatMemberResponse { + private Long memberId; + private String name; + private String profileImage; + + public ChatMemberResponse(ChatMember chatMember) { + this.memberId = chatMember.getMember().getId(); + this.name = chatMember.getMember().getName(); + this.profileImage = chatMember.getMember().getProfile().getProfileImage(); + } +} diff --git a/src/main/java/kuchat/server/domain/chat/dto/ViewChatResponse.java b/src/main/java/kuchat/server/domain/chat/dto/ViewChatResponse.java new file mode 100644 index 0000000..e9db31b --- /dev/null +++ b/src/main/java/kuchat/server/domain/chat/dto/ViewChatResponse.java @@ -0,0 +1,36 @@ +package kuchat.server.domain.chat.dto; + +import kuchat.server.common.response.BaseResponse; +import kuchat.server.common.response.BaseResponseStatus; +import kuchat.server.domain.chat.Chat; +import kuchat.server.domain.message.dto.RecentMessageResponse; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; +import lombok.extern.slf4j.Slf4j; + +import java.util.List; + + +@Getter +@Setter +@ToString +@Slf4j +@NoArgsConstructor +public class ViewChatResponse extends BaseResponse { + private Long chatId; + private String chatProfile; + private String chatName; // 채팅방 이름 + private List members; // 해당 채팅방 구성원 목록 + private List recentMessages; + + public ViewChatResponse(BaseResponseStatus responseStatus, Chat chat, + List members){ + super(responseStatus); + this.chatId = chat.getId(); + this.chatProfile = chat.getImage(); + this.chatName = chat.getName(); + this.members = members; + } +} diff --git a/src/main/java/kuchat/server/domain/chat/repository/ChatMemberRepository.java b/src/main/java/kuchat/server/domain/chat/repository/ChatMemberRepository.java index e37c0fb..cb13690 100644 --- a/src/main/java/kuchat/server/domain/chat/repository/ChatMemberRepository.java +++ b/src/main/java/kuchat/server/domain/chat/repository/ChatMemberRepository.java @@ -4,6 +4,8 @@ import kuchat.server.domain.chat.ChatMember; import kuchat.server.domain.member.Member; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import java.util.List; import java.util.Optional; @@ -11,5 +13,6 @@ public interface ChatMemberRepository extends JpaRepository { Optional findByMemberAndChat(Member member, Chat chat); - List findByChat(Chat chat); + @Query("select cm from ChatMember cm join fetch cm.member where cm.chat = :chat") + List findByChat(@Param("chat") Chat chat); } diff --git a/src/main/java/kuchat/server/domain/chat/service/ChatService.java b/src/main/java/kuchat/server/domain/chat/service/ChatService.java index bd29b8e..e79870b 100644 --- a/src/main/java/kuchat/server/domain/chat/service/ChatService.java +++ b/src/main/java/kuchat/server/domain/chat/service/ChatService.java @@ -2,16 +2,17 @@ import kuchat.server.common.exception.KuchatException; import kuchat.server.domain.chat.Chat; +import kuchat.server.domain.chat.ChatMember; +import kuchat.server.domain.chat.dto.ChatMemberResponse; import kuchat.server.domain.chat.dto.CreateChatRequest; import kuchat.server.domain.chat.dto.CreateChatResponse; +import kuchat.server.domain.chat.dto.ViewChatResponse; import kuchat.server.domain.chat.repository.ChatRepository; import kuchat.server.domain.member.Member; import kuchat.server.domain.member.service.MemberService; -import kuchat.server.domain.message.dto.RecentMessageResponses; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; -import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -48,15 +49,20 @@ public CreateChatResponse create(Member creator, CreateChatRequest request) { return new CreateChatResponse(SUCCESS, savedChat.getId()); } - - public Chat validateEnter(Member member, Long chatId) { + public ViewChatResponse validateEnter(Member member, Long chatId) { Chat chat = chatRepository.findById(chatId) .orElseThrow(() -> new KuchatException(NOT_FOUND_CHAT)); - chatMemberService.getChatMembersByChat(chat).stream() + List chatMembers = chatMemberService.getChatMembersByChat(chat); + checkMemberInChat(member, chatMembers, chat); + return new ViewChatResponse(SUCCESS, chat, getChatMemberResponses(chatMembers)); + } + + + private void checkMemberInChat(Member member, List chatMembers, Chat chat) { + chatMembers.stream() .filter(chatMember -> chatMember.matches(chat, member)) .findFirst() .orElseThrow(() -> new KuchatException(UNAUTHORIZED_CHAT_MEMBER)); - return chat; } private List getParticipants(Member creator, CreateChatRequest request) { @@ -64,4 +70,10 @@ private List getParticipants(Member creator, CreateChatRequest request) members.add(creator); return members; } + + private List getChatMemberResponses(List chatMembers) { + return chatMembers.stream() + .map(ChatMemberResponse::new) + .toList(); + } } diff --git a/src/main/java/kuchat/server/domain/message/dto/RecentMessageResponse.java b/src/main/java/kuchat/server/domain/message/dto/RecentMessageResponse.java new file mode 100644 index 0000000..5896c63 --- /dev/null +++ b/src/main/java/kuchat/server/domain/message/dto/RecentMessageResponse.java @@ -0,0 +1,29 @@ +package kuchat.server.domain.message.dto; + +import kuchat.server.domain.message.Message; +import lombok.*; +import lombok.extern.slf4j.Slf4j; + +import java.time.LocalDateTime; + +/** + * 채팅방 화면을 조회하는 API의 응답인 ViewChatResponse 내에서 사용하기 때문에 BaseResponse를 상속받지 않는다. + */ +@Getter +@Setter +@ToString +@Slf4j +@NoArgsConstructor +@AllArgsConstructor +public class RecentMessageResponse { + private Long senderId; + private LocalDateTime sendTime; + private String content; + + public RecentMessageResponse(Message message) { + this.senderId = message.getSenderId(); + this.sendTime = message.getCreatedDate(); + this.content = message.getContent(); + } + +} diff --git a/src/main/java/kuchat/server/domain/message/dto/RecentMessageResponses.java b/src/main/java/kuchat/server/domain/message/dto/RecentMessageResponses.java deleted file mode 100644 index 7da2eab..0000000 --- a/src/main/java/kuchat/server/domain/message/dto/RecentMessageResponses.java +++ /dev/null @@ -1,13 +0,0 @@ -package kuchat.server.domain.message.dto; - -import kuchat.server.common.response.BaseResponse; -import org.springframework.data.domain.Page; - -public class RecentMessageResponses extends BaseResponse { - - Page messageResponses; - - public static class RecentMessageResponse{ - - } -} diff --git a/src/main/java/kuchat/server/domain/message/repository/MessageRepository.java b/src/main/java/kuchat/server/domain/message/repository/MessageRepository.java index c6ef792..6612168 100644 --- a/src/main/java/kuchat/server/domain/message/repository/MessageRepository.java +++ b/src/main/java/kuchat/server/domain/message/repository/MessageRepository.java @@ -12,6 +12,7 @@ @Repository public interface MessageRepository extends JpaRepository { - @Query("select m from Message m where m.chat = :chat") - Page findRecent30MessagesByChat(@Param("chat") Chat chat, Pageable pageable); + @Query("select m from Message m " + + "where m.chat.id = :chatId") + Page findRecent30MessagesByChat(@Param("chatId") Long chatId, Pageable pageable); } diff --git a/src/main/java/kuchat/server/domain/message/service/MessageService.java b/src/main/java/kuchat/server/domain/message/service/MessageService.java index b0b03dd..0f8a715 100644 --- a/src/main/java/kuchat/server/domain/message/service/MessageService.java +++ b/src/main/java/kuchat/server/domain/message/service/MessageService.java @@ -3,12 +3,11 @@ import kuchat.server.domain.chat.Chat; import kuchat.server.domain.chat.dto.CreateChatResponse; import kuchat.server.domain.message.Message; -import kuchat.server.domain.message.dto.RecentMessageResponses; +import kuchat.server.domain.message.dto.RecentMessageResponse; import kuchat.server.domain.message.repository.MessageRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.messaging.simp.SimpMessagingTemplate; import org.springframework.stereotype.Service; @@ -44,8 +43,10 @@ public void notifyNewChat(Chat chat, List memberIds) { } } - public RecentMessageResponses getRecentMessages(Chat chat, Pageable pageable) { - Page recentMessages = messageRepository.findRecent30MessagesByChat(chat, pageable); - return null; + public List getRecentMessages(Long chatId, Pageable pageable) { + Page recentMessages = messageRepository.findRecent30MessagesByChat(chatId, pageable); + return recentMessages.stream() + .map(RecentMessageResponse::new) + .toList(); } } From b205545dff3d755c39d475a3ec122f67177cc239 Mon Sep 17 00:00:00 2001 From: lyouxsun Date: Thu, 6 Mar 2025 23:30:08 +0900 Subject: [PATCH 03/21] =?UTF-8?q?feat:=20=EC=B5=9C=EA=B7=BC=2030=EA=B0=9C?= =?UTF-8?q?=20=EB=A9=94=EC=8B=9C=EC=A7=80=20=EC=A1=B0=ED=9A=8C=20=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=84=B0=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Member와 Message 매핑 - 페치 조인을 통해 Message 조회 시 필드의 sender도 함께 조회 - 각 메시지 데이터에 sender id, 이름, 프로필 이미지 추가 --- .../server/common/config/WebMvcConfig.java | 8 +-- .../AuthArgumentResolver.java | 4 +- .../chat/controller/ChatController.java | 2 +- .../chat/dto/RecentMessageResponse.java | 51 +++++++++++++++++++ .../domain/chat/dto/ViewChatResponse.java | 6 +-- .../domain/chat/service/ChatService.java | 10 ++-- .../kuchat/server/domain/message/Message.java | 15 +++--- .../message/controller/MessageController.java | 18 +++++-- .../message/dto/RecentMessageResponse.java | 29 ----------- .../message/repository/MessageRepository.java | 3 +- .../message/service/MessageService.java | 6 ++- .../oauth/controller/OAuthController.java | 2 +- src/main/resources/templates/index.html | 2 +- 13 files changed, 98 insertions(+), 58 deletions(-) create mode 100644 src/main/java/kuchat/server/domain/chat/dto/RecentMessageResponse.java delete mode 100644 src/main/java/kuchat/server/domain/message/dto/RecentMessageResponse.java diff --git a/src/main/java/kuchat/server/common/config/WebMvcConfig.java b/src/main/java/kuchat/server/common/config/WebMvcConfig.java index 66ee753..9d80065 100644 --- a/src/main/java/kuchat/server/common/config/WebMvcConfig.java +++ b/src/main/java/kuchat/server/common/config/WebMvcConfig.java @@ -53,9 +53,11 @@ public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOrigins("https://kuchat.netlify.app") .allowedMethods("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS") - .exposedHeaders("*") - .allowedHeaders("*") - .allowCredentials(true); // 쿠키 허용 +// .exposedHeaders("*") +// .allowedHeaders("*") + .exposedHeaders("Authorization", "Set-Cookie") + .allowCredentials(true) // 쿠키 허용 + .maxAge(3000); // 원하는 시간만큼 pre-flight 리퀘스트를 캐싱 } } diff --git a/src/main/java/kuchat/server/domain/auth/argumentResolver/AuthArgumentResolver.java b/src/main/java/kuchat/server/domain/auth/argumentResolver/AuthArgumentResolver.java index c8e6fe5..4a45edb 100644 --- a/src/main/java/kuchat/server/domain/auth/argumentResolver/AuthArgumentResolver.java +++ b/src/main/java/kuchat/server/domain/auth/argumentResolver/AuthArgumentResolver.java @@ -13,6 +13,8 @@ import org.springframework.web.method.support.ModelAndViewContainer; import java.util.Arrays; +import java.util.Optional; +import java.util.stream.Stream; import static kuchat.server.common.response.BaseResponseStatus.NOT_FOUND_TOKEN; @@ -32,7 +34,7 @@ public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer m NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest(); Cookie[] cookies = request.getCookies(); - String rawToken = Arrays.stream(cookies) + String rawToken = Optional.ofNullable(cookies).stream().flatMap(Arrays::stream) .filter(cookie -> "accessToken".equals(cookie.getName())) .map(Cookie::getValue) .findFirst() diff --git a/src/main/java/kuchat/server/domain/chat/controller/ChatController.java b/src/main/java/kuchat/server/domain/chat/controller/ChatController.java index f6c3474..2ae5f2a 100644 --- a/src/main/java/kuchat/server/domain/chat/controller/ChatController.java +++ b/src/main/java/kuchat/server/domain/chat/controller/ChatController.java @@ -9,7 +9,7 @@ import kuchat.server.domain.chat.dto.ViewChatResponse; import kuchat.server.domain.chat.service.ChatService; import kuchat.server.domain.member.Member; -import kuchat.server.domain.message.dto.RecentMessageResponse; +import kuchat.server.domain.chat.dto.RecentMessageResponse; import kuchat.server.domain.message.service.MessageService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; diff --git a/src/main/java/kuchat/server/domain/chat/dto/RecentMessageResponse.java b/src/main/java/kuchat/server/domain/chat/dto/RecentMessageResponse.java new file mode 100644 index 0000000..e24ee91 --- /dev/null +++ b/src/main/java/kuchat/server/domain/chat/dto/RecentMessageResponse.java @@ -0,0 +1,51 @@ +package kuchat.server.domain.chat.dto; + +import kuchat.server.domain.member.Member; +import kuchat.server.domain.message.Message; +import lombok.*; +import lombok.extern.slf4j.Slf4j; + +import java.time.LocalDateTime; + +/** + * 채팅방 화면을 조회하는 API의 응답인 ViewChatResponse 내에서 사용하기 때문에 BaseResponse를 상속받지 않는다. + */ +@Getter +@Setter +@ToString +@Slf4j +@NoArgsConstructor +@AllArgsConstructor +public class RecentMessageResponse { + private Long messageId; + private SenderResponse sender; + private LocalDateTime sendTime; + private String content; + + public RecentMessageResponse(Message message) { + this.messageId = message.getId(); + this.sender = new SenderResponse(message.getSender()); + this.sendTime = message.getCreatedDate(); + this.content = message.getContent(); + } + + @Getter + @Setter + @ToString + @Slf4j + @NoArgsConstructor + @AllArgsConstructor + public static class SenderResponse { + + private Long id; + private String name; + private String profileImage; + + public SenderResponse(Member sender) { + this.id = sender.getId(); + this.name = sender.getName(); + this.profileImage = sender.getProfile().getProfileImage(); + } + } + +} diff --git a/src/main/java/kuchat/server/domain/chat/dto/ViewChatResponse.java b/src/main/java/kuchat/server/domain/chat/dto/ViewChatResponse.java index e9db31b..b4c4d1c 100644 --- a/src/main/java/kuchat/server/domain/chat/dto/ViewChatResponse.java +++ b/src/main/java/kuchat/server/domain/chat/dto/ViewChatResponse.java @@ -3,7 +3,6 @@ import kuchat.server.common.response.BaseResponse; import kuchat.server.common.response.BaseResponseStatus; import kuchat.server.domain.chat.Chat; -import kuchat.server.domain.message.dto.RecentMessageResponse; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @@ -22,15 +21,12 @@ public class ViewChatResponse extends BaseResponse { private Long chatId; private String chatProfile; private String chatName; // 채팅방 이름 - private List members; // 해당 채팅방 구성원 목록 private List recentMessages; - public ViewChatResponse(BaseResponseStatus responseStatus, Chat chat, - List members){ + public ViewChatResponse(BaseResponseStatus responseStatus, Chat chat){ super(responseStatus); this.chatId = chat.getId(); this.chatProfile = chat.getImage(); this.chatName = chat.getName(); - this.members = members; } } diff --git a/src/main/java/kuchat/server/domain/chat/service/ChatService.java b/src/main/java/kuchat/server/domain/chat/service/ChatService.java index e79870b..39fde7d 100644 --- a/src/main/java/kuchat/server/domain/chat/service/ChatService.java +++ b/src/main/java/kuchat/server/domain/chat/service/ChatService.java @@ -50,11 +50,10 @@ public CreateChatResponse create(Member creator, CreateChatRequest request) { } public ViewChatResponse validateEnter(Member member, Long chatId) { - Chat chat = chatRepository.findById(chatId) - .orElseThrow(() -> new KuchatException(NOT_FOUND_CHAT)); + Chat chat = getChatById(chatId); List chatMembers = chatMemberService.getChatMembersByChat(chat); checkMemberInChat(member, chatMembers, chat); - return new ViewChatResponse(SUCCESS, chat, getChatMemberResponses(chatMembers)); + return new ViewChatResponse(SUCCESS, chat); } @@ -76,4 +75,9 @@ private List getChatMemberResponses(List chatMem .map(ChatMemberResponse::new) .toList(); } + + public Chat getChatById(Long chatId) { + return chatRepository.findById(chatId) + .orElseThrow(() -> new KuchatException(NOT_FOUND_CHAT)); + } } diff --git a/src/main/java/kuchat/server/domain/message/Message.java b/src/main/java/kuchat/server/domain/message/Message.java index 0dcfa16..45b43cd 100644 --- a/src/main/java/kuchat/server/domain/message/Message.java +++ b/src/main/java/kuchat/server/domain/message/Message.java @@ -4,6 +4,7 @@ import kuchat.server.domain.BaseTime; import kuchat.server.domain.chat.Chat; import kuchat.server.domain.enums.MessageType; +import kuchat.server.domain.member.Member; import kuchat.server.domain.message.dto.MessageRequest; import lombok.Getter; import lombok.NoArgsConstructor; @@ -17,7 +18,7 @@ public class Message extends BaseTime { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "message_id") - private Long messageId; + private Long id; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "chat_id") @@ -27,11 +28,9 @@ public class Message extends BaseTime { // @JoinColumn(name = "parent_message_id", referencedColumnName = "message_id") // private Message parent = null; // chatroomId 없이 messageId만 가지면 된다. -// @OneToMany(mappedBy = "parent") -// private List children = new ArrayList<>(); - - @Column(name = "sender_id") - private Long senderId; + @OneToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "sender_id") + private Member sender; @Column(name = "message_type") @Enumerated(EnumType.STRING) @@ -39,10 +38,10 @@ public class Message extends BaseTime { private String content; - public Message(MessageRequest messageRequest, Chat chat) { + public Message(MessageRequest messageRequest, Chat chat, Member sender) { this.chat = chat; this.messageType = MessageType.fromString(messageRequest.getMessageType()); - this.senderId = messageRequest.getSenderId(); + this.sender = sender; this.content = messageRequest.getContent(); } diff --git a/src/main/java/kuchat/server/domain/message/controller/MessageController.java b/src/main/java/kuchat/server/domain/message/controller/MessageController.java index 868aede..edc4c0b 100644 --- a/src/main/java/kuchat/server/domain/message/controller/MessageController.java +++ b/src/main/java/kuchat/server/domain/message/controller/MessageController.java @@ -2,7 +2,12 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; +import kuchat.server.domain.chat.Chat; +import kuchat.server.domain.chat.service.ChatService; +import kuchat.server.domain.member.Member; +import kuchat.server.domain.member.service.MemberService; import kuchat.server.domain.message.dto.MessageRequest; +import kuchat.server.domain.message.service.MessageService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.messaging.handler.annotation.DestinationVariable; @@ -19,16 +24,23 @@ public class MessageController { private final SimpMessagingTemplate simpMessagingTemplate; + private final MemberService memberService; + private final ChatService chatService; + private final MessageService messageService; + @Operation(summary = "채팅방 구성원이 보낸 메시지 전송") @MessageMapping("/chat/{chatroomId}") // /pub/chatroom 으로 들어오는 메시지를 처리하는 api public void sendMessage(@Payload MessageRequest messageRequest, - @DestinationVariable("chatroomId") Long chatroomId) { + @DestinationVariable("chatId") Long chatId) { log.info("message request = {}", messageRequest.toString()); - log.info("채팅방 번호 = {}", chatroomId); + log.info("채팅방 번호 = {}", chatId); + + Chat chat = chatService.getChatById(chatId); + Member sender = memberService.getMemberById(messageRequest.getSenderId()); +// messageService.save(chat, sender, messageRequest); -// String sender = messageRequest.getSenderId(); /** TODO. MessageService 에서 갠톡인지 단톡인지 구분해서 처리 * 1. Chat 조회 diff --git a/src/main/java/kuchat/server/domain/message/dto/RecentMessageResponse.java b/src/main/java/kuchat/server/domain/message/dto/RecentMessageResponse.java deleted file mode 100644 index 5896c63..0000000 --- a/src/main/java/kuchat/server/domain/message/dto/RecentMessageResponse.java +++ /dev/null @@ -1,29 +0,0 @@ -package kuchat.server.domain.message.dto; - -import kuchat.server.domain.message.Message; -import lombok.*; -import lombok.extern.slf4j.Slf4j; - -import java.time.LocalDateTime; - -/** - * 채팅방 화면을 조회하는 API의 응답인 ViewChatResponse 내에서 사용하기 때문에 BaseResponse를 상속받지 않는다. - */ -@Getter -@Setter -@ToString -@Slf4j -@NoArgsConstructor -@AllArgsConstructor -public class RecentMessageResponse { - private Long senderId; - private LocalDateTime sendTime; - private String content; - - public RecentMessageResponse(Message message) { - this.senderId = message.getSenderId(); - this.sendTime = message.getCreatedDate(); - this.content = message.getContent(); - } - -} diff --git a/src/main/java/kuchat/server/domain/message/repository/MessageRepository.java b/src/main/java/kuchat/server/domain/message/repository/MessageRepository.java index 6612168..6ac1564 100644 --- a/src/main/java/kuchat/server/domain/message/repository/MessageRepository.java +++ b/src/main/java/kuchat/server/domain/message/repository/MessageRepository.java @@ -1,6 +1,5 @@ package kuchat.server.domain.message.repository; -import kuchat.server.domain.chat.Chat; import kuchat.server.domain.message.Message; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -12,7 +11,7 @@ @Repository public interface MessageRepository extends JpaRepository { - @Query("select m from Message m " + + @Query("select m from Message m join fetch m.sender " + "where m.chat.id = :chatId") Page findRecent30MessagesByChat(@Param("chatId") Long chatId, Pageable pageable); } diff --git a/src/main/java/kuchat/server/domain/message/service/MessageService.java b/src/main/java/kuchat/server/domain/message/service/MessageService.java index 0f8a715..2abf9a4 100644 --- a/src/main/java/kuchat/server/domain/message/service/MessageService.java +++ b/src/main/java/kuchat/server/domain/message/service/MessageService.java @@ -3,7 +3,7 @@ import kuchat.server.domain.chat.Chat; import kuchat.server.domain.chat.dto.CreateChatResponse; import kuchat.server.domain.message.Message; -import kuchat.server.domain.message.dto.RecentMessageResponse; +import kuchat.server.domain.chat.dto.RecentMessageResponse; import kuchat.server.domain.message.repository.MessageRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -13,6 +13,7 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.util.Comparator; import java.util.List; import static kuchat.server.common.response.BaseResponseStatus.SUCCESS; @@ -47,6 +48,9 @@ public List getRecentMessages(Long chatId, Pageable pagea Page recentMessages = messageRepository.findRecent30MessagesByChat(chatId, pageable); return recentMessages.stream() .map(RecentMessageResponse::new) + .sorted(Comparator.comparing(RecentMessageResponse::getSendTime)) .toList(); } + + } diff --git a/src/main/java/kuchat/server/domain/oauth/controller/OAuthController.java b/src/main/java/kuchat/server/domain/oauth/controller/OAuthController.java index 0937dba..228f583 100644 --- a/src/main/java/kuchat/server/domain/oauth/controller/OAuthController.java +++ b/src/main/java/kuchat/server/domain/oauth/controller/OAuthController.java @@ -13,7 +13,7 @@ @RequestMapping("/oauth") @RestController @RequiredArgsConstructor -@CrossOrigin(origins = "https://kuchat.netlify.app", allowCredentials = "true") +//@CrossOrigin(origins = "https://kuchat.netlify.app", allowCredentials = "true") public class OAuthController { private final GoogleOAuthService googleOAuthService; diff --git a/src/main/resources/templates/index.html b/src/main/resources/templates/index.html index b962a53..d5b1157 100644 --- a/src/main/resources/templates/index.html +++ b/src/main/resources/templates/index.html @@ -26,7 +26,7 @@

로그인되지 않은 상태입니다.

로그인 후 이용해 주세요.

- Google Login + Google Login
From b8f13daf885d9fa77b49548dda027bb37314bf92 Mon Sep 17 00:00:00 2001 From: lyouxsun Date: Sun, 9 Mar 2025 09:44:11 +0900 Subject: [PATCH 04/21] =?UTF-8?q?feat:=20=EC=8B=A4=EC=8B=9C=EA=B0=84=20?= =?UTF-8?q?=EC=B1=84=ED=8C=85=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 웹소켓 연결 - 웹소켓을 통한 채팅 전송 --- build.gradle | 6 +- .../server/common/config/WebSocketConfig.java | 30 ++++++---- .../kuchat/server/domain/message/Message.java | 2 +- .../WebSocketHandshakeInterceptor.java | 23 ++++++++ .../domain/message/WebSocketInterceptor.java | 58 +++---------------- .../message/controller/MessageController.java | 30 ++++------ .../domain/message/dto/MessageRequest.java | 6 ++ .../domain/message/dto/MessageResponse.java | 30 ++++++++++ .../message/service/MessageService.java | 16 ++++- 9 files changed, 116 insertions(+), 85 deletions(-) create mode 100644 src/main/java/kuchat/server/domain/message/WebSocketHandshakeInterceptor.java create mode 100644 src/main/java/kuchat/server/domain/message/dto/MessageResponse.java diff --git a/build.gradle b/build.gradle index d0b3bba..a4420ce 100644 --- a/build.gradle +++ b/build.gradle @@ -48,8 +48,10 @@ dependencies { // chat implementation 'org.springframework.boot:spring-boot-starter-websocket' - implementation 'org.webjars:sockjs-client:1.1.2' - implementation 'org.webjars:stomp-websocket:2.3.3' +// implementation 'org.webjars:sockjs-client:1.5.1' + implementation 'org.webjars:stomp-websocket:2.3.4' + implementation 'org.springframework:spring-messaging:6.1.4' + implementation 'org.springframework.security:spring-security-messaging:6.2.3' } diff --git a/src/main/java/kuchat/server/common/config/WebSocketConfig.java b/src/main/java/kuchat/server/common/config/WebSocketConfig.java index 4110f63..8052b74 100644 --- a/src/main/java/kuchat/server/common/config/WebSocketConfig.java +++ b/src/main/java/kuchat/server/common/config/WebSocketConfig.java @@ -1,8 +1,8 @@ package kuchat.server.common.config; +import kuchat.server.domain.message.WebSocketHandshakeInterceptor; import kuchat.server.domain.message.WebSocketInterceptor; -import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Configuration; import org.springframework.messaging.simp.config.ChannelRegistration; @@ -13,16 +13,14 @@ import org.springframework.web.socket.config.annotation.WebSocketTransportRegistration; @Slf4j -@RequiredArgsConstructor -@Configuration @EnableWebSocketMessageBroker +@Configuration public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { - private final WebSocketInterceptor webSocketInterceptor; - @Override public void configureClientInboundChannel(ChannelRegistration registration) { - registration.interceptors(webSocketInterceptor); + log.info("✅ WebSocket Inbound Channel Interceptor 활성화"); + registration.interceptors(new WebSocketInterceptor()); } /** @@ -32,12 +30,14 @@ public void configureClientInboundChannel(ChannelRegistration registration) { public void configureMessageBroker(MessageBrokerRegistry mqRegistry) { // 클라이언트 → 서버 로 오는 요청 - // @MessageMapping 메서드가 /msg 으로 시작하는 요청을 처리하도록 한다. - mqRegistry.setApplicationDestinationPrefixes("/ku"); + // @MessageMapping 메서드가 /pub 으로 시작하는 요청을 처리하도록 한다. + mqRegistry.setApplicationDestinationPrefixes("/pub"); + mqRegistry.enableSimpleBroker("/sub"); // 서버 → 클라이언트 로 가는 요청 // 브로커가 자동으로 메시지를 전송해준다. - mqRegistry.enableSimpleBroker("/queue", "/topic"); // queue : 개인톡, topic : 단체톡 +// mqRegistry.enableSimpleBroker("/queue", "/topic"); // queue : 개인톡, topic : 단체톡 +// mqRegistry.enableSimpleBroker("/sub"); // enableSimpleBroker : 스프링이 제공하는 인메모리 브로커를 사용하겠다는 의미 } @@ -47,12 +47,18 @@ public void configureMessageBroker(MessageBrokerRegistry mqRegistry) { */ @Override public void registerStompEndpoints(StompEndpointRegistry registry) { - log.info("[registerStompEndpoints] Registering STOMP endpoint at /stomp"); + log.info("[registerStompEndpoints] Registering STOMP endpoint at /ws-connect"); // 클라이언트가 websocket 연결을 맺기 위해 접속해야 하는 http 엔드포인트 registry.addEndpoint("/ws-connect") - .setAllowedOrigins("*"); -// .withSockJS(); // 이 옵션 추가하면 오류가 나는 이유가 뭘까? + .setAllowedOriginPatterns("*") + .addInterceptors(new WebSocketHandshakeInterceptor()); +// .withSockJS(); + + registry.addEndpoint("/ws-connect") + .setAllowedOriginPatterns("*") + .addInterceptors(new WebSocketHandshakeInterceptor()) + .withSockJS(); } /** diff --git a/src/main/java/kuchat/server/domain/message/Message.java b/src/main/java/kuchat/server/domain/message/Message.java index 45b43cd..622267b 100644 --- a/src/main/java/kuchat/server/domain/message/Message.java +++ b/src/main/java/kuchat/server/domain/message/Message.java @@ -40,7 +40,7 @@ public class Message extends BaseTime { public Message(MessageRequest messageRequest, Chat chat, Member sender) { this.chat = chat; - this.messageType = MessageType.fromString(messageRequest.getMessageType()); + this.messageType = MessageType.valueOf(messageRequest.getMessageType()); this.sender = sender; this.content = messageRequest.getContent(); } diff --git a/src/main/java/kuchat/server/domain/message/WebSocketHandshakeInterceptor.java b/src/main/java/kuchat/server/domain/message/WebSocketHandshakeInterceptor.java new file mode 100644 index 0000000..f2fadb3 --- /dev/null +++ b/src/main/java/kuchat/server/domain/message/WebSocketHandshakeInterceptor.java @@ -0,0 +1,23 @@ +package kuchat.server.domain.message; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.server.ServerHttpRequest; +import org.springframework.http.server.ServerHttpResponse; +import org.springframework.web.socket.WebSocketHandler; +import org.springframework.web.socket.server.HandshakeInterceptor; + +import java.util.Map; + +@Slf4j +public class WebSocketHandshakeInterceptor implements HandshakeInterceptor { + @Override + public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map attributes) throws Exception { + log.info("🌐 WebSocket 연결 시도: {}", request.getRemoteAddress()); + return true; + } + + @Override + public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) { + log.info("✅ WebSocket 핸드셰이크 완료: {}", request.getRemoteAddress()); + } +} diff --git a/src/main/java/kuchat/server/domain/message/WebSocketInterceptor.java b/src/main/java/kuchat/server/domain/message/WebSocketInterceptor.java index f61824d..5dd5cdf 100644 --- a/src/main/java/kuchat/server/domain/message/WebSocketInterceptor.java +++ b/src/main/java/kuchat/server/domain/message/WebSocketInterceptor.java @@ -1,54 +1,32 @@ package kuchat.server.domain.message; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; -import kuchat.server.common.exception.KuchatException; import lombok.extern.slf4j.Slf4j; -import org.springframework.http.server.ServerHttpRequest; -import org.springframework.http.server.ServerHttpResponse; import org.springframework.messaging.Message; import org.springframework.messaging.MessageChannel; import org.springframework.messaging.simp.stomp.StompCommand; import org.springframework.messaging.simp.stomp.StompHeaderAccessor; import org.springframework.messaging.support.ChannelInterceptor; -import org.springframework.stereotype.Component; -import org.springframework.web.socket.WebSocketHandler; -import org.springframework.web.socket.server.HandshakeInterceptor; import java.nio.charset.StandardCharsets; -import java.util.HashMap; -import java.util.Map; -import static kuchat.server.common.response.BaseResponseStatus.CONVERT_TO_JSON_FAIL; - -@Component @Slf4j -public class WebSocketInterceptor implements ChannelInterceptor, HandshakeInterceptor { - - private final ObjectMapper objectMapper = new ObjectMapper(); +public class WebSocketInterceptor implements ChannelInterceptor { @Override public Message preSend(Message message, MessageChannel channel) { StompHeaderAccessor accessor = StompHeaderAccessor.wrap(message); StompCommand command = accessor.getCommand(); + log.info("📩 [STOMP {} 요청 감지] destination={}, payload={}", + command.name(), + accessor.getDestination(), + message.getPayload()); + Object payload = message.getPayload(); log.info("[preSend] payload = " + new String((byte[]) payload, StandardCharsets.UTF_8)); -// Map payloadMap; -// -// try { -// payloadMap = objectMapper.readValue((byte[]) payload, new TypeReference>() { -// }); -// } catch (JsonProcessingException e) { -// throw new KuchatException(CONVERT_TO_JSON_FAIL); -// } - - log.info("[preSend] 메시지 헤더 = {}", message.getHeaders()); // log.info("[preSend] 메시지 내용 = {}", payloadMap); - if (command == StompCommand.SUBSCRIBE) { log.info(accessor.getDestination()); } @@ -62,20 +40,8 @@ public Message postReceive(Message message, MessageChannel channel) { Object payload = message.getPayload(); log.info("[postReceive] " + payload.toString()); - - - Map payloadMap; - - try { - payloadMap = objectMapper.readValue((String) payload, new TypeReference>() { - }); - } catch (JsonProcessingException e) { - throw new KuchatException(CONVERT_TO_JSON_FAIL); - } - - log.info("[postReceive] 메시지 헤더 = {}", message.getHeaders()); - log.info("[postReceive] 메시지 내용 = {}", payloadMap); + log.info("[postReceive] 메시지 내용 = {}", payload.toString()); if (command == StompCommand.SUBSCRIBE) { @@ -84,14 +50,4 @@ public Message postReceive(Message message, MessageChannel channel) { return message; } - @Override - public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map attributes) throws Exception { - log.info("[beforeHandshake] 웹소켓 연결 성공"); - return true; - } - - @Override - public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) { - log.info("[beforeHandshake] 웹소켓 연결 성공2"); - } } diff --git a/src/main/java/kuchat/server/domain/message/controller/MessageController.java b/src/main/java/kuchat/server/domain/message/controller/MessageController.java index edc4c0b..c738442 100644 --- a/src/main/java/kuchat/server/domain/message/controller/MessageController.java +++ b/src/main/java/kuchat/server/domain/message/controller/MessageController.java @@ -7,40 +7,35 @@ import kuchat.server.domain.member.Member; import kuchat.server.domain.member.service.MemberService; import kuchat.server.domain.message.dto.MessageRequest; +import kuchat.server.domain.message.dto.MessageResponse; import kuchat.server.domain.message.service.MessageService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.messaging.handler.annotation.DestinationVariable; import org.springframework.messaging.handler.annotation.MessageMapping; import org.springframework.messaging.handler.annotation.Payload; -import org.springframework.messaging.simp.SimpMessagingTemplate; -import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RestController; @Slf4j @Tag(name = "Message", description = "메시지") @RequiredArgsConstructor -//@RestController -@Controller +//@RequestMapping("/message") // HTTP API 에만 적용된다. (WS에는 적용X) +@RestController public class MessageController { - private final SimpMessagingTemplate simpMessagingTemplate; private final MemberService memberService; private final ChatService chatService; private final MessageService messageService; - @Operation(summary = "채팅방 구성원이 보낸 메시지 전송") - @MessageMapping("/chat/{chatroomId}") // /pub/chatroom 으로 들어오는 메시지를 처리하는 api - public void sendMessage(@Payload MessageRequest messageRequest, - @DestinationVariable("chatId") Long chatId) { +// @Operation(summary = "채팅방 구성원이 보낸 메시지 전송") + @MessageMapping("/message") + public void createMessage(@Payload MessageRequest messageRequest) { + log.info("📩 [sendMessage] : STOMP 메시지 수신 messageRequest = {}", messageRequest); - log.info("message request = {}", messageRequest.toString()); - log.info("채팅방 번호 = {}", chatId); - - Chat chat = chatService.getChatById(chatId); + Chat chat = chatService.getChatById(messageRequest.getChatId()); Member sender = memberService.getMemberById(messageRequest.getSenderId()); -// messageService.save(chat, sender, messageRequest); - + MessageResponse response = messageService.save(messageRequest, chat, sender); + messageService.broadcast(response); /** TODO. MessageService 에서 갠톡인지 단톡인지 구분해서 처리 * 1. Chat 조회 @@ -49,8 +44,7 @@ public void sendMessage(@Payload MessageRequest messageRequest, * 4. 갠톡, 단톡 구분해서 라우팅 * 갠톡 : simpMessagingTemplate을 사용하여 한명씩 직접 라우팅 (sender, receiver 에게 총 2번 보내야함) * 단톡 : simpMessagingTemplate를 사용하여 chatId 를 통해 한번에 라우팅 - */ - + */ } } diff --git a/src/main/java/kuchat/server/domain/message/dto/MessageRequest.java b/src/main/java/kuchat/server/domain/message/dto/MessageRequest.java index f09fddb..a442516 100644 --- a/src/main/java/kuchat/server/domain/message/dto/MessageRequest.java +++ b/src/main/java/kuchat/server/domain/message/dto/MessageRequest.java @@ -1,5 +1,6 @@ package kuchat.server.domain.message.dto; +import com.fasterxml.jackson.annotation.JsonProperty; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import lombok.*; @@ -12,15 +13,20 @@ @NoArgsConstructor @EqualsAndHashCode public class MessageRequest { + + @JsonProperty("chatId") @NotBlank private Long chatId; // 채팅방 ID + @JsonProperty("messageType") @NotBlank private String messageType; // 메시지 타입 (ENTER, CHAT, LEAVE) + @JsonProperty("senderId") @NotBlank private Long senderId; + @JsonProperty("content") @NotNull private String content; } diff --git a/src/main/java/kuchat/server/domain/message/dto/MessageResponse.java b/src/main/java/kuchat/server/domain/message/dto/MessageResponse.java new file mode 100644 index 0000000..827f5e1 --- /dev/null +++ b/src/main/java/kuchat/server/domain/message/dto/MessageResponse.java @@ -0,0 +1,30 @@ +package kuchat.server.domain.message.dto; + +import kuchat.server.domain.enums.MessageType; +import kuchat.server.domain.message.Message; +import lombok.*; + +import java.time.LocalDateTime; + +@Getter +@Setter +@ToString +@AllArgsConstructor +@NoArgsConstructor +@EqualsAndHashCode +public class MessageResponse { + private Long chatId; + private Long senderId; + private LocalDateTime createdAt; + private String messageType; + private String content; + + public MessageResponse(Message message){ + this.chatId = message.getChat().getId(); + this.senderId = message.getSender().getId(); + this.createdAt = message.getCreatedDate(); + this.messageType = message.getMessageType().name(); + this.content = message.getContent(); + } + +} diff --git a/src/main/java/kuchat/server/domain/message/service/MessageService.java b/src/main/java/kuchat/server/domain/message/service/MessageService.java index 2abf9a4..00a82c6 100644 --- a/src/main/java/kuchat/server/domain/message/service/MessageService.java +++ b/src/main/java/kuchat/server/domain/message/service/MessageService.java @@ -2,8 +2,11 @@ import kuchat.server.domain.chat.Chat; import kuchat.server.domain.chat.dto.CreateChatResponse; +import kuchat.server.domain.member.Member; import kuchat.server.domain.message.Message; import kuchat.server.domain.chat.dto.RecentMessageResponse; +import kuchat.server.domain.message.dto.MessageRequest; +import kuchat.server.domain.message.dto.MessageResponse; import kuchat.server.domain.message.repository.MessageRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -28,6 +31,7 @@ public class MessageService { private final SimpMessagingTemplate simpMessagingTemplate; private final MessageRepository messageRepository; + @Transactional public void notifyNewChat(Chat chat, List memberIds) { if (chat.isGroup()) { // 단체 채팅방이면 `/topic/new-chat`으로 전송 (모든 사용자에게 전송) @@ -44,6 +48,17 @@ public void notifyNewChat(Chat chat, List memberIds) { } } + @Transactional + public MessageResponse save(MessageRequest messageRequest, Chat chat, Member sender) { + Message message = new Message(messageRequest, chat, sender); + Message saved = messageRepository.save(message); + return new MessageResponse(saved); + } + + public void broadcast(MessageResponse response) { + simpMessagingTemplate.convertAndSend("/sub/chat/" + response.getChatId(), response); + } + public List getRecentMessages(Long chatId, Pageable pageable) { Page recentMessages = messageRepository.findRecent30MessagesByChat(chatId, pageable); return recentMessages.stream() @@ -52,5 +67,4 @@ public List getRecentMessages(Long chatId, Pageable pagea .toList(); } - } From ad820874a763385d7f05f3dfff33d5823e2bcd3d Mon Sep 17 00:00:00 2001 From: lyouxsun Date: Sun, 9 Mar 2025 10:09:24 +0900 Subject: [PATCH 05/21] =?UTF-8?q?fix:=20docker=20=ED=98=B8=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=ED=8F=AC=ED=8A=B8=20=EB=B2=88=ED=98=B8=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/cd-workflow.yml | 2 +- src/main/java/kuchat/server/common/config/WebMvcConfig.java | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/cd-workflow.yml b/.github/workflows/cd-workflow.yml index 19178f8..d3b96c4 100644 --- a/.github/workflows/cd-workflow.yml +++ b/.github/workflows/cd-workflow.yml @@ -68,7 +68,7 @@ jobs: sudo docker pull ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_IMAGE }} echo "🌱Running new container" - sudo docker run -d -p 8000:9000 --name kuchat ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_IMAGE }} + sudo docker run -d -p 9000:9000 --name kuchat ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_IMAGE }} echo "🚮Cleaning up old images" sudo docker image prune -f diff --git a/src/main/java/kuchat/server/common/config/WebMvcConfig.java b/src/main/java/kuchat/server/common/config/WebMvcConfig.java index 9d80065..8100198 100644 --- a/src/main/java/kuchat/server/common/config/WebMvcConfig.java +++ b/src/main/java/kuchat/server/common/config/WebMvcConfig.java @@ -51,10 +51,8 @@ public void addArgumentResolvers(List resolvers) public void addCorsMappings(CorsRegistry registry) { log.info("[addCorsMappings] CorsMapping 호출"); registry.addMapping("/**") - .allowedOrigins("https://kuchat.netlify.app") + .allowedOrigins("https://kuchat.netlify.app", "http://localhost:3000") .allowedMethods("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS") -// .exposedHeaders("*") -// .allowedHeaders("*") .exposedHeaders("Authorization", "Set-Cookie") .allowCredentials(true) // 쿠키 허용 .maxAge(3000); // 원하는 시간만큼 pre-flight 리퀘스트를 캐싱 From 2890b98a855bd4d276f4dfd022f9346b5f73697f Mon Sep 17 00:00:00 2001 From: lyouxsun Date: Sun, 9 Mar 2025 10:20:03 +0900 Subject: [PATCH 06/21] =?UTF-8?q?fix:=20@EnableWebMVC=20=EC=96=B4=EB=85=B8?= =?UTF-8?q?=ED=85=8C=EC=9D=B4=EC=85=98=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/kuchat/server/common/config/WebMvcConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/kuchat/server/common/config/WebMvcConfig.java b/src/main/java/kuchat/server/common/config/WebMvcConfig.java index 8100198..b9c6ba9 100644 --- a/src/main/java/kuchat/server/common/config/WebMvcConfig.java +++ b/src/main/java/kuchat/server/common/config/WebMvcConfig.java @@ -15,7 +15,7 @@ @Slf4j @RequiredArgsConstructor @Configuration -@EnableWebMvc +//@EnableWebMvc public class WebMvcConfig implements WebMvcConfigurer { private final AuthArgumentResolver authArgumentResolver; From f455117db284ba63c69ea4c57613309984561f48 Mon Sep 17 00:00:00 2001 From: lyouxsun Date: Sun, 9 Mar 2025 10:31:30 +0900 Subject: [PATCH 07/21] =?UTF-8?q?fix:=20guest-token=20=EC=A0=84=EB=8B=AC?= =?UTF-8?q?=20=EB=B0=A9=EC=8B=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/controller/AuthController.java | 2 +- .../domain/auth/dto/AuthTokenResponse.java | 1 - .../domain/auth/dto/GuestTokenResponse.java | 21 ++++++++++++++ .../domain/member/dto/AuthTokenResponse.java | 25 ---------------- .../domain/member/service/MemberService.java | 29 +++++++------------ .../oauth/controller/OAuthController.java | 13 +++++---- .../oauth/service/GoogleOAuthService.java | 7 +++-- 7 files changed, 44 insertions(+), 54 deletions(-) create mode 100644 src/main/java/kuchat/server/domain/auth/dto/GuestTokenResponse.java delete mode 100644 src/main/java/kuchat/server/domain/member/dto/AuthTokenResponse.java diff --git a/src/main/java/kuchat/server/domain/auth/controller/AuthController.java b/src/main/java/kuchat/server/domain/auth/controller/AuthController.java index 6e40cbe..b373da0 100644 --- a/src/main/java/kuchat/server/domain/auth/controller/AuthController.java +++ b/src/main/java/kuchat/server/domain/auth/controller/AuthController.java @@ -21,7 +21,7 @@ public class AuthController { private final JwtTokenService jwtTokenService; @PostMapping("/refresh-token") - public ResponseEntity reissueToken(@RequestHeader(value = "Authorization", required = false) String refreshToken) { + public ResponseEntity reissueToken(@RequestHeader(value = "Authorization", required = false) String refreshToken) { Long memberId = jwtTokenService.validateRefreshToken(refreshToken); AuthTokenResponse response = jwtTokenService.generateAuthToken(STUDENT, memberId); return ResponseEntity.ok(response); diff --git a/src/main/java/kuchat/server/domain/auth/dto/AuthTokenResponse.java b/src/main/java/kuchat/server/domain/auth/dto/AuthTokenResponse.java index 59d28d1..13d9655 100644 --- a/src/main/java/kuchat/server/domain/auth/dto/AuthTokenResponse.java +++ b/src/main/java/kuchat/server/domain/auth/dto/AuthTokenResponse.java @@ -9,7 +9,6 @@ @AllArgsConstructor @NoArgsConstructor public class AuthTokenResponse extends BaseResponse { - private String accessToken; private String refreshToken; diff --git a/src/main/java/kuchat/server/domain/auth/dto/GuestTokenResponse.java b/src/main/java/kuchat/server/domain/auth/dto/GuestTokenResponse.java new file mode 100644 index 0000000..23c50a0 --- /dev/null +++ b/src/main/java/kuchat/server/domain/auth/dto/GuestTokenResponse.java @@ -0,0 +1,21 @@ +package kuchat.server.domain.auth.dto; + +import kuchat.server.common.response.BaseResponse; +import kuchat.server.common.response.BaseResponseStatus; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.ToString; + +@Getter +@ToString +@AllArgsConstructor +@NoArgsConstructor +public class GuestTokenResponse extends BaseResponse { + private String guestToken; + + public GuestTokenResponse(BaseResponseStatus responseStatus, String guestToken) { + super(responseStatus); + this.guestToken = guestToken; + } +} diff --git a/src/main/java/kuchat/server/domain/member/dto/AuthTokenResponse.java b/src/main/java/kuchat/server/domain/member/dto/AuthTokenResponse.java deleted file mode 100644 index eb96b35..0000000 --- a/src/main/java/kuchat/server/domain/member/dto/AuthTokenResponse.java +++ /dev/null @@ -1,25 +0,0 @@ -package kuchat.server.domain.member.dto; - -import kuchat.server.common.response.BaseResponse; -import kuchat.server.common.response.BaseResponseStatus; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; -import lombok.ToString; - -@Setter -@Getter -@ToString -@NoArgsConstructor -public class AuthTokenResponse extends BaseResponse { - private Long id; - private String accessToken; - private String refreshToken; - - public AuthTokenResponse(BaseResponseStatus responseStatus, Long id, String accessToken, String refreshToken) { - this.responseStatus = responseStatus; - this.id = id; - this.accessToken = accessToken; - this.refreshToken = refreshToken; - } -} diff --git a/src/main/java/kuchat/server/domain/member/service/MemberService.java b/src/main/java/kuchat/server/domain/member/service/MemberService.java index f61b4ae..f92a0f6 100644 --- a/src/main/java/kuchat/server/domain/member/service/MemberService.java +++ b/src/main/java/kuchat/server/domain/member/service/MemberService.java @@ -3,17 +3,17 @@ import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletResponse; import kuchat.server.common.exception.KuchatException; -import kuchat.server.domain.auth.dto.AuthTokenResponse; -import kuchat.server.domain.auth.service.JwtTokenService; -import kuchat.server.domain.oauth.dto.GoogleInfoResponse; -import kuchat.server.domain.oauth.dto.GoogleTokenResponse; import kuchat.server.common.redis.RedisService; import kuchat.server.common.response.BaseResponse; import kuchat.server.common.response.BaseResponseStatus; +import kuchat.server.domain.auth.dto.AuthTokenResponse; +import kuchat.server.domain.auth.dto.GuestTokenResponse; +import kuchat.server.domain.auth.service.JwtTokenService; import kuchat.server.domain.enums.Role; import kuchat.server.domain.member.Member; import kuchat.server.domain.member.dto.SignupRequest; import kuchat.server.domain.member.repository.MemberRepository; +import kuchat.server.domain.oauth.dto.GoogleInfoResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; @@ -51,9 +51,8 @@ public ResponseEntity signup(Member member, SignupRequest signupRe AuthTokenResponse authTokenResponse = jwtTokenService.generateAuthToken(member.getRole(), member.getId()); log.info("[signup] member id : " + member.getId()); - kuchat.server.domain.member.dto.AuthTokenResponse response = new kuchat.server.domain.member.dto.AuthTokenResponse( + AuthTokenResponse response = new AuthTokenResponse( SUCCESS, - member.getId(), authTokenResponse.getAccessToken(), authTokenResponse.getRefreshToken() ); @@ -100,28 +99,22 @@ public Member lookupMemberByGoogleId(String id) { .orElse(null); } - public kuchat.server.domain.member.dto.AuthTokenResponse processLoginOrSignup(Member member, - GoogleTokenResponse tokenResponse, - GoogleInfoResponse infoResponse, - HttpServletResponse httpServletResponse) { + public BaseResponse processLoginOrSignup(Member member, + GoogleInfoResponse infoResponse, + HttpServletResponse httpServletResponse) { if (member == null || member.getRole() == Role.GUEST) { log.info("[processLoginOrSignup] provider id = {}", infoResponse.getId()); String guestToken = jwtTokenService.generateGuestToken(GOOGLE.getValue(), infoResponse.getId()); log.info("[processLoginOrSignup] 신규 회원 guest token 생성 = {}", guestToken); - try { - httpServletResponse.sendRedirect("https://kuchat.netlify.app/signup?guest-token=" + guestToken); - } catch (IOException e) { - log.info("[processLoginOrSignup] 신규 회원 처리 시 IOException = {}", e.getMessage()); - } - return null; + return new GuestTokenResponse(SUCCESS, guestToken); } - log.info("[SuccessHandler] 기존 회원인 경우, provider id = {}",infoResponse.getId()); + log.info("[SuccessHandler] 기존 회원인 경우, provider id = {}", infoResponse.getId()); AuthTokenResponse authTokenResponse = jwtTokenService.generateAuthToken(Role.STUDENT, member.getId()); log.info("[SuccessHandler] 로그인 성공!!! 토큰 발급 완료 access token = {}, refresh token = {}", authTokenResponse.getAccessToken(), authTokenResponse.getRefreshToken()); setAuthCookie(httpServletResponse, "accessToken", authTokenResponse.getAccessToken()); setAuthCookie(httpServletResponse, "refreshToken", authTokenResponse.getRefreshToken()); - return new kuchat.server.domain.member.dto.AuthTokenResponse(SUCCESS, member.getId(), authTokenResponse.getAccessToken(), authTokenResponse.getRefreshToken()); + return new AuthTokenResponse(SUCCESS, authTokenResponse.getAccessToken(), authTokenResponse.getRefreshToken()); } private void setAuthCookie(HttpServletResponse response, String name, String token) { diff --git a/src/main/java/kuchat/server/domain/oauth/controller/OAuthController.java b/src/main/java/kuchat/server/domain/oauth/controller/OAuthController.java index 228f583..93cadc4 100644 --- a/src/main/java/kuchat/server/domain/oauth/controller/OAuthController.java +++ b/src/main/java/kuchat/server/domain/oauth/controller/OAuthController.java @@ -1,9 +1,10 @@ package kuchat.server.domain.oauth.controller; import jakarta.servlet.http.HttpServletResponse; +import kuchat.server.common.response.BaseResponse; +import kuchat.server.domain.auth.dto.AuthTokenResponse; import kuchat.server.domain.oauth.dto.AuthorizationCodeRequest; import kuchat.server.domain.oauth.service.GoogleOAuthService; -import kuchat.server.domain.member.dto.AuthTokenResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; @@ -19,11 +20,11 @@ public class OAuthController { private final GoogleOAuthService googleOAuthService; @PostMapping("/google") - public ResponseEntity callback(@RequestBody AuthorizationCodeRequest authorizationCodeRequest, - HttpServletResponse response){ + public ResponseEntity callback(@RequestBody AuthorizationCodeRequest authorizationCodeRequest, + HttpServletResponse response){ log.info("[callback] 구글 인가코드 발급 완료 = {}", authorizationCodeRequest.getCode()); - AuthTokenResponse authTokenResponse = googleOAuthService.process(authorizationCodeRequest.getCode(), response); - log.info("[callback] 클라이언트에게 줄 response body = {}", authTokenResponse); - return ResponseEntity.ok(authTokenResponse); + BaseResponse tokenResponse = googleOAuthService.process(authorizationCodeRequest.getCode(), response); + log.info("[callback] 클라이언트에게 줄 response body = {}", tokenResponse); + return ResponseEntity.ok(tokenResponse); } } diff --git a/src/main/java/kuchat/server/domain/oauth/service/GoogleOAuthService.java b/src/main/java/kuchat/server/domain/oauth/service/GoogleOAuthService.java index 0022dcc..3e5e604 100644 --- a/src/main/java/kuchat/server/domain/oauth/service/GoogleOAuthService.java +++ b/src/main/java/kuchat/server/domain/oauth/service/GoogleOAuthService.java @@ -1,10 +1,11 @@ package kuchat.server.domain.oauth.service; import jakarta.servlet.http.HttpServletResponse; +import kuchat.server.common.response.BaseResponse; +import kuchat.server.domain.auth.dto.AuthTokenResponse; import kuchat.server.domain.oauth.dto.GoogleInfoResponse; import kuchat.server.domain.oauth.dto.GoogleTokenResponse; import kuchat.server.domain.member.Member; -import kuchat.server.domain.member.dto.AuthTokenResponse; import kuchat.server.domain.member.service.MemberService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; @@ -38,12 +39,12 @@ public GoogleOAuthService(MemberService memberService, RestClient.Builder builde .build(); } - public AuthTokenResponse process(String code, HttpServletResponse httpServletResponse) { + public BaseResponse process(String code, HttpServletResponse httpServletResponse) { GoogleTokenResponse tokenResponse = requestAccessToken(code); GoogleInfoResponse infoResponse = requestUserInfo(tokenResponse.getAccessToken()); log.info("[process] 구글에서 제공한 user info = {}", infoResponse); Member member = memberService.lookupMemberByGoogleId(infoResponse.getId()); - return memberService.processLoginOrSignup(member, tokenResponse, infoResponse, httpServletResponse); + return memberService.processLoginOrSignup(member, infoResponse, httpServletResponse); } public GoogleTokenResponse requestAccessToken(String code) { From 0885631a75b53be44d0d295ea199847e2e9c22e3 Mon Sep 17 00:00:00 2001 From: lyouxsun Date: Sun, 9 Mar 2025 10:39:35 +0900 Subject: [PATCH 08/21] =?UTF-8?q?fix:=20SecurityConfig=EC=97=90=EC=84=9C?= =?UTF-8?q?=20CORS=20=EC=97=90=EB=9F=AC=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../server/common/config/SecurityConfig.java | 19 +++++++++++++++++++ .../server/common/config/WebMvcConfig.java | 12 ------------ 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/src/main/java/kuchat/server/common/config/SecurityConfig.java b/src/main/java/kuchat/server/common/config/SecurityConfig.java index 8ba4188..b992c94 100644 --- a/src/main/java/kuchat/server/common/config/SecurityConfig.java +++ b/src/main/java/kuchat/server/common/config/SecurityConfig.java @@ -12,6 +12,10 @@ import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer.FrameOptionsConfig; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.web.SecurityFilterChain; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; + +import java.util.List; @Slf4j @RequiredArgsConstructor @@ -26,6 +30,8 @@ public class SecurityConfig { @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { return http + .cors(corsCustomizer -> + corsCustomizer.configurationSource(corsConfigurationSource())) .httpBasic(AbstractHttpConfigurer::disable) // jwt 토큰을 사용한 bearer 방식을 사용하므로 default 설정 disable .headers(headers -> headers.frameOptions(FrameOptionsConfig::disable)) // h2 콘솔에 접근하기 위해서는 X-Frame-Options Click jacking 공격을 막는 설정 disable .csrf(AbstractHttpConfigurer::disable) // rest api 방식에서는 jwt 또는 oauth2 방식을 사용하여 인증하므로 csrf 보안기능 필요 X @@ -55,4 +61,17 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { ) .build(); } + + private UrlBasedCorsConfigurationSource corsConfigurationSource() { + CorsConfiguration configuration = new CorsConfiguration(); + configuration.setAllowedOrigins(List.of("https://kuchat.netlify.app", "http://localhost:3000")); + configuration.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS")); + configuration.setAllowedHeaders(List.of("Authorization", "Content-Type", "Set-Cookie")); + configuration.setExposedHeaders(List.of("Authorization", "Set-Cookie")); + configuration.setAllowCredentials(true); + + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/**", configuration); + return source; + } } diff --git a/src/main/java/kuchat/server/common/config/WebMvcConfig.java b/src/main/java/kuchat/server/common/config/WebMvcConfig.java index b9c6ba9..b74b611 100644 --- a/src/main/java/kuchat/server/common/config/WebMvcConfig.java +++ b/src/main/java/kuchat/server/common/config/WebMvcConfig.java @@ -15,7 +15,6 @@ @Slf4j @RequiredArgsConstructor @Configuration -//@EnableWebMvc public class WebMvcConfig implements WebMvcConfigurer { private final AuthArgumentResolver authArgumentResolver; @@ -47,15 +46,4 @@ public void addArgumentResolvers(List resolvers) resolvers.add(guestArgumentResolver); } - @Override - public void addCorsMappings(CorsRegistry registry) { - log.info("[addCorsMappings] CorsMapping 호출"); - registry.addMapping("/**") - .allowedOrigins("https://kuchat.netlify.app", "http://localhost:3000") - .allowedMethods("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS") - .exposedHeaders("Authorization", "Set-Cookie") - .allowCredentials(true) // 쿠키 허용 - .maxAge(3000); // 원하는 시간만큼 pre-flight 리퀘스트를 캐싱 - } - } From 9de23d8b12ce0aea3f93ee707fe77e2eccc045ce Mon Sep 17 00:00:00 2001 From: lyouxsun Date: Tue, 11 Mar 2025 15:26:03 +0900 Subject: [PATCH 09/21] =?UTF-8?q?fix:=20=EA=B5=AC=EA=B8=80=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=20=ED=95=9C=20=EC=8B=A0=EA=B7=9C=ED=9A=8C?= =?UTF-8?q?=EC=9B=90=20=EC=A0=80=EC=9E=A5=20=EB=88=84=EB=9D=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kuchat/server/domain/member/Member.java | 5 +++++ .../domain/member/service/MemberService.java | 19 ++++++++++++------- .../oauth/dto/AuthorizationCodeRequest.java | 2 ++ .../oauth/service/GoogleOAuthService.java | 2 +- 4 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/main/java/kuchat/server/domain/member/Member.java b/src/main/java/kuchat/server/domain/member/Member.java index cab0eeb..606c993 100644 --- a/src/main/java/kuchat/server/domain/member/Member.java +++ b/src/main/java/kuchat/server/domain/member/Member.java @@ -11,6 +11,7 @@ import kuchat.server.domain.enums.Status; import kuchat.server.domain.member.dto.ProfileUpdateRequest; import kuchat.server.domain.member.dto.SignupRequest; +import kuchat.server.domain.oauth.dto.GoogleInfoResponse; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; @@ -74,6 +75,10 @@ public Member(String email, Platform platform, String providerId, String profile role = Role.GUEST; } + public Member(GoogleInfoResponse infoResponse) { + + } + public void updateInfo(SignupRequest request, String defaultImage) { this.language = new Language(request); profile.update(request, defaultImage); diff --git a/src/main/java/kuchat/server/domain/member/service/MemberService.java b/src/main/java/kuchat/server/domain/member/service/MemberService.java index f92a0f6..7d30d1c 100644 --- a/src/main/java/kuchat/server/domain/member/service/MemberService.java +++ b/src/main/java/kuchat/server/domain/member/service/MemberService.java @@ -21,7 +21,6 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.io.IOException; import java.util.List; import static kuchat.server.common.response.BaseResponseStatus.*; @@ -94,14 +93,21 @@ public List getMembers(List friends) { return memberRepository.findAllById(friends); } - public Member lookupMemberByGoogleId(String id) { - return memberRepository.findByPlatformAndProviderId(GOOGLE, id) - .orElse(null); + public Member lookupMemberByGoogleInfo(GoogleInfoResponse infoResponse) { + return memberRepository.findByPlatformAndProviderId(GOOGLE, infoResponse.getId()) + .orElseGet(() -> { + Member member = new Member(infoResponse.getEmail(), + GOOGLE, + infoResponse.getId(), + infoResponse.getPicture()); + memberRepository.save(member); + return getMemberById(member.getId()); + }); } public BaseResponse processLoginOrSignup(Member member, - GoogleInfoResponse infoResponse, - HttpServletResponse httpServletResponse) { + GoogleInfoResponse infoResponse, + HttpServletResponse httpServletResponse) { if (member == null || member.getRole() == Role.GUEST) { log.info("[processLoginOrSignup] provider id = {}", infoResponse.getId()); String guestToken = jwtTokenService.generateGuestToken(GOOGLE.getValue(), infoResponse.getId()); @@ -119,7 +125,6 @@ public BaseResponse processLoginOrSignup(Member member, private void setAuthCookie(HttpServletResponse response, String name, String token) { Cookie cookie = new Cookie(name, token); - cookie.setHttpOnly(true); // XSS 공격 방지 cookie.setSecure(true); // HTTPS 에서만 전송 (개발 환경에서는 설정 비활성화 가능) cookie.setMaxAge(60 * 60 * 24); // 쿠키 만료 시간 설정 (1시간) response.addCookie(cookie); diff --git a/src/main/java/kuchat/server/domain/oauth/dto/AuthorizationCodeRequest.java b/src/main/java/kuchat/server/domain/oauth/dto/AuthorizationCodeRequest.java index fc1ad0d..6989f00 100644 --- a/src/main/java/kuchat/server/domain/oauth/dto/AuthorizationCodeRequest.java +++ b/src/main/java/kuchat/server/domain/oauth/dto/AuthorizationCodeRequest.java @@ -1,5 +1,6 @@ package kuchat.server.domain.oauth.dto; +import jakarta.validation.constraints.NotBlank; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @@ -8,5 +9,6 @@ @Setter @NoArgsConstructor public class AuthorizationCodeRequest { + @NotBlank private String code; } diff --git a/src/main/java/kuchat/server/domain/oauth/service/GoogleOAuthService.java b/src/main/java/kuchat/server/domain/oauth/service/GoogleOAuthService.java index 3e5e604..291e905 100644 --- a/src/main/java/kuchat/server/domain/oauth/service/GoogleOAuthService.java +++ b/src/main/java/kuchat/server/domain/oauth/service/GoogleOAuthService.java @@ -43,7 +43,7 @@ public BaseResponse process(String code, HttpServletResponse httpServletResponse GoogleTokenResponse tokenResponse = requestAccessToken(code); GoogleInfoResponse infoResponse = requestUserInfo(tokenResponse.getAccessToken()); log.info("[process] 구글에서 제공한 user info = {}", infoResponse); - Member member = memberService.lookupMemberByGoogleId(infoResponse.getId()); + Member member = memberService.lookupMemberByGoogleInfo(infoResponse); return memberService.processLoginOrSignup(member, infoResponse, httpServletResponse); } From d8a9459f76182cf41262b7fdd5607a8649c532dd Mon Sep 17 00:00:00 2001 From: lyouxsun Date: Tue, 11 Mar 2025 15:31:42 +0900 Subject: [PATCH 10/21] =?UTF-8?q?fix:=20=ED=8A=B8=EB=9E=9C=EC=9E=AD?= =?UTF-8?q?=EC=85=98=20=EC=96=B4=EB=85=B8=ED=85=8C=EC=9D=B4=EC=85=98=20?= =?UTF-8?q?=EB=88=84=EB=9D=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/kuchat/server/domain/member/service/MemberService.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/kuchat/server/domain/member/service/MemberService.java b/src/main/java/kuchat/server/domain/member/service/MemberService.java index 7d30d1c..3acb2c6 100644 --- a/src/main/java/kuchat/server/domain/member/service/MemberService.java +++ b/src/main/java/kuchat/server/domain/member/service/MemberService.java @@ -93,6 +93,7 @@ public List getMembers(List friends) { return memberRepository.findAllById(friends); } + @Transactional public Member lookupMemberByGoogleInfo(GoogleInfoResponse infoResponse) { return memberRepository.findByPlatformAndProviderId(GOOGLE, infoResponse.getId()) .orElseGet(() -> { From 19b8ab1df3094438e302ae7086c09a3831bc8edc Mon Sep 17 00:00:00 2001 From: lyouxsun Date: Wed, 12 Mar 2025 13:46:17 +0900 Subject: [PATCH 11/21] =?UTF-8?q?fix:=20=ED=81=B4=EB=9D=BC=EC=9D=B4?= =?UTF-8?q?=EC=96=B8=ED=8A=B8=20=EC=BF=A0=ED=82=A4=20=EB=88=84=EB=9D=BD=20?= =?UTF-8?q?=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../server/common/config/SecurityConfig.java | 53 ++--------------- .../server/common/config/WebMvcConfig.java | 11 ++++ .../server/common/config/WebSocketConfig.java | 14 +++-- .../exception/MessageControllerAdvice.java | 25 ++++++++ .../exception/RequestControllerAdvice.java | 11 +++- .../common/response/BaseResponseStatus.java | 2 +- .../AuthArgumentResolver.java | 6 +- .../auth/controller/AuthController.java | 13 ++++ .../domain/auth/dto/AuthTokenResponse.java | 5 +- .../domain/auth/service/JwtTokenService.java | 2 +- .../chat/controller/ChatController.java | 6 +- .../domain/chat/service/ChatService.java | 2 +- .../member/controller/MemberController.java | 38 +++++++----- .../member/controller/ProfileController.java | 4 +- .../domain/member/service/MemberService.java | 59 +++++++------------ .../kuchat/server/domain/message/Message.java | 2 +- .../message/controller/MessageController.java | 7 ++- .../domain/message/dto/MessageRequest.java | 15 +++-- .../message/service/MessageService.java | 12 +++- .../service/WebSocketEventHandler.java | 21 +++++++ .../oauth/controller/OAuthController.java | 25 +++++--- .../oauth/service/GoogleOAuthService.java | 8 ++- .../service/OAuth2LogoutSuccessHandler.java | 59 ------------------- .../server/domain/utils/CookieUtil.java | 51 ++++++++++++++++ .../ValidatorUtil.java} | 4 +- 25 files changed, 251 insertions(+), 204 deletions(-) create mode 100644 src/main/java/kuchat/server/common/exception/MessageControllerAdvice.java create mode 100644 src/main/java/kuchat/server/domain/message/service/WebSocketEventHandler.java delete mode 100644 src/main/java/kuchat/server/domain/oauth/service/OAuth2LogoutSuccessHandler.java create mode 100644 src/main/java/kuchat/server/domain/utils/CookieUtil.java rename src/main/java/kuchat/server/domain/{Validator.java => utils/ValidatorUtil.java} (92%) diff --git a/src/main/java/kuchat/server/common/config/SecurityConfig.java b/src/main/java/kuchat/server/common/config/SecurityConfig.java index b992c94..5f85a27 100644 --- a/src/main/java/kuchat/server/common/config/SecurityConfig.java +++ b/src/main/java/kuchat/server/common/config/SecurityConfig.java @@ -1,7 +1,5 @@ package kuchat.server.common.config; -import kuchat.server.domain.auth.JwtTokenInterceptor; -import kuchat.server.domain.oauth.service.OAuth2LogoutSuccessHandler; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Bean; @@ -12,10 +10,6 @@ import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer.FrameOptionsConfig; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.web.SecurityFilterChain; -import org.springframework.web.cors.CorsConfiguration; -import org.springframework.web.cors.UrlBasedCorsConfigurationSource; - -import java.util.List; @Slf4j @RequiredArgsConstructor @@ -23,55 +17,18 @@ @EnableWebSecurity // spring security 기능을 활성화시키는 어노테이션 (스프링 시큐리티 필터가 스프링 필터 체인에 등록됨) public class SecurityConfig { - private final OAuth2LogoutSuccessHandler oAuth2LogoutSuccessHandler; - private final JwtTokenInterceptor jwtTokenInterceptor; - - @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { return http - .cors(corsCustomizer -> - corsCustomizer.configurationSource(corsConfigurationSource())) .httpBasic(AbstractHttpConfigurer::disable) // jwt 토큰을 사용한 bearer 방식을 사용하므로 default 설정 disable .headers(headers -> headers.frameOptions(FrameOptionsConfig::disable)) // h2 콘솔에 접근하기 위해서는 X-Frame-Options Click jacking 공격을 막는 설정 disable - .csrf(AbstractHttpConfigurer::disable) // rest api 방식에서는 jwt 또는 oauth2 방식을 사용하여 인증하므로 csrf 보안기능 필요 X - .sessionManagement(sessionManagement -> sessionManagement - .sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 세션을 사용하지 않으므로 disable (stateless) - ) - .authorizeHttpRequests((authorize) -> authorize - .anyRequest().permitAll() + .csrf(csrf -> csrf.ignoringRequestMatchers("ws-connect/**")) // websocket 경로는 CSRF 예외 처리 // rest api 방식에서는 jwt 또는 oauth2 방식을 사용하여 인증하므로 csrf 보안기능 필요 X + .sessionManagement(sessionManagement -> + sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 세션을 사용하지 않으므로 disable (stateless) ) -// .authorizeHttpRequests((authorize) -> authorize // 인증, 인가 설정 시 HttpServletRequest 를 사용한다는 의미 -// .requestMatchers("/index.html", "/", "/css/**", "/images/**", "/js/**", "/h2-console/**", -// "/swagger-ui/**", "/swagger-resources/**", "/v3/api-docs/**", "/oauth/login", -// "/member/signup", "/publish/**","/subscribe/**", "/ws-connect").permitAll() // 인증 절차 없이 접근 가능해야 하는 페이지 모두 추가하기 -// .anyRequest().authenticated() // 이 외에 모든 페이지는 인증된 사용자만 접근 가능 -// ) - .logout(logout -> logout - .logoutUrl("/member/logout") - .logoutSuccessUrl("/") - .deleteCookies("Authorization") - .invalidateHttpSession(true) - .clearAuthentication(true) - .addLogoutHandler((request, response, auth) -> { - // 추가 로그아웃 처리 로직 (선택) - log.info("추가된 Custom logout handler 없음"); - }) - .logoutSuccessHandler(oAuth2LogoutSuccessHandler) + .authorizeHttpRequests((authorize) -> + authorize.anyRequest().permitAll() ) .build(); } - - private UrlBasedCorsConfigurationSource corsConfigurationSource() { - CorsConfiguration configuration = new CorsConfiguration(); - configuration.setAllowedOrigins(List.of("https://kuchat.netlify.app", "http://localhost:3000")); - configuration.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS")); - configuration.setAllowedHeaders(List.of("Authorization", "Content-Type", "Set-Cookie")); - configuration.setExposedHeaders(List.of("Authorization", "Set-Cookie")); - configuration.setAllowCredentials(true); - - UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); - source.registerCorsConfiguration("/**", configuration); - return source; - } } diff --git a/src/main/java/kuchat/server/common/config/WebMvcConfig.java b/src/main/java/kuchat/server/common/config/WebMvcConfig.java index b74b611..3402f1e 100644 --- a/src/main/java/kuchat/server/common/config/WebMvcConfig.java +++ b/src/main/java/kuchat/server/common/config/WebMvcConfig.java @@ -46,4 +46,15 @@ public void addArgumentResolvers(List resolvers) resolvers.add(guestArgumentResolver); } + @Override + public void addCorsMappings(CorsRegistry registry) { + log.info("[addCorsMappings] CorsMapping 호출"); + registry.addMapping("/**") + .allowedOrigins("https://kuchat.netlify.app", "http://localhost:3000") + .allowedMethods("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS") + .exposedHeaders("Authorization", "Set-Cookie") + .allowCredentials(true) // 쿠키 허용 + .maxAge(3000); // 원하는 시간만큼 pre-flight 리퀘스트를 캐싱 + } + } diff --git a/src/main/java/kuchat/server/common/config/WebSocketConfig.java b/src/main/java/kuchat/server/common/config/WebSocketConfig.java index 8052b74..98ba286 100644 --- a/src/main/java/kuchat/server/common/config/WebSocketConfig.java +++ b/src/main/java/kuchat/server/common/config/WebSocketConfig.java @@ -34,6 +34,9 @@ public void configureMessageBroker(MessageBrokerRegistry mqRegistry) { mqRegistry.setApplicationDestinationPrefixes("/pub"); mqRegistry.enableSimpleBroker("/sub"); + // 개인 메시징을 위한 설정 (사실 기본적으로 자동 적용됨) + mqRegistry.setUserDestinationPrefix("/user"); + // 서버 → 클라이언트 로 가는 요청 // 브로커가 자동으로 메시지를 전송해준다. // mqRegistry.enableSimpleBroker("/queue", "/topic"); // queue : 개인톡, topic : 단체톡 @@ -53,12 +56,11 @@ public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint("/ws-connect") .setAllowedOriginPatterns("*") .addInterceptors(new WebSocketHandshakeInterceptor()); -// .withSockJS(); - registry.addEndpoint("/ws-connect") - .setAllowedOriginPatterns("*") - .addInterceptors(new WebSocketHandshakeInterceptor()) - .withSockJS(); +// registry.addEndpoint("/ws-connect") +// .setAllowedOriginPatterns("*") +// .addInterceptors(new WebSocketHandshakeInterceptor()) +// .withSockJS(); } /** @@ -66,7 +68,7 @@ public void registerStompEndpoints(StompEndpointRegistry registry) { */ @Override public void configureWebSocketTransport(WebSocketTransportRegistration registry) { - registry.setMessageSizeLimit(32 * 1024); // 메시지 크기 제한 : 디폴트 64KB 에서 32KB 로 변경 + registry.setMessageSizeLimit(32 * 1024); // 메시지 크기 제한 : 디폴트 64KB 에서 32KB 로 변경 registry.setTimeToFirstMessage(30 * 1000); // 클라이언트가 websocket을 연결한 후 30초 내에 STOMP 메시지를 보내야 함. // 보내지 않으면 서버가 클라이언트 연결 종료 } diff --git a/src/main/java/kuchat/server/common/exception/MessageControllerAdvice.java b/src/main/java/kuchat/server/common/exception/MessageControllerAdvice.java new file mode 100644 index 0000000..ed05473 --- /dev/null +++ b/src/main/java/kuchat/server/common/exception/MessageControllerAdvice.java @@ -0,0 +1,25 @@ +package kuchat.server.common.exception; + +import kuchat.server.common.response.BaseResponse; +import kuchat.server.common.response.BaseResponseStatus; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.messaging.converter.MessageConversionException; +import org.springframework.messaging.handler.annotation.MessageExceptionHandler; +import org.springframework.messaging.simp.annotation.SendToUser; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +import static kuchat.server.common.response.BaseResponseStatus.MESSAGE_FORMAT_ERROR; + +@Slf4j +@RestControllerAdvice +public class MessageControllerAdvice { + + @MessageExceptionHandler(MessageConversionException.class) + @SendToUser("/queue/errors") + public BaseResponse handleMessageConversionException(MessageConversionException e) { + log.error("[handleMessageConversionException] 에러 메시지 = {}", e.getMessage()); + return new BaseResponse(MESSAGE_FORMAT_ERROR); + } + +} diff --git a/src/main/java/kuchat/server/common/exception/RequestControllerAdvice.java b/src/main/java/kuchat/server/common/exception/RequestControllerAdvice.java index 466c719..43005f1 100644 --- a/src/main/java/kuchat/server/common/exception/RequestControllerAdvice.java +++ b/src/main/java/kuchat/server/common/exception/RequestControllerAdvice.java @@ -11,12 +11,19 @@ import org.springframework.web.multipart.MaxUploadSizeExceededException; import org.springframework.web.multipart.MultipartException; import org.springframework.web.servlet.NoHandlerFoundException; +import org.springframework.web.servlet.View; import static kuchat.server.common.response.BaseResponseStatus.*; @Slf4j @RestControllerAdvice public class RequestControllerAdvice { + private final View error; + + public RequestControllerAdvice(View error) { + this.error = error; + } + @ExceptionHandler(MissingServletRequestParameterException.class) public ResponseEntity handleMissingParameterException(MissingServletRequestParameterException e) { log.error("[handleMissingParameterException] 클라이언트 요청에서 request parameter가 누락된 경우"); @@ -43,7 +50,7 @@ public ResponseEntity handleMissingMultipartException(MultipartExc } @ExceptionHandler(HttpRequestMethodNotSupportedException.class) - public ResponseEntity handleMissingPathVariableException(HttpRequestMethodNotSupportedException e){ + public ResponseEntity handleMissingPathVariableException(HttpRequestMethodNotSupportedException e) { log.error("[handleMissingPathVariableException] 요청 url에서 path variable이 누락된 경우"); log.error(e.getMessage()); log.error(String.valueOf(e.getHeaders())); @@ -53,7 +60,7 @@ public ResponseEntity handleMissingPathVariableException(HttpReque } @ExceptionHandler(NoHandlerFoundException.class) - public ResponseEntity handlerNotFoundException(NoHandlerFoundException e){ + public ResponseEntity handlerNotFoundException(NoHandlerFoundException e) { log.error("[handlerNotFoundException] 구현되지 않은 API로 요청을 보낸 경우"); log.error(e.getMessage()); HttpStatus httpStatus = API_NOT_FOUND.getHttpStatus(); diff --git a/src/main/java/kuchat/server/common/response/BaseResponseStatus.java b/src/main/java/kuchat/server/common/response/BaseResponseStatus.java index 6d3bb22..7928f43 100644 --- a/src/main/java/kuchat/server/common/response/BaseResponseStatus.java +++ b/src/main/java/kuchat/server/common/response/BaseResponseStatus.java @@ -68,7 +68,7 @@ public enum BaseResponseStatus { //- 6000번대 : 메시지(message)/소켓 관련 코드 WEBSOCKET_CONNECTION_FAIL(6000, HttpStatus.INTERNAL_SERVER_ERROR, "웹소켓 서버 접속에 실패했습니다."), WEBSOCKET_CLOSE_FAIL(6001, HttpStatus.INTERNAL_SERVER_ERROR, "웹소켓 서버와의 연결 종료에 실패했습니다."), - MESSAGE_FORMAT_ERROR(6002, HttpStatus.BAD_REQUEST, "클라이언트에서 요청한 메시지 json 객체의 형식이 잘못됐습니다."), + MESSAGE_FORMAT_ERROR(6002, HttpStatus.BAD_REQUEST, "메시지에서 하나 이상의 필드가 누락되었습니다. json 데이터를 확인해주세요."), CONVERT_TO_JSON_FAIL(6003, HttpStatus.INTERNAL_SERVER_ERROR, "메시지를 json 형태로 바꾸는데 실패했습니다"), CONVERT_TO_OBJECT_FAIL(6004, HttpStatus.INTERNAL_SERVER_ERROR, "json을 메세지 객체 형태로 바꾸는데 실패했습니다"), MESSAGE_SEND_FAIL(6005, HttpStatus.INTERNAL_SERVER_ERROR, "메시지 전송에 실패했습니다."), diff --git a/src/main/java/kuchat/server/domain/auth/argumentResolver/AuthArgumentResolver.java b/src/main/java/kuchat/server/domain/auth/argumentResolver/AuthArgumentResolver.java index 4a45edb..bea5cfc 100644 --- a/src/main/java/kuchat/server/domain/auth/argumentResolver/AuthArgumentResolver.java +++ b/src/main/java/kuchat/server/domain/auth/argumentResolver/AuthArgumentResolver.java @@ -14,7 +14,6 @@ import java.util.Arrays; import java.util.Optional; -import java.util.stream.Stream; import static kuchat.server.common.response.BaseResponseStatus.NOT_FOUND_TOKEN; @@ -33,8 +32,9 @@ public boolean supportsParameter(MethodParameter parameter) { public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest(); - Cookie[] cookies = request.getCookies(); - String rawToken = Optional.ofNullable(cookies).stream().flatMap(Arrays::stream) + + String rawToken = Optional.ofNullable(request.getCookies()).stream() + .flatMap(Arrays::stream) .filter(cookie -> "accessToken".equals(cookie.getName())) .map(Cookie::getValue) .findFirst() diff --git a/src/main/java/kuchat/server/domain/auth/controller/AuthController.java b/src/main/java/kuchat/server/domain/auth/controller/AuthController.java index b373da0..7e1fcfb 100644 --- a/src/main/java/kuchat/server/domain/auth/controller/AuthController.java +++ b/src/main/java/kuchat/server/domain/auth/controller/AuthController.java @@ -1,7 +1,12 @@ package kuchat.server.domain.auth.controller; +import jakarta.servlet.http.HttpServletResponse; +import kuchat.server.common.response.BaseResponse; import kuchat.server.domain.auth.service.JwtTokenService; import kuchat.server.domain.auth.dto.AuthTokenResponse; +import kuchat.server.domain.member.Member; +import kuchat.server.domain.member.service.MemberService; +import kuchat.server.domain.utils.CookieUtil; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; @@ -19,6 +24,7 @@ public class AuthController { private final JwtTokenService jwtTokenService; + private final MemberService memberService; @PostMapping("/refresh-token") public ResponseEntity reissueToken(@RequestHeader(value = "Authorization", required = false) String refreshToken) { @@ -26,4 +32,11 @@ public ResponseEntity reissueToken(@RequestHeader(value = "Au AuthTokenResponse response = jwtTokenService.generateAuthToken(STUDENT, memberId); return ResponseEntity.ok(response); } + + @PostMapping("/logout") + public ResponseEntity logout(@RequestHeader("Authorization") String accessToken, HttpServletResponse response) { + Member member = jwtTokenService.extractMemberByAccessToken(accessToken); + memberService.logout(member); + return CookieUtil.removeAuthToken(); + } } diff --git a/src/main/java/kuchat/server/domain/auth/dto/AuthTokenResponse.java b/src/main/java/kuchat/server/domain/auth/dto/AuthTokenResponse.java index 13d9655..0486512 100644 --- a/src/main/java/kuchat/server/domain/auth/dto/AuthTokenResponse.java +++ b/src/main/java/kuchat/server/domain/auth/dto/AuthTokenResponse.java @@ -2,7 +2,10 @@ import kuchat.server.common.response.BaseResponse; import kuchat.server.common.response.BaseResponseStatus; -import lombok.*; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.ToString; @Getter @ToString diff --git a/src/main/java/kuchat/server/domain/auth/service/JwtTokenService.java b/src/main/java/kuchat/server/domain/auth/service/JwtTokenService.java index 305340e..63b3397 100644 --- a/src/main/java/kuchat/server/domain/auth/service/JwtTokenService.java +++ b/src/main/java/kuchat/server/domain/auth/service/JwtTokenService.java @@ -55,7 +55,7 @@ public AuthTokenResponse generateAuthToken(Role role, Long memberId) { } public String generateGuestToken(String platform, String providerId) { - log.info("[generateGuestToken]"); + log.info("[generateGuestToken] guest token 발급 providerId = {}", providerId); final Claims claims = Jwts.claims(); // claims = jwt token에 들어갈 정보, claim에 email을 넣어줘야 회원 식별 가능 claims.put("platform", platform); claims.put("providerId", providerId); diff --git a/src/main/java/kuchat/server/domain/chat/controller/ChatController.java b/src/main/java/kuchat/server/domain/chat/controller/ChatController.java index 2ae5f2a..9a163b2 100644 --- a/src/main/java/kuchat/server/domain/chat/controller/ChatController.java +++ b/src/main/java/kuchat/server/domain/chat/controller/ChatController.java @@ -3,7 +3,7 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import kuchat.server.common.response.BaseResponse; -import kuchat.server.domain.Validator; +import kuchat.server.domain.utils.ValidatorUtil; import kuchat.server.domain.auth.argumentResolver.Auth; import kuchat.server.domain.chat.dto.CreateChatRequest; import kuchat.server.domain.chat.dto.ViewChatResponse; @@ -38,7 +38,7 @@ public class ChatController { public ResponseEntity create(@Auth Member member, @RequestBody @Validated CreateChatRequest request, BindingResult bindingResult) { log.info("[create] id = {} 인 사용자가 {} 와 개인 채팅방 생성 요청", member.getId(), request.toString()); - Validator.validateRequest(bindingResult); + ValidatorUtil.validateRequest(bindingResult); return ResponseEntity.ok(chatService.create(member, request)); } @@ -56,4 +56,6 @@ public ResponseEntity view(@Auth Member member, return ResponseEntity.ok(response); } + + } diff --git a/src/main/java/kuchat/server/domain/chat/service/ChatService.java b/src/main/java/kuchat/server/domain/chat/service/ChatService.java index 39fde7d..d25d5f6 100644 --- a/src/main/java/kuchat/server/domain/chat/service/ChatService.java +++ b/src/main/java/kuchat/server/domain/chat/service/ChatService.java @@ -65,7 +65,7 @@ private void checkMemberInChat(Member member, List chatMembers, Chat } private List getParticipants(Member creator, CreateChatRequest request) { - List members = memberService.getMembers(request.getFriends()); + List members = memberService.getMembersByIds(request.getFriends()); members.add(creator); return members; } diff --git a/src/main/java/kuchat/server/domain/member/controller/MemberController.java b/src/main/java/kuchat/server/domain/member/controller/MemberController.java index 9bbec93..f0b04d3 100644 --- a/src/main/java/kuchat/server/domain/member/controller/MemberController.java +++ b/src/main/java/kuchat/server/domain/member/controller/MemberController.java @@ -3,11 +3,14 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletResponse; import kuchat.server.common.exception.KuchatException; import kuchat.server.domain.auth.argumentResolver.Auth; import kuchat.server.domain.auth.argumentResolver.Guest; import kuchat.server.common.response.BaseResponse; -import kuchat.server.domain.Validator; +import kuchat.server.domain.utils.CookieUtil; +import kuchat.server.domain.utils.ValidatorUtil; +import kuchat.server.domain.auth.dto.AuthTokenResponse; import kuchat.server.domain.enums.LearnLanguage; import kuchat.server.domain.enums.SettingLanguage; import kuchat.server.domain.member.Member; @@ -40,28 +43,30 @@ public class MemberController { @SecurityRequirement(name = "JWT") @PostMapping("/signup") public ResponseEntity signup(@Guest Member member, + HttpServletResponse response, @Validated @RequestBody SignupRequest signupRequest, BindingResult bindingResult) { log.info("[signup] 회원가입 요청"); log.info("[signup] signupRequest = {}", signupRequest.toString()); validateGuestToken(member); - Validator.validateRequest(bindingResult); - return memberService.signup(member, signupRequest); + ValidatorUtil.validateRequest(bindingResult); + AuthTokenResponse tokenResponse = memberService.signup(member, signupRequest, response); + return CookieUtil.setAuthToken(tokenResponse); } - @GetMapping("/signup") - public ResponseEntity signup(@RequestParam("guest-token") String token) { - log.info("[signup] 토큰이 없어도 회원가입 페이지로 이동 가능"); - List languages = Arrays.stream(LearnLanguage.values()) - .map(LearnLanguage::getValue) - .toList(); - - List settingLanguages = SettingLanguage.getValues(); - SignupInfoResponse response = new SignupInfoResponse(token, languages, settingLanguages); - - return ResponseEntity.ok(response); - } +// @GetMapping("/signup") +// public ResponseEntity signup(@RequestParam("guest-token") String token) { +// log.info("[signup] 토큰이 없어도 회원가입 페이지로 이동 가능"); +// List languages = Arrays.stream(LearnLanguage.values()) +// .map(LearnLanguage::getValue) +// .toList(); +// +// List settingLanguages = SettingLanguage.getValues(); +// SignupInfoResponse response = new SignupInfoResponse(token, languages, settingLanguages); +// +// return ResponseEntity.ok(response); +// } private void validateGuestToken(Member member) { if (member == null) { @@ -75,7 +80,8 @@ private void validateGuestToken(Member member) { @DeleteMapping("/quit") public ResponseEntity quit(@Auth Member member) { log.info("[quit] memberId = {}", member.getId()); - return memberService.quit(member); + memberService.quit(member); + return CookieUtil.removeAuthToken(); } } diff --git a/src/main/java/kuchat/server/domain/member/controller/ProfileController.java b/src/main/java/kuchat/server/domain/member/controller/ProfileController.java index 7a4c0d1..fd87260 100644 --- a/src/main/java/kuchat/server/domain/member/controller/ProfileController.java +++ b/src/main/java/kuchat/server/domain/member/controller/ProfileController.java @@ -5,7 +5,7 @@ import io.swagger.v3.oas.annotations.tags.Tag; import kuchat.server.domain.auth.argumentResolver.Auth; import kuchat.server.common.response.BaseResponse; -import kuchat.server.domain.Validator; +import kuchat.server.domain.utils.ValidatorUtil; import kuchat.server.domain.member.Member; import kuchat.server.domain.member.dto.DetailProfileResponse; import kuchat.server.domain.member.dto.ProfileUpdateRequest; @@ -46,7 +46,7 @@ public ResponseEntity updateMyProfile(@Auth Member member, @Validated @RequestBody ProfileUpdateRequest requestBody, BindingResult bindingResult) { log.info("[updateMyProfile] 프로필 수정 요청 = {}", requestBody.toString()); - Validator.validateRequest(bindingResult); + ValidatorUtil.validateRequest(bindingResult); return profileService.updateProfile(member, requestBody); } diff --git a/src/main/java/kuchat/server/domain/member/service/MemberService.java b/src/main/java/kuchat/server/domain/member/service/MemberService.java index 3acb2c6..97a1064 100644 --- a/src/main/java/kuchat/server/domain/member/service/MemberService.java +++ b/src/main/java/kuchat/server/domain/member/service/MemberService.java @@ -1,6 +1,5 @@ package kuchat.server.domain.member.service; -import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletResponse; import kuchat.server.common.exception.KuchatException; import kuchat.server.common.redis.RedisService; @@ -17,7 +16,6 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; -import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -32,31 +30,21 @@ @Service public class MemberService { private final MemberRepository memberRepository; - private final JwtTokenService jwtTokenService; private final RedisService redisService; + private final JwtTokenService jwtTokenService; @Value("${default.profile.address}") private String defaultImage; @Transactional - public ResponseEntity signup(Member member, SignupRequest signupRequest) { + public AuthTokenResponse signup(Member member, SignupRequest signupRequest, HttpServletResponse response) { log.info("[signup] member = {}", member.toString()); validateStudentId(signupRequest.getStudentIdNumber()); member.updateInfo(signupRequest, defaultImage); memberRepository.findById(member.getId()); - // 엑세스 토큰, 리프레시 토큰 발급 - AuthTokenResponse authTokenResponse = jwtTokenService.generateAuthToken(member.getRole(), member.getId()); - log.info("[signup] member id : " + member.getId()); - - AuthTokenResponse response = new AuthTokenResponse( - SUCCESS, - authTokenResponse.getAccessToken(), - authTokenResponse.getRefreshToken() - ); - - return ResponseEntity.ok(response); + return jwtTokenService.generateAuthToken(member.getRole(), member.getId()); } private void validateStudentId(String studentId) { @@ -69,14 +57,14 @@ private void validateStudentId(String studentId) { @Transactional public void logout(Member member) { + log.info("[logout] 로그아웃 요청! 사용자 id = {}, name = {}", member.getId(), member.getName()); redisService.removeRefreshToken(member.getId()); } @Transactional - public ResponseEntity quit(Member member) { + public void quit(Member member) { redisService.removeRefreshToken(member.getId()); // 로그아웃 처리 memberRepository.delete(member); // 탈퇴 처리 - return ResponseEntity.ok(new BaseResponse(SUCCESS)); } public Member getMemberByPlusId(String plusId) { @@ -89,7 +77,7 @@ public Member getMemberById(Long id) { .orElseThrow(() -> new KuchatException(NOT_FOUND_MEMBER)); } - public List getMembers(List friends) { + public List getMembersByIds(List friends) { return memberRepository.findAllById(friends); } @@ -106,28 +94,25 @@ public Member lookupMemberByGoogleInfo(GoogleInfoResponse infoResponse) { }); } - public BaseResponse processLoginOrSignup(Member member, - GoogleInfoResponse infoResponse, - HttpServletResponse httpServletResponse) { - if (member == null || member.getRole() == Role.GUEST) { - log.info("[processLoginOrSignup] provider id = {}", infoResponse.getId()); - String guestToken = jwtTokenService.generateGuestToken(GOOGLE.getValue(), infoResponse.getId()); - log.info("[processLoginOrSignup] 신규 회원 guest token 생성 = {}", guestToken); + public BaseResponse processLoginOrSignup(Member member) { + if (member.getRole() == Role.GUEST) { + String guestToken = jwtTokenService.generateGuestToken(GOOGLE.getValue(), member.getProviderId()); return new GuestTokenResponse(SUCCESS, guestToken); } - log.info("[SuccessHandler] 기존 회원인 경우, provider id = {}", infoResponse.getId()); - AuthTokenResponse authTokenResponse = jwtTokenService.generateAuthToken(Role.STUDENT, member.getId()); - log.info("[SuccessHandler] 로그인 성공!!! 토큰 발급 완료 access token = {}, refresh token = {}", - authTokenResponse.getAccessToken(), authTokenResponse.getRefreshToken()); - setAuthCookie(httpServletResponse, "accessToken", authTokenResponse.getAccessToken()); - setAuthCookie(httpServletResponse, "refreshToken", authTokenResponse.getRefreshToken()); - return new AuthTokenResponse(SUCCESS, authTokenResponse.getAccessToken(), authTokenResponse.getRefreshToken()); + return jwtTokenService.generateAuthToken(member.getRole(), member.getId()); + + // guest-token 응답 시 응답으로 넘기기 + + // access token 은 쿠키로 넘겨야함. + } + + public GuestTokenResponse handleGuest(Member member){ + String guestToken = jwtTokenService.generateGuestToken(GOOGLE.getValue(), member.getProviderId()); + return new GuestTokenResponse(SUCCESS, guestToken); } - private void setAuthCookie(HttpServletResponse response, String name, String token) { - Cookie cookie = new Cookie(name, token); - cookie.setSecure(true); // HTTPS 에서만 전송 (개발 환경에서는 설정 비활성화 가능) - cookie.setMaxAge(60 * 60 * 24); // 쿠키 만료 시간 설정 (1시간) - response.addCookie(cookie); + public AuthTokenResponse handleStudent(Member member){ + return jwtTokenService.generateAuthToken(member.getRole(), member.getId()); } + } diff --git a/src/main/java/kuchat/server/domain/message/Message.java b/src/main/java/kuchat/server/domain/message/Message.java index 622267b..27dd838 100644 --- a/src/main/java/kuchat/server/domain/message/Message.java +++ b/src/main/java/kuchat/server/domain/message/Message.java @@ -40,7 +40,7 @@ public class Message extends BaseTime { public Message(MessageRequest messageRequest, Chat chat, Member sender) { this.chat = chat; - this.messageType = MessageType.valueOf(messageRequest.getMessageType()); + this.messageType = messageRequest.getMessageType(); this.sender = sender; this.content = messageRequest.getContent(); } diff --git a/src/main/java/kuchat/server/domain/message/controller/MessageController.java b/src/main/java/kuchat/server/domain/message/controller/MessageController.java index c738442..332fec0 100644 --- a/src/main/java/kuchat/server/domain/message/controller/MessageController.java +++ b/src/main/java/kuchat/server/domain/message/controller/MessageController.java @@ -13,6 +13,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.messaging.handler.annotation.MessageMapping; import org.springframework.messaging.handler.annotation.Payload; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.RestController; @Slf4j @@ -27,10 +28,10 @@ public class MessageController { private final MessageService messageService; -// @Operation(summary = "채팅방 구성원이 보낸 메시지 전송") + @Operation(summary = "채팅방 구성원이 보낸 메시지 전송") @MessageMapping("/message") - public void createMessage(@Payload MessageRequest messageRequest) { - log.info("📩 [sendMessage] : STOMP 메시지 수신 messageRequest = {}", messageRequest); + public void createMessage(@Validated @Payload MessageRequest messageRequest) { + log.info("[sendMessage] : STOMP 메시지 수신 messageRequest = {}", messageRequest); Chat chat = chatService.getChatById(messageRequest.getChatId()); Member sender = memberService.getMemberById(messageRequest.getSenderId()); diff --git a/src/main/java/kuchat/server/domain/message/dto/MessageRequest.java b/src/main/java/kuchat/server/domain/message/dto/MessageRequest.java index a442516..00b1c50 100644 --- a/src/main/java/kuchat/server/domain/message/dto/MessageRequest.java +++ b/src/main/java/kuchat/server/domain/message/dto/MessageRequest.java @@ -1,13 +1,12 @@ package kuchat.server.domain.message.dto; import com.fasterxml.jackson.annotation.JsonProperty; -import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; +import kuchat.server.domain.enums.MessageType; import lombok.*; -import java.util.Objects; - -@Getter @Setter +@Getter +@Setter @ToString @AllArgsConstructor @NoArgsConstructor @@ -15,15 +14,15 @@ public class MessageRequest { @JsonProperty("chatId") - @NotBlank + @NotNull private Long chatId; // 채팅방 ID @JsonProperty("messageType") - @NotBlank - private String messageType; // 메시지 타입 (ENTER, CHAT, LEAVE) + @NotNull + private MessageType messageType; // 메시지 타입 (ENTER, CHAT, LEAVE) @JsonProperty("senderId") - @NotBlank + @NotNull private Long senderId; @JsonProperty("content") diff --git a/src/main/java/kuchat/server/domain/message/service/MessageService.java b/src/main/java/kuchat/server/domain/message/service/MessageService.java index 00a82c6..11d445f 100644 --- a/src/main/java/kuchat/server/domain/message/service/MessageService.java +++ b/src/main/java/kuchat/server/domain/message/service/MessageService.java @@ -1,5 +1,6 @@ package kuchat.server.domain.message.service; +import kuchat.server.common.exception.KuchatException; import kuchat.server.domain.chat.Chat; import kuchat.server.domain.chat.dto.CreateChatResponse; import kuchat.server.domain.member.Member; @@ -19,6 +20,7 @@ import java.util.Comparator; import java.util.List; +import static kuchat.server.common.response.BaseResponseStatus.MESSAGE_FORMAT_ERROR; import static kuchat.server.common.response.BaseResponseStatus.SUCCESS; @@ -50,11 +52,19 @@ public void notifyNewChat(Chat chat, List memberIds) { @Transactional public MessageResponse save(MessageRequest messageRequest, Chat chat, Member sender) { - Message message = new Message(messageRequest, chat, sender); + Message message = createMessage(messageRequest, chat, sender); Message saved = messageRepository.save(message); return new MessageResponse(saved); } + private Message createMessage(MessageRequest messageRequest, Chat chat, Member sender) { + try{ + return new Message(messageRequest, chat, sender); + } catch (IllegalArgumentException e){ + throw new KuchatException(MESSAGE_FORMAT_ERROR); + } + } + public void broadcast(MessageResponse response) { simpMessagingTemplate.convertAndSend("/sub/chat/" + response.getChatId(), response); } diff --git a/src/main/java/kuchat/server/domain/message/service/WebSocketEventHandler.java b/src/main/java/kuchat/server/domain/message/service/WebSocketEventHandler.java new file mode 100644 index 0000000..b3e3992 --- /dev/null +++ b/src/main/java/kuchat/server/domain/message/service/WebSocketEventHandler.java @@ -0,0 +1,21 @@ +package kuchat.server.domain.message.service; + +import org.springframework.context.event.EventListener; +import org.springframework.stereotype.Component; +import org.springframework.web.socket.messaging.SessionConnectedEvent; +import org.springframework.web.socket.messaging.SessionDisconnectEvent; + +@Component +public class WebSocketEventHandler { + + @EventListener + public void connectHandler(SessionConnectedEvent e){ + // 연결을 성공적으로 마친 경우 + // redis에 세션 연결 정보 저장? + } + + @EventListener + public void disconnectHandler(SessionDisconnectEvent e){ + // 연결이 끊어진 경우 + } +} diff --git a/src/main/java/kuchat/server/domain/oauth/controller/OAuthController.java b/src/main/java/kuchat/server/domain/oauth/controller/OAuthController.java index 93cadc4..3728789 100644 --- a/src/main/java/kuchat/server/domain/oauth/controller/OAuthController.java +++ b/src/main/java/kuchat/server/domain/oauth/controller/OAuthController.java @@ -1,30 +1,41 @@ package kuchat.server.domain.oauth.controller; -import jakarta.servlet.http.HttpServletResponse; +import com.nimbusds.oauth2.sdk.TokenResponse; import kuchat.server.common.response.BaseResponse; import kuchat.server.domain.auth.dto.AuthTokenResponse; +import kuchat.server.domain.auth.dto.GuestTokenResponse; +import kuchat.server.domain.member.Member; +import kuchat.server.domain.member.service.MemberService; import kuchat.server.domain.oauth.dto.AuthorizationCodeRequest; +import kuchat.server.domain.oauth.dto.GoogleInfoResponse; import kuchat.server.domain.oauth.service.GoogleOAuthService; +import kuchat.server.domain.utils.CookieUtil; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import static kuchat.server.domain.enums.Role.GUEST; + @Slf4j @RequestMapping("/oauth") @RestController @RequiredArgsConstructor -//@CrossOrigin(origins = "https://kuchat.netlify.app", allowCredentials = "true") public class OAuthController { private final GoogleOAuthService googleOAuthService; + private final MemberService memberService; @PostMapping("/google") - public ResponseEntity callback(@RequestBody AuthorizationCodeRequest authorizationCodeRequest, - HttpServletResponse response){ + public ResponseEntity callback(@RequestBody AuthorizationCodeRequest authorizationCodeRequest){ log.info("[callback] 구글 인가코드 발급 완료 = {}", authorizationCodeRequest.getCode()); - BaseResponse tokenResponse = googleOAuthService.process(authorizationCodeRequest.getCode(), response); - log.info("[callback] 클라이언트에게 줄 response body = {}", tokenResponse); - return ResponseEntity.ok(tokenResponse); + GoogleInfoResponse userInfoResponse = googleOAuthService.getUserInfo(authorizationCodeRequest.getCode()); + Member member = memberService.lookupMemberByGoogleInfo(userInfoResponse); + if (member.getRole() == GUEST){ + GuestTokenResponse guestTokenResponse = memberService.handleGuest(member); + return ResponseEntity.ok(guestTokenResponse); + } + AuthTokenResponse authTokenResponse = memberService.handleStudent(member); + return CookieUtil.setAuthToken(authTokenResponse); } } diff --git a/src/main/java/kuchat/server/domain/oauth/service/GoogleOAuthService.java b/src/main/java/kuchat/server/domain/oauth/service/GoogleOAuthService.java index 291e905..59731f2 100644 --- a/src/main/java/kuchat/server/domain/oauth/service/GoogleOAuthService.java +++ b/src/main/java/kuchat/server/domain/oauth/service/GoogleOAuthService.java @@ -2,6 +2,7 @@ import jakarta.servlet.http.HttpServletResponse; import kuchat.server.common.response.BaseResponse; +import kuchat.server.common.response.BaseResponseStatus; import kuchat.server.domain.auth.dto.AuthTokenResponse; import kuchat.server.domain.oauth.dto.GoogleInfoResponse; import kuchat.server.domain.oauth.dto.GoogleTokenResponse; @@ -9,7 +10,9 @@ import kuchat.server.domain.member.service.MemberService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; @@ -39,12 +42,11 @@ public GoogleOAuthService(MemberService memberService, RestClient.Builder builde .build(); } - public BaseResponse process(String code, HttpServletResponse httpServletResponse) { + public GoogleInfoResponse getUserInfo(String code) { GoogleTokenResponse tokenResponse = requestAccessToken(code); GoogleInfoResponse infoResponse = requestUserInfo(tokenResponse.getAccessToken()); log.info("[process] 구글에서 제공한 user info = {}", infoResponse); - Member member = memberService.lookupMemberByGoogleInfo(infoResponse); - return memberService.processLoginOrSignup(member, infoResponse, httpServletResponse); + return infoResponse; } public GoogleTokenResponse requestAccessToken(String code) { diff --git a/src/main/java/kuchat/server/domain/oauth/service/OAuth2LogoutSuccessHandler.java b/src/main/java/kuchat/server/domain/oauth/service/OAuth2LogoutSuccessHandler.java deleted file mode 100644 index 923413a..0000000 --- a/src/main/java/kuchat/server/domain/oauth/service/OAuth2LogoutSuccessHandler.java +++ /dev/null @@ -1,59 +0,0 @@ -package kuchat.server.domain.oauth.service; - -import com.fasterxml.jackson.databind.ObjectMapper; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import kuchat.server.common.exception.KuchatException; -import kuchat.server.domain.auth.service.JwtTokenService; -import kuchat.server.common.response.BaseResponse; -import kuchat.server.domain.member.Member; -import kuchat.server.domain.member.service.MemberService; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.security.core.Authentication; -import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; -import org.springframework.stereotype.Service; - -import java.io.IOException; - -import static kuchat.server.common.response.BaseResponseStatus.SUCCESS; - -@Slf4j -@RequiredArgsConstructor -@Service -public class OAuth2LogoutSuccessHandler implements LogoutSuccessHandler { - - private final MemberService memberService; - private final JwtTokenService jwtTokenService; - - @Override - public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { - try { - String accessToken = request.getHeader("Authorization"); - Member member = jwtTokenService.extractMemberByAccessToken(accessToken); - memberService.logout(member); - - log.info("[onLogoutSuccess] 로그아웃 요청 사용자 id = {}, name = {}" + member.getId(), member.getName()); - ObjectMapper objectMapper = new ObjectMapper(); - String jsonResponse = objectMapper.writeValueAsString(new BaseResponse(SUCCESS)); - - response.setContentType("application/json"); - response.setCharacterEncoding("UTF-8"); - response.setStatus(HttpServletResponse.SC_OK); - response.getWriter().write(jsonResponse); - response.getWriter().flush(); - } catch (KuchatException e){ - log.error("[onLogoutSuccess] 로그아웃 처리 중 예외 발생", e.getMessage()); - - ObjectMapper objectMapper = new ObjectMapper(); - String errorResponse = objectMapper.writeValueAsString(e.getResponse()); - - response.setContentType("application/json"); - response.setCharacterEncoding("UTF-8"); - response.setStatus(e.getResponse().getHttpStatus().value()); - response.getWriter().write(errorResponse); - response.getWriter().flush(); - } - } -} diff --git a/src/main/java/kuchat/server/domain/utils/CookieUtil.java b/src/main/java/kuchat/server/domain/utils/CookieUtil.java new file mode 100644 index 0000000..a7c8f7e --- /dev/null +++ b/src/main/java/kuchat/server/domain/utils/CookieUtil.java @@ -0,0 +1,51 @@ +package kuchat.server.domain.utils; + +import kuchat.server.common.response.BaseResponse; +import kuchat.server.domain.auth.dto.AuthTokenResponse; +import kuchat.server.domain.auth.dto.GuestTokenResponse; +import org.springframework.http.HttpHeaders; +import org.springframework.http.ResponseCookie; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.context.SecurityContextHolder; + +import static kuchat.server.common.response.BaseResponseStatus.SUCCESS; + +public class CookieUtil { + + private static final int REFRESH_TOKEN_EXPIRATION = 7 * 24 * 60 * 60; // 7일 + private static final int ACCESS_TOKEN_EXPIRATION = 30 * 60; // 30분 + private static final String REFRESH_TOKEN = "refreshToken"; + private static final String ACCESS_TOKEN = "accessToken"; + + public static ResponseEntity setAuthToken(AuthTokenResponse response) { + HttpHeaders headers = new HttpHeaders(); + ResponseCookie refreshTokenCookie = createCookie( + REFRESH_TOKEN, response.getRefreshToken(), REFRESH_TOKEN_EXPIRATION); + ResponseCookie accessTokenCookie = createCookie( + ACCESS_TOKEN, response.getAccessToken(), ACCESS_TOKEN_EXPIRATION); + + headers.add(HttpHeaders.SET_COOKIE, refreshTokenCookie.toString()); + headers.add(HttpHeaders.SET_COOKIE, accessTokenCookie.toString()); + + return ResponseEntity.ok().headers(headers).body(response); + } + + public static ResponseEntity removeAuthToken() { + ResponseCookie noAccessTokenCookie = createCookie(ACCESS_TOKEN, "", 0); + ResponseCookie noRefreshTokenCookie = createCookie(REFRESH_TOKEN, "", 0); + HttpHeaders headers = new HttpHeaders(); + headers.add(HttpHeaders.SET_COOKIE, noAccessTokenCookie.toString()); + headers.add(HttpHeaders.SET_COOKIE, noRefreshTokenCookie.toString()); + return ResponseEntity.ok().headers(headers).body(new BaseResponse(SUCCESS)); + } + + private static ResponseCookie createCookie(String key, String value, int age) { + return ResponseCookie.from(key, value) + .httpOnly(true) + .secure(true) + .path("/") + .maxAge(age) + .sameSite("None") + .build(); + } +} diff --git a/src/main/java/kuchat/server/domain/Validator.java b/src/main/java/kuchat/server/domain/utils/ValidatorUtil.java similarity index 92% rename from src/main/java/kuchat/server/domain/Validator.java rename to src/main/java/kuchat/server/domain/utils/ValidatorUtil.java index 57e5fea..08fb94c 100644 --- a/src/main/java/kuchat/server/domain/Validator.java +++ b/src/main/java/kuchat/server/domain/utils/ValidatorUtil.java @@ -1,4 +1,4 @@ -package kuchat.server.domain; +package kuchat.server.domain.utils; import kuchat.server.common.exception.KuchatException; import kuchat.server.common.response.Error; @@ -9,7 +9,7 @@ import static kuchat.server.common.response.BaseResponseStatus.INFO_BAD_REQUEST; -public class Validator { +public class ValidatorUtil { public static void validateRequest(BindingResult bindingResult) { if (bindingResult.hasErrors()) { From 6360eadde755edee9c2bfe9c08ea611d0f07f0dd Mon Sep 17 00:00:00 2001 From: lyouxsun Date: Wed, 12 Mar 2025 14:18:29 +0900 Subject: [PATCH 12/21] =?UTF-8?q?fix:=20HomeController=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kuchat/server/domain/HomeController.java | 59 ------------------- .../server/domain/utils/CookieUtil.java | 2 - 2 files changed, 61 deletions(-) delete mode 100644 src/main/java/kuchat/server/domain/HomeController.java diff --git a/src/main/java/kuchat/server/domain/HomeController.java b/src/main/java/kuchat/server/domain/HomeController.java deleted file mode 100644 index d5b380a..0000000 --- a/src/main/java/kuchat/server/domain/HomeController.java +++ /dev/null @@ -1,59 +0,0 @@ -package kuchat.server.domain; - -import jakarta.servlet.http.Cookie; -import jakarta.servlet.http.HttpServletResponse; -import kuchat.server.common.exception.KuchatException; -import kuchat.server.domain.auth.service.JwtTokenService; -import kuchat.server.domain.member.Member; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Controller; -import org.springframework.ui.Model; -import org.springframework.web.bind.annotation.CookieValue; -import org.springframework.web.bind.annotation.GetMapping; - -@Slf4j -@RequiredArgsConstructor -@Controller -public class HomeController { - - private final JwtTokenService jwtTokenService; - - @GetMapping("/") - public String home(@CookieValue(name = "Authorization", required = false) String token, - Model model, HttpServletResponse response) { - log.info("[home] 홈화면으로 이동"); - try { - Member member = jwtTokenService.extractMemberByAccessToken(token); - log.info("[home] 쿠키에 담긴 회원 정보 = {}", member.toString()); - model.addAttribute("member", member); - } catch (KuchatException e) { - Cookie cookie = new Cookie("Authorization", ""); - cookie.setPath("/"); - cookie.setMaxAge(0); - response.addCookie(cookie); - } - return "index"; - } - -// @GetMapping("/member/signup") -// public String signup(Model model) { -// log.info("[signup] 토큰이 없어도 회원가입 페이지로 이동 가능"); -// List languages = Arrays.stream(LearnLanguage.values()) -// .map(LearnLanguage::getValue) -// .toList(); -// model.addAttribute("languages", languages); -// -// List settingLanguages = SettingLanguage.getValues(); -// model.addAttribute("settingLanguages", settingLanguages); -// -// return "signup"; -// } - - @GetMapping("/room") - public String chatroomList() { - log.info("[chatroomList] 채팅방 목록 조회"); - return "chatroom"; - } - -} diff --git a/src/main/java/kuchat/server/domain/utils/CookieUtil.java b/src/main/java/kuchat/server/domain/utils/CookieUtil.java index a7c8f7e..40193eb 100644 --- a/src/main/java/kuchat/server/domain/utils/CookieUtil.java +++ b/src/main/java/kuchat/server/domain/utils/CookieUtil.java @@ -2,11 +2,9 @@ import kuchat.server.common.response.BaseResponse; import kuchat.server.domain.auth.dto.AuthTokenResponse; -import kuchat.server.domain.auth.dto.GuestTokenResponse; import org.springframework.http.HttpHeaders; import org.springframework.http.ResponseCookie; import org.springframework.http.ResponseEntity; -import org.springframework.security.core.context.SecurityContextHolder; import static kuchat.server.common.response.BaseResponseStatus.SUCCESS; From 767d116f14d5a9a14c816867eb975e29a7cb8215 Mon Sep 17 00:00:00 2001 From: lyouxsun Date: Wed, 12 Mar 2025 14:29:59 +0900 Subject: [PATCH 13/21] =?UTF-8?q?fix:=20Platform=20value=20=ED=95=84?= =?UTF-8?q?=EB=93=9C=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kuchat/server/domain/enums/Platform.java | 16 ++-------------- .../domain/member/service/MemberService.java | 4 ++-- 2 files changed, 4 insertions(+), 16 deletions(-) diff --git a/src/main/java/kuchat/server/domain/enums/Platform.java b/src/main/java/kuchat/server/domain/enums/Platform.java index ae39a1e..9e8f83c 100644 --- a/src/main/java/kuchat/server/domain/enums/Platform.java +++ b/src/main/java/kuchat/server/domain/enums/Platform.java @@ -8,27 +8,15 @@ @Slf4j @Getter public enum Platform { - GOOGLE("google"), - NAVER("naver"), - KAKAO("kakao"); - - private String value; - - Platform(String value) { - this.value = value; - } + GOOGLE, NAVER, KAKAO; public static Platform of(String value) { log.info("[of] platform string = {}", value); for (Platform platform : Platform.values()) { - if (platform.getValue().equals(value)) { + if (platform.name().equals(value)) { return platform; } } return null; } - - public String getValue(){ - return value.toUpperCase(); - } } diff --git a/src/main/java/kuchat/server/domain/member/service/MemberService.java b/src/main/java/kuchat/server/domain/member/service/MemberService.java index 97a1064..86cdb15 100644 --- a/src/main/java/kuchat/server/domain/member/service/MemberService.java +++ b/src/main/java/kuchat/server/domain/member/service/MemberService.java @@ -96,7 +96,7 @@ public Member lookupMemberByGoogleInfo(GoogleInfoResponse infoResponse) { public BaseResponse processLoginOrSignup(Member member) { if (member.getRole() == Role.GUEST) { - String guestToken = jwtTokenService.generateGuestToken(GOOGLE.getValue(), member.getProviderId()); + String guestToken = jwtTokenService.generateGuestToken(GOOGLE.name(), member.getProviderId()); return new GuestTokenResponse(SUCCESS, guestToken); } return jwtTokenService.generateAuthToken(member.getRole(), member.getId()); @@ -107,7 +107,7 @@ public BaseResponse processLoginOrSignup(Member member) { } public GuestTokenResponse handleGuest(Member member){ - String guestToken = jwtTokenService.generateGuestToken(GOOGLE.getValue(), member.getProviderId()); + String guestToken = jwtTokenService.generateGuestToken(GOOGLE.name(), member.getProviderId()); return new GuestTokenResponse(SUCCESS, guestToken); } From 2e3fdced5b14ba8363c20b7c25c13c346fcf67cb Mon Sep 17 00:00:00 2001 From: lyouxsun Date: Wed, 12 Mar 2025 15:08:14 +0900 Subject: [PATCH 14/21] =?UTF-8?q?fix:=20Spring=20Security=20=EC=A3=BC?= =?UTF-8?q?=EC=84=9D=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../server/common/config/SecurityConfig.java | 68 +++++++++---------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/src/main/java/kuchat/server/common/config/SecurityConfig.java b/src/main/java/kuchat/server/common/config/SecurityConfig.java index 5f85a27..e060b19 100644 --- a/src/main/java/kuchat/server/common/config/SecurityConfig.java +++ b/src/main/java/kuchat/server/common/config/SecurityConfig.java @@ -1,34 +1,34 @@ -package kuchat.server.common.config; - -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; -import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer.FrameOptionsConfig; -import org.springframework.security.config.http.SessionCreationPolicy; -import org.springframework.security.web.SecurityFilterChain; - -@Slf4j -@RequiredArgsConstructor -@Configuration -@EnableWebSecurity // spring security 기능을 활성화시키는 어노테이션 (스프링 시큐리티 필터가 스프링 필터 체인에 등록됨) -public class SecurityConfig { - - @Bean - public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - return http - .httpBasic(AbstractHttpConfigurer::disable) // jwt 토큰을 사용한 bearer 방식을 사용하므로 default 설정 disable - .headers(headers -> headers.frameOptions(FrameOptionsConfig::disable)) // h2 콘솔에 접근하기 위해서는 X-Frame-Options Click jacking 공격을 막는 설정 disable - .csrf(csrf -> csrf.ignoringRequestMatchers("ws-connect/**")) // websocket 경로는 CSRF 예외 처리 // rest api 방식에서는 jwt 또는 oauth2 방식을 사용하여 인증하므로 csrf 보안기능 필요 X - .sessionManagement(sessionManagement -> - sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 세션을 사용하지 않으므로 disable (stateless) - ) - .authorizeHttpRequests((authorize) -> - authorize.anyRequest().permitAll() - ) - .build(); - } -} +//package kuchat.server.common.config; +// +//import lombok.RequiredArgsConstructor; +//import lombok.extern.slf4j.Slf4j; +//import org.springframework.context.annotation.Bean; +//import org.springframework.context.annotation.Configuration; +//import org.springframework.security.config.annotation.web.builders.HttpSecurity; +//import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +//import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; +//import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer.FrameOptionsConfig; +//import org.springframework.security.config.http.SessionCreationPolicy; +//import org.springframework.security.web.SecurityFilterChain; +// +//@Slf4j +//@RequiredArgsConstructor +//@Configuration +//@EnableWebSecurity // spring security 기능을 활성화시키는 어노테이션 (스프링 시큐리티 필터가 스프링 필터 체인에 등록됨) +//public class SecurityConfig { +// +// @Bean +// public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { +// return http +// .httpBasic(AbstractHttpConfigurer::disable) // jwt 토큰을 사용한 bearer 방식을 사용하므로 default 설정 disable +// .headers(headers -> headers.frameOptions(FrameOptionsConfig::disable)) // h2 콘솔에 접근하기 위해서는 X-Frame-Options Click jacking 공격을 막는 설정 disable +// .csrf(csrf -> csrf.ignoringRequestMatchers("ws-connect/**")) // websocket 경로는 CSRF 예외 처리 // rest api 방식에서는 jwt 또는 oauth2 방식을 사용하여 인증하므로 csrf 보안기능 필요 X +// .sessionManagement(sessionManagement -> +// sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 세션을 사용하지 않으므로 disable (stateless) +// ) +// .authorizeHttpRequests((authorize) -> +// authorize.anyRequest().permitAll() +// ) +// .build(); +// } +//} From e2129ec9ffec9dc209211a878af87b7d34aaaded Mon Sep 17 00:00:00 2001 From: lyouxsun Date: Wed, 12 Mar 2025 15:25:19 +0900 Subject: [PATCH 15/21] =?UTF-8?q?fix:=20SpringSecurity=20=EB=9D=BC?= =?UTF-8?q?=EC=9D=B4=EB=B8=8C=EB=9F=AC=EB=A6=AC=20=EC=9D=98=EC=A1=B4?= =?UTF-8?q?=EC=84=B1=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/build.gradle b/build.gradle index a4420ce..4175b12 100644 --- a/build.gradle +++ b/build.gradle @@ -39,11 +39,8 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-redis' implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE' - // oauth2 - implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' - implementation 'org.springframework.boot:spring-boot-starter-security' + // auth implementation 'javax.xml.bind:jaxb-api:2.3.1' // jwt 생성에 필요한 DatatypeConverter - testImplementation 'org.springframework.security:spring-security-test' implementation 'io.jsonwebtoken:jjwt:0.9.1' // chat From a59092530e2fa5c73cc7cee923658338bc60ff57 Mon Sep 17 00:00:00 2001 From: lyouxsun Date: Wed, 12 Mar 2025 15:34:29 +0900 Subject: [PATCH 16/21] =?UTF-8?q?fix:=20OAuthController=20import=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/oauth/controller/OAuthController.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/main/java/kuchat/server/domain/oauth/controller/OAuthController.java b/src/main/java/kuchat/server/domain/oauth/controller/OAuthController.java index 3728789..8adca04 100644 --- a/src/main/java/kuchat/server/domain/oauth/controller/OAuthController.java +++ b/src/main/java/kuchat/server/domain/oauth/controller/OAuthController.java @@ -1,6 +1,5 @@ package kuchat.server.domain.oauth.controller; -import com.nimbusds.oauth2.sdk.TokenResponse; import kuchat.server.common.response.BaseResponse; import kuchat.server.domain.auth.dto.AuthTokenResponse; import kuchat.server.domain.auth.dto.GuestTokenResponse; @@ -13,7 +12,10 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; import static kuchat.server.domain.enums.Role.GUEST; @@ -27,11 +29,11 @@ public class OAuthController { private final MemberService memberService; @PostMapping("/google") - public ResponseEntity callback(@RequestBody AuthorizationCodeRequest authorizationCodeRequest){ + public ResponseEntity callback(@RequestBody AuthorizationCodeRequest authorizationCodeRequest) { log.info("[callback] 구글 인가코드 발급 완료 = {}", authorizationCodeRequest.getCode()); GoogleInfoResponse userInfoResponse = googleOAuthService.getUserInfo(authorizationCodeRequest.getCode()); Member member = memberService.lookupMemberByGoogleInfo(userInfoResponse); - if (member.getRole() == GUEST){ + if (member.getRole() == GUEST) { GuestTokenResponse guestTokenResponse = memberService.handleGuest(member); return ResponseEntity.ok(guestTokenResponse); } From c16624bace97808cfb3328db659836b683afeba7 Mon Sep 17 00:00:00 2001 From: lyouxsun Date: Wed, 12 Mar 2025 16:08:30 +0900 Subject: [PATCH 17/21] =?UTF-8?q?fix:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EC=8B=9C=EC=97=90=EB=A7=8C=20=EC=BF=A0=ED=82=A4=20?= =?UTF-8?q?=EB=88=84=EB=9D=BD=20=EB=AC=B8=EC=A0=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../server/common/config/SecurityConfig.java | 34 ------------- .../server/common/config/WebMvcConfig.java | 25 +-------- .../domain/auth/JwtTokenInterceptor.java | 51 ------------------- .../kuchat/server/domain/member/Member.java | 3 +- .../member/controller/MemberController.java | 17 +------ .../domain/member/service/MemberService.java | 17 +------ .../server/domain/utils/CookieUtil.java | 8 ++- 7 files changed, 11 insertions(+), 144 deletions(-) delete mode 100644 src/main/java/kuchat/server/common/config/SecurityConfig.java delete mode 100644 src/main/java/kuchat/server/domain/auth/JwtTokenInterceptor.java diff --git a/src/main/java/kuchat/server/common/config/SecurityConfig.java b/src/main/java/kuchat/server/common/config/SecurityConfig.java deleted file mode 100644 index e060b19..0000000 --- a/src/main/java/kuchat/server/common/config/SecurityConfig.java +++ /dev/null @@ -1,34 +0,0 @@ -//package kuchat.server.common.config; -// -//import lombok.RequiredArgsConstructor; -//import lombok.extern.slf4j.Slf4j; -//import org.springframework.context.annotation.Bean; -//import org.springframework.context.annotation.Configuration; -//import org.springframework.security.config.annotation.web.builders.HttpSecurity; -//import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -//import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; -//import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer.FrameOptionsConfig; -//import org.springframework.security.config.http.SessionCreationPolicy; -//import org.springframework.security.web.SecurityFilterChain; -// -//@Slf4j -//@RequiredArgsConstructor -//@Configuration -//@EnableWebSecurity // spring security 기능을 활성화시키는 어노테이션 (스프링 시큐리티 필터가 스프링 필터 체인에 등록됨) -//public class SecurityConfig { -// -// @Bean -// public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { -// return http -// .httpBasic(AbstractHttpConfigurer::disable) // jwt 토큰을 사용한 bearer 방식을 사용하므로 default 설정 disable -// .headers(headers -> headers.frameOptions(FrameOptionsConfig::disable)) // h2 콘솔에 접근하기 위해서는 X-Frame-Options Click jacking 공격을 막는 설정 disable -// .csrf(csrf -> csrf.ignoringRequestMatchers("ws-connect/**")) // websocket 경로는 CSRF 예외 처리 // rest api 방식에서는 jwt 또는 oauth2 방식을 사용하여 인증하므로 csrf 보안기능 필요 X -// .sessionManagement(sessionManagement -> -// sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 세션을 사용하지 않으므로 disable (stateless) -// ) -// .authorizeHttpRequests((authorize) -> -// authorize.anyRequest().permitAll() -// ) -// .build(); -// } -//} diff --git a/src/main/java/kuchat/server/common/config/WebMvcConfig.java b/src/main/java/kuchat/server/common/config/WebMvcConfig.java index 3402f1e..fd59e16 100644 --- a/src/main/java/kuchat/server/common/config/WebMvcConfig.java +++ b/src/main/java/kuchat/server/common/config/WebMvcConfig.java @@ -1,14 +1,13 @@ package kuchat.server.common.config; -import kuchat.server.domain.auth.JwtTokenInterceptor; import kuchat.server.domain.auth.argumentResolver.AuthArgumentResolver; import kuchat.server.domain.auth.argumentResolver.GuestArgumentResolver; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.boot.web.servlet.view.MustacheViewResolver; import org.springframework.context.annotation.Configuration; import org.springframework.web.method.support.HandlerMethodArgumentResolver; -import org.springframework.web.servlet.config.annotation.*; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import java.util.List; @@ -19,26 +18,6 @@ public class WebMvcConfig implements WebMvcConfigurer { private final AuthArgumentResolver authArgumentResolver; private final GuestArgumentResolver guestArgumentResolver; - private final JwtTokenInterceptor jwtTokenInterceptor; - - - @Override - public void configureViewResolvers(ViewResolverRegistry registry) { - MustacheViewResolver viewResolver = new MustacheViewResolver(); - viewResolver.setCharset("UTF-8"); - viewResolver.setContentType("text/html; charset=utf-8"); - viewResolver.setPrefix("classpath:/templates/"); - viewResolver.setSuffix(".html"); - registry.viewResolver(viewResolver); - } - - @Override - public void addInterceptors(InterceptorRegistry registry) { - log.info("[interceptor 등록]"); - registry.addInterceptor(jwtTokenInterceptor) - .excludePathPatterns("/member/signup/**") - .addPathPatterns("/member/my-profile", "/friend/**", "/block/**", "/chatroom/**"); - } @Override public void addArgumentResolvers(List resolvers) { diff --git a/src/main/java/kuchat/server/domain/auth/JwtTokenInterceptor.java b/src/main/java/kuchat/server/domain/auth/JwtTokenInterceptor.java deleted file mode 100644 index 388fa38..0000000 --- a/src/main/java/kuchat/server/domain/auth/JwtTokenInterceptor.java +++ /dev/null @@ -1,51 +0,0 @@ -package kuchat.server.domain.auth; - -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import kuchat.server.common.exception.KuchatException; -import kuchat.server.domain.auth.service.JwtTokenService; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.http.HttpHeaders; -import org.springframework.stereotype.Component; -import org.springframework.web.servlet.HandlerInterceptor; - -import static kuchat.server.common.response.BaseResponseStatus.*; - -@Slf4j -@RequiredArgsConstructor -@Component -public class JwtTokenInterceptor implements HandlerInterceptor { - - private final JwtTokenService jwtTokenService; - - @Override - public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { - // true 반환 시 인터셉터 통과, 컨트롤러로! - String accessToken = request.getHeader(HttpHeaders.AUTHORIZATION); - if (accessToken == null) { - log.info("[validateToken] token is null"); - throw new KuchatException(NOT_FOUND_TOKEN); - } - accessToken = accessToken.replaceAll("Bearer ", ""); - log.info("[preHandle] access token = {}", accessToken); - - if (request.getRequestURI().contains("/member/signup") || - request.getRequestURI().equals("/") || - request.getRequestURI().equals("/login")) { - log.info("[preHandle] request uri = {} 는 인증절차 필요X", accessToken); - return true; - } - - Long memberId = jwtTokenService.getMemberId(accessToken); - log.info("[preHandle] member id by token = {}", memberId); - request.setAttribute("memberId", memberId); - - String role = jwtTokenService.getClaims(accessToken).get("role", String.class); - log.info("[preHandle] role by token = {}", role); - request.setAttribute("role", role); - - return true; - } - -} diff --git a/src/main/java/kuchat/server/domain/member/Member.java b/src/main/java/kuchat/server/domain/member/Member.java index 606c993..98881ba 100644 --- a/src/main/java/kuchat/server/domain/member/Member.java +++ b/src/main/java/kuchat/server/domain/member/Member.java @@ -82,11 +82,10 @@ public Member(GoogleInfoResponse infoResponse) { public void updateInfo(SignupRequest request, String defaultImage) { this.language = new Language(request); profile.update(request, defaultImage); - this.studentId = request.getStudentIdNumber(); this.plusId = generatePlusId(10); this.status = Status.ACTIVE; - this.role = Role.STUDENT; // 추가정보 받은 후 처리 + this.role = Role.STUDENT; } public String generatePlusId(int length) { diff --git a/src/main/java/kuchat/server/domain/member/controller/MemberController.java b/src/main/java/kuchat/server/domain/member/controller/MemberController.java index f0b04d3..9114b84 100644 --- a/src/main/java/kuchat/server/domain/member/controller/MemberController.java +++ b/src/main/java/kuchat/server/domain/member/controller/MemberController.java @@ -46,28 +46,13 @@ public ResponseEntity signup(@Guest Member member, HttpServletResponse response, @Validated @RequestBody SignupRequest signupRequest, BindingResult bindingResult) { - log.info("[signup] 회원가입 요청"); - log.info("[signup] signupRequest = {}", signupRequest.toString()); - + log.info("[signup] 회원가입 요청 signupRequest = {}", signupRequest.toString()); validateGuestToken(member); ValidatorUtil.validateRequest(bindingResult); AuthTokenResponse tokenResponse = memberService.signup(member, signupRequest, response); return CookieUtil.setAuthToken(tokenResponse); } -// @GetMapping("/signup") -// public ResponseEntity signup(@RequestParam("guest-token") String token) { -// log.info("[signup] 토큰이 없어도 회원가입 페이지로 이동 가능"); -// List languages = Arrays.stream(LearnLanguage.values()) -// .map(LearnLanguage::getValue) -// .toList(); -// -// List settingLanguages = SettingLanguage.getValues(); -// SignupInfoResponse response = new SignupInfoResponse(token, languages, settingLanguages); -// -// return ResponseEntity.ok(response); -// } - private void validateGuestToken(Member member) { if (member == null) { log.error("[signup] guest token을 가지고 찾은 멤버가 null인 오류"); diff --git a/src/main/java/kuchat/server/domain/member/service/MemberService.java b/src/main/java/kuchat/server/domain/member/service/MemberService.java index 86cdb15..896efcd 100644 --- a/src/main/java/kuchat/server/domain/member/service/MemberService.java +++ b/src/main/java/kuchat/server/domain/member/service/MemberService.java @@ -39,12 +39,9 @@ public class MemberService { @Transactional public AuthTokenResponse signup(Member member, SignupRequest signupRequest, HttpServletResponse response) { log.info("[signup] member = {}", member.toString()); - validateStudentId(signupRequest.getStudentIdNumber()); member.updateInfo(signupRequest, defaultImage); - memberRepository.findById(member.getId()); - - return jwtTokenService.generateAuthToken(member.getRole(), member.getId()); + return handleStudent(member); } private void validateStudentId(String studentId) { @@ -94,18 +91,6 @@ public Member lookupMemberByGoogleInfo(GoogleInfoResponse infoResponse) { }); } - public BaseResponse processLoginOrSignup(Member member) { - if (member.getRole() == Role.GUEST) { - String guestToken = jwtTokenService.generateGuestToken(GOOGLE.name(), member.getProviderId()); - return new GuestTokenResponse(SUCCESS, guestToken); - } - return jwtTokenService.generateAuthToken(member.getRole(), member.getId()); - - // guest-token 응답 시 응답으로 넘기기 - - // access token 은 쿠키로 넘겨야함. - } - public GuestTokenResponse handleGuest(Member member){ String guestToken = jwtTokenService.generateGuestToken(GOOGLE.name(), member.getProviderId()); return new GuestTokenResponse(SUCCESS, guestToken); diff --git a/src/main/java/kuchat/server/domain/utils/CookieUtil.java b/src/main/java/kuchat/server/domain/utils/CookieUtil.java index 40193eb..2c775e0 100644 --- a/src/main/java/kuchat/server/domain/utils/CookieUtil.java +++ b/src/main/java/kuchat/server/domain/utils/CookieUtil.java @@ -25,7 +25,9 @@ public static ResponseEntity setAuthToken(AuthTokenResponse respon headers.add(HttpHeaders.SET_COOKIE, refreshTokenCookie.toString()); headers.add(HttpHeaders.SET_COOKIE, accessTokenCookie.toString()); - return ResponseEntity.ok().headers(headers).body(response); + return ResponseEntity.ok() + .headers(headers) + .body(new BaseResponse(SUCCESS)); } public static ResponseEntity removeAuthToken() { @@ -34,7 +36,9 @@ public static ResponseEntity removeAuthToken() { HttpHeaders headers = new HttpHeaders(); headers.add(HttpHeaders.SET_COOKIE, noAccessTokenCookie.toString()); headers.add(HttpHeaders.SET_COOKIE, noRefreshTokenCookie.toString()); - return ResponseEntity.ok().headers(headers).body(new BaseResponse(SUCCESS)); + return ResponseEntity.ok() + .headers(headers) + .body(new BaseResponse(SUCCESS)); } private static ResponseCookie createCookie(String key, String value, int age) { From 6413e8702d27ed0b22b9e488396b268e19bc0c85 Mon Sep 17 00:00:00 2001 From: lyouxsun Date: Wed, 12 Mar 2025 16:14:49 +0900 Subject: [PATCH 18/21] =?UTF-8?q?fix:=20JwtTokenService=20member=20id=20?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/kuchat/server/domain/auth/service/JwtTokenService.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/kuchat/server/domain/auth/service/JwtTokenService.java b/src/main/java/kuchat/server/domain/auth/service/JwtTokenService.java index 63b3397..505f8f4 100644 --- a/src/main/java/kuchat/server/domain/auth/service/JwtTokenService.java +++ b/src/main/java/kuchat/server/domain/auth/service/JwtTokenService.java @@ -137,6 +137,7 @@ public Member extractMemberByAccessToken(String accessToken) { log.info("[extractMemberByAccessToken] accessToken = {} ", accessToken); String token = validate(accessToken, ACCESS); Long memberId = getMemberId(token); + log.info("[extractMemberByAccessToken] member id = {}", memberId); return memberRepository.findById(memberId) .orElseThrow(() -> new KuchatException(NOT_FOUND_MEMBER)); } From 97e3c885f7692200fc52baf9c768020660be93af Mon Sep 17 00:00:00 2001 From: lyouxsun Date: Wed, 12 Mar 2025 22:05:26 +0900 Subject: [PATCH 19/21] =?UTF-8?q?feat:=20=EC=B1=84=ED=8C=85=EB=B0=A9=20?= =?UTF-8?q?=EB=82=98=EA=B0=80=EA=B8=B0=20=EA=B8=B0=EB=8A=A5=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 --- .../common/response/BaseResponseStatus.java | 14 +++------ .../java/kuchat/server/domain/chat/Chat.java | 1 - .../kuchat/server/domain/chat/ChatMember.java | 5 ---- .../chat/controller/ChatController.java | 18 +++++++++--- .../chat/repository/ChatMemberRepository.java | 4 ++- .../chat/service/ChatMemberService.java | 15 ++++++++-- .../domain/chat/service/ChatService.java | 29 ++++++++++--------- 7 files changed, 48 insertions(+), 38 deletions(-) diff --git a/src/main/java/kuchat/server/common/response/BaseResponseStatus.java b/src/main/java/kuchat/server/common/response/BaseResponseStatus.java index 7928f43..076da2f 100644 --- a/src/main/java/kuchat/server/common/response/BaseResponseStatus.java +++ b/src/main/java/kuchat/server/common/response/BaseResponseStatus.java @@ -53,18 +53,12 @@ public enum BaseResponseStatus { //- 5000번대 : 채팅방(chat) 관련 코드 NOT_FOUND_CHAT(5000, HttpStatus.NOT_FOUND, "존재하지 않는 채팅방입니다."), ALREADY_CHAT(5001, HttpStatus.OK, "이미 존재하는 채팅방입니다."), - UNAUTHORIZED_CHAT_MEMBER(5002, HttpStatus.UNAUTHORIZED, "채팅방에 접근할 수 있는 회원이 아닙니다."), - NOT_FOUND_CHATMEMBER(5003, HttpStatus.BAD_REQUEST, "채팅방 구성원이 아닙니다."), + UNAUTHORIZED_CHAT_MEMBER(5002, HttpStatus.UNAUTHORIZED, "채팅방에 접근할 수 없는 회원입니다. 권한을 확인해주세요."), + EMPTY_CHAT_MEMBER(5003, HttpStatus.BAD_REQUEST, "채팅방 구성원이 없습니다. 다시 시도해주세요."), + NOT_FOUND_CHAT_MEMBER(5004, HttpStatus.BAD_REQUEST, "채팅방 구성원이 아닙니다."), + ALREADY_LEFT_CHAT(5005, HttpStatus.BAD_REQUEST, "해당 회원은 이미 채팅방을 나갔습니다."), -// EXIT_CHATROOM_FAIL(5001, HttpStatus.INTERNAL_SERVER_ERROR, "채팅방 나가기에 실패했습니다."), -// MALFORMED_CHATROOM_ID(5002, HttpStatus.BAD_REQUEST, "uri의 채팅방 id가 올바르지 않습니다."), -// DUPLICATED_JOIN(5003, HttpStatus.BAD_REQUEST, "이미 참여하고 있는 회원은 초대할 수 없습니다."), -// EMPTY_CHATROOM(5004, HttpStatus.BAD_REQUEST, "채팅방 참여 인원이 없어 채팅방을 만들 수 없습니다."), -// DUPLICATE_CHATROOM_NAME(5005, HttpStatus.BAD_REQUEST, "이미 존재하는 채팅방 이름입니다. 다른 이름으로 시도해주세요."), -// STOMP_ACCESSOR_NULL(5006, HttpStatus.BAD_REQUEST, "받은 요청의 stomp header accessor가 존재하지 않습니다.(null) 다시 시도해주세요."), -// CHATROOM_BAD_REQUEST(5007, HttpStatus.BAD_REQUEST, "채팅방 생성/수정 요청이 올바르지 않습니다. 다시 시도해 주세요."), - //- 6000번대 : 메시지(message)/소켓 관련 코드 WEBSOCKET_CONNECTION_FAIL(6000, HttpStatus.INTERNAL_SERVER_ERROR, "웹소켓 서버 접속에 실패했습니다."), WEBSOCKET_CLOSE_FAIL(6001, HttpStatus.INTERNAL_SERVER_ERROR, "웹소켓 서버와의 연결 종료에 실패했습니다."), diff --git a/src/main/java/kuchat/server/domain/chat/Chat.java b/src/main/java/kuchat/server/domain/chat/Chat.java index 1869cf8..33e1f61 100644 --- a/src/main/java/kuchat/server/domain/chat/Chat.java +++ b/src/main/java/kuchat/server/domain/chat/Chat.java @@ -11,7 +11,6 @@ import java.util.ArrayList; import java.util.List; -import static kuchat.server.common.response.BaseResponseStatus.NOT_FOUND_CHATMEMBER; import static kuchat.server.domain.enums.ChatState.ACTIVE; import static kuchat.server.domain.enums.ChatState.CLOSED; diff --git a/src/main/java/kuchat/server/domain/chat/ChatMember.java b/src/main/java/kuchat/server/domain/chat/ChatMember.java index 6c7c875..4e31c9f 100644 --- a/src/main/java/kuchat/server/domain/chat/ChatMember.java +++ b/src/main/java/kuchat/server/domain/chat/ChatMember.java @@ -12,14 +12,9 @@ @Entity @Table(name = "chat_member") @NoArgsConstructor(access = AccessLevel.PROTECTED) -//@SequenceGenerator ( -// name = "CHAT_MEMBER_SEQ_GENERATOR" , -// sequenceName ="CHAT_MEMBER_SEQ", //매핑할 데이터베이스 시퀀스 이름 -// initialValue = 1, allocationSize = 100) public class ChatMember extends BaseTime { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) -// @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "CHAT_MEMBER_SEQ_GENERATOR") @Column(name = "chat_member_id") private Long id; diff --git a/src/main/java/kuchat/server/domain/chat/controller/ChatController.java b/src/main/java/kuchat/server/domain/chat/controller/ChatController.java index 9a163b2..a9948d7 100644 --- a/src/main/java/kuchat/server/domain/chat/controller/ChatController.java +++ b/src/main/java/kuchat/server/domain/chat/controller/ChatController.java @@ -3,14 +3,15 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import kuchat.server.common.response.BaseResponse; -import kuchat.server.domain.utils.ValidatorUtil; import kuchat.server.domain.auth.argumentResolver.Auth; import kuchat.server.domain.chat.dto.CreateChatRequest; +import kuchat.server.domain.chat.dto.RecentMessageResponse; import kuchat.server.domain.chat.dto.ViewChatResponse; import kuchat.server.domain.chat.service.ChatService; import kuchat.server.domain.member.Member; -import kuchat.server.domain.chat.dto.RecentMessageResponse; +import kuchat.server.domain.member.service.MemberService; import kuchat.server.domain.message.service.MessageService; +import kuchat.server.domain.utils.ValidatorUtil; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.data.domain.PageRequest; @@ -23,6 +24,8 @@ import java.util.List; +import static kuchat.server.common.response.BaseResponseStatus.SUCCESS; + @Slf4j @Tag(name = "Chat", description = "1:1 채팅방 + 단체 채팅방 모두에 대한 요청을 받음") @RequiredArgsConstructor @@ -32,6 +35,7 @@ public class ChatController { private final ChatService chatService; private final MessageService messageService; + private final MemberService memberService; @Operation(summary = "채팅방 생성 (1:1 채팅, 그룹 채팅 모두 해당)") @PostMapping @@ -50,12 +54,18 @@ public ResponseEntity view(@Auth Member member, @PathVariable("chatId") Long chatId) { log.info("[enter] member id = {} 가 chat id = {} 채팅방 조회", member.getId(), chatId); Pageable pageable = PageRequest.of(page, size, Sort.Direction.DESC, "createdDate"); - ViewChatResponse response = chatService.validateEnter(member, chatId); + ViewChatResponse response = chatService.getChatInfo(member, chatId); List recentMessages = messageService.getRecentMessages(response.getChatId(), pageable); response.setRecentMessages(recentMessages); return ResponseEntity.ok(response); } - + @Operation(summary = "채팅방 나가기") + @PostMapping("/{chatId}") + public ResponseEntity exit(@Auth Member member, @PathVariable("chatId") Long chatId) { + log.info("[exit] member id = {} 가 chat id = {} 채팅방 나가기 요청", member.getId(), chatId); + chatService.exit(member, chatId); + return ResponseEntity.ok(new BaseResponse(SUCCESS)); + } } diff --git a/src/main/java/kuchat/server/domain/chat/repository/ChatMemberRepository.java b/src/main/java/kuchat/server/domain/chat/repository/ChatMemberRepository.java index cb13690..b8e1495 100644 --- a/src/main/java/kuchat/server/domain/chat/repository/ChatMemberRepository.java +++ b/src/main/java/kuchat/server/domain/chat/repository/ChatMemberRepository.java @@ -14,5 +14,7 @@ public interface ChatMemberRepository extends JpaRepository { Optional findByMemberAndChat(Member member, Chat chat); @Query("select cm from ChatMember cm join fetch cm.member where cm.chat = :chat") - List findByChat(@Param("chat") Chat chat); + List findByChatWithMember(@Param("chat") Chat chat); + + } diff --git a/src/main/java/kuchat/server/domain/chat/service/ChatMemberService.java b/src/main/java/kuchat/server/domain/chat/service/ChatMemberService.java index 9544e24..9ed9c2a 100644 --- a/src/main/java/kuchat/server/domain/chat/service/ChatMemberService.java +++ b/src/main/java/kuchat/server/domain/chat/service/ChatMemberService.java @@ -13,7 +13,7 @@ import java.util.ArrayList; import java.util.List; -import static kuchat.server.common.response.BaseResponseStatus.UNAUTHORIZED_CHAT_MEMBER; +import static kuchat.server.common.response.BaseResponseStatus.*; @Slf4j @RequiredArgsConstructor @@ -33,10 +33,19 @@ public void saveChatMembers(List members, Chat chat) { } public List getChatMembersByChat(Chat chat) { - List chatMembers = chatMemberRepository.findByChat(chat); + List chatMembers = chatMemberRepository.findByChatWithMember(chat); if (chatMembers.isEmpty()) { - throw new KuchatException(UNAUTHORIZED_CHAT_MEMBER); + throw new KuchatException(EMPTY_CHAT_MEMBER); } return chatMembers; } + + public void remove(ChatMember chatMember) { + try{ + chatMemberRepository.delete(chatMember); + } catch (IllegalArgumentException e){ + log.info("[remove] 이미 삭제된 ChatMember 입니다. chatMember id = {}", chatMember.getChat()); + throw new KuchatException(ALREADY_LEFT_CHAT); + } + } } diff --git a/src/main/java/kuchat/server/domain/chat/service/ChatService.java b/src/main/java/kuchat/server/domain/chat/service/ChatService.java index d25d5f6..29ad2ac 100644 --- a/src/main/java/kuchat/server/domain/chat/service/ChatService.java +++ b/src/main/java/kuchat/server/domain/chat/service/ChatService.java @@ -3,7 +3,6 @@ import kuchat.server.common.exception.KuchatException; import kuchat.server.domain.chat.Chat; import kuchat.server.domain.chat.ChatMember; -import kuchat.server.domain.chat.dto.ChatMemberResponse; import kuchat.server.domain.chat.dto.CreateChatRequest; import kuchat.server.domain.chat.dto.CreateChatResponse; import kuchat.server.domain.chat.dto.ViewChatResponse; @@ -49,16 +48,17 @@ public CreateChatResponse create(Member creator, CreateChatRequest request) { return new CreateChatResponse(SUCCESS, savedChat.getId()); } - public ViewChatResponse validateEnter(Member member, Long chatId) { - Chat chat = getChatById(chatId); - List chatMembers = chatMemberService.getChatMembersByChat(chat); - checkMemberInChat(member, chatMembers, chat); - return new ViewChatResponse(SUCCESS, chat); + + public ViewChatResponse getChatInfo(Member member, Long chatId) { + ChatMember chatMember = checkMemberInChat(member, chatId); + return new ViewChatResponse(SUCCESS, chatMember.getChat()); } - private void checkMemberInChat(Member member, List chatMembers, Chat chat) { - chatMembers.stream() + private ChatMember checkMemberInChat(Member member, Long chatId) { + Chat chat = getChatById(chatId); + List chatMembers = chatMemberService.getChatMembersByChat(chat); + return chatMembers.stream() .filter(chatMember -> chatMember.matches(chat, member)) .findFirst() .orElseThrow(() -> new KuchatException(UNAUTHORIZED_CHAT_MEMBER)); @@ -70,14 +70,15 @@ private List getParticipants(Member creator, CreateChatRequest request) return members; } - private List getChatMemberResponses(List chatMembers) { - return chatMembers.stream() - .map(ChatMemberResponse::new) - .toList(); - } - public Chat getChatById(Long chatId) { return chatRepository.findById(chatId) .orElseThrow(() -> new KuchatException(NOT_FOUND_CHAT)); } + + @Transactional + public void exit(Member member, Long chatId) { + ChatMember chatMember = checkMemberInChat(member, chatId); + chatMemberService.remove(chatMember); + log.info("[exit] 나가기 처리 완료"); + } } From e07bca9442493479ff63336407460f7e4e4d9186 Mon Sep 17 00:00:00 2001 From: lyouxsun Date: Thu, 13 Mar 2025 14:25:11 +0900 Subject: [PATCH 20/21] =?UTF-8?q?feat:=20=EC=B1=84=ED=8C=85=EB=B0=A9=20?= =?UTF-8?q?=EB=AA=A9=EB=A1=9D=20=EC=A1=B0=ED=9A=8C=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/kuchat/server/domain/BaseTime.java | 2 +- .../chat/controller/ChatController.java | 16 ++++++- .../server/domain/chat/dto/ChatResponses.java | 47 +++++++++++++++++++ .../chat/repository/ChatMemberRepository.java | 12 +++-- .../chat/repository/ChatRepository.java | 2 + .../chat/service/ChatMemberService.java | 10 ++++ .../domain/chat/service/ChatService.java | 12 +++++ .../friend/controller/FriendController.java | 5 +- .../server/domain/utils/ValidatorUtil.java | 7 +++ 9 files changed, 106 insertions(+), 7 deletions(-) create mode 100644 src/main/java/kuchat/server/domain/chat/dto/ChatResponses.java diff --git a/src/main/java/kuchat/server/domain/BaseTime.java b/src/main/java/kuchat/server/domain/BaseTime.java index c9ef391..b034d09 100644 --- a/src/main/java/kuchat/server/domain/BaseTime.java +++ b/src/main/java/kuchat/server/domain/BaseTime.java @@ -19,6 +19,6 @@ public abstract class BaseTime { @LastModifiedDate @Column(name = "modified_at", updatable = false) - private LocalDateTime modifedDate; + private LocalDateTime modifiedDate; } diff --git a/src/main/java/kuchat/server/domain/chat/controller/ChatController.java b/src/main/java/kuchat/server/domain/chat/controller/ChatController.java index a9948d7..726da04 100644 --- a/src/main/java/kuchat/server/domain/chat/controller/ChatController.java +++ b/src/main/java/kuchat/server/domain/chat/controller/ChatController.java @@ -4,6 +4,7 @@ import io.swagger.v3.oas.annotations.tags.Tag; import kuchat.server.common.response.BaseResponse; import kuchat.server.domain.auth.argumentResolver.Auth; +import kuchat.server.domain.chat.dto.ChatResponses; import kuchat.server.domain.chat.dto.CreateChatRequest; import kuchat.server.domain.chat.dto.RecentMessageResponse; import kuchat.server.domain.chat.dto.ViewChatResponse; @@ -53,7 +54,7 @@ public ResponseEntity view(@Auth Member member, @RequestParam(defaultValue = "30") int size, @PathVariable("chatId") Long chatId) { log.info("[enter] member id = {} 가 chat id = {} 채팅방 조회", member.getId(), chatId); - Pageable pageable = PageRequest.of(page, size, Sort.Direction.DESC, "createdDate"); + Pageable pageable = PageRequest.of(page, ValidatorUtil.sizeValidator(size), Sort.Direction.DESC, "createdDate"); ViewChatResponse response = chatService.getChatInfo(member, chatId); List recentMessages = messageService.getRecentMessages(response.getChatId(), pageable); response.setRecentMessages(recentMessages); @@ -68,4 +69,17 @@ public ResponseEntity exit(@Auth Member member, @PathVariable("cha return ResponseEntity.ok(new BaseResponse(SUCCESS)); } + @Operation(summary = "채팅방 목록 조회 + 검색 기능") + @GetMapping + public ResponseEntity getChatList(@Auth Member member, + @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "30") int size, + @RequestParam(defaultValue = "") String name) { + log.info("[getChatList] 채팅방 목록 조회 , 검색 키워드 = {}", name); + Pageable pageable = PageRequest.of( + page, ValidatorUtil.sizeValidator(size), Sort.Direction.DESC, "createdDate"); + ChatResponses response = chatService.getChatList(member, name, pageable); + return ResponseEntity.ok(response); + } + } diff --git a/src/main/java/kuchat/server/domain/chat/dto/ChatResponses.java b/src/main/java/kuchat/server/domain/chat/dto/ChatResponses.java new file mode 100644 index 0000000..316c283 --- /dev/null +++ b/src/main/java/kuchat/server/domain/chat/dto/ChatResponses.java @@ -0,0 +1,47 @@ +package kuchat.server.domain.chat.dto; + +import kuchat.server.domain.chat.Chat; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; +import lombok.extern.slf4j.Slf4j; + +import java.time.LocalDateTime; +import java.util.List; + +@Getter +@Setter +@ToString +@Slf4j +@NoArgsConstructor +public class ChatResponses { + + private List chatResponses; + + public ChatResponses(List chats) { + chatResponses = chats.stream() + .map(ChatResponse::new) + .toList(); + } + + @Getter + @Setter + @ToString + @Slf4j + @NoArgsConstructor + public static class ChatResponse { + private String name; + private String profileImage; +// private String homeTown; +// private String recentMessage; + private LocalDateTime lastSendTime; +// private int unreadMessageCount; + + public ChatResponse(Chat chat){ + this.name = chat.getName(); + this.profileImage = chat.getImage(); + this.lastSendTime = chat.getModifiedDate(); + } + } +} diff --git a/src/main/java/kuchat/server/domain/chat/repository/ChatMemberRepository.java b/src/main/java/kuchat/server/domain/chat/repository/ChatMemberRepository.java index b8e1495..ad8c9c9 100644 --- a/src/main/java/kuchat/server/domain/chat/repository/ChatMemberRepository.java +++ b/src/main/java/kuchat/server/domain/chat/repository/ChatMemberRepository.java @@ -3,18 +3,24 @@ import kuchat.server.domain.chat.Chat; import kuchat.server.domain.chat.ChatMember; import kuchat.server.domain.member.Member; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import java.util.List; -import java.util.Optional; public interface ChatMemberRepository extends JpaRepository { - Optional findByMemberAndChat(Member member, Chat chat); @Query("select cm from ChatMember cm join fetch cm.member where cm.chat = :chat") List findByChatWithMember(@Param("chat") Chat chat); - + @Query("select cm " + + "from ChatMember cm join fetch cm.chat " + + "where cm.member = :member and cm.chat.name like :chatName " + + "order by cm.chat.modifiedDate DESC") + Page findByMemberAndChatName(@Param("member") Member member, + @Param("chatName") String chatName, + Pageable pageable); } diff --git a/src/main/java/kuchat/server/domain/chat/repository/ChatRepository.java b/src/main/java/kuchat/server/domain/chat/repository/ChatRepository.java index 6c7c572..9561dd9 100644 --- a/src/main/java/kuchat/server/domain/chat/repository/ChatRepository.java +++ b/src/main/java/kuchat/server/domain/chat/repository/ChatRepository.java @@ -2,6 +2,8 @@ import kuchat.server.domain.chat.Chat; import kuchat.server.domain.member.Member; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; diff --git a/src/main/java/kuchat/server/domain/chat/service/ChatMemberService.java b/src/main/java/kuchat/server/domain/chat/service/ChatMemberService.java index 9ed9c2a..7f320b8 100644 --- a/src/main/java/kuchat/server/domain/chat/service/ChatMemberService.java +++ b/src/main/java/kuchat/server/domain/chat/service/ChatMemberService.java @@ -7,10 +7,14 @@ import kuchat.server.domain.member.Member; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.ArrayList; +import java.util.Comparator; import java.util.List; import static kuchat.server.common.response.BaseResponseStatus.*; @@ -48,4 +52,10 @@ public void remove(ChatMember chatMember) { throw new KuchatException(ALREADY_LEFT_CHAT); } } + + public List getByMemberAndChatName(Member member, String chatName, Pageable pageable) { + return chatMemberRepository.findByMemberAndChatName(member, "%"+chatName+"%", pageable).stream() + .map(ChatMember::getChat) + .toList(); + } } diff --git a/src/main/java/kuchat/server/domain/chat/service/ChatService.java b/src/main/java/kuchat/server/domain/chat/service/ChatService.java index 29ad2ac..3602df8 100644 --- a/src/main/java/kuchat/server/domain/chat/service/ChatService.java +++ b/src/main/java/kuchat/server/domain/chat/service/ChatService.java @@ -3,15 +3,21 @@ import kuchat.server.common.exception.KuchatException; import kuchat.server.domain.chat.Chat; import kuchat.server.domain.chat.ChatMember; +import kuchat.server.domain.chat.dto.ChatResponses; import kuchat.server.domain.chat.dto.CreateChatRequest; import kuchat.server.domain.chat.dto.CreateChatResponse; import kuchat.server.domain.chat.dto.ViewChatResponse; import kuchat.server.domain.chat.repository.ChatRepository; +import kuchat.server.domain.friend.Friend; +import kuchat.server.domain.friend.dto.FriendApplyResponses; import kuchat.server.domain.member.Member; import kuchat.server.domain.member.service.MemberService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -81,4 +87,10 @@ public void exit(Member member, Long chatId) { chatMemberService.remove(chatMember); log.info("[exit] 나가기 처리 완료"); } + + public ChatResponses getChatList(Member member, String chatName, Pageable pageable) { + log.info("[getChatList] 자신이 참여하고 있는 채팅방 목록 조회"); + List chats = chatMemberService.getByMemberAndChatName(member, chatName, pageable); + return new ChatResponses(chats); + } } diff --git a/src/main/java/kuchat/server/domain/friend/controller/FriendController.java b/src/main/java/kuchat/server/domain/friend/controller/FriendController.java index c8fdaeb..5a89741 100644 --- a/src/main/java/kuchat/server/domain/friend/controller/FriendController.java +++ b/src/main/java/kuchat/server/domain/friend/controller/FriendController.java @@ -7,6 +7,7 @@ import kuchat.server.domain.friend.dto.FriendResponses; import kuchat.server.domain.friend.service.FriendService; import kuchat.server.domain.member.Member; +import kuchat.server.domain.utils.ValidatorUtil; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.data.domain.PageRequest; @@ -45,7 +46,7 @@ public ResponseEntity getFriendApplyList(@Auth Member memb @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "20") int size) { log.info("[getFriendApplyList] id={} , name={} 가 받은 친구 신청 목록 조회", member.getId(), member.getName()); - Pageable pageable = PageRequest.of(page, size, Sort.Direction.DESC, "createdDate"); + Pageable pageable = PageRequest.of(page, ValidatorUtil.sizeValidator(size), Sort.Direction.DESC, "createdDate"); return friendService.getFriendApplyList(member, pageable); } @@ -72,7 +73,7 @@ public ResponseEntity getFriendList(@Auth Member member, @RequestParam(defaultValue = "20") int size, @RequestParam(value = "name", required = false) String friendName) { log.info("[getFriendList] 친구 목록 검색 및 조회. 검색 문자열 = '{}'", friendName); - Pageable pageable = PageRequest.of(page, size, Sort.Direction.ASC, "receiver.profile.name"); + Pageable pageable = PageRequest.of(page, ValidatorUtil.sizeValidator(size), Sort.Direction.ASC, "receiver.profile.name"); return friendService.getFriendList(member, friendName, pageable); } } diff --git a/src/main/java/kuchat/server/domain/utils/ValidatorUtil.java b/src/main/java/kuchat/server/domain/utils/ValidatorUtil.java index 08fb94c..ad12473 100644 --- a/src/main/java/kuchat/server/domain/utils/ValidatorUtil.java +++ b/src/main/java/kuchat/server/domain/utils/ValidatorUtil.java @@ -20,4 +20,11 @@ public static void validateRequest(BindingResult bindingResult) { throw new KuchatException(errorResponse); } } + + public static int sizeValidator(int size){ + if (size > 100){ + return 50; + } + return size; + } } From f0d5599db4b3d093d14bf2d0d5cbb21940caf940 Mon Sep 17 00:00:00 2001 From: lyouxsun Date: Thu, 13 Mar 2025 14:35:00 +0900 Subject: [PATCH 21/21] =?UTF-8?q?fix:=20=EC=B1=84=ED=8C=85=EB=B0=A9=20?= =?UTF-8?q?=EB=AA=A9=EB=A1=9D=20=EC=A0=95=EB=A0=AC=20=EA=B8=B0=EC=A4=80=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - createdDate에서 modifiedDate로 변경 --- .../server/domain/chat/controller/ChatController.java | 7 +++++-- .../java/kuchat/server/domain/chat/dto/ChatResponses.java | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/java/kuchat/server/domain/chat/controller/ChatController.java b/src/main/java/kuchat/server/domain/chat/controller/ChatController.java index 726da04..7a5f6f3 100644 --- a/src/main/java/kuchat/server/domain/chat/controller/ChatController.java +++ b/src/main/java/kuchat/server/domain/chat/controller/ChatController.java @@ -4,6 +4,7 @@ import io.swagger.v3.oas.annotations.tags.Tag; import kuchat.server.common.response.BaseResponse; import kuchat.server.domain.auth.argumentResolver.Auth; +import kuchat.server.domain.chat.Chat; import kuchat.server.domain.chat.dto.ChatResponses; import kuchat.server.domain.chat.dto.CreateChatRequest; import kuchat.server.domain.chat.dto.RecentMessageResponse; @@ -18,11 +19,13 @@ import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; +import org.springframework.data.web.PageableDefault; import org.springframework.http.ResponseEntity; import org.springframework.validation.BindingResult; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; +import java.net.URLDecoder; import java.util.List; import static kuchat.server.common.response.BaseResponseStatus.SUCCESS; @@ -74,10 +77,10 @@ public ResponseEntity exit(@Auth Member member, @PathVariable("cha public ResponseEntity getChatList(@Auth Member member, @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "30") int size, - @RequestParam(defaultValue = "") String name) { + @RequestParam(defaultValue = "") String name){ log.info("[getChatList] 채팅방 목록 조회 , 검색 키워드 = {}", name); Pageable pageable = PageRequest.of( - page, ValidatorUtil.sizeValidator(size), Sort.Direction.DESC, "createdDate"); + page, ValidatorUtil.sizeValidator(size), Sort.Direction.DESC, "modifiedDate"); ChatResponses response = chatService.getChatList(member, name, pageable); return ResponseEntity.ok(response); } diff --git a/src/main/java/kuchat/server/domain/chat/dto/ChatResponses.java b/src/main/java/kuchat/server/domain/chat/dto/ChatResponses.java index 316c283..6476f71 100644 --- a/src/main/java/kuchat/server/domain/chat/dto/ChatResponses.java +++ b/src/main/java/kuchat/server/domain/chat/dto/ChatResponses.java @@ -33,9 +33,9 @@ public ChatResponses(List chats) { public static class ChatResponse { private String name; private String profileImage; + private LocalDateTime lastSendTime; // private String homeTown; // private String recentMessage; - private LocalDateTime lastSendTime; // private int unreadMessageCount; public ChatResponse(Chat chat){