forked from EveryUniv/next-student-council-backend
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #10 from kjungw1025/feat/chatting
feat: WebSocket과 Kafka를 이용한 채팅 기능 임시 구현
- Loading branch information
Showing
128 changed files
with
62,053 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
149 changes: 149 additions & 0 deletions
149
src/main/java/com/dku/council/domain/chat/controller/ChatController.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
package com.dku.council.domain.chat.controller; | ||
|
||
import com.dku.council.domain.chat.model.MessageType; | ||
import com.dku.council.domain.chat.model.dto.Message; | ||
import com.dku.council.domain.chat.model.dto.request.RequestChatDto; | ||
import com.dku.council.domain.chat.model.dto.response.ResponseChatDto; | ||
import com.dku.council.domain.chat.service.ChatService; | ||
import com.dku.council.domain.chat.service.MessageSender; | ||
import com.dku.council.global.auth.role.UserAuth; | ||
import io.swagger.v3.oas.annotations.tags.Tag; | ||
import lombok.RequiredArgsConstructor; | ||
import lombok.extern.slf4j.Slf4j; | ||
import org.springframework.beans.factory.annotation.Value; | ||
import org.springframework.context.event.EventListener; | ||
import org.springframework.messaging.handler.annotation.MessageMapping; | ||
import org.springframework.messaging.handler.annotation.Payload; | ||
import org.springframework.messaging.simp.SimpMessageHeaderAccessor; | ||
import org.springframework.messaging.simp.SimpMessageSendingOperations; | ||
import org.springframework.messaging.simp.stomp.StompHeaderAccessor; | ||
import org.springframework.stereotype.Controller; | ||
import org.springframework.web.bind.annotation.GetMapping; | ||
import org.springframework.web.bind.annotation.ResponseBody; | ||
import org.springframework.web.socket.messaging.SessionDisconnectEvent; | ||
|
||
import java.util.List; | ||
|
||
@Tag(name = "채팅", description = "채팅 송/수신 관련 api") | ||
@Controller | ||
@RequiredArgsConstructor | ||
@Slf4j | ||
public class ChatController { | ||
|
||
@Value("${spring.kafka.consumer.topic}") | ||
private String topic; | ||
|
||
/** | ||
* 아래에서 사용되는 convertAndSend 를 사용하기 위한 선언 | ||
* convertAndSend 는 객체를 인자로 넘겨주면 자동으로 Message 객체로 변환 후 도착지로 전송한다. | ||
*/ | ||
private final SimpMessageSendingOperations template; | ||
|
||
private final ChatService chatService; | ||
private final MessageSender sender; | ||
|
||
/** | ||
* 채팅방 별, 입장 이벤트 발생시 처리되는 기능 | ||
* | ||
* @param chat 채팅으로 보낼 메시지 정보 관련 param | ||
* | ||
* MessageMapping 을 통해 webSocket 로 들어오는 메시지를 발신 처리한다. | ||
* 이때 클라이언트에서는 /pub/chat/sendMessage 로 요청하게 되고 이것을 controller 가 받아서 처리한다. | ||
* 처리가 완료되면 /sub/chatRoom/enter/roomId 로 메시지가 전송된다. | ||
*/ | ||
@MessageMapping("/chat/enterUser") | ||
public void enterUser(@Payload RequestChatDto chat, | ||
SimpMessageHeaderAccessor headerAccessor) { | ||
// 채팅방 유저+1 | ||
chatService.plusUserCnt(chat.getRoomId()); | ||
|
||
// 채팅방에 유저 추가 및 UserUUID 반환 | ||
String username = chatService.addUser(chat.getRoomId(), chat.getSender()); | ||
log.info("enterUser에서 uuid " + username); | ||
log.info("enterUser에서 roomId " + chat.getRoomId()); | ||
|
||
// 반환 결과를 socket session 에 userUUID 로 저장 | ||
headerAccessor.getSessionAttributes().put("username", username); | ||
headerAccessor.getSessionAttributes().put("roomId", chat.getRoomId()); | ||
|
||
Message message = Message.builder() | ||
.type(chat.getType()) | ||
.roomId(chat.getRoomId()) | ||
.sender(chat.getSender()) | ||
.message(chat.getSender() + " 님 입장!!") | ||
.build(); | ||
|
||
sender.send(topic, message); | ||
} | ||
|
||
/** | ||
* 채팅방 별, 채팅 메시지 전송 기능 | ||
* | ||
* @param chat 채팅으로 보낼 메시지 정보 관련 param | ||
*/ | ||
@MessageMapping("/chat/sendMessage") | ||
public void sendMessage(@Payload RequestChatDto chat) { | ||
log.info("CHAT {}", chat); | ||
|
||
Message message = Message.builder() | ||
.type(chat.getType()) | ||
.roomId(chat.getRoomId()) | ||
.sender(chat.getSender()) | ||
.message(chat.getMessage()) | ||
.build(); | ||
|
||
sender.send(topic, message); | ||
} | ||
|
||
/** | ||
* 채팅방 별, 퇴장 이벤트 발생시 처리되는 기능 | ||
* | ||
* 유저 퇴장 시에는 EventListener 을 통해서 유저 퇴장을 확인 | ||
*/ | ||
@EventListener | ||
public void webSocketDisconnectListener(SessionDisconnectEvent event) { | ||
log.info("DisConnEvent {}", event); | ||
|
||
StompHeaderAccessor headerAccessor = StompHeaderAccessor.wrap(event.getMessage()); | ||
|
||
// stomp 세션에 있던 username과 roomId 를 확인해서 채팅방 유저 리스트와 room 에서 해당 유저를 삭제 | ||
String username = (String) headerAccessor.getSessionAttributes().get("username"); | ||
String roomId = (String) headerAccessor.getSessionAttributes().get("roomId"); | ||
log.info("퇴장 controller에서 uuid " + username); | ||
log.info("퇴장 controller에서 roomId " + roomId); | ||
|
||
log.info("headAccessor {}", headerAccessor); | ||
|
||
// 채팅방 유저 -1 | ||
chatService.minusUserCnt(roomId); | ||
|
||
// 채팅방 유저 리스트에서 유저 삭제 | ||
chatService.delUser(roomId, username); | ||
|
||
if (username != null) { | ||
log.info("User Disconnected : ", username); | ||
|
||
// builder 어노테이션 활용 | ||
Message message = Message.builder() | ||
.type(MessageType.LEAVE) | ||
.sender(username) | ||
.roomId(roomId) | ||
.message(username + " 님 퇴장!!") | ||
.build(); | ||
|
||
sender.send(topic, message); | ||
} | ||
} | ||
|
||
/** | ||
* 채팅방 별, 채팅에 참여한 유저 리스트 반환 | ||
* | ||
* @param roomId 채팅방 id | ||
*/ | ||
@GetMapping("/chat/userlist") | ||
@UserAuth | ||
@ResponseBody | ||
public List<String> userList(String roomId) { | ||
return chatService.getUserList(roomId); | ||
} | ||
} |
133 changes: 133 additions & 0 deletions
133
src/main/java/com/dku/council/domain/chat/controller/ChatRoomController.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
package com.dku.council.domain.chat.controller; | ||
|
||
import com.dku.council.domain.chat.model.dto.response.ResponseChatRoomDto; | ||
import com.dku.council.domain.chat.service.ChatService; | ||
import com.dku.council.domain.user.model.dto.response.ResponseUserInfoForChattingDto; | ||
import com.dku.council.domain.user.service.UserService; | ||
import com.dku.council.global.auth.jwt.AppAuthentication; | ||
import com.dku.council.global.auth.role.UserAuth; | ||
import io.swagger.v3.oas.annotations.tags.Tag; | ||
import lombok.RequiredArgsConstructor; | ||
import lombok.extern.slf4j.Slf4j; | ||
import org.springframework.beans.factory.annotation.Autowired; | ||
import org.springframework.stereotype.Controller; | ||
import org.springframework.ui.Model; | ||
import org.springframework.web.bind.annotation.*; | ||
import org.springframework.web.servlet.mvc.support.RedirectAttributes; | ||
|
||
@Tag(name = "채팅방", description = "채팅방 관련 api") | ||
@Controller | ||
@RequestMapping("/chatRoom") | ||
@RequiredArgsConstructor | ||
@Slf4j | ||
public class ChatRoomController { | ||
@Autowired | ||
private ChatService chatService; | ||
|
||
private final UserService userService; | ||
|
||
/** | ||
* 채팅방 리스트 화면 | ||
*/ | ||
@GetMapping | ||
@UserAuth | ||
public String goChatRoom(Model model, AppAuthentication auth) { | ||
ResponseUserInfoForChattingDto responseUserInfoForChattingDto = userService.getUserInfoForChatting(auth.getUserId()); | ||
|
||
model.addAttribute("list", chatService.findAllRoom()); | ||
model.addAttribute("user", responseUserInfoForChattingDto); | ||
|
||
log.info("SHOW ALL ChatList {}", chatService.findAllRoom()); | ||
|
||
return "/page/chatting/roomlist"; | ||
} | ||
|
||
/** | ||
* 채팅방 생성 | ||
* | ||
* @param name 채팅방 이름 | ||
* @param roomPwd 채팅방 비밀번호 | ||
* @param secretCheck 채팅방 잠금 설정 여부 | ||
* @param maxUserCount 채팅방 최대 인원 수 설정 (default = 10) | ||
*/ | ||
@PostMapping("/create") | ||
@UserAuth | ||
public String createRoom(@RequestParam("roomName") String name, | ||
@RequestParam("roomPwd") String roomPwd, | ||
@RequestParam("secretChk") String secretCheck, | ||
@RequestParam(value = "maxUserCount", defaultValue = "10") String maxUserCount, | ||
AppAuthentication auth, | ||
RedirectAttributes rttr) { | ||
|
||
ResponseChatRoomDto room = chatService.createChatRoom(name, | ||
roomPwd, | ||
Boolean.parseBoolean(secretCheck), | ||
Integer.parseInt(maxUserCount), | ||
auth.getUserId()); | ||
|
||
log.info("CREATE Chat Room [{}]", room); | ||
|
||
rttr.addFlashAttribute("roomName", room); | ||
return "redirect:/chatRoom"; | ||
} | ||
|
||
|
||
// 채팅방 입장 화면 | ||
// 파라미터로 넘어오는 roomId 를 확인후 해당 roomId 를 기준으로 | ||
// 채팅방을 찾아서 클라이언트를 chatroom 으로 보낸다. | ||
@GetMapping("/enter") | ||
@UserAuth | ||
public String roomDetail(Model model, String roomId, AppAuthentication auth){ | ||
|
||
log.info("/chatRoom/enter : roomId {}", roomId); | ||
|
||
model.addAttribute("user", userService.getUserInfoForChatting(auth.getUserId())); | ||
model.addAttribute("room", chatService.findRoomById(roomId)); | ||
|
||
return "/page/chatting/chatroom"; | ||
} | ||
|
||
/** | ||
* 채팅방 비밀번호 확인 | ||
* | ||
* @param roomId 채팅방 id | ||
* @param roomPwd 사용자가 입력한 비밀번호 | ||
* @return 사용자가 입력한 비밀번호가 일치하면 true, 아니면 false | ||
*/ | ||
@PostMapping("/confirmPwd/{roomId}") | ||
@UserAuth | ||
@ResponseBody | ||
public boolean confirmPwd(@PathVariable String roomId, | ||
@RequestParam String roomPwd){ | ||
|
||
return chatService.confirmPwd(roomId, roomPwd); | ||
} | ||
|
||
/** | ||
* 채팅방 삭제 | ||
* | ||
* @param roomId 채팅방 id | ||
*/ | ||
@DeleteMapping("/delete/{roomId}") | ||
@UserAuth | ||
public String delChatRoom(@PathVariable String roomId, AppAuthentication auth){ | ||
|
||
// roomId(UUID 값) 기준으로 채팅방 삭제 | ||
chatService.delChatRoom(auth.getUserId(), roomId, auth.isAdmin()); | ||
|
||
return "redirect:/chatRoom"; | ||
} | ||
|
||
/** | ||
* maxUserCount에 따른 채팅방 입장 여부 | ||
* | ||
* @param roomId 채팅방 Id | ||
* @return true/false | ||
*/ | ||
@GetMapping("/chkUserCnt/{roomId}") | ||
@ResponseBody | ||
public boolean chkUserCnt(@PathVariable String roomId){ | ||
|
||
return chatService.chkRoomUserCnt(roomId); | ||
} | ||
} |
8 changes: 8 additions & 0 deletions
8
src/main/java/com/dku/council/domain/chat/exception/ChatRoomNotFoundException.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package com.dku.council.domain.chat.exception; | ||
|
||
import com.dku.council.global.error.exception.LocalizedMessageException; | ||
import org.springframework.http.HttpStatus; | ||
|
||
public class ChatRoomNotFoundException extends LocalizedMessageException { | ||
public ChatRoomNotFoundException() { super(HttpStatus.NOT_FOUND, "notfound.chat-room"); } | ||
} |
23 changes: 23 additions & 0 deletions
23
src/main/java/com/dku/council/domain/chat/model/ChatRoomStatus.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
package com.dku.council.domain.chat.model; | ||
|
||
public enum ChatRoomStatus { | ||
/** | ||
* 활성화 상태 | ||
*/ | ||
ACTIVE, | ||
|
||
/** | ||
* 닫힌 상태 | ||
*/ | ||
CLOSED, | ||
|
||
/** | ||
* 삭제된 상태 | ||
*/ | ||
DELETED, | ||
|
||
/** | ||
* 운영자에 의해 삭제된 상태 | ||
*/ | ||
DELETED_BY_ADMIN | ||
} |
19 changes: 19 additions & 0 deletions
19
src/main/java/com/dku/council/domain/chat/model/MessageType.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package com.dku.council.domain.chat.model; | ||
|
||
public enum MessageType { | ||
/** | ||
* 채팅방 입장 | ||
*/ | ||
ENTER, | ||
|
||
/** | ||
* 채팅방 대화중 | ||
* (해당 채팅방을 sub하고 있는 모든 client들에게 전달됨) | ||
*/ | ||
TALK, | ||
|
||
/** | ||
* 채팅방 퇴장 | ||
*/ | ||
LEAVE; | ||
} |
Oops, something went wrong.