diff --git a/src/main/java/com/example/swcompetitionproject/config/StompWebSocketConfig.java b/src/main/java/com/example/swcompetitionproject/config/StompWebSocketConfig.java index f29bf04..871ebbb 100644 --- a/src/main/java/com/example/swcompetitionproject/config/StompWebSocketConfig.java +++ b/src/main/java/com/example/swcompetitionproject/config/StompWebSocketConfig.java @@ -10,7 +10,9 @@ public class StompWebSocketConfig implements WebSocketMessageBrokerConfigurer { @Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint("/ws/chat") // 클라이언트에서 서버로 WebSocket 연결하기 위해 /ws/chat 으로 요청을 보내도록 엔트포인트 설정 - .setAllowedOriginPatterns("*") // 클라이언트에서 웹 소켓 서버에 요청하는 모든 요청을 수락, CORS 방지 + .setAllowedOriginPatterns("*"); // 클라이언트에서 웹 소켓 서버에 요청하는 모든 요청을 수락, CORS 방지 + registry.addEndpoint("/ws/chat") + .setAllowedOriginPatterns("*") .withSockJS(); } diff --git a/src/main/java/com/example/swcompetitionproject/controller/ChatMessageController.java b/src/main/java/com/example/swcompetitionproject/controller/ChatMessageController.java index 0f9b5c9..cb94a4e 100644 --- a/src/main/java/com/example/swcompetitionproject/controller/ChatMessageController.java +++ b/src/main/java/com/example/swcompetitionproject/controller/ChatMessageController.java @@ -2,6 +2,7 @@ import com.example.swcompetitionproject.authentication.AuthenticatedUser; import com.example.swcompetitionproject.dto.request.chatting.ChatMessageDto; +import com.example.swcompetitionproject.entity.Message; import com.example.swcompetitionproject.entity.User; import com.example.swcompetitionproject.service.ChattingService; import lombok.RequiredArgsConstructor; @@ -11,6 +12,7 @@ import org.springframework.messaging.simp.SimpMessagingTemplate; import org.springframework.stereotype.Controller; +import java.util.List; import java.util.UUID; /** @@ -22,38 +24,57 @@ @RequiredArgsConstructor @Controller public class ChatMessageController { - private final SimpMessagingTemplate template; + private final SimpMessagingTemplate template; // 기본브로커 private final ChattingService chattingService; - //채팅방 입장하기 + /** + * 채팅방 입장하기 + **/ @MessageMapping("/ws/chat/{roomId}/enter") - public void enter(@DestinationVariable UUID roomId, @AuthenticatedUser User user, ChatMessageDto message) { - // 사용자를 채팅방에 추가 - chattingService.addUserToRoom(user, roomId); + public void enter(@DestinationVariable UUID roomId, @AuthenticatedUser User user) { + // 새로운 유저 판단 + boolean isNewUser = chattingService.isNewUserInRoom(user, roomId); - // 입장 메시지 생성 및 전송 - message.setContent(user.getStudentNumber() + "님이 입장하셨습니다."); - template.convertAndSend("/sub/ws/chat/room/" + roomId, message); - } + // 사용자가 처음 입장하는 경우에만 입장 메시지 전송 + if (isNewUser) { + // 사용자를 채팅방에 추가 + chattingService.addUserToRoom(user, roomId); + // 입장 메시지 생성 및 전송 + ChatMessageDto enterMessageDto = new ChatMessageDto(roomId, user.getName() + "님이 입장하셨습니다.", user.getStudentNumber()); + template.convertAndSend("/sub/ws/chat/room/" + roomId, enterMessageDto); + } - //채팅방 메세지 보내기 - @MessageMapping("/ws/chat/{roomId}/send") - public void message(@DestinationVariable UUID roomId, @AuthenticatedUser User user, ChatMessageDto message) { - // 메시지 저장 - chattingService.saveMessage(user, roomId, message); + // 기존 메시지들 전송 (처음 입장하는 유저가 아닌 경우에만) + if (!isNewUser) { + List previousMessages = chattingService.getMessagesByRoomId(roomId); + for (Message message : previousMessages) { + ChatMessageDto messageDto = new ChatMessageDto(roomId, message.getContent(), message.getSender()); + template.convertAndSend("/sub/ws/chat/room/" + roomId, messageDto); + } + } + } + /** + * 채팅방에 메시지 보내기 + **/ + @MessageMapping("/ws/chat/send") + public void message(ChatMessageDto message) { + // MessageRepository 에 메시지 저장 + chattingService.saveMessage(message); // 메시지 전송 - template.convertAndSend("/sub/ws/chat/room/" + roomId, message); + template.convertAndSend("/sub/ws/chat/room/" + message.getRoomId(), message); } - // 채팅방 퇴장하기 + /** + * 채팅방 퇴장하기 + **/ @MessageMapping("/ws/chat/{roomId}/quit") - public void quit(@DestinationVariable UUID roomId, @AuthenticatedUser User user, ChatMessageDto message) { + public void quit(@DestinationVariable UUID roomId, @AuthenticatedUser User user) { // 사용자를 채팅방에서 제거 chattingService.removeUserFromRoom(user, roomId); // 퇴장 메시지 생성 및 전송 - message.setContent(user.getStudentNumber() + "님이 퇴장하셨습니다."); - template.convertAndSend("/sub/ws/chat/room/" + roomId, message); + ChatMessageDto quitMessage = new ChatMessageDto(roomId, user.getName() + "님이 퇴장하셨습니다.", user.getStudentNumber()); + template.convertAndSend("/sub/ws/chat/room/" + roomId, quitMessage); } } diff --git a/src/main/java/com/example/swcompetitionproject/dto/request/chatting/ChatMessageDto.java b/src/main/java/com/example/swcompetitionproject/dto/request/chatting/ChatMessageDto.java index c4b3afe..2d656a3 100644 --- a/src/main/java/com/example/swcompetitionproject/dto/request/chatting/ChatMessageDto.java +++ b/src/main/java/com/example/swcompetitionproject/dto/request/chatting/ChatMessageDto.java @@ -5,10 +5,16 @@ import lombok.Getter; import lombok.Setter; +import java.util.UUID; + @Getter @Setter @AllArgsConstructor public class ChatMessageDto { + @NotBlank(message = "roomId를 입력해주세요.") + private UUID roomId; @NotBlank(message = "메세지 내용을 입력해주세요.") private String content; + @NotBlank(message = "메세지 발신자를 입력해주세요.") + private String sender; } diff --git a/src/main/java/com/example/swcompetitionproject/entity/ChattingRoom.java b/src/main/java/com/example/swcompetitionproject/entity/ChattingRoom.java index 487731b..877f656 100644 --- a/src/main/java/com/example/swcompetitionproject/entity/ChattingRoom.java +++ b/src/main/java/com/example/swcompetitionproject/entity/ChattingRoom.java @@ -28,9 +28,6 @@ public class ChattingRoom extends BaseEntity { @JoinColumn(name = "board_id") private Board board; - @OneToMany(mappedBy = "chattingRoom", cascade = CascadeType.ALL, orphanRemoval = true) - private List roomMembers; - @OneToMany(mappedBy = "chattingRoom", cascade = CascadeType.ALL, orphanRemoval = true) private List messages; diff --git a/src/main/java/com/example/swcompetitionproject/entity/RoomMember.java b/src/main/java/com/example/swcompetitionproject/entity/RoomMember.java deleted file mode 100644 index 99ea8f2..0000000 --- a/src/main/java/com/example/swcompetitionproject/entity/RoomMember.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.example.swcompetitionproject.entity; - -import jakarta.persistence.*; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; - -@Builder -@AllArgsConstructor -@NoArgsConstructor -@Entity -@Getter -@Table(name = "room_members") -public class RoomMember extends BaseEntity { - //채팅방 멤버 이름 - @Column(nullable = false) - private String name; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "chattingRoom_id") - private ChattingRoom chattingRoom; -} diff --git a/src/main/java/com/example/swcompetitionproject/exception/ErrorCode.java b/src/main/java/com/example/swcompetitionproject/exception/ErrorCode.java index 12e8db3..a3396b4 100644 --- a/src/main/java/com/example/swcompetitionproject/exception/ErrorCode.java +++ b/src/main/java/com/example/swcompetitionproject/exception/ErrorCode.java @@ -11,9 +11,11 @@ public enum ErrorCode { //UnauthorizedException INVALID_TOKEN("4010", "유효하지 않은 토큰입니다."), - INVALID_DORMITORY("4042", "유효하지 않은 기숙사입니다."), + INVALID_DORMITORY("4012", "유효하지 않은 기숙사입니다."), + ROOM_FULL("4013","채팅방 인원이 가득찼습니다."), //ForbiddenException + NO_ACCESS("4030", "접근 권한이 없습니다."), //NotFoundException COOKIE_NOT_FOUND("4040", "쿠키를 찾을 수 없습니다."), diff --git a/src/main/java/com/example/swcompetitionproject/repository/MessageRepository.java b/src/main/java/com/example/swcompetitionproject/repository/MessageRepository.java index 3c71dd0..a4d223d 100644 --- a/src/main/java/com/example/swcompetitionproject/repository/MessageRepository.java +++ b/src/main/java/com/example/swcompetitionproject/repository/MessageRepository.java @@ -1,9 +1,12 @@ package com.example.swcompetitionproject.repository; +import com.example.swcompetitionproject.entity.ChattingRoom; import com.example.swcompetitionproject.entity.Message; import org.springframework.data.jpa.repository.JpaRepository; +import java.util.List; import java.util.UUID; public interface MessageRepository extends JpaRepository { + List findByChattingRoomOrderByCreatedAtAsc(ChattingRoom room); } diff --git a/src/main/java/com/example/swcompetitionproject/repository/RoomMemberRepository.java b/src/main/java/com/example/swcompetitionproject/repository/RoomMemberRepository.java deleted file mode 100644 index 80509a9..0000000 --- a/src/main/java/com/example/swcompetitionproject/repository/RoomMemberRepository.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.example.swcompetitionproject.repository; - -import com.example.swcompetitionproject.entity.ChattingRoom; -import com.example.swcompetitionproject.entity.RoomMember; -import org.springframework.data.jpa.repository.JpaRepository; - -import java.util.Optional; -import java.util.UUID; - -public interface RoomMemberRepository extends JpaRepository { - Optional findByNameAndChattingRoom(String name, ChattingRoom chattingRoom); -} diff --git a/src/main/java/com/example/swcompetitionproject/repository/UserRoomRepository.java b/src/main/java/com/example/swcompetitionproject/repository/UserRoomRepository.java index 7e140e0..b6dae2e 100644 --- a/src/main/java/com/example/swcompetitionproject/repository/UserRoomRepository.java +++ b/src/main/java/com/example/swcompetitionproject/repository/UserRoomRepository.java @@ -1,12 +1,18 @@ package com.example.swcompetitionproject.repository; +import com.example.swcompetitionproject.entity.ChattingRoom; import com.example.swcompetitionproject.entity.User; import com.example.swcompetitionproject.entity.UserRoom; import org.springframework.data.jpa.repository.JpaRepository; import java.util.List; +import java.util.Optional; import java.util.UUID; public interface UserRoomRepository extends JpaRepository { List findByUser(User user); + + Optional findByUserAndChattingRoom(User user, ChattingRoom chattingRoom); + + Boolean existsByUserAndChattingRoom(User user, ChattingRoom chattingRoom); } diff --git a/src/main/java/com/example/swcompetitionproject/service/BoardService.java b/src/main/java/com/example/swcompetitionproject/service/BoardService.java index c1712d8..dd8d4d7 100644 --- a/src/main/java/com/example/swcompetitionproject/service/BoardService.java +++ b/src/main/java/com/example/swcompetitionproject/service/BoardService.java @@ -9,11 +9,13 @@ import com.example.swcompetitionproject.entity.DormitoryType; import com.example.swcompetitionproject.entity.User; import com.example.swcompetitionproject.exception.ErrorCode; +import com.example.swcompetitionproject.exception.ForbiddenException; import com.example.swcompetitionproject.exception.NotFoundException; import com.example.swcompetitionproject.exception.UnauthorizedException; import com.example.swcompetitionproject.repository.BoardRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import java.util.List; import java.util.UUID; @@ -28,6 +30,7 @@ public class BoardService { /** * 게시글 전체 조회 **/ + @Transactional public BoardListData getBoardList(String dormitory) { DormitoryType dormitoryType = dormitoryNameValidate(dormitory); List boards = boardRepository.findAllByDormitoryOrderByCreatedAtDesc(dormitoryType); @@ -37,6 +40,7 @@ public BoardListData getBoardList(String dormitory) { /** * 게시글 상세 조회 **/ + @Transactional public BoardData getBoardById(String dormitory, UUID boardId) { DormitoryType dormitoryType = dormitoryNameValidate(dormitory); Board board = boardRepository.findBoardByDormitoryAndId(dormitoryType, boardId) @@ -47,6 +51,7 @@ public BoardData getBoardById(String dormitory, UUID boardId) { /** * 게시글 작성 **/ + @Transactional public void createBoard(String dormitory, CreateBoardDto createBoardDto, User user) { DormitoryType dormitoryType = dormitoryNameValidate(dormitory); @@ -90,6 +95,7 @@ public void createBoard(String dormitory, CreateBoardDto createBoardDto, User us /** * 게시글 수정 **/ + @Transactional public void updateBoard(String dormitory, UUID boardId, UpdateBoardDto updateBoardDto, User user) { DormitoryType dormitoryType = dormitoryNameValidate(dormitory); Board updateBoard = boardValidate(dormitoryType, boardId, user); @@ -100,6 +106,7 @@ public void updateBoard(String dormitory, UUID boardId, UpdateBoardDto updateBoa /** * 게시글 삭제 **/ + @Transactional public void deleteBoard(String dormitory, UUID boardId, User user) { DormitoryType dormitoryType = dormitoryNameValidate(dormitory); Board board = boardValidate(dormitoryType, boardId, user); @@ -108,11 +115,11 @@ public void deleteBoard(String dormitory, UUID boardId, User user) { } /** - * 게시글 존재 여부 확인 로직 + * 접근 유저의 게시글 권한 확인 **/ public Board boardValidate(DormitoryType dormitoryType, UUID boardId, User user) { return boardRepository.findBoardByDormitoryAndIdAndUser(dormitoryType, boardId, user) - .orElseThrow(() -> new NotFoundException(ErrorCode.BOARD_NOT_FOUND)); + .orElseThrow(() -> new ForbiddenException(ErrorCode.NO_ACCESS)); } /** diff --git a/src/main/java/com/example/swcompetitionproject/service/ChattingService.java b/src/main/java/com/example/swcompetitionproject/service/ChattingService.java index b2b3ebc..4b5c067 100644 --- a/src/main/java/com/example/swcompetitionproject/service/ChattingService.java +++ b/src/main/java/com/example/swcompetitionproject/service/ChattingService.java @@ -5,6 +5,7 @@ import com.example.swcompetitionproject.entity.*; import com.example.swcompetitionproject.exception.ErrorCode; import com.example.swcompetitionproject.exception.NotFoundException; +import com.example.swcompetitionproject.exception.UnauthorizedException; import com.example.swcompetitionproject.repository.*; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -20,10 +21,11 @@ public class ChattingService { private final ChattingRoomRepository chattingRoomRepository; private final MessageRepository messageRepository; - private final RoomMemberRepository roomMemberRepository; private final UserRoomRepository userRoomRepository; - //채팅방 목록 조회 + /** + * 채팅방 목록 조회 + **/ @Transactional public ChattingRoomListData getRoomList(User user) { List rooms = userRoomRepository.findByUser(user).stream() @@ -33,12 +35,14 @@ public ChattingRoomListData getRoomList(User user) { return ChattingRoomListData.from(rooms); } - //채팅방 생성하기 - 게시글 생성 시 채팅방 생성 + /** + * 채팅방 생성하기 - 게시글 작성 시에 채팅방 생성 + **/ @Transactional public ChattingRoom createRoom(User user, Board board) { ChattingRoom room = ChattingRoom.builder() .title(board.getTitle()) - .manager(user.getStudentNumber()) + .manager(user.getName()) .memberCount(1) .board(board) .build(); @@ -52,55 +56,87 @@ public ChattingRoom createRoom(User user, Board board) { return chattingRoomRepository.save(room); } - //채팅 메시지 저장 + /** + * 채팅 메세지 저장하기 + **/ @Transactional - public Message saveMessage(User user, UUID roomId, ChatMessageDto message) { - ChattingRoom room = chattingRoomRepository.findById(roomId) + public Message saveMessage(ChatMessageDto message) { + ChattingRoom room = chattingRoomRepository.findById(message.getRoomId()) .orElseThrow(() -> new NotFoundException(ErrorCode.ROOM_NOT_FOUND)); Message newMessage = Message.builder() .content(message.getContent()) - .sender(user.getStudentNumber()) + .sender(message.getSender()) .chattingRoom(room) .build(); return messageRepository.save(newMessage); } - //사용자를 채팅방에 추가 + /** + * 채팅방에 사용자 추가하기 + **/ @Transactional public void addUserToRoom(User user, UUID roomId) { ChattingRoom room = chattingRoomRepository.findById(roomId) .orElseThrow(() -> new NotFoundException(ErrorCode.ROOM_NOT_FOUND)); - RoomMember member = RoomMember.builder() - .name(user.getStudentNumber()) + // 채팅방의 현재 인원 수와 최대 인원 수 비교 + if (room.getMemberCount() >= room.getBoard().getTotal()) { + throw new UnauthorizedException(ErrorCode.ROOM_FULL); + } + + UserRoom userRoom = UserRoom.builder() + .user(user) .chattingRoom(room) .build(); - roomMemberRepository.save(member); + userRoomRepository.save(userRoom); room.setMemberCount(room.getMemberCount() + 1); chattingRoomRepository.save(room); } - // 채팅방 퇴장하기 + /** + * 채팅방 퇴장하기 + **/ @Transactional public void removeUserFromRoom(User user, UUID roomId) { ChattingRoom room = chattingRoomRepository.findById(roomId) .orElseThrow(() -> new NotFoundException(ErrorCode.ROOM_NOT_FOUND)); - RoomMember member = roomMemberRepository.findByNameAndChattingRoom(user.getStudentNumber(), room) + UserRoom userRoom = userRoomRepository.findByUserAndChattingRoom(user, room) .orElseThrow(() -> new NotFoundException(ErrorCode.USER_NOT_FOUND)); - - roomMemberRepository.delete(member); + userRoomRepository.delete(userRoom); room.setMemberCount(room.getMemberCount() - 1); chattingRoomRepository.save(room); } - // 채팅방 삭제하기 + /** + * 채팅방 삭제하기 + **/ public void deleteRoom(Board board) { chattingRoomRepository.deleteByBoard(board); } -} + /** + * 기존 메시지 조회 및 반환 + **/ + @Transactional + public List getMessagesByRoomId(UUID roomId) { + ChattingRoom room = chattingRoomRepository.findById(roomId) + .orElseThrow(() -> new NotFoundException(ErrorCode.ROOM_NOT_FOUND)); + return messageRepository.findByChattingRoomOrderByCreatedAtAsc(room); + } + + /** + * 채팅방에 처음 입장한 유저인지 확인 + **/ + @Transactional + public boolean isNewUserInRoom(User user, UUID roomId) { + ChattingRoom chattingRoom = chattingRoomRepository.findById(roomId) + .orElseThrow(() -> new NotFoundException(ErrorCode.ROOM_NOT_FOUND)); + + return !userRoomRepository.existsByUserAndChattingRoom(user, chattingRoom); + } +}