From 85ec15eadbf89f222cdbf53decd244028b776a1f Mon Sep 17 00:00:00 2001 From: eckrin Date: Thu, 26 Oct 2023 23:16:52 +0900 Subject: [PATCH 1/4] =?UTF-8?q?refactor:=20websocket=EC=9D=84=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=ED=95=9C=20=EC=B1=84=ED=8C=85=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 --- .DS_Store | Bin 6148 -> 0 bytes .gitignore | 3 + build.gradle | 4 ++ src/main/java/com/kusitms/jipbap/.DS_Store | Bin 6148 -> 0 bytes .../com/kusitms/jipbap/JipbapApplication.java | 4 ++ .../jipbap/config/SpringSecurityConfig.java | 3 +- .../jipbap/message/ChatController.java | 24 ++++++++ .../kusitms/jipbap/message/ChatMessage.java | 17 ++++++ .../com/kusitms/jipbap/message/ChatRoom.java | 33 +++++++++++ .../kusitms/jipbap/message/ChatService.java | 52 ++++++++++++++++++ .../jipbap/message/WebSockChatHandler.java | 29 ++++++++++ .../kusitms/jipbap/message/WebSockConfig.java | 20 +++++++ 12 files changed, 188 insertions(+), 1 deletion(-) delete mode 100644 .DS_Store delete mode 100644 src/main/java/com/kusitms/jipbap/.DS_Store create mode 100644 src/main/java/com/kusitms/jipbap/message/ChatController.java create mode 100644 src/main/java/com/kusitms/jipbap/message/ChatMessage.java create mode 100644 src/main/java/com/kusitms/jipbap/message/ChatRoom.java create mode 100644 src/main/java/com/kusitms/jipbap/message/ChatService.java create mode 100644 src/main/java/com/kusitms/jipbap/message/WebSockChatHandler.java create mode 100644 src/main/java/com/kusitms/jipbap/message/WebSockConfig.java diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 2cfd07b02f3ba4ebe93f2fbcb57ba71de12a3e16..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHK!AiqG5Z!HS(@?}56!f;>wO~@I9=wED4_=MvL8T_PXf$R^)7nER`an<8+J2{F&>P%9%Bw;tOkZ$vZ46~IF7b}lJS&*xw=Qh`p2of ziBlObM3dt`Du8QOW%n#$eHOF%^}~;14ej0FYivBu`(~|Q<9?dtL)U%drE+DZYFV~b zx30oFISz->aFhqp-~z8srA*^lKZ?(i-niqeAIfYPC0TEv5|VBYLN3pftSiTX9A({H z<%W8|wrh6B*_uq6yRC-s+~%|)CZ5~cYlwZ%n@;W8#`ey^N#`Mb%;Yn?G=Regs<3K! zMlaBr6W}QvWU0(`Oq{-0aD=i6u|vKfFb*a3(acl zfJT)7z!h*SKp%e_F-I8o4HueW1i*DFuukQs#Nav|{KCZfh6~L)opDoqaPwqtDip4s z4)+%-oN-?>wWI(kFs}gCegFs0|D&Jle-%UxDL@J=rvkjv4%-3D&77?Zv&6GjfL((j pFs{)2nF1Yk6hkZ?#hYLX;1}2d_6--B!2_Tl0VM-9q`;pl@Bs*-S;YVV diff --git a/.gitignore b/.gitignore index c2065bc..72bc1b2 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,6 @@ out/ ### VS Code ### .vscode/ + +### JREBEL ### +rebel.xml diff --git a/build.gradle b/build.gradle index ebd5d98..c493570 100644 --- a/build.gradle +++ b/build.gradle @@ -50,6 +50,10 @@ dependencies { compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' + + +// https://mvnrepository.com/artifact/it.ozimov/embedded-redis + compileOnly 'it.ozimov:embedded-redis:0.7.2' } tasks.named('test') { diff --git a/src/main/java/com/kusitms/jipbap/.DS_Store b/src/main/java/com/kusitms/jipbap/.DS_Store deleted file mode 100644 index 5008ddfcf53c02e82d7eee2e57c38e5672ef89f6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0 findAllRoom() { + return chatService.findAllRoom(); + } +} \ No newline at end of file diff --git a/src/main/java/com/kusitms/jipbap/message/ChatMessage.java b/src/main/java/com/kusitms/jipbap/message/ChatMessage.java new file mode 100644 index 0000000..d5c5ec9 --- /dev/null +++ b/src/main/java/com/kusitms/jipbap/message/ChatMessage.java @@ -0,0 +1,17 @@ +package com.kusitms.jipbap.message; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class ChatMessage { + // 메시지 타입 : 입장, 채팅 + public enum MessageType { + ENTER, TALK + } + private MessageType type; // 메시지 타입 + private String roomId; // 방번호 + private String sender; // 메시지 보낸사람 + private String message; // 메시지 +} \ No newline at end of file diff --git a/src/main/java/com/kusitms/jipbap/message/ChatRoom.java b/src/main/java/com/kusitms/jipbap/message/ChatRoom.java new file mode 100644 index 0000000..12ed99f --- /dev/null +++ b/src/main/java/com/kusitms/jipbap/message/ChatRoom.java @@ -0,0 +1,33 @@ +package com.kusitms.jipbap.message; + +import lombok.Builder; +import lombok.Getter; +import org.springframework.web.socket.WebSocketSession; + +import java.util.HashSet; +import java.util.Set; + +@Getter +public class ChatRoom { + private String roomId; + private String name; + private Set sessions = new HashSet<>(); + + @Builder + public ChatRoom(String roomId, String name) { + this.roomId = roomId; + this.name = name; + } + + public void handleActions(WebSocketSession session, ChatMessage chatMessage, ChatService chatService) { + if (chatMessage.getType().equals(ChatMessage.MessageType.ENTER)) { + sessions.add(session); + chatMessage.setMessage(chatMessage.getSender() + "님이 입장했습니다."); + } + sendMessage(chatMessage, chatService); + } + + public void sendMessage(T message, ChatService chatService) { + sessions.parallelStream().forEach(session -> chatService.sendMessage(session, message)); + } +} \ No newline at end of file diff --git a/src/main/java/com/kusitms/jipbap/message/ChatService.java b/src/main/java/com/kusitms/jipbap/message/ChatService.java new file mode 100644 index 0000000..3fc1ebb --- /dev/null +++ b/src/main/java/com/kusitms/jipbap/message/ChatService.java @@ -0,0 +1,52 @@ +package com.kusitms.jipbap.message; + +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.annotation.PostConstruct; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.web.socket.TextMessage; +import org.springframework.web.socket.WebSocketSession; + +import java.io.IOException; +import java.util.*; + +@Slf4j +@RequiredArgsConstructor +@Service +public class ChatService { + + private final ObjectMapper objectMapper; + private Map chatRooms; + + @PostConstruct + private void init() { + chatRooms = new LinkedHashMap<>(); + } + + public List findAllRoom() { + return new ArrayList<>(chatRooms.values()); + } + + public ChatRoom findRoomById(String roomId) { + return chatRooms.get(roomId); + } + + public ChatRoom createRoom(String name) { + String randomId = UUID.randomUUID().toString(); + ChatRoom chatRoom = ChatRoom.builder() + .roomId(randomId) + .name(name) + .build(); + chatRooms.put(randomId, chatRoom); + return chatRoom; + } + + public void sendMessage(WebSocketSession session, T message) { + try { + session.sendMessage(new TextMessage(objectMapper.writeValueAsString(message))); + } catch (IOException e) { + log.error(e.getMessage(), e); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/kusitms/jipbap/message/WebSockChatHandler.java b/src/main/java/com/kusitms/jipbap/message/WebSockChatHandler.java new file mode 100644 index 0000000..d9db7e5 --- /dev/null +++ b/src/main/java/com/kusitms/jipbap/message/WebSockChatHandler.java @@ -0,0 +1,29 @@ +package com.kusitms.jipbap.message; + + +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import org.springframework.web.socket.TextMessage; +import org.springframework.web.socket.WebSocketSession; +import org.springframework.web.socket.handler.TextWebSocketHandler; + +@Slf4j +@RequiredArgsConstructor +@Component +public class WebSockChatHandler extends TextWebSocketHandler { + private final ObjectMapper objectMapper; + private final ChatService chatService; + + @Override + protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { + String payload = message.getPayload(); + log.info("payload {}", payload); +// 삭제 TextMessage textMessage = new TextMessage("Welcome chatting sever~^^ "); +// 삭제 session.sendMessage(textMessage); + ChatMessage chatMessage = objectMapper.readValue(payload, ChatMessage.class); + ChatRoom room = chatService.findRoomById(chatMessage.getRoomId()); + room.handleActions(session, chatMessage, chatService); + } +} \ No newline at end of file diff --git a/src/main/java/com/kusitms/jipbap/message/WebSockConfig.java b/src/main/java/com/kusitms/jipbap/message/WebSockConfig.java new file mode 100644 index 0000000..5c16ea4 --- /dev/null +++ b/src/main/java/com/kusitms/jipbap/message/WebSockConfig.java @@ -0,0 +1,20 @@ +package com.kusitms.jipbap.message; + +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.socket.WebSocketHandler; +import org.springframework.web.socket.config.annotation.EnableWebSocket; +import org.springframework.web.socket.config.annotation.WebSocketConfigurer; +import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry; + +@RequiredArgsConstructor +@Configuration +@EnableWebSocket +public class WebSockConfig implements WebSocketConfigurer { + private final WebSocketHandler webSocketHandler; + + @Override + public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { + registry.addHandler(webSocketHandler, "/ws/chat").setAllowedOrigins("*"); + } +} \ No newline at end of file From 619796b0dc1e76bcb9e68b66dd7295a81c0273df Mon Sep 17 00:00:00 2001 From: eckrin Date: Fri, 27 Oct 2023 13:24:42 +0900 Subject: [PATCH 2/4] =?UTF-8?q?refactor:=20stomp=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=ED=95=98=EC=97=AC=20=EA=B8=B0=EB=8A=A5=20=ED=99=95=EC=9E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Related to: Issue #6 --- build.gradle | 2 + .../jipbap/config/SpringSecurityConfig.java | 3 +- .../jipbap/message/ChatController.java | 31 +++++++---- .../kusitms/jipbap/message/ChatMessage.java | 2 +- .../com/kusitms/jipbap/message/ChatRoom.java | 25 +++------ .../jipbap/message/ChatRoomController.java | 34 ++++++++++++ .../jipbap/message/ChatRoomRepository.java | 34 ++++++++++++ .../kusitms/jipbap/message/ChatService.java | 52 ------------------ .../jipbap/message/WebSockChatHandler.java | 29 ---------- .../kusitms/jipbap/message/WebSockConfig.java | 28 ++++++---- .../message/deprecated/ChatService.java | 54 +++++++++++++++++++ .../deprecated/WebSockChatHandler.java | 31 +++++++++++ 12 files changed, 205 insertions(+), 120 deletions(-) create mode 100644 src/main/java/com/kusitms/jipbap/message/ChatRoomController.java create mode 100644 src/main/java/com/kusitms/jipbap/message/ChatRoomRepository.java delete mode 100644 src/main/java/com/kusitms/jipbap/message/ChatService.java delete mode 100644 src/main/java/com/kusitms/jipbap/message/WebSockChatHandler.java create mode 100644 src/main/java/com/kusitms/jipbap/message/deprecated/ChatService.java create mode 100644 src/main/java/com/kusitms/jipbap/message/deprecated/WebSockChatHandler.java diff --git a/build.gradle b/build.gradle index c493570..4ec9ca3 100644 --- a/build.gradle +++ b/build.gradle @@ -40,6 +40,8 @@ dependencies { implementation 'javax.validation:validation-api:2.0.1.Final' //websocket implementation 'org.springframework.boot:spring-boot-starter-websocket' + implementation 'org.webjars:sockjs-client:1.1.2' + implementation 'org.webjars:stomp-websocket:2.3.3-1' //swagger (spring 3.x버전이라 springfox 적용불가, springdoc 중에서 호환되는 종속성 사용) implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.2' //mysql (spring 3.x) diff --git a/src/main/java/com/kusitms/jipbap/config/SpringSecurityConfig.java b/src/main/java/com/kusitms/jipbap/config/SpringSecurityConfig.java index 7011cec..41091e9 100644 --- a/src/main/java/com/kusitms/jipbap/config/SpringSecurityConfig.java +++ b/src/main/java/com/kusitms/jipbap/config/SpringSecurityConfig.java @@ -65,7 +65,8 @@ public WebSecurityCustomizer webSecurityCustomizer() { "/error", "/auth/**", "/ws/**", //ws://localhost:8080/ws/chat - "/chat/**" + "/chat/**", + "/ws-stomp/**" ); } } \ No newline at end of file diff --git a/src/main/java/com/kusitms/jipbap/message/ChatController.java b/src/main/java/com/kusitms/jipbap/message/ChatController.java index 4318dd6..aa2aefa 100644 --- a/src/main/java/com/kusitms/jipbap/message/ChatController.java +++ b/src/main/java/com/kusitms/jipbap/message/ChatController.java @@ -1,24 +1,37 @@ package com.kusitms.jipbap.message; import lombok.RequiredArgsConstructor; +import org.springframework.messaging.handler.annotation.MessageMapping; +import org.springframework.messaging.simp.SimpMessageSendingOperations; import org.springframework.web.bind.annotation.*; import java.util.List; @RequiredArgsConstructor @RestController -@RequestMapping("/chat") +//Publisher public class ChatController { - private final ChatService chatService; +// private final ChatService chatService; + private final SimpMessageSendingOperations messagingTemplate; + +// @PostMapping +// public ChatRoom createRoom(@RequestParam String name) { +// return chatService.createRoom(name); +// } +// +// @GetMapping +// public List findAllRoom() { +// return chatService.findAllRoom(); +// } +// package com.websocket.chat.controller; - @PostMapping - public ChatRoom createRoom(@RequestParam String name) { - return chatService.createRoom(name); - } - @GetMapping - public List findAllRoom() { - return chatService.findAllRoom(); + @MessageMapping("/chat/message") + public void message(ChatMessage message) { + if (ChatMessage.MessageType.JOIN.equals(message.getType())) + message.setMessage(message.getSender() + "님이 입장하셨습니다."); + messagingTemplate.convertAndSend("/sub/chat/room/" + message.getRoomId(), message); // sub/chat/room/{roomId} 경로로 메세지를 보낸다. } + } \ No newline at end of file diff --git a/src/main/java/com/kusitms/jipbap/message/ChatMessage.java b/src/main/java/com/kusitms/jipbap/message/ChatMessage.java index d5c5ec9..b684b65 100644 --- a/src/main/java/com/kusitms/jipbap/message/ChatMessage.java +++ b/src/main/java/com/kusitms/jipbap/message/ChatMessage.java @@ -8,7 +8,7 @@ public class ChatMessage { // 메시지 타입 : 입장, 채팅 public enum MessageType { - ENTER, TALK + JOIN, TALK } private MessageType type; // 메시지 타입 private String roomId; // 방번호 diff --git a/src/main/java/com/kusitms/jipbap/message/ChatRoom.java b/src/main/java/com/kusitms/jipbap/message/ChatRoom.java index 12ed99f..7664832 100644 --- a/src/main/java/com/kusitms/jipbap/message/ChatRoom.java +++ b/src/main/java/com/kusitms/jipbap/message/ChatRoom.java @@ -2,32 +2,23 @@ import lombok.Builder; import lombok.Getter; +import lombok.Setter; import org.springframework.web.socket.WebSocketSession; import java.util.HashSet; import java.util.Set; +import java.util.UUID; @Getter +@Setter public class ChatRoom { private String roomId; private String name; - private Set sessions = new HashSet<>(); - @Builder - public ChatRoom(String roomId, String name) { - this.roomId = roomId; - this.name = name; - } - - public void handleActions(WebSocketSession session, ChatMessage chatMessage, ChatService chatService) { - if (chatMessage.getType().equals(ChatMessage.MessageType.ENTER)) { - sessions.add(session); - chatMessage.setMessage(chatMessage.getSender() + "님이 입장했습니다."); - } - sendMessage(chatMessage, chatService); - } - - public void sendMessage(T message, ChatService chatService) { - sessions.parallelStream().forEach(session -> chatService.sendMessage(session, message)); + public static ChatRoom create(String name) { + ChatRoom chatRoom = new ChatRoom(); + chatRoom.roomId = UUID.randomUUID().toString(); + chatRoom.name = name; + return chatRoom; } } \ No newline at end of file diff --git a/src/main/java/com/kusitms/jipbap/message/ChatRoomController.java b/src/main/java/com/kusitms/jipbap/message/ChatRoomController.java new file mode 100644 index 0000000..064e7e0 --- /dev/null +++ b/src/main/java/com/kusitms/jipbap/message/ChatRoomController.java @@ -0,0 +1,34 @@ +package com.kusitms.jipbap.message; + + +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RequiredArgsConstructor +@RestController +@RequestMapping("/chat") +//Subscriber +public class ChatRoomController { + + private final ChatRoomRepository chatRoomRepository; + + // 모든 채팅방 목록 반환 + @GetMapping("/rooms") + public List room() { + return chatRoomRepository.findAllRoom(); + } + + // 채팅방 생성 + @PostMapping("/room") + public ChatRoom createRoom(@RequestParam String name) { + return chatRoomRepository.createChatRoom(name); + } + + // 특정 채팅방 조회 + @GetMapping("/room/{roomId}") + public ChatRoom roomInfo(@PathVariable String roomId) { + return chatRoomRepository.findRoomById(roomId); + } +} \ No newline at end of file diff --git a/src/main/java/com/kusitms/jipbap/message/ChatRoomRepository.java b/src/main/java/com/kusitms/jipbap/message/ChatRoomRepository.java new file mode 100644 index 0000000..572ef0d --- /dev/null +++ b/src/main/java/com/kusitms/jipbap/message/ChatRoomRepository.java @@ -0,0 +1,34 @@ +package com.kusitms.jipbap.message; + +import jakarta.annotation.PostConstruct; +import org.springframework.stereotype.Repository; + +import java.util.*; + +@Repository +public class ChatRoomRepository { + + private Map chatRoomMap; + + @PostConstruct + private void init() { + chatRoomMap = new LinkedHashMap<>(); + } + + public List findAllRoom() { + // 채팅방 생성순서 최근 순으로 반환 + List chatRooms = new ArrayList<>(chatRoomMap.values()); + Collections.reverse(chatRooms); + return chatRooms; + } + + public ChatRoom findRoomById(String id) { + return chatRoomMap.get(id); + } + + public ChatRoom createChatRoom(String name) { + ChatRoom chatRoom = ChatRoom.create(name); + chatRoomMap.put(chatRoom.getRoomId(), chatRoom); + return chatRoom; + } +} \ No newline at end of file diff --git a/src/main/java/com/kusitms/jipbap/message/ChatService.java b/src/main/java/com/kusitms/jipbap/message/ChatService.java deleted file mode 100644 index 3fc1ebb..0000000 --- a/src/main/java/com/kusitms/jipbap/message/ChatService.java +++ /dev/null @@ -1,52 +0,0 @@ -package com.kusitms.jipbap.message; - -import com.fasterxml.jackson.databind.ObjectMapper; -import jakarta.annotation.PostConstruct; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; -import org.springframework.web.socket.TextMessage; -import org.springframework.web.socket.WebSocketSession; - -import java.io.IOException; -import java.util.*; - -@Slf4j -@RequiredArgsConstructor -@Service -public class ChatService { - - private final ObjectMapper objectMapper; - private Map chatRooms; - - @PostConstruct - private void init() { - chatRooms = new LinkedHashMap<>(); - } - - public List findAllRoom() { - return new ArrayList<>(chatRooms.values()); - } - - public ChatRoom findRoomById(String roomId) { - return chatRooms.get(roomId); - } - - public ChatRoom createRoom(String name) { - String randomId = UUID.randomUUID().toString(); - ChatRoom chatRoom = ChatRoom.builder() - .roomId(randomId) - .name(name) - .build(); - chatRooms.put(randomId, chatRoom); - return chatRoom; - } - - public void sendMessage(WebSocketSession session, T message) { - try { - session.sendMessage(new TextMessage(objectMapper.writeValueAsString(message))); - } catch (IOException e) { - log.error(e.getMessage(), e); - } - } -} \ No newline at end of file diff --git a/src/main/java/com/kusitms/jipbap/message/WebSockChatHandler.java b/src/main/java/com/kusitms/jipbap/message/WebSockChatHandler.java deleted file mode 100644 index d9db7e5..0000000 --- a/src/main/java/com/kusitms/jipbap/message/WebSockChatHandler.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.kusitms.jipbap.message; - - -import com.fasterxml.jackson.databind.ObjectMapper; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; -import org.springframework.web.socket.TextMessage; -import org.springframework.web.socket.WebSocketSession; -import org.springframework.web.socket.handler.TextWebSocketHandler; - -@Slf4j -@RequiredArgsConstructor -@Component -public class WebSockChatHandler extends TextWebSocketHandler { - private final ObjectMapper objectMapper; - private final ChatService chatService; - - @Override - protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { - String payload = message.getPayload(); - log.info("payload {}", payload); -// 삭제 TextMessage textMessage = new TextMessage("Welcome chatting sever~^^ "); -// 삭제 session.sendMessage(textMessage); - ChatMessage chatMessage = objectMapper.readValue(payload, ChatMessage.class); - ChatRoom room = chatService.findRoomById(chatMessage.getRoomId()); - room.handleActions(session, chatMessage, chatService); - } -} \ No newline at end of file diff --git a/src/main/java/com/kusitms/jipbap/message/WebSockConfig.java b/src/main/java/com/kusitms/jipbap/message/WebSockConfig.java index 5c16ea4..ca66c69 100644 --- a/src/main/java/com/kusitms/jipbap/message/WebSockConfig.java +++ b/src/main/java/com/kusitms/jipbap/message/WebSockConfig.java @@ -1,20 +1,26 @@ package com.kusitms.jipbap.message; -import lombok.RequiredArgsConstructor; + import org.springframework.context.annotation.Configuration; -import org.springframework.web.socket.WebSocketHandler; -import org.springframework.web.socket.config.annotation.EnableWebSocket; -import org.springframework.web.socket.config.annotation.WebSocketConfigurer; -import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry; +import org.springframework.messaging.simp.config.MessageBrokerRegistry; +import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; +import org.springframework.web.socket.config.annotation.StompEndpointRegistry; +import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer; -@RequiredArgsConstructor @Configuration -@EnableWebSocket -public class WebSockConfig implements WebSocketConfigurer { - private final WebSocketHandler webSocketHandler; +@EnableWebSocketMessageBroker +public class WebSockConfig implements WebSocketMessageBrokerConfigurer { + + @Override + public void configureMessageBroker(MessageBrokerRegistry config) { + config.enableSimpleBroker("/sub"); //message broker가 subscriber들에게 메세지를 전달할 url (구독 요청) + config.setApplicationDestinationPrefixes("/pub"); //클라이언트가 서버로 메세지를 보낼 url 접두사 지정 (발행 요청) + } @Override - public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { - registry.addHandler(webSocketHandler, "/ws/chat").setAllowedOrigins("*"); + public void registerStompEndpoints(StompEndpointRegistry registry) { + registry.addEndpoint("/ws-stomp") //websocket 연결 endpoint + .setAllowedOriginPatterns("*"); +// .withSockJS(); //제거시 endpoint 연결 성공 } } \ No newline at end of file diff --git a/src/main/java/com/kusitms/jipbap/message/deprecated/ChatService.java b/src/main/java/com/kusitms/jipbap/message/deprecated/ChatService.java new file mode 100644 index 0000000..363bf49 --- /dev/null +++ b/src/main/java/com/kusitms/jipbap/message/deprecated/ChatService.java @@ -0,0 +1,54 @@ +//package com.kusitms.jipbap.message; +// +//import com.fasterxml.jackson.databind.ObjectMapper; +//import jakarta.annotation.PostConstruct; +//import lombok.RequiredArgsConstructor; +//import lombok.extern.slf4j.Slf4j; +//import org.springframework.stereotype.Service; +//import org.springframework.web.socket.TextMessage; +//import org.springframework.web.socket.WebSocketSession; +// +//import java.io.IOException; +//import java.util.*; +// +//@Slf4j +//@RequiredArgsConstructor +//@Service +//public class ChatService { +// +// private final ObjectMapper objectMapper; +// private Map chatRooms; +// +// @PostConstruct +// private void init() { +// chatRooms = new LinkedHashMap<>(); +// } +// +// public List findAllRoom() { +// return new ArrayList<>(chatRooms.values()); +// } +// +// public ChatRoom findRoomById(String roomId) { +// return chatRooms.get(roomId); +// } +// +// public ChatRoom createRoom(String name) { +// String randomId = UUID.randomUUID().toString(); +// ChatRoom chatRoom = ChatRoom.builder() +// .roomId(randomId) +// .name(name) +// .build(); +// chatRooms.put(randomId, chatRoom); +// return chatRoom; +// } +// +// public void sendMessage(WebSocketSession session, T message) { +// try { +// session.sendMessage(new TextMessage(objectMapper.writeValueAsString(message))); +// } catch (IOException e) { +// log.error(e.getMessage(), e); +// } +// } +//} + +//ChatRoomRepository가 대체 \ No newline at end of file diff --git a/src/main/java/com/kusitms/jipbap/message/deprecated/WebSockChatHandler.java b/src/main/java/com/kusitms/jipbap/message/deprecated/WebSockChatHandler.java new file mode 100644 index 0000000..2695797 --- /dev/null +++ b/src/main/java/com/kusitms/jipbap/message/deprecated/WebSockChatHandler.java @@ -0,0 +1,31 @@ +//package com.kusitms.jipbap.message; +// +// +//import com.fasterxml.jackson.databind.ObjectMapper; +//import lombok.RequiredArgsConstructor; +//import lombok.extern.slf4j.Slf4j; +//import org.springframework.stereotype.Component; +//import org.springframework.web.socket.TextMessage; +//import org.springframework.web.socket.WebSocketSession; +//import org.springframework.web.socket.handler.TextWebSocketHandler; +// +//@Slf4j +//@RequiredArgsConstructor +//@Component +//public class WebSockChatHandler extends TextWebSocketHandler { +// private final ObjectMapper objectMapper; +// private final ChatService chatService; +// +// @Override +// protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { +// String payload = message.getPayload(); +// log.info("payload {}", payload); +//// 삭제 TextMessage textMessage = new TextMessage("Welcome chatting sever~^^ "); +//// 삭제 session.sendMessage(textMessage); +// ChatMessage chatMessage = objectMapper.readValue(payload, ChatMessage.class); +// ChatRoom room = chatService.findRoomById(chatMessage.getRoomId()); +// room.handleActions(session, chatMessage, chatService); +// } +//} + +//ChatController의 /chat/message가 대체 \ No newline at end of file From 976ccb2af769994dd1bd518eb2b503152c989dda Mon Sep 17 00:00:00 2001 From: eckrin Date: Fri, 27 Oct 2023 16:52:07 +0900 Subject: [PATCH 3/4] =?UTF-8?q?refactor:=20Redis=EB=A5=BC=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=ED=95=98=EC=97=AC=20=EC=B1=84=ED=8C=85=20=EA=B3=A0?= =?UTF-8?q?=EB=8F=84=ED=99=94=ED=95=98=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Related to: Issue #6 --- build.gradle | 6 +- .../com/kusitms/jipbap/JipbapApplication.java | 2 - .../jipbap/message/ChatController.java | 23 ++++++-- .../kusitms/jipbap/message/ChatMessage.java | 2 +- .../com/kusitms/jipbap/message/ChatRoom.java | 6 +- .../jipbap/message/ChatRoomRepository.java | 57 +++++++++++++++---- .../kusitms/jipbap/message/RedisConfig.java | 30 ++++++++++ .../jipbap/message/RedisPublisher.java | 19 +++++++ .../jipbap/message/RedisSubscriber.java | 38 +++++++++++++ .../kusitms/jipbap/message/WebSockConfig.java | 2 +- .../deprecated/EmbeddedRedisConfig.java | 31 ++++++++++ 11 files changed, 192 insertions(+), 24 deletions(-) create mode 100644 src/main/java/com/kusitms/jipbap/message/RedisConfig.java create mode 100644 src/main/java/com/kusitms/jipbap/message/RedisPublisher.java create mode 100644 src/main/java/com/kusitms/jipbap/message/RedisSubscriber.java create mode 100644 src/main/java/com/kusitms/jipbap/message/deprecated/EmbeddedRedisConfig.java diff --git a/build.gradle b/build.gradle index 4ec9ca3..463d8d7 100644 --- a/build.gradle +++ b/build.gradle @@ -48,14 +48,12 @@ dependencies { runtimeOnly 'com.mysql:mysql-connector-j' //redis implementation 'org.springframework.boot:spring-boot-starter-data-redis' + //embedded redis - https://mvnrepository.com/artifact/it.ozimov/embedded-redis +// implementation 'it.ozimov:embedded-redis:0.7.2' //lombok compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' - - -// https://mvnrepository.com/artifact/it.ozimov/embedded-redis - compileOnly 'it.ozimov:embedded-redis:0.7.2' } tasks.named('test') { diff --git a/src/main/java/com/kusitms/jipbap/JipbapApplication.java b/src/main/java/com/kusitms/jipbap/JipbapApplication.java index 9c866cc..d5d189c 100644 --- a/src/main/java/com/kusitms/jipbap/JipbapApplication.java +++ b/src/main/java/com/kusitms/jipbap/JipbapApplication.java @@ -3,9 +3,7 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.data.jpa.repository.config.EnableJpaAuditing; -import org.springframework.data.redis.repository.configuration.EnableRedisRepositories; import org.springframework.scheduling.annotation.EnableScheduling; -import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; @EnableScheduling @EnableJpaAuditing diff --git a/src/main/java/com/kusitms/jipbap/message/ChatController.java b/src/main/java/com/kusitms/jipbap/message/ChatController.java index aa2aefa..5ec4f29 100644 --- a/src/main/java/com/kusitms/jipbap/message/ChatController.java +++ b/src/main/java/com/kusitms/jipbap/message/ChatController.java @@ -13,25 +13,38 @@ public class ChatController { // private final ChatService chatService; - private final SimpMessageSendingOperations messagingTemplate; +// private final SimpMessageSendingOperations messagingTemplate; + private final RedisPublisher redisPublisher; + private final ChatRoomRepository chatRoomRepository; // @PostMapping // public ChatRoom createRoom(@RequestParam String name) { // return chatService.createRoom(name); // } -// + // @GetMapping // public List findAllRoom() { // return chatService.findAllRoom(); // } // package com.websocket.chat.controller; +// @MessageMapping("/chat/message") +// public void message(ChatMessage message) { +// if (ChatMessage.MessageType.JOIN.equals(message.getType())) +// message.setMessage(message.getSender() + "님이 입장하셨습니다."); +// messagingTemplate.convertAndSend("/sub/chat/room/" + message.getRoomId(), message); // sub/chat/room/{roomId} 경로로 메세지를 보낸다. +// } + /** + * websocket "/pub/chat/message"로 들어오는 메시징을 처리한다. + */ @MessageMapping("/chat/message") public void message(ChatMessage message) { - if (ChatMessage.MessageType.JOIN.equals(message.getType())) + if (ChatMessage.MessageType.ENTER.equals(message.getType())) { + chatRoomRepository.enterChatRoom(message.getRoomId()); message.setMessage(message.getSender() + "님이 입장하셨습니다."); - messagingTemplate.convertAndSend("/sub/chat/room/" + message.getRoomId(), message); // sub/chat/room/{roomId} 경로로 메세지를 보낸다. + } + // Websocket에 발행된 메시지를 redis로 발행한다(publish) + redisPublisher.publish(chatRoomRepository.getTopic(message.getRoomId()), message); } - } \ No newline at end of file diff --git a/src/main/java/com/kusitms/jipbap/message/ChatMessage.java b/src/main/java/com/kusitms/jipbap/message/ChatMessage.java index b684b65..d5c5ec9 100644 --- a/src/main/java/com/kusitms/jipbap/message/ChatMessage.java +++ b/src/main/java/com/kusitms/jipbap/message/ChatMessage.java @@ -8,7 +8,7 @@ public class ChatMessage { // 메시지 타입 : 입장, 채팅 public enum MessageType { - JOIN, TALK + ENTER, TALK } private MessageType type; // 메시지 타입 private String roomId; // 방번호 diff --git a/src/main/java/com/kusitms/jipbap/message/ChatRoom.java b/src/main/java/com/kusitms/jipbap/message/ChatRoom.java index 7664832..80fc729 100644 --- a/src/main/java/com/kusitms/jipbap/message/ChatRoom.java +++ b/src/main/java/com/kusitms/jipbap/message/ChatRoom.java @@ -5,13 +5,17 @@ import lombok.Setter; import org.springframework.web.socket.WebSocketSession; +import java.io.Serial; +import java.io.Serializable; import java.util.HashSet; import java.util.Set; import java.util.UUID; @Getter @Setter -public class ChatRoom { +public class ChatRoom implements Serializable { + + private static final long serialVersionUID = 4780475579618133057L; private String roomId; private String name; diff --git a/src/main/java/com/kusitms/jipbap/message/ChatRoomRepository.java b/src/main/java/com/kusitms/jipbap/message/ChatRoomRepository.java index 572ef0d..e8a5d35 100644 --- a/src/main/java/com/kusitms/jipbap/message/ChatRoomRepository.java +++ b/src/main/java/com/kusitms/jipbap/message/ChatRoomRepository.java @@ -1,34 +1,71 @@ package com.kusitms.jipbap.message; import jakarta.annotation.PostConstruct; +import lombok.RequiredArgsConstructor; +import org.springframework.data.redis.core.HashOperations; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.listener.ChannelTopic; +import org.springframework.data.redis.listener.RedisMessageListenerContainer; import org.springframework.stereotype.Repository; -import java.util.*; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +/** + * HGET CHAT_ROOM(key) aeif-4ifehw-2ef-4g5(room id) value(chatroom) + * redis hash의 + */ +@RequiredArgsConstructor @Repository public class ChatRoomRepository { - - private Map chatRoomMap; + // 채팅방(topic)에 발행되는 메시지를 처리할 Listener + private final RedisMessageListenerContainer redisMessageListener; + // 구독 처리 서비스 + private final RedisSubscriber redisSubscriber; + // Redis + private static final String CHAT_ROOMS = "CHAT_ROOM"; + private final RedisTemplate redisTemplate; + private HashOperations opsHashChatRoom; + // 채팅방의 대화 메시지를 발행하기 위한 redis topic 정보. 서버별로 채팅방에 매치되는 topic정보를 Map에 넣어 roomId로 찾을수 있도록 한다. + private Map topics; @PostConstruct private void init() { - chatRoomMap = new LinkedHashMap<>(); + opsHashChatRoom = redisTemplate.opsForHash(); + topics = new HashMap<>(); } public List findAllRoom() { - // 채팅방 생성순서 최근 순으로 반환 - List chatRooms = new ArrayList<>(chatRoomMap.values()); - Collections.reverse(chatRooms); - return chatRooms; + return opsHashChatRoom.values(CHAT_ROOMS); } public ChatRoom findRoomById(String id) { - return chatRoomMap.get(id); + return opsHashChatRoom.get(CHAT_ROOMS, id); } + /** + * 채팅방 생성 : 서버간 채팅방 공유를 위해 redis hash에 저장한다. + */ public ChatRoom createChatRoom(String name) { ChatRoom chatRoom = ChatRoom.create(name); - chatRoomMap.put(chatRoom.getRoomId(), chatRoom); + opsHashChatRoom.put(CHAT_ROOMS, chatRoom.getRoomId(), chatRoom); return chatRoom; } + + /** + * 채팅방 입장 : redis에 topic을 만들고 pub/sub 통신을 하기 위해 리스너를 설정한다. + */ + public void enterChatRoom(String roomId) { + ChannelTopic topic = topics.get(roomId); + if (topic == null) { + topic = new ChannelTopic(roomId); + redisMessageListener.addMessageListener(redisSubscriber, topic); + topics.put(roomId, topic); + } + } + + public ChannelTopic getTopic(String roomId) { + return topics.get(roomId); + } } \ No newline at end of file diff --git a/src/main/java/com/kusitms/jipbap/message/RedisConfig.java b/src/main/java/com/kusitms/jipbap/message/RedisConfig.java new file mode 100644 index 0000000..cdf4a4a --- /dev/null +++ b/src/main/java/com/kusitms/jipbap/message/RedisConfig.java @@ -0,0 +1,30 @@ +package com.kusitms.jipbap.message; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.listener.RedisMessageListenerContainer; +import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; +import org.springframework.data.redis.serializer.StringRedisSerializer; + +@Configuration +public class RedisConfig { + + //redis pub/sub을 처리하는 Listener + @Bean + public RedisMessageListenerContainer redisMessageListener(RedisConnectionFactory connectionFactory) { + RedisMessageListenerContainer container = new RedisMessageListenerContainer(); + container.setConnectionFactory(connectionFactory); + return container; + } + + @Bean + public RedisTemplate redisTemplate(RedisConnectionFactory connectionFactory) { + RedisTemplate redisTemplate = new RedisTemplate<>(); + redisTemplate.setConnectionFactory(connectionFactory); + redisTemplate.setKeySerializer(new StringRedisSerializer()); + redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<>(String.class)); + return redisTemplate; + } +} \ No newline at end of file diff --git a/src/main/java/com/kusitms/jipbap/message/RedisPublisher.java b/src/main/java/com/kusitms/jipbap/message/RedisPublisher.java new file mode 100644 index 0000000..c0a5f40 --- /dev/null +++ b/src/main/java/com/kusitms/jipbap/message/RedisPublisher.java @@ -0,0 +1,19 @@ +package com.kusitms.jipbap.message; + +import lombok.RequiredArgsConstructor; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.listener.ChannelTopic; +import org.springframework.stereotype.Service; + +/** + * 메세지를 redis topic에 publish해주는 서비스 + */ +@RequiredArgsConstructor +@Service +public class RedisPublisher { + private final RedisTemplate redisTemplate; + + public void publish(ChannelTopic topic, ChatMessage message) { + redisTemplate.convertAndSend(topic.getTopic(), message); + } +} \ No newline at end of file diff --git a/src/main/java/com/kusitms/jipbap/message/RedisSubscriber.java b/src/main/java/com/kusitms/jipbap/message/RedisSubscriber.java new file mode 100644 index 0000000..959152d --- /dev/null +++ b/src/main/java/com/kusitms/jipbap/message/RedisSubscriber.java @@ -0,0 +1,38 @@ +package com.kusitms.jipbap.message; + +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.redis.connection.Message; +import org.springframework.data.redis.connection.MessageListener; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.messaging.simp.SimpMessageSendingOperations; +import org.springframework.stereotype.Service; + +@Slf4j +@RequiredArgsConstructor +@Service +public class RedisSubscriber implements MessageListener { + + private final ObjectMapper objectMapper; + private final RedisTemplate redisTemplate; + private final SimpMessageSendingOperations messagingTemplate; + + /** + * RedisPublisher에서 pub된 메세지를 대기하고 있던 onMessage가 받아 처리한다. + * messagingTemplate을 사용하여 subscriber에게 메세지를 전달한다. + */ + @Override + public void onMessage(Message message, byte[] pattern) { + try { + // redis에서 발행된 데이터를 받아 deserialize + String publishMessage = (String) redisTemplate.getStringSerializer().deserialize(message.getBody()); + // ChatMessage 객채로 맵핑 + ChatMessage roomMessage = objectMapper.readValue(publishMessage, ChatMessage.class); + // Websocket 구독자에게 채팅 메시지 Send + messagingTemplate.convertAndSend("/sub/chat/room/" + roomMessage.getRoomId(), roomMessage); + } catch (Exception e) { + log.error(e.getMessage()); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/kusitms/jipbap/message/WebSockConfig.java b/src/main/java/com/kusitms/jipbap/message/WebSockConfig.java index ca66c69..63d3dfc 100644 --- a/src/main/java/com/kusitms/jipbap/message/WebSockConfig.java +++ b/src/main/java/com/kusitms/jipbap/message/WebSockConfig.java @@ -21,6 +21,6 @@ public void configureMessageBroker(MessageBrokerRegistry config) { public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint("/ws-stomp") //websocket 연결 endpoint .setAllowedOriginPatterns("*"); -// .withSockJS(); //제거시 endpoint 연결 성공 +// .withSockJS(); //제거해야 endpoint 연결 성공 } } \ No newline at end of file diff --git a/src/main/java/com/kusitms/jipbap/message/deprecated/EmbeddedRedisConfig.java b/src/main/java/com/kusitms/jipbap/message/deprecated/EmbeddedRedisConfig.java new file mode 100644 index 0000000..65b7002 --- /dev/null +++ b/src/main/java/com/kusitms/jipbap/message/deprecated/EmbeddedRedisConfig.java @@ -0,0 +1,31 @@ +//package com.kusitms.jipbap.message; +// +//import jakarta.annotation.PostConstruct; +//import jakarta.annotation.PreDestroy; +//import org.springframework.beans.factory.annotation.Value; +//import org.springframework.context.annotation.Configuration; +//import redis.embedded.RedisServer; +// +//@Configuration +//public class EmbeddedRedisConfig { +// +// @Value("${spring.data.redis.port}") +// private int redisPort; +// +// private RedisServer redisServer; +// +// @PostConstruct +// public void redisServer() { +// redisServer = RedisServer.builder() +// .port(6380) +// .build(); +// redisServer.start(); //doesn't work in macos 14 sonoma +// } +// +// @PreDestroy +// public void stopRedis() { +// if (redisServer != null) { +// redisServer.stop(); +// } +// } +//} \ No newline at end of file From 491758b780046e43fa35f776da6a07482cb6df3a Mon Sep 17 00:00:00 2001 From: eckrin Date: Wed, 1 Nov 2023 11:20:07 +0900 Subject: [PATCH 4/4] =?UTF-8?q?feat:=20Stomp+Redis=20=EC=B1=84=ED=8C=85=20?= =?UTF-8?q?=EA=B8=B0=EB=B3=B8=20=ED=94=84=EB=A0=88=EC=9E=84=20=EC=84=B8?= =?UTF-8?q?=ED=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Related to: Issue #6 --- .../jipbap/chat/config/RedisConfig.java | 58 ++++++ .../config}/WebSockConfig.java | 2 +- .../chat/controller/MessageController.java | 40 ++++ .../chat/controller/RoomController.java | 45 ++++ .../jipbap/chat/domain/dto/MessageDto.java | 26 +++ .../chat/domain/dto/MessageRequestDto.java | 14 ++ .../chat/domain/dto/MessageResponseDto.java | 51 +++++ .../jipbap/chat/domain/dto/RoomDto.java | 42 ++++ .../jipbap/chat/domain/entity/Message.java | 37 ++++ .../jipbap/chat/domain/entity/Room.java | 51 +++++ .../exception/RoomNotExistsException.java | 7 + .../chat/repository/MessageRepository.java | 14 ++ .../chat/repository/RoomRepository.java | 21 ++ .../jipbap/chat/service/MessageService.java | 78 +++++++ .../service}/RedisPublisher.java | 12 +- .../service}/RedisSubscriber.java | 21 +- .../jipbap/chat/service/RoomService.java | 197 ++++++++++++++++++ .../jipbap/config/SpringSecurityConfig.java | 1 - .../com/kusitms/jipbap/config/WebConfig.java | 28 +++ .../jipbap/message/ChatController.java | 50 ----- .../kusitms/jipbap/message/ChatMessage.java | 17 -- .../com/kusitms/jipbap/message/ChatRoom.java | 28 --- .../jipbap/message/ChatRoomController.java | 34 --- .../jipbap/message/ChatRoomRepository.java | 71 ------- .../kusitms/jipbap/message/RedisConfig.java | 30 --- .../message/deprecated/ChatService.java | 54 ----- .../deprecated/EmbeddedRedisConfig.java | 31 --- .../deprecated/WebSockChatHandler.java | 31 --- .../java/com/kusitms/jipbap/store/Store.java | 39 ++++ .../kusitms/jipbap/store/StoreController.java | 34 +++ .../kusitms/jipbap/store/StoreRepository.java | 6 + .../store/dto/RegisterStoreRequestDto.java | 15 ++ .../exception/StoreNotExistsException.java | 7 + .../kusitms/jipbap/test/TestController.java | 20 +- 34 files changed, 847 insertions(+), 365 deletions(-) create mode 100644 src/main/java/com/kusitms/jipbap/chat/config/RedisConfig.java rename src/main/java/com/kusitms/jipbap/{message => chat/config}/WebSockConfig.java (96%) create mode 100644 src/main/java/com/kusitms/jipbap/chat/controller/MessageController.java create mode 100644 src/main/java/com/kusitms/jipbap/chat/controller/RoomController.java create mode 100644 src/main/java/com/kusitms/jipbap/chat/domain/dto/MessageDto.java create mode 100644 src/main/java/com/kusitms/jipbap/chat/domain/dto/MessageRequestDto.java create mode 100644 src/main/java/com/kusitms/jipbap/chat/domain/dto/MessageResponseDto.java create mode 100644 src/main/java/com/kusitms/jipbap/chat/domain/dto/RoomDto.java create mode 100644 src/main/java/com/kusitms/jipbap/chat/domain/entity/Message.java create mode 100644 src/main/java/com/kusitms/jipbap/chat/domain/entity/Room.java create mode 100644 src/main/java/com/kusitms/jipbap/chat/exception/RoomNotExistsException.java create mode 100644 src/main/java/com/kusitms/jipbap/chat/repository/MessageRepository.java create mode 100644 src/main/java/com/kusitms/jipbap/chat/repository/RoomRepository.java create mode 100644 src/main/java/com/kusitms/jipbap/chat/service/MessageService.java rename src/main/java/com/kusitms/jipbap/{message => chat/service}/RedisPublisher.java (53%) rename src/main/java/com/kusitms/jipbap/{message => chat/service}/RedisSubscriber.java (67%) create mode 100644 src/main/java/com/kusitms/jipbap/chat/service/RoomService.java create mode 100644 src/main/java/com/kusitms/jipbap/config/WebConfig.java delete mode 100644 src/main/java/com/kusitms/jipbap/message/ChatController.java delete mode 100644 src/main/java/com/kusitms/jipbap/message/ChatMessage.java delete mode 100644 src/main/java/com/kusitms/jipbap/message/ChatRoom.java delete mode 100644 src/main/java/com/kusitms/jipbap/message/ChatRoomController.java delete mode 100644 src/main/java/com/kusitms/jipbap/message/ChatRoomRepository.java delete mode 100644 src/main/java/com/kusitms/jipbap/message/RedisConfig.java delete mode 100644 src/main/java/com/kusitms/jipbap/message/deprecated/ChatService.java delete mode 100644 src/main/java/com/kusitms/jipbap/message/deprecated/EmbeddedRedisConfig.java delete mode 100644 src/main/java/com/kusitms/jipbap/message/deprecated/WebSockChatHandler.java create mode 100644 src/main/java/com/kusitms/jipbap/store/Store.java create mode 100644 src/main/java/com/kusitms/jipbap/store/StoreController.java create mode 100644 src/main/java/com/kusitms/jipbap/store/StoreRepository.java create mode 100644 src/main/java/com/kusitms/jipbap/store/dto/RegisterStoreRequestDto.java create mode 100644 src/main/java/com/kusitms/jipbap/store/exception/StoreNotExistsException.java diff --git a/src/main/java/com/kusitms/jipbap/chat/config/RedisConfig.java b/src/main/java/com/kusitms/jipbap/chat/config/RedisConfig.java new file mode 100644 index 0000000..41140b6 --- /dev/null +++ b/src/main/java/com/kusitms/jipbap/chat/config/RedisConfig.java @@ -0,0 +1,58 @@ +package com.kusitms.jipbap.chat.config; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import com.kusitms.jipbap.chat.domain.dto.MessageDto; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.listener.RedisMessageListenerContainer; +import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; +import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; +import org.springframework.data.redis.serializer.StringRedisSerializer; + +@Configuration +public class RedisConfig { + + @Bean + public ObjectMapper objectMapper() { + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + objectMapper.registerModules(new JavaTimeModule(), new Jdk8Module()); + return objectMapper; + } + + // redis 연결, redis 의 pub/sub 기능을 이용하기 위해 pub/sub 메시지를 처리하는 MessageListener 설정(등록) + @Bean + public RedisMessageListenerContainer redisMessageListener(RedisConnectionFactory connectionFactory) { // 1. + RedisMessageListenerContainer container = new RedisMessageListenerContainer(); + container.setConnectionFactory(connectionFactory); // 2. + + return container; + } + + // Redis 데이터베이스와의 상호작용을 위한 RedisTemplate 을 설정. JSON 형식으로 담기 위해 직렬화 + @Bean + public RedisTemplate redisTemplate(RedisConnectionFactory connectionFactory) { + RedisTemplate redisTemplate = new RedisTemplate<>(); + redisTemplate.setConnectionFactory(connectionFactory); + redisTemplate.setKeySerializer(new StringRedisSerializer()); // Key Serializer + redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<>(String.class)); // Value Serializer + + return redisTemplate; + } + + // Redis 에 메시지 내역을 저장하기 위한 RedisTemplate을 설정 + @Bean + public RedisTemplate redisTemplateMessage(RedisConnectionFactory connectionFactory) { + RedisTemplate redisTemplateMessage = new RedisTemplate<>(); + redisTemplateMessage.setConnectionFactory(connectionFactory); + redisTemplateMessage.setKeySerializer(new StringRedisSerializer()); // Key Serializer + redisTemplateMessage.setValueSerializer(new GenericJackson2JsonRedisSerializer(objectMapper())); // Value Serializer + + return redisTemplateMessage; + } +} \ No newline at end of file diff --git a/src/main/java/com/kusitms/jipbap/message/WebSockConfig.java b/src/main/java/com/kusitms/jipbap/chat/config/WebSockConfig.java similarity index 96% rename from src/main/java/com/kusitms/jipbap/message/WebSockConfig.java rename to src/main/java/com/kusitms/jipbap/chat/config/WebSockConfig.java index 63d3dfc..eed0f43 100644 --- a/src/main/java/com/kusitms/jipbap/message/WebSockConfig.java +++ b/src/main/java/com/kusitms/jipbap/chat/config/WebSockConfig.java @@ -1,4 +1,4 @@ -package com.kusitms.jipbap.message; +package com.kusitms.jipbap.chat.config; import org.springframework.context.annotation.Configuration; diff --git a/src/main/java/com/kusitms/jipbap/chat/controller/MessageController.java b/src/main/java/com/kusitms/jipbap/chat/controller/MessageController.java new file mode 100644 index 0000000..2a87f26 --- /dev/null +++ b/src/main/java/com/kusitms/jipbap/chat/controller/MessageController.java @@ -0,0 +1,40 @@ +package com.kusitms.jipbap.chat.controller; + + +import com.kusitms.jipbap.chat.domain.dto.MessageDto; +import com.kusitms.jipbap.chat.service.RoomService; +import com.kusitms.jipbap.chat.service.MessageService; +import com.kusitms.jipbap.chat.service.RedisPublisher; +import com.kusitms.jipbap.common.response.CommonResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.messaging.handler.annotation.MessageMapping; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequiredArgsConstructor +public class MessageController { + private final RedisPublisher redisPublisher; + private final RoomService roomService; + private final MessageService messageService; + + // 대화 & 대화 저장 + @MessageMapping("/message") + public void message(MessageDto messageDto) { + // 클라이언트의 채팅방(topic) 입장, 대화를 위해 리스너와 연동 + roomService.enterMessageRoom(messageDto.getRoomId()); + + // Websocket 에 발행된 메시지를 redis 로 발행. 해당 채팅방을 구독한 클라이언트에게 메시지가 실시간 전송됨 (1:N, 1:1 에서 사용 가능) + redisPublisher.publish(roomService.getTopic(messageDto.getRoomId()), messageDto); + + // DB & Redis 에 대화 저장 + messageService.saveMessage(messageDto); + } + + // 대화 내역 조회 + @GetMapping("/chat/room/{roomId}/message") + public CommonResponse> loadMessage(@PathVariable String roomId) { + return new CommonResponse<>(messageService.loadMessage(roomId)); + } +} \ No newline at end of file diff --git a/src/main/java/com/kusitms/jipbap/chat/controller/RoomController.java b/src/main/java/com/kusitms/jipbap/chat/controller/RoomController.java new file mode 100644 index 0000000..135b427 --- /dev/null +++ b/src/main/java/com/kusitms/jipbap/chat/controller/RoomController.java @@ -0,0 +1,45 @@ +package com.kusitms.jipbap.chat.controller; + +import com.kusitms.jipbap.chat.domain.dto.MessageRequestDto; +import com.kusitms.jipbap.chat.domain.dto.MessageResponseDto; +import com.kusitms.jipbap.chat.domain.dto.RoomDto; +import com.kusitms.jipbap.chat.service.RoomService; +import com.kusitms.jipbap.common.response.CommonResponse; +import com.kusitms.jipbap.security.Auth; +import com.kusitms.jipbap.security.AuthInfo; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/chat") +@RequiredArgsConstructor +public class RoomController { + private final RoomService roomService; + + // 채팅방 생성 + @PostMapping("/room") + public CommonResponse createRoom(@RequestBody MessageRequestDto messageRequestDto, @Auth AuthInfo authInfo) { + return new CommonResponse<>(roomService.createRoom(messageRequestDto, authInfo.getEmail())); + } + + // 사용자 관련 채팅방 전체 조회 + @GetMapping("/rooms") + public CommonResponse> findAllRoomByUser(@Auth AuthInfo authInfo) { + return new CommonResponse<>(roomService.findAllRoomByUser(authInfo.getEmail())); + } + + // 사용자 관련 채팅방 선택 조회 + @GetMapping("/room/{roomId}") + public CommonResponse findRoom(@PathVariable String roomId, @Auth AuthInfo authInfo) { + return new CommonResponse<>(roomService.findRoom(roomId, authInfo.getEmail())); + } + + // 채팅방 삭제 + @DeleteMapping("/room/{id}") + public CommonResponse deleteRoom(@PathVariable Long id, @Auth AuthInfo authInfo) { + roomService.deleteRoom(id, authInfo.getEmail()); + return new CommonResponse<>("채팅방 삭제 완료"); + } +} \ No newline at end of file diff --git a/src/main/java/com/kusitms/jipbap/chat/domain/dto/MessageDto.java b/src/main/java/com/kusitms/jipbap/chat/domain/dto/MessageDto.java new file mode 100644 index 0000000..1965576 --- /dev/null +++ b/src/main/java/com/kusitms/jipbap/chat/domain/dto/MessageDto.java @@ -0,0 +1,26 @@ +package com.kusitms.jipbap.chat.domain.dto; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.kusitms.jipbap.chat.domain.entity.Message; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@NoArgsConstructor +@JsonIgnoreProperties(ignoreUnknown = true) +public class MessageDto { + private String senderName; + private String roomId; + private String message; + private String sentTime; + + // 대화 조회 + public MessageDto(Message message) { + this.senderName = message.getSenderName(); + this.roomId = message.getRoom().getRoomId(); + this.message = message.getMessage(); + } +} \ No newline at end of file diff --git a/src/main/java/com/kusitms/jipbap/chat/domain/dto/MessageRequestDto.java b/src/main/java/com/kusitms/jipbap/chat/domain/dto/MessageRequestDto.java new file mode 100644 index 0000000..ef252da --- /dev/null +++ b/src/main/java/com/kusitms/jipbap/chat/domain/dto/MessageRequestDto.java @@ -0,0 +1,14 @@ +package com.kusitms.jipbap.chat.domain.dto; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +@JsonIgnoreProperties(ignoreUnknown =true) +public class MessageRequestDto { + private Long receiverId; // 메세지 수신자 + private String receiverName; // 수신자 이름 (채팅방명으로 쓰임) + private Long storeId; // 1:1 채팅하기를 시작한 가게 정보 +} \ No newline at end of file diff --git a/src/main/java/com/kusitms/jipbap/chat/domain/dto/MessageResponseDto.java b/src/main/java/com/kusitms/jipbap/chat/domain/dto/MessageResponseDto.java new file mode 100644 index 0000000..903da35 --- /dev/null +++ b/src/main/java/com/kusitms/jipbap/chat/domain/dto/MessageResponseDto.java @@ -0,0 +1,51 @@ +package com.kusitms.jipbap.chat.domain.dto; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.kusitms.jipbap.chat.domain.entity.Room; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown =true) +public class MessageResponseDto { + private Long id; + private String roomName; + private String sender; + private String roomId; + private String receiver; + private String message; + private String createdAt; + + // 채팅방 생성 + public MessageResponseDto(Room room) { + this.id = room.getId(); + this.roomName = room.getRoomName(); + this.sender = room.getSenderName(); + this.roomId = room.getRoomId(); + this.receiver = room.getReceiverName(); + } + + // 사용자 관련 채팅방 전체 조회 + public MessageResponseDto(Long id, String roomName, String roomId, String sender, String receiver) { + this.id = id; + this.roomName = roomName; + this.sender = sender; + this.roomId = roomId; + this.receiver = receiver; + } + + public MessageResponseDto(String roomId) { + this.roomId = roomId; + } + + public void setLatestMessageContent(String message) { + this.message = message; + } + + public void setLatestMessageCreatedAt(String createdAt) { + this.createdAt = createdAt; + } +} \ No newline at end of file diff --git a/src/main/java/com/kusitms/jipbap/chat/domain/dto/RoomDto.java b/src/main/java/com/kusitms/jipbap/chat/domain/dto/RoomDto.java new file mode 100644 index 0000000..7818ff3 --- /dev/null +++ b/src/main/java/com/kusitms/jipbap/chat/domain/dto/RoomDto.java @@ -0,0 +1,42 @@ +package com.kusitms.jipbap.chat.domain.dto; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.kusitms.jipbap.user.User; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; + +import java.io.Serializable; +import java.util.UUID; + +@Getter +@Setter +@AllArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown =true) +public class RoomDto implements Serializable { + + private static final long serialVersionUID = 298188411555580374L; // Redis 에 저장되는 객체들이 직렬화가 가능하도록 + + private Long id; + private String roomName; // 채팅방 제목 + private String roomId; + private String senderName; // 메시지 송신자 이름 + private String receiverName; // 메시지 수신자 이름 + + // 채팅방 생성 + private RoomDto() { + + } + public static RoomDto create(MessageRequestDto messageRequestDto, User user) { + RoomDto roomDto = new RoomDto(); + roomDto.roomName = messageRequestDto.getReceiverName(); + roomDto.roomId = UUID.randomUUID().toString(); + roomDto.senderName = user.getUsername(); + roomDto.receiverName = messageRequestDto.getReceiverName(); + + return roomDto; + } + +} \ No newline at end of file diff --git a/src/main/java/com/kusitms/jipbap/chat/domain/entity/Message.java b/src/main/java/com/kusitms/jipbap/chat/domain/entity/Message.java new file mode 100644 index 0000000..36ded5e --- /dev/null +++ b/src/main/java/com/kusitms/jipbap/chat/domain/entity/Message.java @@ -0,0 +1,37 @@ +package com.kusitms.jipbap.chat.domain.entity; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.kusitms.jipbap.common.entity.DateEntity; +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Getter +@Table(name = "tb_message") +@NoArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +public class Message extends DateEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private String senderName; + + private String receiverName; + + private String message; + + private String sentTime; + + // 1. + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "room_id") + private Room room; + + // 대화 저장 + public Message(String senderName, Room room, String message) { + this.senderName = senderName; + this.room = room; + this.message = message; + } +} \ No newline at end of file diff --git a/src/main/java/com/kusitms/jipbap/chat/domain/entity/Room.java b/src/main/java/com/kusitms/jipbap/chat/domain/entity/Room.java new file mode 100644 index 0000000..71771f5 --- /dev/null +++ b/src/main/java/com/kusitms/jipbap/chat/domain/entity/Room.java @@ -0,0 +1,51 @@ +package com.kusitms.jipbap.chat.domain.entity; + +import com.kusitms.jipbap.store.Store; +import com.kusitms.jipbap.user.User; +import jakarta.persistence.*; +import lombok.*; + +import java.util.ArrayList; +import java.util.List; + +@Entity +@Setter +@Getter +@Table(name = "tb_room") +@NoArgsConstructor +public class Room { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; // auto-incremential + + @Column(unique = true) + private String roomId; // uuid + + private String roomName; + + private String senderName; // 채팅방 생성자 이름 + + private String receiverName; // 채팅방 수신자 이름 + + @OneToMany(mappedBy = "room", cascade = CascadeType.REMOVE) + private List messageList = new ArrayList<>(); + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id", nullable = false) + private User user; + + @ManyToOne(fetch = FetchType.LAZY) // 채팅방과 Store는 N:1 연관관계 + @JoinColumn(name = "store_id") + private Store store; + + // 채팅방 생성 + public Room(Long id, String roomName, String senderName, String roomId, String receiverName, User user, Store store) { + this.id = id; + this.roomName = roomName; + this.senderName = senderName; + this.roomId = roomId; + this.receiverName = receiverName; + this.user = user; + this.store = store; + } +} \ No newline at end of file diff --git a/src/main/java/com/kusitms/jipbap/chat/exception/RoomNotExistsException.java b/src/main/java/com/kusitms/jipbap/chat/exception/RoomNotExistsException.java new file mode 100644 index 0000000..fbe4d1a --- /dev/null +++ b/src/main/java/com/kusitms/jipbap/chat/exception/RoomNotExistsException.java @@ -0,0 +1,7 @@ +package com.kusitms.jipbap.chat.exception; + +public class RoomNotExistsException extends RuntimeException{ + public RoomNotExistsException(String message) { + super(message); + } +} diff --git a/src/main/java/com/kusitms/jipbap/chat/repository/MessageRepository.java b/src/main/java/com/kusitms/jipbap/chat/repository/MessageRepository.java new file mode 100644 index 0000000..52251d4 --- /dev/null +++ b/src/main/java/com/kusitms/jipbap/chat/repository/MessageRepository.java @@ -0,0 +1,14 @@ +package com.kusitms.jipbap.chat.repository; + +import com.kusitms.jipbap.chat.domain.entity.Message; +import com.kusitms.jipbap.chat.domain.entity.Room; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface MessageRepository extends JpaRepository { + + List findTop100ByRoomOrderById(Room room); + + Message findTopByRoomOrderById(Room room); +} \ No newline at end of file diff --git a/src/main/java/com/kusitms/jipbap/chat/repository/RoomRepository.java b/src/main/java/com/kusitms/jipbap/chat/repository/RoomRepository.java new file mode 100644 index 0000000..40d289c --- /dev/null +++ b/src/main/java/com/kusitms/jipbap/chat/repository/RoomRepository.java @@ -0,0 +1,21 @@ +package com.kusitms.jipbap.chat.repository; + +import com.kusitms.jipbap.chat.domain.entity.Room; +import com.kusitms.jipbap.user.User; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; +import java.util.Optional; + +public interface RoomRepository extends JpaRepository { + + List findByUserOrReceiverName(User user, String receiverName); + + Room findByIdAndUserOrIdAndReceiverName(Long id1, User user, Long id2, String nickname); + + Room findBySenderNameAndReceiverName(String senderName, String receiverName); + + Room findByRoomIdAndUserOrRoomIdAndReceiverName(String roomId1, User user, String roomId2, String nickname); + + Optional findByRoomId(String roomId); +} \ No newline at end of file diff --git a/src/main/java/com/kusitms/jipbap/chat/service/MessageService.java b/src/main/java/com/kusitms/jipbap/chat/service/MessageService.java new file mode 100644 index 0000000..e15be88 --- /dev/null +++ b/src/main/java/com/kusitms/jipbap/chat/service/MessageService.java @@ -0,0 +1,78 @@ +package com.kusitms.jipbap.chat.service; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.kusitms.jipbap.chat.domain.dto.MessageDto; +import com.kusitms.jipbap.chat.domain.entity.Message; +import com.kusitms.jipbap.chat.domain.entity.Room; +import com.kusitms.jipbap.chat.exception.RoomNotExistsException; +import com.kusitms.jipbap.chat.repository.MessageRepository; +import com.kusitms.jipbap.chat.repository.RoomRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.redis.core.RedisOperations; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; +import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; + +@Slf4j +@Service +@RequiredArgsConstructor +public class MessageService { + private final RedisTemplate redisTemplateMessage; + private final MessageRepository messageRepository; + private final RoomRepository roomRepository; + private final ObjectMapper objectMapper; + + // 메세지 저장 + public void saveMessage(MessageDto messageDto) { + Room room = roomRepository.findByRoomId(messageDto.getRoomId()).orElseThrow( + ()->new RoomNotExistsException("채팅방이 더 이상 존재하지 않습니다.") + ); + + // DB 저장 + Message message = new Message(messageDto.getSenderName(), room, messageDto.getMessage()); + messageRepository.save(message); + + // 1. 직렬화 + redisTemplateMessage.setValueSerializer(new GenericJackson2JsonRedisSerializer(objectMapper)); + + // 2. redis 저장 + redisTemplateMessage.opsForList().rightPush(messageDto.getRoomId(), messageDto); + + // 3. redistemplate의 expire() 을 이용해서, Key 를 만료시킬 수 있음 + redisTemplateMessage.expire(messageDto.getRoomId(), 60, TimeUnit.MINUTES); + } + + // 6. 대화 조회 - Redis & DB (TLB 캐시전략 유사) + public List loadMessage(String roomId) { + List messageList = new ArrayList<>(); + + // Redis 에서 해당 채팅방의 메시지 100개 가져오기 + List redisMessageList = redisTemplateMessage.opsForList().range(roomId, 0, 99); + Room room = roomRepository.findByRoomId(roomId).orElseThrow(()-> new RoomNotExistsException("현재 채팅방이 존재하지 않습니다.")); + + // 4. Redis 에서 가져온 메시지가 없다면, DB 에서 메시지 100개 가져오기 + if (redisMessageList == null || redisMessageList.isEmpty()) { + // 5. 생성시간이 빠른 순서부터 100개의 데이터 정렬 + List dbMessageList = messageRepository.findTop100ByRoomOrderById(room); // db 저장된 채팅내역 + + // db 저장된 내역을 redis에 저장 + for (Message message : dbMessageList) { + MessageDto messageDto = new MessageDto(message); + messageList.add(messageDto); + redisTemplateMessage.setValueSerializer(new Jackson2JsonRedisSerializer<>(Message.class)); // 직렬화 이후 + redisTemplateMessage.opsForList().rightPush(roomId, messageDto); // redis 저장 + } + } else { + // 7. 최근 내역 100개 저장 + messageList.addAll(redisMessageList); + } + + return messageList; + } +} \ No newline at end of file diff --git a/src/main/java/com/kusitms/jipbap/message/RedisPublisher.java b/src/main/java/com/kusitms/jipbap/chat/service/RedisPublisher.java similarity index 53% rename from src/main/java/com/kusitms/jipbap/message/RedisPublisher.java rename to src/main/java/com/kusitms/jipbap/chat/service/RedisPublisher.java index c0a5f40..5011c6b 100644 --- a/src/main/java/com/kusitms/jipbap/message/RedisPublisher.java +++ b/src/main/java/com/kusitms/jipbap/chat/service/RedisPublisher.java @@ -1,19 +1,21 @@ -package com.kusitms.jipbap.message; +package com.kusitms.jipbap.chat.service; +import com.kusitms.jipbap.chat.domain.dto.MessageDto; import lombok.RequiredArgsConstructor; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.listener.ChannelTopic; import org.springframework.stereotype.Service; -/** - * 메세지를 redis topic에 publish해주는 서비스 - */ @RequiredArgsConstructor @Service public class RedisPublisher { private final RedisTemplate redisTemplate; - public void publish(ChannelTopic topic, ChatMessage message) { + /** + * 메세지를 redis topic에 publish해주는 서비스 + * 메시지를 발행 후, 대기 중이던 redis 구독 서비스(RedisSubscriber)가 메시지를 처리 + */ + public void publish(ChannelTopic topic, MessageDto message) { redisTemplate.convertAndSend(topic.getTopic(), message); } } \ No newline at end of file diff --git a/src/main/java/com/kusitms/jipbap/message/RedisSubscriber.java b/src/main/java/com/kusitms/jipbap/chat/service/RedisSubscriber.java similarity index 67% rename from src/main/java/com/kusitms/jipbap/message/RedisSubscriber.java rename to src/main/java/com/kusitms/jipbap/chat/service/RedisSubscriber.java index 959152d..a2e51d8 100644 --- a/src/main/java/com/kusitms/jipbap/message/RedisSubscriber.java +++ b/src/main/java/com/kusitms/jipbap/chat/service/RedisSubscriber.java @@ -1,6 +1,7 @@ -package com.kusitms.jipbap.message; +package com.kusitms.jipbap.chat.service; import com.fasterxml.jackson.databind.ObjectMapper; +import com.kusitms.jipbap.chat.domain.dto.MessageDto; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.data.redis.connection.Message; @@ -10,26 +11,24 @@ import org.springframework.stereotype.Service; @Slf4j -@RequiredArgsConstructor @Service +@RequiredArgsConstructor public class RedisSubscriber implements MessageListener { - private final ObjectMapper objectMapper; private final RedisTemplate redisTemplate; private final SimpMessageSendingOperations messagingTemplate; - /** - * RedisPublisher에서 pub된 메세지를 대기하고 있던 onMessage가 받아 처리한다. - * messagingTemplate을 사용하여 subscriber에게 메세지를 전달한다. - */ + // 2. Redis 에서 메시지가 발행(publish)되면, listener 가 해당 메시지를 읽어서 처리 @Override public void onMessage(Message message, byte[] pattern) { try { - // redis에서 발행된 데이터를 받아 deserialize + // redis 에서 발행된 데이터를 받아 역직렬화 String publishMessage = (String) redisTemplate.getStringSerializer().deserialize(message.getBody()); - // ChatMessage 객채로 맵핑 - ChatMessage roomMessage = objectMapper.readValue(publishMessage, ChatMessage.class); - // Websocket 구독자에게 채팅 메시지 Send + + // 4. 해당 객체를 MessageDto 객체로 맵핑 + MessageDto roomMessage = objectMapper.readValue(publishMessage, MessageDto.class); + + // 5. Websocket 구독자에게 채팅 메시지 전송 messagingTemplate.convertAndSend("/sub/chat/room/" + roomMessage.getRoomId(), roomMessage); } catch (Exception e) { log.error(e.getMessage()); diff --git a/src/main/java/com/kusitms/jipbap/chat/service/RoomService.java b/src/main/java/com/kusitms/jipbap/chat/service/RoomService.java new file mode 100644 index 0000000..cb17808 --- /dev/null +++ b/src/main/java/com/kusitms/jipbap/chat/service/RoomService.java @@ -0,0 +1,197 @@ +package com.kusitms.jipbap.chat.service; + +import com.kusitms.jipbap.auth.exception.InvalidEmailException; +import com.kusitms.jipbap.chat.domain.dto.MessageRequestDto; +import com.kusitms.jipbap.chat.domain.dto.MessageResponseDto; +import com.kusitms.jipbap.chat.domain.dto.RoomDto; +import com.kusitms.jipbap.chat.domain.entity.Message; +import com.kusitms.jipbap.chat.domain.entity.Room; +import com.kusitms.jipbap.chat.exception.RoomNotExistsException; +import com.kusitms.jipbap.chat.repository.MessageRepository; +import com.kusitms.jipbap.chat.repository.RoomRepository; +import com.kusitms.jipbap.store.Store; +import com.kusitms.jipbap.store.StoreRepository; +import com.kusitms.jipbap.store.exception.StoreNotExistsException; +import com.kusitms.jipbap.user.User; +import com.kusitms.jipbap.user.UserRepository; +import jakarta.annotation.PostConstruct; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.redis.core.HashOperations; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.listener.ChannelTopic; +import org.springframework.data.redis.listener.RedisMessageListenerContainer; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Slf4j +@Service +@RequiredArgsConstructor +public class RoomService { + + private final RoomRepository roomRepository; + private final MessageRepository messageRepository; + private final UserRepository userRepository; + private final StoreRepository storeRepository; + + // 채팅방(=topic)에 발행되는 메시지 처리하는 리스너 + private final RedisMessageListenerContainer redisMessageListener; + + // 구독(sub) 처리를 위한 서비스 + private final RedisSubscriber redisSubscriber; + + // 1. redis key = 'MESSAGE_ROOM' + private static final String Message_Rooms = "MESSAGE_ROOM"; + private final RedisTemplate redisTemplate; + private HashOperations opsHashMessageRoom; + + // 2. 채팅방의 대화 메시지 발행을 위한 redis topic(채팅방) 정보 + private Map topics; + + // 3. redis 의 Hash 데이터에 접근하기 위한 HashOperations, topics 초기화. + @PostConstruct + private void init() { + opsHashMessageRoom = redisTemplate.opsForHash(); + topics = new HashMap<>(); + } + + // 채팅방 생성 + public MessageResponseDto createRoom(MessageRequestDto dto, String email) { + User sender = userRepository.findByEmail(email).orElseThrow(()->new InvalidEmailException("회원정보가 존재하지 않습니다.")); + User receiver = userRepository.findById(dto.getReceiverId()).orElseThrow(()->new InvalidEmailException("수신 회원정보가 존재하지 않습니다.")); + Store store = storeRepository.findById(dto.getStoreId()).orElseThrow(()->new StoreNotExistsException("가게 정보를 찾을 수 없습니다.")); + + // 4. 다른 사람들은 들어올 수 없도록 1:1 (구매자:판매자) 채팅방 구성하기 + Room messageRoom = roomRepository.findBySenderNameAndReceiverName(sender.getUsername(), receiver.getUsername()); + // 5. room 생성하기 - (이미 생성된 채팅방이 아닌 경우) + if (messageRoom == null) { + RoomDto roomDto = RoomDto.create(dto, sender); + opsHashMessageRoom.put(Message_Rooms, roomDto.getRoomId(), roomDto); // redis hash 에 채팅방 저장해서, 서버간 채팅방 공유 가능 + messageRoom = roomRepository.save(new Room(roomDto.getId(), roomDto.getRoomName(), roomDto.getSenderName(), roomDto.getRoomId(), roomDto.getReceiverName(), sender, store)); + + return new MessageResponseDto(messageRoom); + // 6. 이미 생성된 채팅방인 경우 기존 채팅방 이동 + } else { + return new MessageResponseDto(messageRoom.getRoomId()); + } + } + + // 7. 사용자 관련 채팅방 전체 조회 (생성, 수신) + public List findAllRoomByUser(String email) { + User user = userRepository.findByEmail(email).orElseThrow(()->new InvalidEmailException("회원정보가 존재하지 않습니다.")); + List messageRooms = roomRepository.findByUserOrReceiverName(user, user.getUsername()); // sender & receiver 모두 해당 채팅방 조회 가능 (1:1 대화) + + List messageRoomDtos = new ArrayList<>(); + + for (Room messageRoom : messageRooms) { + // user 가 sender 인 경우 채팅방의 이름이 receiver로 보이도록 + if (user.getUsername().equals(messageRoom.getSenderName())) { + MessageResponseDto messageRoomDto = new MessageResponseDto( + messageRoom.getId(), + messageRoom.getReceiverName(), // roomName + messageRoom.getRoomId(), + messageRoom.getSenderName(), + messageRoom.getReceiverName()); + + // 8. 가장 최신 메시지 & 생성 시간 조회 + Message latestMessage = messageRepository.findTopByRoomOrderById(messageRoom); + if (latestMessage != null) { + messageRoomDto.setLatestMessageCreatedAt(latestMessage.getSentTime()); + messageRoomDto.setLatestMessageContent(latestMessage.getMessage()); + } + + messageRoomDtos.add(messageRoomDto); + // user 가 receiver 인 경우 채팅방의 이름이 sender로 보이도록 + } else { + MessageResponseDto messageRoomDto = new MessageResponseDto( + messageRoom.getId(), + messageRoom.getSenderName(), // roomName + messageRoom.getRoomId(), + messageRoom.getSenderName(), + messageRoom.getReceiverName()); + + // 가장 최신 메시지 & 생성 시간 조회 + Message latestMessage = messageRepository.findTopByRoomOrderById(messageRoom); + if (latestMessage != null) { + messageRoomDto.setLatestMessageCreatedAt(latestMessage.getSentTime()); + messageRoomDto.setLatestMessageContent(latestMessage.getMessage()); + } + + messageRoomDtos.add(messageRoomDto); + } + } + + return messageRoomDtos; + } + + // 사용자 관련 채팅방 선택 조회 + public RoomDto findRoom(String roomId, String email) { + Room room = roomRepository.findByRoomId(roomId).orElseThrow( + ()->new RoomNotExistsException("채팅방 정보를 찾을 수 없습니다.") + ); + + User user = userRepository.findByEmail(email).orElseThrow( + ()->new InvalidEmailException("회원 정보가 존재하지 않습니다.") + ); + User receiver = userRepository.findByEmail(email).orElseThrow( + ()->new InvalidEmailException("채팅 수신 회원 정보가 존재하지 않습니다.") + ); + Store store = storeRepository.findById(room.getStore().getId()).orElseThrow( + ()->new StoreNotExistsException("가게 정보가 존재하지 않습니다.") + ); + + // 9. sender & receiver 모두 messageRoom 조회 가능 + room = roomRepository.findByRoomIdAndUserOrRoomIdAndReceiverName(roomId, user, roomId, user.getUsername()); + if (room == null) { + throw new IllegalArgumentException("채팅방이 존재하지 않습니다."); + } + + RoomDto roomDto = new RoomDto( + room.getId(), + room.getRoomName(), + room.getRoomId(), + room.getSenderName(), + room.getReceiverName() + ); + + return roomDto; + } + + // 10. 채팅방 삭제 + public void deleteRoom(Long id, String email) { + User user = userRepository.findByEmail(email).orElseThrow(()->new InvalidEmailException("회원정보가 존재하지 않습니다.")); + + Room room = roomRepository.findByIdAndUserOrIdAndReceiverName(id, user, id, user.getUsername()); + + // sender 가 삭제할 경우 + if (user.getUsername().equals(room.getSenderName())) { + roomRepository.delete(room); + opsHashMessageRoom.delete(Message_Rooms, room.getRoomId()); + // receiver 가 삭제할 경우 + } else if (user.getUsername().equals(room.getReceiverName())) { + room.setReceiverName("Not_Exist_Receiver"); + roomRepository.save(room); + } + + } + + // 채팅방 입장 + public void enterMessageRoom(String roomId) { + ChannelTopic topic = topics.get(roomId); + + if (topic == null) { + topic = new ChannelTopic(roomId); + redisMessageListener.addMessageListener(redisSubscriber, topic); // pub/sub 통신을 위해 리스너를 설정. 대화가 가능해진다 + topics.put(roomId, topic); + } + } + + // redis 채널에서 채팅방 조회 + public ChannelTopic getTopic(String roomId) { + return topics.get(roomId); + } +} \ No newline at end of file diff --git a/src/main/java/com/kusitms/jipbap/config/SpringSecurityConfig.java b/src/main/java/com/kusitms/jipbap/config/SpringSecurityConfig.java index 41091e9..d58d994 100644 --- a/src/main/java/com/kusitms/jipbap/config/SpringSecurityConfig.java +++ b/src/main/java/com/kusitms/jipbap/config/SpringSecurityConfig.java @@ -65,7 +65,6 @@ public WebSecurityCustomizer webSecurityCustomizer() { "/error", "/auth/**", "/ws/**", //ws://localhost:8080/ws/chat - "/chat/**", "/ws-stomp/**" ); } diff --git a/src/main/java/com/kusitms/jipbap/config/WebConfig.java b/src/main/java/com/kusitms/jipbap/config/WebConfig.java new file mode 100644 index 0000000..810d114 --- /dev/null +++ b/src/main/java/com/kusitms/jipbap/config/WebConfig.java @@ -0,0 +1,28 @@ +package com.kusitms.jipbap.config; + +import com.kusitms.jipbap.security.AuthArgumentResolver; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +import java.util.List; + +@Configuration +@EnableWebMvc +public class WebConfig implements WebMvcConfigurer { + + @Bean + public AuthArgumentResolver authArgumentResolver() { + return new AuthArgumentResolver(); + } + + @Override + public void addArgumentResolvers(List resolvers) { + resolvers.add(authArgumentResolver()); + WebMvcConfigurer.super.addArgumentResolvers(resolvers); + } + + +} diff --git a/src/main/java/com/kusitms/jipbap/message/ChatController.java b/src/main/java/com/kusitms/jipbap/message/ChatController.java deleted file mode 100644 index 5ec4f29..0000000 --- a/src/main/java/com/kusitms/jipbap/message/ChatController.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.kusitms.jipbap.message; - -import lombok.RequiredArgsConstructor; -import org.springframework.messaging.handler.annotation.MessageMapping; -import org.springframework.messaging.simp.SimpMessageSendingOperations; -import org.springframework.web.bind.annotation.*; - -import java.util.List; - -@RequiredArgsConstructor -@RestController -//Publisher -public class ChatController { - -// private final ChatService chatService; -// private final SimpMessageSendingOperations messagingTemplate; - private final RedisPublisher redisPublisher; - private final ChatRoomRepository chatRoomRepository; - -// @PostMapping -// public ChatRoom createRoom(@RequestParam String name) { -// return chatService.createRoom(name); -// } - -// @GetMapping -// public List findAllRoom() { -// return chatService.findAllRoom(); -// } -// package com.websocket.chat.controller; - -// @MessageMapping("/chat/message") -// public void message(ChatMessage message) { -// if (ChatMessage.MessageType.JOIN.equals(message.getType())) -// message.setMessage(message.getSender() + "님이 입장하셨습니다."); -// messagingTemplate.convertAndSend("/sub/chat/room/" + message.getRoomId(), message); // sub/chat/room/{roomId} 경로로 메세지를 보낸다. -// } - - /** - * websocket "/pub/chat/message"로 들어오는 메시징을 처리한다. - */ - @MessageMapping("/chat/message") - public void message(ChatMessage message) { - if (ChatMessage.MessageType.ENTER.equals(message.getType())) { - chatRoomRepository.enterChatRoom(message.getRoomId()); - message.setMessage(message.getSender() + "님이 입장하셨습니다."); - } - // Websocket에 발행된 메시지를 redis로 발행한다(publish) - redisPublisher.publish(chatRoomRepository.getTopic(message.getRoomId()), message); - } -} \ No newline at end of file diff --git a/src/main/java/com/kusitms/jipbap/message/ChatMessage.java b/src/main/java/com/kusitms/jipbap/message/ChatMessage.java deleted file mode 100644 index d5c5ec9..0000000 --- a/src/main/java/com/kusitms/jipbap/message/ChatMessage.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.kusitms.jipbap.message; - -import lombok.Getter; -import lombok.Setter; - -@Getter -@Setter -public class ChatMessage { - // 메시지 타입 : 입장, 채팅 - public enum MessageType { - ENTER, TALK - } - private MessageType type; // 메시지 타입 - private String roomId; // 방번호 - private String sender; // 메시지 보낸사람 - private String message; // 메시지 -} \ No newline at end of file diff --git a/src/main/java/com/kusitms/jipbap/message/ChatRoom.java b/src/main/java/com/kusitms/jipbap/message/ChatRoom.java deleted file mode 100644 index 80fc729..0000000 --- a/src/main/java/com/kusitms/jipbap/message/ChatRoom.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.kusitms.jipbap.message; - -import lombok.Builder; -import lombok.Getter; -import lombok.Setter; -import org.springframework.web.socket.WebSocketSession; - -import java.io.Serial; -import java.io.Serializable; -import java.util.HashSet; -import java.util.Set; -import java.util.UUID; - -@Getter -@Setter -public class ChatRoom implements Serializable { - - private static final long serialVersionUID = 4780475579618133057L; - private String roomId; - private String name; - - public static ChatRoom create(String name) { - ChatRoom chatRoom = new ChatRoom(); - chatRoom.roomId = UUID.randomUUID().toString(); - chatRoom.name = name; - return chatRoom; - } -} \ No newline at end of file diff --git a/src/main/java/com/kusitms/jipbap/message/ChatRoomController.java b/src/main/java/com/kusitms/jipbap/message/ChatRoomController.java deleted file mode 100644 index 064e7e0..0000000 --- a/src/main/java/com/kusitms/jipbap/message/ChatRoomController.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.kusitms.jipbap.message; - - -import lombok.RequiredArgsConstructor; -import org.springframework.web.bind.annotation.*; - -import java.util.List; - -@RequiredArgsConstructor -@RestController -@RequestMapping("/chat") -//Subscriber -public class ChatRoomController { - - private final ChatRoomRepository chatRoomRepository; - - // 모든 채팅방 목록 반환 - @GetMapping("/rooms") - public List room() { - return chatRoomRepository.findAllRoom(); - } - - // 채팅방 생성 - @PostMapping("/room") - public ChatRoom createRoom(@RequestParam String name) { - return chatRoomRepository.createChatRoom(name); - } - - // 특정 채팅방 조회 - @GetMapping("/room/{roomId}") - public ChatRoom roomInfo(@PathVariable String roomId) { - return chatRoomRepository.findRoomById(roomId); - } -} \ No newline at end of file diff --git a/src/main/java/com/kusitms/jipbap/message/ChatRoomRepository.java b/src/main/java/com/kusitms/jipbap/message/ChatRoomRepository.java deleted file mode 100644 index e8a5d35..0000000 --- a/src/main/java/com/kusitms/jipbap/message/ChatRoomRepository.java +++ /dev/null @@ -1,71 +0,0 @@ -package com.kusitms.jipbap.message; - -import jakarta.annotation.PostConstruct; -import lombok.RequiredArgsConstructor; -import org.springframework.data.redis.core.HashOperations; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.data.redis.listener.ChannelTopic; -import org.springframework.data.redis.listener.RedisMessageListenerContainer; -import org.springframework.stereotype.Repository; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * HGET CHAT_ROOM(key) aeif-4ifehw-2ef-4g5(room id) value(chatroom) - * redis hash의 - */ -@RequiredArgsConstructor -@Repository -public class ChatRoomRepository { - // 채팅방(topic)에 발행되는 메시지를 처리할 Listener - private final RedisMessageListenerContainer redisMessageListener; - // 구독 처리 서비스 - private final RedisSubscriber redisSubscriber; - // Redis - private static final String CHAT_ROOMS = "CHAT_ROOM"; - private final RedisTemplate redisTemplate; - private HashOperations opsHashChatRoom; - // 채팅방의 대화 메시지를 발행하기 위한 redis topic 정보. 서버별로 채팅방에 매치되는 topic정보를 Map에 넣어 roomId로 찾을수 있도록 한다. - private Map topics; - - @PostConstruct - private void init() { - opsHashChatRoom = redisTemplate.opsForHash(); - topics = new HashMap<>(); - } - - public List findAllRoom() { - return opsHashChatRoom.values(CHAT_ROOMS); - } - - public ChatRoom findRoomById(String id) { - return opsHashChatRoom.get(CHAT_ROOMS, id); - } - - /** - * 채팅방 생성 : 서버간 채팅방 공유를 위해 redis hash에 저장한다. - */ - public ChatRoom createChatRoom(String name) { - ChatRoom chatRoom = ChatRoom.create(name); - opsHashChatRoom.put(CHAT_ROOMS, chatRoom.getRoomId(), chatRoom); - return chatRoom; - } - - /** - * 채팅방 입장 : redis에 topic을 만들고 pub/sub 통신을 하기 위해 리스너를 설정한다. - */ - public void enterChatRoom(String roomId) { - ChannelTopic topic = topics.get(roomId); - if (topic == null) { - topic = new ChannelTopic(roomId); - redisMessageListener.addMessageListener(redisSubscriber, topic); - topics.put(roomId, topic); - } - } - - public ChannelTopic getTopic(String roomId) { - return topics.get(roomId); - } -} \ No newline at end of file diff --git a/src/main/java/com/kusitms/jipbap/message/RedisConfig.java b/src/main/java/com/kusitms/jipbap/message/RedisConfig.java deleted file mode 100644 index cdf4a4a..0000000 --- a/src/main/java/com/kusitms/jipbap/message/RedisConfig.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.kusitms.jipbap.message; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.data.redis.connection.RedisConnectionFactory; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.data.redis.listener.RedisMessageListenerContainer; -import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; -import org.springframework.data.redis.serializer.StringRedisSerializer; - -@Configuration -public class RedisConfig { - - //redis pub/sub을 처리하는 Listener - @Bean - public RedisMessageListenerContainer redisMessageListener(RedisConnectionFactory connectionFactory) { - RedisMessageListenerContainer container = new RedisMessageListenerContainer(); - container.setConnectionFactory(connectionFactory); - return container; - } - - @Bean - public RedisTemplate redisTemplate(RedisConnectionFactory connectionFactory) { - RedisTemplate redisTemplate = new RedisTemplate<>(); - redisTemplate.setConnectionFactory(connectionFactory); - redisTemplate.setKeySerializer(new StringRedisSerializer()); - redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<>(String.class)); - return redisTemplate; - } -} \ No newline at end of file diff --git a/src/main/java/com/kusitms/jipbap/message/deprecated/ChatService.java b/src/main/java/com/kusitms/jipbap/message/deprecated/ChatService.java deleted file mode 100644 index 363bf49..0000000 --- a/src/main/java/com/kusitms/jipbap/message/deprecated/ChatService.java +++ /dev/null @@ -1,54 +0,0 @@ -//package com.kusitms.jipbap.message; -// -//import com.fasterxml.jackson.databind.ObjectMapper; -//import jakarta.annotation.PostConstruct; -//import lombok.RequiredArgsConstructor; -//import lombok.extern.slf4j.Slf4j; -//import org.springframework.stereotype.Service; -//import org.springframework.web.socket.TextMessage; -//import org.springframework.web.socket.WebSocketSession; -// -//import java.io.IOException; -//import java.util.*; -// -//@Slf4j -//@RequiredArgsConstructor -//@Service -//public class ChatService { -// -// private final ObjectMapper objectMapper; -// private Map chatRooms; -// -// @PostConstruct -// private void init() { -// chatRooms = new LinkedHashMap<>(); -// } -// -// public List findAllRoom() { -// return new ArrayList<>(chatRooms.values()); -// } -// -// public ChatRoom findRoomById(String roomId) { -// return chatRooms.get(roomId); -// } -// -// public ChatRoom createRoom(String name) { -// String randomId = UUID.randomUUID().toString(); -// ChatRoom chatRoom = ChatRoom.builder() -// .roomId(randomId) -// .name(name) -// .build(); -// chatRooms.put(randomId, chatRoom); -// return chatRoom; -// } -// -// public void sendMessage(WebSocketSession session, T message) { -// try { -// session.sendMessage(new TextMessage(objectMapper.writeValueAsString(message))); -// } catch (IOException e) { -// log.error(e.getMessage(), e); -// } -// } -//} - -//ChatRoomRepository가 대체 \ No newline at end of file diff --git a/src/main/java/com/kusitms/jipbap/message/deprecated/EmbeddedRedisConfig.java b/src/main/java/com/kusitms/jipbap/message/deprecated/EmbeddedRedisConfig.java deleted file mode 100644 index 65b7002..0000000 --- a/src/main/java/com/kusitms/jipbap/message/deprecated/EmbeddedRedisConfig.java +++ /dev/null @@ -1,31 +0,0 @@ -//package com.kusitms.jipbap.message; -// -//import jakarta.annotation.PostConstruct; -//import jakarta.annotation.PreDestroy; -//import org.springframework.beans.factory.annotation.Value; -//import org.springframework.context.annotation.Configuration; -//import redis.embedded.RedisServer; -// -//@Configuration -//public class EmbeddedRedisConfig { -// -// @Value("${spring.data.redis.port}") -// private int redisPort; -// -// private RedisServer redisServer; -// -// @PostConstruct -// public void redisServer() { -// redisServer = RedisServer.builder() -// .port(6380) -// .build(); -// redisServer.start(); //doesn't work in macos 14 sonoma -// } -// -// @PreDestroy -// public void stopRedis() { -// if (redisServer != null) { -// redisServer.stop(); -// } -// } -//} \ No newline at end of file diff --git a/src/main/java/com/kusitms/jipbap/message/deprecated/WebSockChatHandler.java b/src/main/java/com/kusitms/jipbap/message/deprecated/WebSockChatHandler.java deleted file mode 100644 index 2695797..0000000 --- a/src/main/java/com/kusitms/jipbap/message/deprecated/WebSockChatHandler.java +++ /dev/null @@ -1,31 +0,0 @@ -//package com.kusitms.jipbap.message; -// -// -//import com.fasterxml.jackson.databind.ObjectMapper; -//import lombok.RequiredArgsConstructor; -//import lombok.extern.slf4j.Slf4j; -//import org.springframework.stereotype.Component; -//import org.springframework.web.socket.TextMessage; -//import org.springframework.web.socket.WebSocketSession; -//import org.springframework.web.socket.handler.TextWebSocketHandler; -// -//@Slf4j -//@RequiredArgsConstructor -//@Component -//public class WebSockChatHandler extends TextWebSocketHandler { -// private final ObjectMapper objectMapper; -// private final ChatService chatService; -// -// @Override -// protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { -// String payload = message.getPayload(); -// log.info("payload {}", payload); -//// 삭제 TextMessage textMessage = new TextMessage("Welcome chatting sever~^^ "); -//// 삭제 session.sendMessage(textMessage); -// ChatMessage chatMessage = objectMapper.readValue(payload, ChatMessage.class); -// ChatRoom room = chatService.findRoomById(chatMessage.getRoomId()); -// room.handleActions(session, chatMessage, chatService); -// } -//} - -//ChatController의 /chat/message가 대체 \ No newline at end of file diff --git a/src/main/java/com/kusitms/jipbap/store/Store.java b/src/main/java/com/kusitms/jipbap/store/Store.java new file mode 100644 index 0000000..cbf6724 --- /dev/null +++ b/src/main/java/com/kusitms/jipbap/store/Store.java @@ -0,0 +1,39 @@ +package com.kusitms.jipbap.store; + +import com.kusitms.jipbap.common.entity.DateEntity; +import com.kusitms.jipbap.user.User; +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.hibernate.annotations.ColumnDefault; + +@Entity +@Table(name = "tb_store") +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class Store extends DateEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name ="store_id") + private Long id; //고유 pk + + @OneToOne(fetch = FetchType.LAZY) + @JoinColumn + private User owner; + + private String name; + private String description; + + @ColumnDefault("false") + @Column(columnDefinition = "TINYINT(1)") + private Boolean koreanYn; //한국인 인증 여부 + + private Double avgRate; + + private String image; +} \ No newline at end of file diff --git a/src/main/java/com/kusitms/jipbap/store/StoreController.java b/src/main/java/com/kusitms/jipbap/store/StoreController.java new file mode 100644 index 0000000..deaed16 --- /dev/null +++ b/src/main/java/com/kusitms/jipbap/store/StoreController.java @@ -0,0 +1,34 @@ +package com.kusitms.jipbap.store; + +import com.kusitms.jipbap.common.response.CommonResponse; +import com.kusitms.jipbap.store.dto.RegisterStoreRequestDto; +import com.kusitms.jipbap.user.User; +import com.kusitms.jipbap.user.UserRepository; +import com.kusitms.jipbap.user.exception.UserNotFoundException; +import lombok.RequiredArgsConstructor; +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 javax.validation.Valid; + +@RestController +@RequestMapping("/store") +@RequiredArgsConstructor +public class StoreController { + + private final StoreRepository storeRepository; + private final UserRepository userRepository; + + @PostMapping + public CommonResponse registerStore(@Valid @RequestBody RegisterStoreRequestDto dto) { + User user = userRepository.findById(dto.getUserId()).orElseThrow(()->new UserNotFoundException("유저 없음")); + + storeRepository.save( + new Store(null, user, dto.getName(), dto.getDescription(), dto.getKoreanYn(), dto.getAvgRate(), null) + ); + + return new CommonResponse<>("가게 등록 완료"); + } +} diff --git a/src/main/java/com/kusitms/jipbap/store/StoreRepository.java b/src/main/java/com/kusitms/jipbap/store/StoreRepository.java new file mode 100644 index 0000000..27a10f8 --- /dev/null +++ b/src/main/java/com/kusitms/jipbap/store/StoreRepository.java @@ -0,0 +1,6 @@ +package com.kusitms.jipbap.store; + +import org.springframework.data.jpa.repository.JpaRepository; + +public interface StoreRepository extends JpaRepository { +} diff --git a/src/main/java/com/kusitms/jipbap/store/dto/RegisterStoreRequestDto.java b/src/main/java/com/kusitms/jipbap/store/dto/RegisterStoreRequestDto.java new file mode 100644 index 0000000..688a652 --- /dev/null +++ b/src/main/java/com/kusitms/jipbap/store/dto/RegisterStoreRequestDto.java @@ -0,0 +1,15 @@ +package com.kusitms.jipbap.store.dto; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class RegisterStoreRequestDto { + + private Long userId; + private String name; + private String description; + private Boolean koreanYn; + private Double avgRate; +} diff --git a/src/main/java/com/kusitms/jipbap/store/exception/StoreNotExistsException.java b/src/main/java/com/kusitms/jipbap/store/exception/StoreNotExistsException.java new file mode 100644 index 0000000..66c3e6a --- /dev/null +++ b/src/main/java/com/kusitms/jipbap/store/exception/StoreNotExistsException.java @@ -0,0 +1,7 @@ +package com.kusitms.jipbap.store.exception; + +public class StoreNotExistsException extends RuntimeException{ + public StoreNotExistsException(String message) { + super(message); + } +} diff --git a/src/main/java/com/kusitms/jipbap/test/TestController.java b/src/main/java/com/kusitms/jipbap/test/TestController.java index 047866b..1f72f06 100644 --- a/src/main/java/com/kusitms/jipbap/test/TestController.java +++ b/src/main/java/com/kusitms/jipbap/test/TestController.java @@ -1,19 +1,37 @@ package com.kusitms.jipbap.test; +import com.kusitms.jipbap.auth.exception.InvalidEmailException; +import com.kusitms.jipbap.security.Auth; +import com.kusitms.jipbap.security.AuthInfo; +import com.kusitms.jipbap.user.User; +import com.kusitms.jipbap.user.UserRepository; import io.swagger.v3.oas.annotations.Operation; +import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController +@RequestMapping("/test") +@RequiredArgsConstructor public class TestController { + private final UserRepository userRepository; + /** * 커넥션 테스트용 컨트롤러 * @return 성공시 String 반환 */ @Operation(summary = "ec2 서버 커넥션 테스트용 컨트롤러") - @GetMapping("/test") + @GetMapping public String test() { return "Healthy Connection"; } + + @Operation(summary = "ec2 서버 커넥션 테스트용 컨트롤러(인증)") + @GetMapping("/auth") + public String test2(@Auth AuthInfo authInfo) { + User user = userRepository.findByEmail(authInfo.getEmail()).orElseThrow(()->new InvalidEmailException("회원정보가 존재하지 않습니다.")); + return "Healthy Connection"; + } }