Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,13 @@ dependencies {
//prometheus
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'io.micrometer:micrometer-registry-prometheus'

// websocket + stomp
implementation 'org.springframework.boot:spring-boot-starter-websocket'

}


tasks.named('test') {
useJUnitPlatform()
}
27 changes: 27 additions & 0 deletions src/main/java/B1G4/bookmark/config/WebSocketConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package B1G4.bookmark.config;

import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Configuration;
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;

@Configuration
@RequiredArgsConstructor
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic"); // 구독 경로
config.setApplicationDestinationPrefixes("/app"); // 클라이언트 발행 경로
}

@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws") // WebSocket 엔드포인트
.setAllowedOriginPatterns("*") // 모든 Origin 허용
.withSockJS(); // SockJS Fallback 지원
}
}
18 changes: 18 additions & 0 deletions src/main/java/B1G4/bookmark/domain/ChatRoom.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package B1G4.bookmark.domain;

import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Entity
public class ChatRoom {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package B1G4.bookmark.repository;

import B1G4.bookmark.domain.ChatRoom;
import org.springframework.data.jpa.repository.JpaRepository;

public interface ChatRoomRepository extends JpaRepository<ChatRoom, Long> {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package B1G4.bookmark.service.ChatRoomService;

public interface ChatRoomService {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package B1G4.bookmark.service.ChatRoomService;

import B1G4.bookmark.domain.Member;
import B1G4.bookmark.repository.ChatRoomRepository;
import B1G4.bookmark.repository.MemberRepository;
import B1G4.bookmark.web.dto.ChatMessageDTO.ChatMessageDTO;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;

@Service
@RequiredArgsConstructor
public class ChatRoomServiceImpl {


private final ChatRoomRepository chatRoomRepository;
private final MemberRepository memberRepository;

// 채팅방 입장 처리
public ChatMessageDTO handleJoinChatRoom(Long chatRoomId, Long senderId) {
// 채팅방 유효성 확인
chatRoomRepository.findById(chatRoomId)
.orElseThrow(() -> new IllegalArgumentException("ChatRoom not found"));

// 사용자 확인 및 닉네임 설정
Member member = memberRepository.findById(senderId)
.orElseThrow(() -> new IllegalArgumentException("Member not found"));

String nickname = member.getNickname();
String message = nickname + " 님이 입장했습니다.";
LocalDateTime timestamp = LocalDateTime.now();

return new ChatMessageDTO(nickname, message, timestamp);
}

// 메시지 전송 처리
public ChatMessageDTO handleSendMessage(Long chatRoomId, ChatMessageDTO messageDTO) {
// 채팅방 유효성 확인
chatRoomRepository.findById(chatRoomId)
.orElseThrow(() -> new IllegalArgumentException("ChatRoom not found"));

// 메시지에 타임스탬프 추가
messageDTO.setTimestamp(LocalDateTime.now());

return messageDTO;
}
}
70 changes: 70 additions & 0 deletions src/main/java/B1G4/bookmark/web/controller/ChatController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package B1G4.bookmark.web.controller;


import B1G4.bookmark.domain.Member;
import B1G4.bookmark.security.handler.annotation.AuthUser;
import B1G4.bookmark.service.ChatRoomService.ChatRoomServiceImpl;
import B1G4.bookmark.web.dto.ChatMessageDTO.ChatMessageDTO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import lombok.RequiredArgsConstructor;
import org.springframework.messaging.handler.annotation.DestinationVariable;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.stereotype.Controller;

@Controller
@RequiredArgsConstructor
public class ChatController {

private final ChatRoomServiceImpl chatRoomService;

// 채팅방 입장
/**
* 채팅방 입장
* 클라이언트는 /app/chat/{chatRoomId}/join으로 메시지를 발행합니다.
* 서버는 /topic/chat/{chatRoomId}로 구독 중인 모든 클라이언트에게 메시지를 브로드캐스트합니다.
*/
@Operation(
summary = "채팅방 입장",
description = "사용자가 특정 채팅방에 입장합니다. 입장 메시지는 구독 중인 모든 사용자에게 전송됩니다."
)
@MessageMapping("/chat/{chatRoomId}/join")
@SendTo("/topic/chat/{chatRoomId}")
public ChatMessageDTO joinChatRoom(
@DestinationVariable @Parameter(
description = "입장할 채팅방의 ID",
required = true,
example = "1"
) Long chatRoomId,
@Parameter(name = "user", hidden = true)
@AuthUser Member member) {
return chatRoomService.handleJoinChatRoom(chatRoomId, member.getId());
}

// 메시지 전송
/**
* 메시지 전송
* 클라이언트는 /app/chat/{chatRoomId}/send로 메시지를 발행합니다.
* 서버는 /topic/chat/{chatRoomId}로 구독 중인 모든 클라이언트에게 메시지를 브로드캐스트합니다.
*/
@Operation(
summary = "메시지 전송",
description = "사용자가 특정 채팅방에 메시지를 전송합니다. 메시지는 구독 중인 모든 사용자에게 브로드캐스트됩니다."
)
@MessageMapping("/chat/{chatRoomId}/send")
@SendTo("/topic/chat/{chatRoomId}")
public ChatMessageDTO sendMessage(
@DestinationVariable @Parameter(
description = "메시지를 전송할 채팅방의 ID",
required = true,
example = "1"
) Long chatRoomId,
@Parameter(
description = "전송할 메시지의 정보 (메시지만 포함)",
required = true
)
ChatMessageDTO messageDTO) {
return chatRoomService.handleSendMessage(chatRoomId, messageDTO);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package B1G4.bookmark.web.dto.ChatMessageDTO;


import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.*;

import java.time.LocalDateTime;

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class ChatMessageDTO {
private String nickname; // 보낸 사용자 닉네임
private String message; // 메시지 내용
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm")
private LocalDateTime timestamp; // 메시지 타임스탬프
}
Loading