📢 다대일 채팅이란?
글을 시작하기에 앞서 다대일 채팅이라는 것에 낯선 독자분들을 위해 다대일 채팅 기능에 대해 간단히 설명드리겠습니다.
다대일 채팅 기능이란, 한쪽(1쪽)에 위치한 사용자는 다수(N쪽)에 있는 사용자들의 채팅을 모두 확인할 수 있고, 자신의 메시지가 N쪽의 모든 사용자에게 전달됩니다. 반면, N쪽의 사용자는 다른 N쪽 사용자들의 채팅을 볼 수 없으며, 오직 1쪽 사용자의 메시지만 확인할 수 있습니다. 또한, N쪽 사용자가 보낸 메시지는 다른 N쪽 사용자에게는 공유되지 않고, 1쪽 사용자에게만 전달됩니다.
이 구조를 그림으로 다음과 같이 표현해봤습니다!!
- 1쪽 사용자 시점: 단체 채팅방에서 모든 참여자와 소통하는 느낌
- N쪽 사용자 시점: 1쪽 사용자와 1:1 채팅을 하는 느낌
이러한 형태는 대표적으로 연예인과 팬들이 소통하는 애플리케이션인 버블과 유사합니다.
이 앱을 보고 다대일 채팅은 1쪽 사용자가 방장 또는 주인 역할을 하며, 다수의 사용자와 효율적으로 소통하고 정보를 전달할 수 있어 매우 유용한 기능이라고 생각했습니다.
저는 이러한 다대일 채팅의 장점을 활용하여 개인 프로젝트로 애플리케이션을 기획부터 개발까지 하게 되었습니다!
이 애플리케이션에서는 누구나 방을 개설하고 방장이 될 수 있으며, 교수와 학생, 선생과 학생, 멘토와 멘티 등 다양한 상황에서 활용될 수 있을 것이라 기대하고 있습니다!
⭐ ERD 구성
다대일 채팅 시스템에서 주요 엔티티는 User, ChatRoom, ChatRoomMember, 그리고 Message입니다. 각각의 엔티티는 아래와 같은 역할을 담당합니다:
- User: 시스템의 사용자 정보와 소셜 로그인 관련 데이터를 관리.
- ChatRoom: 채팅방의 기본 정보와 생성자 정보를 관리.
- ChatRoomMember: 사용자가 특정 채팅방에 속해 있는 관계를 관리.
- Message: 채팅방 내에서 주고받은 메시지를 기록.
1️⃣ User 엔티티
@Entity
@Table(name = "users")
@Getter
@Setter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class User implements UserDetails {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id; // AUTO_INCREMENT 적용
@Column(name = "social_id", nullable = false, unique = true)
private String socialId; // 소셜 로그인에서 받은 사용자 ID (예: Google ID, Kakao ID)
@Column(name = "provider", nullable = false)
private String provider; // 소셜 로그인 제공자 (Google, Kakao, Naver 등)
@Column(name = "access_token", nullable = true)
private String accessToken; // 액세스 토큰 (소셜 로그인 인증 후 받은 토큰)
@Column(name = "name", nullable = true)
private String name; // 사용자 이름 (옵션)
@Column(name = "created_at")
private Timestamp createdAt;
@Column(name = "updated_at")
private Timestamp updatedAt;
@OneToMany(mappedBy = "user")
private List<ChatRoomMember> chatRoomMembers; // User가 참여한 ChatRooms에 대한 관계
@OneToMany(mappedBy = "sender")
private List<Message> messages; // 사용자가 보낸 메시지들에 대한 관계
@PrePersist
public void prePersist() {
// 엔티티가 처음 저장될 때 createdAt을 현재 시간으로 설정
this.createdAt = new Timestamp(System.currentTimeMillis());
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return List.of();
}
@Override
public String getPassword() {
return "";
}
@Override
public String getUsername() {
return "";
}
@Override
public boolean isAccountNonExpired() {
return UserDetails.super.isAccountNonExpired();
}
@Override
public boolean isAccountNonLocked() {
return UserDetails.super.isAccountNonLocked();
}
@Override
public boolean isCredentialsNonExpired() {
return UserDetails.super.isCredentialsNonExpired();
}
@Override
public boolean isEnabled() {
return UserDetails.super.isEnabled();
}
}
- UserDetails 상속: Spring Security와 통합을 위한 인터페이스. 인증/인가와 관련된 메서드들을 제공하지만, 이 프로젝트에서는 비활성화된 기본 구현을 유지함.
- @OneToMany 관계: 사용자와 관련된 채팅방 및 메시지 데이터를 연결함.
2️⃣ ChatRoom 엔티티
@Entity
@Table(name = "chat_rooms")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class ChatRoom {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id; // AUTO_INCREMENT 적용
@ManyToOne
@JoinColumn(name = "created_by", nullable = false)
private User createdBy; // `Users` 테이블의 `id`를 참조하는 외래 키
@Column(name = "name", nullable = false)
private String name;
@Column(name = "password")
private String password;
@Column(name = "created_at")
private Timestamp createdAt;
@Column(name = "updated_at")
private Timestamp updatedAt;
@OneToMany(mappedBy = "chatRoom")
private List<ChatRoomMember> members; // ChatRoom에 속한 멤버들
@PrePersist
public void prePersist() {
// 엔티티가 처음 저장될 때 createdAt과 updatedAt을 현재 시간으로 설정
this.createdAt = new Timestamp(System.currentTimeMillis());
this.updatedAt = this.createdAt;
}
@PreUpdate
public void preUpdate() {
// 엔티티가 수정될 때 updatedAt을 현재 시간으로 설정
this.updatedAt = new Timestamp(System.currentTimeMillis());
}
}
- createdBy 필드: 채팅방을 생성한 사용자를 나타내며, User 엔티티와 @ManyToOne 관계를 맺음.
- 타임스탬프 관리: @PrePersist와 @PreUpdate를 통해 createdAt과 updatedAt 필드를 자동 갱신.
3️⃣ ChatRoomMember 엔티티
@Entity
@Table(name = "chat_room_members")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class ChatRoomMember {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id; // AUTO_INCREMENT 적용
@ManyToOne
@JoinColumn(name = "chatroom_id", nullable = false)
private ChatRoom chatRoom; // `ChatRooms` 테이블의 `id`를 참조하는 외래 키
@ManyToOne
@JoinColumn(name = "user_id", nullable = false)
private User user; // `Users` 테이블의 `id`를 참조하는 외래 키
@Column(name = "joined_at")
private Timestamp joinedAt;
}
- N:N 관계 매핑: 다대일(사용자와 채팅방)을 통해 N:N 관계를 구현.
- joinedAt 필드: 사용자가 채팅방에 참여한 시점을 기록.
4️⃣ Message 엔티티
@Entity
@Table(name = "messages")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Message {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id; // AUTO_INCREMENT 적용
@ManyToOne
@JoinColumn(name = "chatroom_id", nullable = false)
private ChatRoom chatRoom; // `ChatRooms` 테이블의 `id`를 참조하는 외래 키
@ManyToOne
@JoinColumn(name = "sender_id", nullable = false)
private User sender; // `Users` 테이블의 `id`를 참조하는 외래 키
@Column(name = "content", nullable = false)
private String content; // 메시지 내용
@Column(name = "sent_at")
private Timestamp sentAt; // 메시지 전송 시각
}
- 메시지 저장: 각 메시지를 DB에 저장. 데이터 보존성은 높지만, 성능에 영향을 줄 가능성이 있음.
- 개선 방안: Redis와 같은 메모리 캐시를 활용하여 자주 사용되는 메시지를 캐싱하고, 주기적으로 배치 작업을 통해 DB에 저장.
⭐ 다대일 채팅 기능 구현 방법
✅ WebSocketConfig 설정
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
// /queue는 클라이언트로 브로드캐스트할 메시지에 대한 경로
config.enableSimpleBroker("/queue");
// /app은 클라이언트가 서버로 보낼 메시지에 대한 경로
config.setApplicationDestinationPrefixes("/app");
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
// 클라이언트가 연결할 수 있는 웹소켓 엔드포인트를 설정
registry.addEndpoint("/ws") // /ws 경로로 연결을 설정
.setAllowedOriginPatterns("*") // 모든 출처에서 요청을 허용
.withSockJS(); // SockJS를 사용하여 연결 안정성을 높임
}
}
이 클래스는 Spring WebSocket 메시지 브로커를 구성하는 설정 파일로, 프론트엔드에서 WebSocket 연결을 설정할 때 중요한 역할을 합니다.
- 주요 기능:
- configureMessageBroker: 메시지 브로커 경로를 정의합니다.
- /queue: 클라이언트로 브로드캐스트될 메시지의 대상 경로입니다.
- /app: 클라이언트가 서버로 메시지를 보낼 때 사용하는 경로입니다.
- registerStompEndpoints: 클라이언트가 /ws 경로로 연결할 수 있도록 엔드포인트를 설정합니다.
- setAllowedOriginPatterns("*"): 모든 출처에서의 연결을 허용합니다.
- withSockJS: SockJS를 통해 WebSocket을 지원하지 않는 환경에서도 연결 안정성을 보장합니다.
- configureMessageBroker: 메시지 브로커 경로를 정의합니다.
사용 이유
이 클래스는 백엔드의 다른 메서드에서 직접 호출되지 않더라도 WebSocket 연결을 설정하기 위해 필수적입니다. 클라이언트는 /ws 엔드포인트로 연결을 시도하고, 설정된 경로 규칙에 따라 메시지를 주고받을 수 있습니다.
✅ 메시지 전송 로직
1️⃣ DTO 파일
@Getter
@Setter
public class MessageRequest {
private Long chatRoomId;
private String content;
}
클라이언트로부터 메시지 전송 요청을 받을 때 사용하는 데이터 전송 객체입니다.
- chatRoomId: 메시지가 전송될 채팅방 ID.
- content: 전송할 메시지 내용.
2️⃣ Controller 파일
@RestController
@RequestMapping("/messages")
@RequiredArgsConstructor
@Tag(name = "채팅", description = "채팅 API를 제공합니다.")
public class MessageController {
private final MessageService messageService;
// 메시지 전송
@Operation(summary = "메시지 전송", description = "특정 채팅방에 메시지를 전송합니다.")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "메시지 전송 완료"),
@ApiResponse(responseCode = "400", description = "잘못된 요청")
})
@PostMapping
public ResponseEntity<String> sendMessage(
@RequestBody MessageRequest request,
@AuthenticationPrincipal User user) {
messageService.sendMessage(request, user);
return ResponseEntity.ok("메시지 전송 완료");
}
}
RESTful API의 컨트롤러로, 메시지 전송 요청을 처리합니다.
- 핵심 메서드:
- sendMessage:
- 기능: 특정 채팅방에 메시지를 전송합니다.
- 작동 방식:
- MessageService를 호출하여 메시지를 저장하고 전달합니다.
- 결과적으로 클라이언트가 메시지 전송 결과를 확인할 수 있습니다.
- sendMessage:
3️⃣ Service 파일
@Service
@RequiredArgsConstructor
public class MessageService {
private final MessageRepository messageRepository;
private final ChatRoomMemberRepository chatRoomMemberRepository;
private final SimpMessagingTemplate messagingTemplate;
private final ChatRoomRepository chatRoomRepository;
@Transactional
public void sendMessage(MessageRequest request, User user) {
Optional<ChatRoom> optionalChatRoom = chatRoomRepository.findById(request.getChatRoomId());
ChatRoom chatRoom = optionalChatRoom.orElseThrow(() -> new RuntimeException("ChatRoom not found"));
// 메시지 저장
Message message = Message.builder()
.chatRoom(chatRoom)
.sender(user)
.content(request.getContent())
.sentAt(new Timestamp(System.currentTimeMillis()))
.build();
messageRepository.save(message);
if (chatRoom.getCreatedBy().getId().equals(user.getId())) {
System.out.println("나(교수: " + user.getProvider() + user.getName() + ") -> ");
} else {
System.out.println("나(학생: " + user.getProvider() + user.getName() + ") -> ");
}
// 메시지 전송 로직
if (chatRoom.getCreatedBy().getId().equals(user.getId())) {
// 1쪽 User -> 모든 N쪽 User로 메시지 브로드캐스팅
chatRoomMemberRepository.findAllByChatRoom(chatRoom).forEach(member -> {
System.out.println("\t 학생에게 전송 (학생: " +
member.getUser().getProvider() +
member.getUser().getName() + ")");
// 메시지 전송
messagingTemplate.convertAndSendToUser(
member.getUser().getSocialId(),
"/queue/messages",
message.getContent()
);
});
} else {
// N쪽 User -> 1쪽 User로 메시지 전송
System.out.println("\t 교수에게 전송 (교수: " +
chatRoom.getCreatedBy().getProvider() +
chatRoom.getCreatedBy().getName() + ")");
// 메시지 전송
messagingTemplate.convertAndSendToUser(
chatRoom.getCreatedBy().getSocialId(),
"/queue/messages",
message.getContent()
);
}
}
}
이 서비스는 메시지를 저장하고 WebSocket 브로커를 통해 메시지를 전달하는 역할을 합니다.
- 핵심 메서드:
- sendMessage:
- 메시지 저장: MessageRepository를 통해 메시지를 DB에 저장합니다.
- WebSocket 메시지 전송: SimpMessagingTemplate을 사용하여 메시지를 특정 사용자에게 전송합니다.
- convertAndSendToUser:
- 사용 목적: WebSocket을 통해 특정 사용자에게 메시지를 전송.
- 파라미터:
- destination: 전송 대상 경로 (/queue/messages).
- payload: 전달할 메시지 내용.
- convertAndSendToUser:
- sendMessage:
- 전송 로직:
- 교수 -> 학생: 채팅방의 모든 학생들에게 메시지를 브로드캐스팅.
- 학생 -> 교수: 교수가 생성한 채팅방에서 교수에게 메시지 전송.
SimpMessagingTemplate의 역할
WebSocket 통신에서 서버가 클라이언트로 실시간 메시지를 전송할 때 사용하는 Spring 프레임워크의 도구입니다. 이를 통해 사용자에게 메시지를 안정적으로 전달할 수 있습니다.
4️⃣ Repository 파일
@Repository
public interface ChatRoomMemberRepository extends JpaRepository<ChatRoomMember, Long> {
// 주어진 채팅방에 속한 모든 멤버를 조회
List<ChatRoomMember> findAllByChatRoom(ChatRoom chatRoom);
}
이 레포지토리는 특정 채팅방에 속한 멤버 목록을 조회하는 데 사용됩니다.
- 주요 메서드:
- findAllByChatRoom: 주어진 채팅방의 모든 멤버를 조회합니다.
- 활용: 메시지를 브로드캐스트하거나 특정 사용자에게 메시지를 전송할 때 사용됩니다.
📒 다대일 채팅 작동 방식 요약
- 클라이언트가 /ws 엔드포인트를 통해 WebSocket 연결을 설정.
- 메시지 전송 요청이 컨트롤러를 통해 서비스로 전달.
- 서비스는 메시지를 데이터베이스에 저장하고 SimpMessagingTemplate을 통해 실시간으로 사용자에게 전달.
- ChatRoomMemberRepository를 사용하여 대상 사용자 목록을 조회하고 메시지를 보냄.
⭐ 채팅방 개설 및 입장, 채팅방 조회 기능
1️⃣ DTO 파일
@Getter
@Setter
public class ChatRoomCreateRequest {
private String name;
private String password;
}
- 채팅방 생성 요청 시 필요한 데이터를 담는 DTO입니다.
- 필드:
- name: 채팅방 이름 (필수 입력).
- password: 채팅방 비밀번호 (필수 입력).
@Getter
@Setter
public class ChatRoomJoinRequest {
private String password;
}
- 채팅방 입장 요청 시 필요한 데이터를 담는 DTO입니다.
- 필드:
- password: 채팅방 입장 시 입력해야 하는 비밀번호.
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ChatRoomResponse {
private Long id;
private String name;
private String createdByName; // 생성자 이름
private Timestamp createdAt;
}
- 채팅방 정보를 반환할 때 사용하는 DTO입니다.
- 필드:
- id: 채팅방 ID.
- name: 채팅방 이름.
- createdByName: 채팅방 생성자 이름.
- createdAt: 채팅방 생성 시간.
2️⃣ Controller 파일
@RestController
@RequestMapping("/chatrooms")
@RequiredArgsConstructor
@Tag(name = "채팅방", description = "채팅방 개설 및 입장, 조회 API를 제공합니다.")
public class ChatRoomController {
private final ChatRoomService chatRoomService;
// 채팅방 생성
@Operation(summary = "채팅방 생성", description = "새로운 채팅방을 생성합니다.")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "채팅방 생성 성공"),
@ApiResponse(responseCode = "400", description = "잘못된 요청")
})
@PostMapping
public ResponseEntity<ChatRoomResponse> createChatRoom(
@RequestBody ChatRoomCreateRequest request,
@AuthenticationPrincipal User user) {
if (user == null) {
throw new IllegalArgumentException("인증된 사용자가 아닙니다.");
}
if (request.getName() == null || request.getName().isEmpty()) {
throw new IllegalArgumentException("채팅방 이름은 필수입니다.");
}
if (request.getPassword() == null || request.getPassword().isEmpty()) {
throw new IllegalArgumentException("비밀번호는 필수입니다.");
}
ChatRoomResponse chatRoomResponse = chatRoomService.createChatRoom(request, user);
if (chatRoomResponse == null) {
throw new IllegalStateException("채팅방 생성에 실패했습니다.");
}
return ResponseEntity.ok(chatRoomResponse);
}
// 채팅방 참여
@Operation(summary = "채팅방 참여", description = "주어진 비밀번호로 채팅방에 참여합니다.")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "참여 성공"),
@ApiResponse(responseCode = "401", description = "비밀번호가 틀림")
})
@PostMapping("/{chatRoomId}/join")
public ResponseEntity<String> joinChatRoom(
@PathVariable Long chatRoomId,
@RequestBody ChatRoomJoinRequest request,
@AuthenticationPrincipal User user) {
if (user == null) {
throw new IllegalArgumentException("인증된 사용자가 아닙니다.");
}
boolean success = chatRoomService.joinChatRoom(chatRoomId, request, user);
if (success) {
return ResponseEntity.ok("참여 성공");
} else {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("비밀번호가 틀렸습니다.");
}
}
// 로그인한 사용자가 생성한 채팅방 목록 조회
@Operation(summary = "내가 만든 채팅방 리스트", description = "로그인한 사용자가 생성한 채팅방 목록을 조회합니다.")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "채팅방 리스트 조회 성공"),
@ApiResponse(responseCode = "401", description = "인증되지 않은 사용자")
})
@GetMapping("/mylist")
public ResponseEntity<List<ChatRoomResponse>> getMyChatRooms(
@AuthenticationPrincipal User user) {
if (user == null) {
throw new IllegalArgumentException("인증된 사용자가 아닙니다.");
}
List<ChatRoomResponse> chatRooms = chatRoomService.getMyChatRooms(user);
return ResponseEntity.ok(chatRooms);
}
// 내가 참여한 채팅방 리스트 조회
@Operation(summary = "내가 참여한 채팅방 리스트", description = "로그인한 사용자가 참여한 채팅방 목록을 조회합니다.")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "채팅방 리스트 조회 성공"),
@ApiResponse(responseCode = "401", description = "인증되지 않은 사용자")
})
@GetMapping("/list")
public ResponseEntity<List<ChatRoomResponse>> getUserChatRooms(
@AuthenticationPrincipal User user) {
if (user == null) {
throw new IllegalArgumentException("인증된 사용자가 아닙니다.");
}
// 사용자가 참여한 채팅방 목록을 서비스에서 가져옴
List<ChatRoomResponse> chatRooms = chatRoomService.getChatRooms(user);
return ResponseEntity.ok(chatRooms);
}
}
- 클라이언트 요청을 처리하는 컨트롤러입니다. 네 가지 주요 엔드포인트를 제공합니다.
- 채팅방 생성
- POST /chatrooms: 새로운 채팅방을 생성합니다.
- 검증: 요청한 사용자와 입력된 채팅방 이름 및 비밀번호가 유효한지 확인.
- 서비스 계층을 호출하여 ChatRoomResponse 객체를 반환.
- 채팅방 입장
- POST /chatrooms/{chatRoomId}/join: 지정된 ID의 채팅방에 입장합니다.
- 검증: 인증된 사용자와 비밀번호 일치 여부 확인.
- 성공 시 "참여 성공" 메시지 반환, 실패 시 401 상태 코드와 함께 "비밀번호가 틀렸습니다." 메시지 반환.
- 내가 만든 채팅방 조회
- GET /chatrooms/mylist: 사용자가 생성한 채팅방 목록을 반환합니다.
- 검증: 인증된 사용자 여부 확인.
- 서비스 계층에서 데이터 조회 후 ChatRoomResponse 리스트 반환.
- 내가 참여한 채팅방 조회
- GET /chatrooms/list: 사용자가 참여한 채팅방 목록을 반환합니다.
- 검증: 인증된 사용자 여부 확인.
- 서비스 계층에서 데이터 조회 후 ChatRoomResponse 리스트 반환.
3️⃣ Repository 파일
@Repository
public interface ChatRoomRepository extends JpaRepository<ChatRoom, Long> {
// createdBy가 주어진 사용자와 일치하는 채팅방 리스트를 조회
List<ChatRoom> findByCreatedBy(User user);
}
- 채팅방 관련 데이터 조작을 담당합니다.
- findByCreatedBy(User user): 특정 사용자가 생성한 채팅방 목록을 조회.
@Repository
public interface ChatRoomMemberRepository extends JpaRepository<ChatRoomMember, Long> {
// 로그인한 사용자가 참여한 채팅방 목록 조회
List<ChatRoomMember> findByUser(User user);
// 주어진 채팅방에 속한 모든 멤버를 조회
List<ChatRoomMember> findAllByChatRoom(ChatRoom chatRoom);
}
- 채팅방 참여자 관련 데이터를 다룹니다.
- findByUser(User user): 특정 사용자가 참여한 채팅방 멤버 조회.
- findAllByChatRoom(ChatRoom chatRoom): 특정 채팅방의 모든 멤버 조회.
4️⃣ Service 파일
@Service
@RequiredArgsConstructor
public class ChatRoomService {
private final ChatRoomRepository chatRoomRepository;
private final ChatRoomMemberRepository chatRoomMemberRepository;
@Transactional
public ChatRoomResponse createChatRoom(ChatRoomCreateRequest request, User currentUser) {
ChatRoom chatRoom = ChatRoom.builder()
.name(request.getName())
.password(request.getPassword())
.createdBy(currentUser)
.createdAt(new Timestamp(System.currentTimeMillis()))
.build();
ChatRoom savedChatRoom = chatRoomRepository.save(chatRoom);
return new ChatRoomResponse(
savedChatRoom.getId(),
savedChatRoom.getName(),
savedChatRoom.getCreatedBy().getName(),
savedChatRoom.getCreatedAt()
);
}
@Transactional
public boolean joinChatRoom(Long chatRoomId, ChatRoomJoinRequest request, User user) {
ChatRoom chatRoom = chatRoomRepository.findById(chatRoomId)
.orElseThrow(() -> new RuntimeException("ChatRoom not found"));
if (!chatRoom.getPassword().equals(request.getPassword())) {
return false; // 비밀번호가 틀릴 경우 참여 불가
}
ChatRoomMember member = ChatRoomMember.builder()
.chatRoom(chatRoom)
.user(user)
.joinedAt(new Timestamp(System.currentTimeMillis()))
.build();
chatRoomMemberRepository.save(member);
return true;
}
// 로그인한 사용자가 생성한 채팅방 목록 조회
public List<ChatRoomResponse> getMyChatRooms(User user) {
// 로그인한 사용자가 생성한 채팅방만 가져오기
List<ChatRoom> chatRooms = chatRoomRepository.findByCreatedBy(user);
// ChatRoom -> ChatRoomResponse 변환
return chatRooms.stream()
.map(chatRoom -> new ChatRoomResponse(chatRoom.getId(), chatRoom.getName(), user.getName(),chatRoom.getCreatedAt()))
.collect(Collectors.toList());
}
// 로그인한 사용자가 참여한 채팅방 목록 조회
public List<ChatRoomResponse> getChatRooms(User user) {
// 사용자가 참여한 채팅방 멤버들 조회
List<ChatRoomMember> chatRoomMembers = chatRoomMemberRepository.findByUser(user);
// 채팅방 리스트 추출
return chatRoomMembers.stream()
.map(chatRoomMember -> new ChatRoomResponse(
chatRoomMember.getChatRoom().getId(),
chatRoomMember.getChatRoom().getName(),
chatRoomMember.getChatRoom().getCreatedBy().getName(),
chatRoomMember.getChatRoom().getCreatedAt()))
.collect(Collectors.toList());
}
}
- 비즈니스 로직을 처리하며, Controller와 Repository 사이를 연결합니다.
- 채팅방 생성
- createChatRoom 메서드:
- 입력된 요청 데이터를 바탕으로 ChatRoom 엔티티를 생성하고 저장.
- 생성된 채팅방 데이터를 ChatRoomResponse로 변환하여 반환.
- createChatRoom 메서드:
- 채팅방 입장
- joinChatRoom 메서드:
- 채팅방 ID로 ChatRoom 조회 후 비밀번호 검증.
- 비밀번호가 일치하면 ChatRoomMember 엔티티 생성 및 저장.
- joinChatRoom 메서드:
- 내가 만든 채팅방 조회
- getMyChatRooms 메서드:
- Repository에서 해당 사용자가 생성한 채팅방을 조회.
- ChatRoomResponse 리스트로 변환하여 반환.
- getMyChatRooms 메서드:
- 내가 참여한 채팅방 조회
- getChatRooms 메서드:
- Repository에서 해당 사용자가 참여한 채팅방 멤버를 조회.
- ChatRoomResponse 리스트로 변환하여 반환.
- getChatRooms 메서드:
📌 깃허브
🚩 다대일 채팅 백엔드 구현 마침표
이번 글을 통해 다대일 채팅 기능의 백엔드 구현 과정을 정리해 보았습니다.
다대일 채팅, 채팅방 개설, 입장, 그리고 내가 만든/참여한 채팅방 목록 조회 기능을 하나씩 구현하면서, 단순한 기능도 사용자 경험과 보안을 고려하면 더욱 신중하게 설계해야 한다는 것을 느꼈습니다. 특히 데이터베이스 설계와 서비스 로직 간의 관계를 명확히 이해하는 것이 얼마나 중요한지 다시 한번 깨달았습니다.
이 글이 여러분의 프로젝트에 도움이 되어 채팅 기능을 원활히 구현하는 데 기여할 수 있기를 바랍니다. 저또한, 앞으로 새로운 기능을 설계하고 구현하면서 발생하는 문제를 해결하는 과정에서 더 나은 개발자로 성장할 수 있기를 바랍니다!
이제 프론트엔드 구현에 대해서도 다뤄볼 계획입니다.
프론트엔드는 잘 다뤄보지 않았고, 노트북 사양도 감당하지 못하는 거 같아 좀 어려울 거 같지만, 함께 고민하고 성장해 나가길 기대하며, 앞으로도 좋은 기술을 함께 나누는 글로 찾아오겠습니다!
'프로젝트 🧸 > CocO 🐤' 카테고리의 다른 글
[Spring Boot] 구글, 카카오, 네이버 소셜 로그인 완전 기초부터 구현까지 이 글 하나로 끝내기 (1) | 2024.12.21 |
---|---|
[Spring Boot] Naver 소셜 로그인 구현: OAuth2.0 & Spring Security 활용 🐤 (0) | 2024.09.11 |
[Spring Boot] Kakao 소셜 로그인 구현: OAuth2.0 & Spring Security 활용 🐤 (0) | 2024.09.11 |
[Spring Boot] Google 소셜 로그인 구현: OAuth2.0와 Spring Security를 활용한 완벽 가이드 🐤 (0) | 2024.09.10 |
[OAuth 2.0] 구글 소셜 로그인 오류: '이 앱의 요청이 잘못되었습니다' 해결 가이드 (3) | 2024.09.08 |