본문 바로가기

프로젝트 & TIL/일별 공부 기록 (백엔드 스쿨)

83일차 - Stomp를 이용한 채팅 기능 구현하기

의존성 추가하기

- build.gradle

dependencies {
	
    ...
    
	implementation 'org.springframework.boot:spring-boot-starter-websocket'
	implementation 'org.webjars:sockjs-client:1.5.1'
	implementation 'org.webjars:stomp-websocket:2.3.4'
}

Config 파일 수정

@Configuration
@EnableWebSocketMessageBroker
public class StompWebSocketConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/chat")
                .setAllowedOrigins("http://localhost:8080")
                .withSockJS();
    }

    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        registry.setApplicationDestinationPrefixes("/pub");
        registry.enableSimpleBroker("/sub");
    }
}

WebSocketMessageBrokerConfigurer를 구현한다.


ChatRoom 구현

@Getter
@Builder
@Entity
public class ChatRoom {

    @Id
    private String id;
    private String name;

    public static ChatRoom create(String name) {
        ChatRoom chatRoom = new ChatRoom();
        chatRoom.id = UUID.randomUUID().toString();
        chatRoom.name = name;
        return chatRoom;
    }
    
}

ChatMessage 구현

@Getter
@Builder
@Entity
public class ChatMessage {

    @Id @GeneratedValue(strategy = IDENTITY)
    private Long id;
    private String roomId;
    private String sender;
    private String message;
    
}

Repository

public interface ChatRoomRepository extends JpaRepository<ChatRoom, Long> {
}
public interface ChatMessageRepository extends JpaRepository<ChatMessage, Long> {
}

ChatRoomService

@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class ChatRoomService {

    private final ChatRoomRepository chatRoomRepository;

    @Transactional
    public ChatRoom create(String name) {
        ChatRoom chatRoom = ChatRoom.builder().name(name).build();
        chatRoomRepository.save(chatRoom);
        return chatRoom;
    }

    public ChatRoom findById(Long id) {
        Optional<ChatRoom> chatRoom = chatRoomRepository.findById(id);
        if (chatRoom.isEmpty()) {
//            [ErrorCode] 존재하지 않는 채팅방입니다.
        }
        return chatRoom.get();
    }
    
    public List<ChatRoom> findAll() {
        return chatRoomRepository.findAll();
    }

}

ChatController

@Controller
@RequiredArgsConstructor
public class ChatMessageController {

    private final ChatMessageService chatMessageService;
    private final SimpMessageSendingOperations messagingTemplate;

    @MessageMapping("/chat/message")
    public void message(ChatMessage message) {
        messagingTemplate.convertAndSend("/sub/chat/room/%d".formatted(message.getRoomId()));
    }

}

ChatRoomController

@RequiredArgsConstructor
@Controller
@RequestMapping("/chat")
public class ChatRoomController {

    private final ChatRoomService chatRoomService;

    @GetMapping("/room/{id}")
    public String showRoom(@PathVariable Long id, Model model) {
        ChatRoom room = chatRoomService.findById(id);
        model.addAttribute("room", room);
        return "chat/room";
    }

    @GetMapping("/rooms")
    public String findAll(Model model) {
        List<ChatRoom> chatRooms = chatRoomService.findAll();
        model.addAttribute("chatRooms", chatRooms);
        return "chat/rooms";
    }

    @PostMapping("/create")
    public String create(@RequestParam String name) {
        ChatRoom chatRoom = chatRoomService.create(name);
        return "redirect:/room/%d".formatted(chatRoom.getId());
    }

}

뷰 단 생성과 테스트는 아직이다...

글을 작성하면서 블로그를 참고한 내용과 직접 작성한 내용을 섞어놔서 말이 안 되는(?) 부분이 있을 수도 있다.

내 경우에는 RDB를 사용하는 것을 가정하고 구현했고, 여러 블로그들의 포스트 내용은 Map<String, ChatRoom>을 생성해서 DB 대신 사용하는 내용이기 때문이다. ChatMessage와 ChatRoom의 내용도 다르지만 이 부분은 내 코드와 똑같이 구현하려면 다른 엔티티도 만들어야 하는 등의 문제가 있어 다른 블로그들의 내용과 좀더 비슷하게 작성했다.