@Slf4j
@RequiredArgsConstructor
@Controller
public class ChatController {

    private final ChatMessageService chatMessageService;

    @MessageMapping("/chats/join/{postId}")
    @SendTo("/sub/chats/room/{postId}")
    public ChatMessageResponse join(
        @DestinationVariable(value = "postId") String postId,
        ChatMessageRequest req
    ) {
        String content = req.sender() + "님이 입장하셨습니다.";

        return new ChatMessageResponse(content);
    }

    @MessageMapping("/chats/message/{postId}")
    @SendTo("/sub/chats/room/{postId}")
    public ChatMessageResponse message(
        @DestinationVariable(value = "postId") String postId,
        ChatMessageRequest req
    ) {
        String content = req.content();
        String username = req.sender();

        chatMessageService.saveChatMessage(postId, username, content);

        return new ChatMessageResponse(content);
    }

}
public record ChatMessageRequest(String content, String sender) {

}
public record ChatMessageResponse(String content) {

}
@Getter
@NoArgsConstructor
@Document(collection = "chat_message")
public class ChatMessage extends BaseTime {

    @Id
    private String id;

    @Field
    private String postId;

    @Field
    private String sender;

    @Field
    private String content;

    @Field
    private String userId;

    @Builder
    public ChatMessage(String postId, String sender, String content, String userId) {
        this.postId = postId;
        this.sender = sender;
        this.content = content;
        this.userId = userId;
    }

}
@Getter
@NoArgsConstructor
@Entity
@Table(name = "tb_chatroom")
public class ChatRoom {

    @Id
    private Long postId;

    public ChatRoom(Long postId) {
        this.postId = postId;
    }

}
public interface ChatMessageRepository extends MongoRepository<ChatMessage, Long> {

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

}
@RequiredArgsConstructor
@Service
public class ChatMessageServiceImpl implements ChatMessageService {

    private final ChatMessageRepository chatMessageRepository;
    private final UserRepository userRepository;

    @Override
    public void saveChatMessage(String postId, String sender, String content) {

        User user = findByNickname(sender);

        ChatMessage chatMessage = ChatMessage.builder()
            .postId(postId)
            .sender(sender)
            .content(content)
            .userId(String.valueOf(user.getId()))
            .build();

        chatMessageRepository.save(chatMessage);

    }

    private User findByNickname(String nickname) {
        return userRepository.findByNickname(nickname)
            .orElseThrow(() -> new GlobalException(UserErrorCode.UNAUTHORIZED_USER));
    }

}
@RequiredArgsConstructor
@Service
public class ChatRoomServiceImpl implements ChatRoomService {

    private final ChatRoomRepository chatRoomRepository;

    // 채팅방 생성
    @Override
    public void createRoom(Long postId) {
        ChatRoom newRoom = new ChatRoom(postId);
        chatRoomRepository.save(newRoom);
    }

    // 채팅방 삭제
    @Override
    public void deleteRoom(Long postId) {
        chatRoomRepository.deleteById(postId);
    }

}
public interface ChatMessageService {

    /**
     * @param postId  채팅방 id
     * @param sender  채팅 작성자
     * @param content 채팅 내용
     */
    void saveChatMessage(String postId, String sender, String content);

}
public interface ChatRoomService {

    /**
     * @param postId : 채팅방 id로 사용할 postId
     */
    void createRoom(Long postId);

    /**
     * @param postId : 채팅방 삭제를 위한 postId
     */
    void deleteRoom(Long postId);
}
@RequiredArgsConstructor
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

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

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

}
@Controller
@RequiredArgsConstructor
public class ChatFrontController {

    @GetMapping("/chat/{postId}")
    public String getChatPage(
            @PathVariable Long postId,
            Model model,
            @AuthenticationPrincipal UserDetailsImpl userDetails
        ) {
        model.addAttribute("postId", postId);
        model.addAttribute("username", userDetails.getUsername()); // 현재 로그인된 사용자 이름
        return "domain/chat/chatroom"; // Thymeleaf 템플릿 이름
    }

}