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
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;

@SpringBootTest
@ActiveProfiles("test")
class BuySellServiceApplicationTests {

@Test
Expand Down
4 changes: 1 addition & 3 deletions chat-service/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ dependencies {
implementation 'org.springframework.cloud:spring-cloud-starter-bus-amqp'

// Chat-service
implementation 'org.springframework.kafka:spring-kafka'
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
implementation 'org.springframework.boot:spring-boot-starter-data-mongodb'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
Expand All @@ -40,9 +41,6 @@ dependencies {
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'

//Spring webClient
// implementation 'org.springframework.boot:spring-boot-starter-webflux'

// Swagger3
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-api:2.7.0'

Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
package project.chatservice.config;

import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.ChannelRegistration;
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;
import org.springframework.web.socket.config.annotation.WebSocketTransportRegistration;
import project.chatservice.handler.StompHandler;

@Configuration
@RequiredArgsConstructor
@EnableWebSocketMessageBroker // WebSocket 메세지 브로커를 활성화, STOMP 기반 메세지 송수신을 지원
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

private final StompHandler stompHandler;

@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws-connect")
Expand All @@ -22,4 +29,19 @@ public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/topic"); // broker가 해당 주제를 구독하는 클라이언트에게 메세지 전달
registry.setApplicationDestinationPrefixes("/app"); // 클라이언트에서 메세지를 송신할 때 메세지 매핑을 위한 prefix
}

// 클라이언트 인바운드 채널을 구성하는 메서드
@Override
public void configureClientInboundChannel(ChannelRegistration registration) {
// stompHandler를 인터셉터로 등록하여 STOMP 메시지 핸들링을 수행
registration.interceptors(stompHandler);
}

// STOMP에서 64KB 이상의 데이터 전송을 못하는 문제 해결
@Override
public void configureWebSocketTransport(WebSocketTransportRegistration registry) {
registry.setMessageSizeLimit(160 * 64 * 1024);
registry.setSendTimeLimit(100 * 10000);
registry.setSendBufferSizeLimit(3 * 512 * 1024);
}
}
Original file line number Diff line number Diff line change
@@ -1,20 +1,17 @@
package project.chatservice.domain.controller;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import project.chatservice.domain.dto.request.MessageRequest;
import project.chatservice.domain.dto.response.ChatResponseDto;
import project.chatservice.domain.dto.response.MessageResponse;
import project.chatservice.domain.service.ChatProducer;
import project.chatservice.domain.service.ChatService;
import project.globalservice.response.BaseResponse;

@Tag(name = "Chat", description = "채팅 관련 API")
@RestController
Expand All @@ -23,20 +20,18 @@
public class ChatController {

private final ChatService chatService;
private final ChatProducer chatProducer;

@Operation(summary = "채팅 메시지 전송", description = "'topic' 을 구독한 모든 사용자에게 채팅 메시지를 전송합니다.")
@MessageMapping("/chat")
@SendTo("/topic/chat")
public MessageResponse sendChatMessage(MessageRequest request, SimpMessageHeaderAccessor accessor) {
return chatService.processMessage(request, accessor.getSessionId(), (String) accessor.getSessionAttributes().get("nickname"));
log.info("sendChatMessage: {}", request);
chatProducer.sendChatMessage(request.toEntity());
return chatService.processMessage(
request, accessor.getSessionId(),
(String) accessor.getSessionAttributes().get("nickname")
);
}

@GetMapping("/messages")
public BaseResponse<ChatResponseDto> getChatMessages(
@RequestHeader("Authorization") String authorizationHeader,
@RequestParam Long roomId,
@RequestParam int size,
@RequestParam int page) {
ChatResponseDto responseDto = chatService.getMessages(authorizationHeader, roomId, size, page);
return new BaseResponse<>(responseDto);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package project.chatservice.domain.controller;

import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Tag(name = "ChatRoom", description = "채팅방 관련 API")
@RestController
@RequiredArgsConstructor
@RequestMapping("/rooms")
public class ChatRoomController {

}
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
package project.chatservice.domain.dto.request;

import lombok.Builder;
import project.chatservice.domain.entity.Message;

@Builder
public record MessageRequest(
String content,
Long roomId,
Long userId,
Long receiverId
) {}
) {
public Message toEntity() {
return Message.builder()
.content(content)
.roomId(roomId)
.senderId(userId)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package project.chatservice.domain.dto.response;

import lombok.Getter;
import project.chatservice.domain.entity.ChatRoom;
import project.chatservice.domain.entity.UserChatRoom;
import project.chatservice.domain.service.ChatRoomService;

import java.util.List;
import java.util.Objects;

public record ChatRoomResponses(
boolean hasNext,
int number,
int numberOfElements,
List<ChatRoomResponse> chatRoomResponses
) {
public static ChatRoomResponses from(boolean hasNext, int number, int numberOfElements, List<ChatRoom> userChatRooms) {
List<ChatRoomResponse> roomResponses = userChatRooms.stream().map(ChatRoomResponse::from).toList();

return new ChatRoomResponses(hasNext, number, numberOfElements, roomResponses);
}

public record ChatRoomResponse(Long id, String title, boolean isActivated) {
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ChatRoomResponse that = (ChatRoomResponse) o;
return Objects.equals(id, that.id);
}

@Override
public int hashCode() {
return Objects.hashCode(id);
}

public static ChatRoomResponse from(ChatRoom chatRoom) {
return new ChatRoomResponse(chatRoom.getId(), chatRoom.getName(), chatRoom.getIsActivated());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,14 @@ public record MessageResponse(
String sessionId,
String nickname,
Long roomId
) {}
) {
public static MessageResponse from(String content, String sessionId, String nickname, Long roomId) {
return MessageResponse.builder()
.type(MessageType.CHAT)
.content(content)
.sessionId(sessionId)
.nickname(nickname)
.roomId(roomId)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -1,48 +1,43 @@
package project.chatservice.domain.entity;

import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.*;
import org.springframework.data.annotation.CreatedDate;

import java.time.LocalDateTime;
import java.time.ZoneId;


@Table(name = "chat_room")
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
@Builder
public class ChatRoom {

// TODO: 기본적으로 1:1 채팅을 목적으로 함 추후 User쪽이 다 끝나면 그때 마무리

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "room_id")
private Long roomId;
private Long id;

private Long sender;

private Long receiver;
private String name;

@Column(nullable = false)
private Boolean roomActive;
private Boolean isActivated;

@CreatedDate
@Column(nullable = false)
private LocalDateTime createdAt;

// 채팅방 비활성화
public void roomDeActivate() {
this.roomActive = false;
this.isActivated = false;
}

@Builder
public ChatRoom(Long sender, Long receiver) {
this.sender = sender;
this.receiver = receiver;
this.roomActive = true;
this.createdAt = LocalDateTime.now(ZoneId.of("Asia/Seoul"));
public static ChatRoom from(String name) {
return ChatRoom.builder()
.name(name)
.isActivated(true)
.createdAt(LocalDateTime.now(ZoneId.of("Asia/Seoul")))
.build();
}

}
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
package project.chatservice.domain.entity;

import jakarta.persistence.Id;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.*;
import org.springframework.data.mongodb.core.mapping.Document;
import project.chatservice.domain.dto.request.MessageRequest;

import java.io.Serializable;
import java.time.LocalDateTime;
import java.time.ZoneId;

@Getter
@Document(collection = "message")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Message {
@AllArgsConstructor
@Builder
public class Message implements Serializable {

@Id
private String id;
Expand All @@ -27,14 +27,6 @@ public class Message {

private LocalDateTime createdAt;

@Builder
public Message(Long roomId, Long senderId, String content, LocalDateTime createdAt) {
this.roomId = roomId;
this.senderId = senderId;
this.content = content;
this.createdAt = createdAt;
}

public static Message of(MessageRequest chatMessageDto) {
return Message.builder()
.roomId(chatMessageDto.roomId())
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package project.chatservice.domain.entity;

import jakarta.persistence.*;
import lombok.*;

import java.time.LocalDateTime;

@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
@Builder
@Table(
name = "user_chat_room",
uniqueConstraints = {
@UniqueConstraint(columnNames = {"user_id", "chat_room_id"})
}
)
public class UserChatRoom {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

private Long userId;

private Long chatRoomId;

}
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,12 @@
@AllArgsConstructor
public enum ChatExceptionType implements ExceptionType {

/**
* 채팅방
*/
CHAT_ROOM_NOT_FOUND(HttpStatus.NOT_FOUND, "chat-001", "채팅방을 찾을 수 없습니다."),
CHAT_USER_NOT_FOUND(HttpStatus.NOT_FOUND, "chat-002", "채팅방에 참여한 사용자를 찾을 수 없습니다."),
ALREADY_DEACTIVATION_ROOM(HttpStatus.NOT_FOUND, "chat-003", "이미 비활성화된 채팅방입니다"),
NOT_FOUND_CHAT(HttpStatus.NOT_FOUND, "chat-001", "채팅을 찾을 수 없습니다."),

/**
* 메세지
*/
NOT_FOUND_USER_CHAT_ROOM(HttpStatus.NOT_FOUND, "user-chat-room-001", "유저 채팅방을 찾을 수 없습니다."),


/**
* 사용자
*/
INVALID_CHAT_USER(HttpStatus.BAD_REQUEST, "chat-004", "유효한 사용자가 아닙니다."),
INVALID_USER_INFO(HttpStatus.NOT_FOUND, "chat-005", "잘못된 사용자 정보입니다.")
NOT_FOUND_CHAT_ROOM(HttpStatus.NOT_FOUND, "chat-room-001", "채팅방을 찾을 수 없습니다."),
ALREADY_DEACTIVATED_CHAT_ROOM(HttpStatus.BAD_REQUEST, "chat-room-002", "이미 비활성화된 채팅방입니다."),
;

private final HttpStatus httpStatus;
Expand Down
Loading