Skip to content

Commit

Permalink
Merge pull request #9 from Leets-Official/feat/#8/채팅방-구현
Browse files Browse the repository at this point in the history
Feat #9 채팅 도메인 개발
  • Loading branch information
koreaioi authored Oct 6, 2024
2 parents 5ff8ed7 + a730785 commit a958aad
Show file tree
Hide file tree
Showing 11 changed files with 302 additions and 0 deletions.
6 changes: 6 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-web'

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

// Redis
implementation 'org.springframework.boot:spring-boot-starter-data-redis'

// Lombok
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
Expand Down
30 changes: 30 additions & 0 deletions src/main/java/com/leets/X/domain/chat/dto/PublishMessage.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.leets.X.domain.chat.dto;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.io.Serial;
import java.io.Serializable;

/*
* PublishMessage
* Redis의 pub/sub 과정 데이터를 주고 받을 때, 사용할 직렬화 클래스
* 24.09.30 - 역직렬화 에러를 해결하지 못하고 있음
* */

@Getter
@NoArgsConstructor
@AllArgsConstructor
public class PublishMessage implements Serializable {

@Serial
private static final long serialVersionUID = 2082503192322391880L;

private Long roomId;

private Long senderId;

private String content;

}
24 changes: 24 additions & 0 deletions src/main/java/com/leets/X/domain/chat/dto/request/MessageDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.leets.X.domain.chat.dto.request;


import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.io.Serializable;

@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class MessageDto implements Serializable {
private static final long serialVersionUID = 2082503192322391880L;

private Long roomId;

private Long senderId;

private String content;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.leets.X.domain.chat.dto.response;

import com.fasterxml.jackson.annotation.JsonFormat;
import com.leets.X.domain.chat.dto.PublishMessage;

import java.time.LocalDateTime;

public record PublishMessageResponse(
Long roomId,
Long senderId,
String content,
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime createAt
) {
public PublishMessageResponse(PublishMessage publishMessage) {
this(publishMessage.getRoomId(), publishMessage.getSenderId(), publishMessage.getContent(), LocalDateTime.now());
}
}
35 changes: 35 additions & 0 deletions src/main/java/com/leets/X/domain/chat/entity/ChatMessage.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.leets.X.domain.chat.entity;


import com.leets.X.domain.chat.dto.PublishMessage;
import com.leets.X.global.common.domain.BaseTimeEntity;
import jakarta.persistence.Id;
import lombok.Builder;
import lombok.Getter;
import org.springframework.data.mongodb.core.mapping.Document;

import java.time.LocalDateTime;

@Document
@Getter
@Builder
public class ChatMessage extends BaseTimeEntity {

@Id
private Long id; // MongoDb에서 사용하는 ObjectId

private Long roomId;

private Long senderId;

private String content;

public static ChatMessage of(PublishMessage publishMessage) {
return ChatMessage.builder()
.roomId(publishMessage.getRoomId())
.senderId(publishMessage.getSenderId())
.content(publishMessage.getContent())
.build();
}

}
31 changes: 31 additions & 0 deletions src/main/java/com/leets/X/domain/chat/entity/ChatRoom.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.leets.X.domain.chat.entity;


import com.leets.X.domain.user.domain.User;
import com.leets.X.global.common.domain.BaseTimeEntity;
import jakarta.persistence.*;
import lombok.*;

import java.time.LocalDateTime;

@Builder
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PROTECTED) // 기본 생성자 접근 레벨 PROTECTED
@Entity
public class ChatRoom extends BaseTimeEntity {

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

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user1_id")
private User user1;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user2_id")
private User user2;

private String lastMessage;
}
41 changes: 41 additions & 0 deletions src/main/java/com/leets/X/domain/chat/redis/RedisSubscriber.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.leets.X.domain.chat.redis;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.leets.X.domain.chat.dto.PublishMessage;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.messaging.simp.SimpMessageSendingOperations;
import org.springframework.stereotype.Service;


@Service
@Slf4j
@RequiredArgsConstructor
public class RedisSubscriber implements MessageListener{

private final RedisTemplate<String, Object> redisTemplate;
private final ObjectMapper obejctMapper;
private final SimpMessageSendingOperations messageTemplate;

@Override
public void onMessage(Message message, byte[] pattern) {
// 역직렬화 문자열
String tmpMessage = redisTemplate.getStringSerializer().deserialize(message.getBody());

log.info("구독자 전송 전 message: {}", tmpMessage);
try {
// 역직렬화한 문자열을 PublishMessage로 변환
PublishMessage publishMessage = obejctMapper.readValue(tmpMessage, PublishMessage.class);

messageTemplate.convertAndSend("/sub/chats/" + publishMessage.getRoomId(), publishMessage);
log.info("구독자 전송 후 message: {}", publishMessage.getContent());
} catch (JsonProcessingException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
}
75 changes: 75 additions & 0 deletions src/main/java/com/leets/X/global/config/RedisConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package com.leets.X.global.config;

import com.leets.X.domain.chat.dto.PublishMessage;
import com.leets.X.domain.chat.redis.RedisSubscriber;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.listener.ChannelTopic;
import org.springframework.data.redis.listener.adapter.MessageListenerAdapter;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
@RequiredArgsConstructor
public class RedisConfig {

@Value("${REDIS_HOST}")
private String redisHost;

@Value("${REDIS_PORT}")
private int redisPort;

@Bean
public RedisConnectionFactory redisConnectionFactory(){
// Redis 연동 설정 Host 주소와 Post를 주입해준 redisStandaloneConfiguration를
RedisStandaloneConfiguration redisStandaloneConfiguration =
new RedisStandaloneConfiguration("localhost", 6379);
// LettuceConnectionFactory의 생성자로 다시 넣어준다.
return new LettuceConnectionFactory(redisStandaloneConfiguration);
}

@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(connectionFactory);
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<>(String.class));
return redisTemplate;
}

@Bean
public RedisTemplate<String, PublishMessage> chatRedisTemplate(RedisConnectionFactory connectionFactory){
RedisTemplate<String, PublishMessage> chatRedisTemplate = new RedisTemplate<>();
chatRedisTemplate.setConnectionFactory(connectionFactory);
chatRedisTemplate.setKeySerializer(new StringRedisSerializer());
// Value 직렬화 과정에서 에러 발생할 수 있으니 try catch로 예외 처리
chatRedisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<>(String.class));

return chatRedisTemplate;
}

@Bean
public ChannelTopic topic(){
return new ChannelTopic("/chatRoom");
}


@Bean
public ChannelTopic channelTopic() {
return new ChannelTopic("chatroom");
}
// 메세지를 구독자에게 보내는 역할
@Bean
public MessageListenerAdapter listenerAdapter(RedisSubscriber redisSubscriber) {
return new MessageListenerAdapter(redisSubscriber, "onMessage");
}


}

30 changes: 30 additions & 0 deletions src/main/java/com/leets/X/global/config/WebSocketConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.leets.X.global.config;

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
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
// 웹소켓이 handshake를 하기 위해 연결하는 endpoint이다.
registry.addEndpoint("/ws")
.setAllowedOriginPatterns("*") // 나중에 도메인으로 변경
.withSockJS();

}

@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
// 서버 -> 클라이언트로 발행하는 메시지에 대한 endpoint 설정
registry.enableSimpleBroker("/sub");

// 클라이언트 -> 서버로 발행하는 메시지에 대한 endpoint 설정
registry.setApplicationDestinationPrefixes("/pub");
}
}
6 changes: 6 additions & 0 deletions src/main/resources/application-local.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ spring:
dialect: org.hibernate.dialect.MySQLDialect
hibernate:
ddl-auto: update
data:
redis:
host: ${REDIS_HOST}
port: ${REDIS_PORT}
password: ${REDIS_PASSWORD}


x:
jwt:
Expand Down
7 changes: 7 additions & 0 deletions src/main/resources/application-prod.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ spring:
dialect: org.hibernate.dialect.MySQLDialect
hibernate:
ddl-auto: update
data:
redis:
host: ${REDIS_HOST}
port: ${REDIS_PORT}
password: ${REDIS_PASSWORD}


x:
jwt:
Expand All @@ -21,3 +27,4 @@ x:
refresh:
expiration: ${REFRESH_EXP}
header: ${REFRESH_HEAD}

0 comments on commit a958aad

Please sign in to comment.