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
7 changes: 6 additions & 1 deletion src/backend/chat_server/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,14 @@ dependencies {
// Spring
implementation 'org.springframework.boot:spring-boot-starter-web'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
implementation 'org.springframework.boot:spring-boot-starter-actuator'

//db
implementation group: 'org.postgresql', name: 'postgresql', version: '42.7.3'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'

//mongo
implementation('org.springframework.boot:spring-boot-starter-data-mongodb')

// Lombok
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
Expand All @@ -54,6 +56,9 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-redis'

annotationProcessor "org.springframework.boot:spring-boot-configuration-processor"

//JDBC
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
}

tasks.named('test') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ public class StompConnectInterceptor implements ChannelInterceptor {
private final RedisTemplate<String, String> stringOperRedisTemplate;
private final RedisTemplate<String, Object> objectOperRedisTemplate;

private final StompSubscriptionInterceptor stompSubscriptionInterceptor;

@Override
public void afterSendCompletion(Message<?> message, MessageChannel channel, boolean sent, Exception ex) {
if (hasException(ex)) {
Expand Down Expand Up @@ -73,6 +75,7 @@ private void handleUserDisconnection(StompHeaderAccessor accessor) {
String userId = getUserIdFromSession(sessionId);

if (userId != null && !userId.trim().isEmpty()) {
stompSubscriptionInterceptor.handleChatUnsubscription(accessor);
cleanupUserSession(userId, sessionId);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package com.jootalkpia.chat_server.config.interceptor;

import com.jootalkpia.chat_server.domain.ChatMessage;
import com.jootalkpia.chat_server.dto.RedisKeys;
import com.jootalkpia.chat_server.repository.UserChannelRepository;
import com.jootalkpia.chat_server.repository.mongo.ChatMessageRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
Expand All @@ -10,6 +13,7 @@
import org.springframework.messaging.simp.stomp.StompHeaderAccessor;
import org.springframework.messaging.support.ChannelInterceptor;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.util.HashSet;
import java.util.List;
Expand All @@ -23,10 +27,14 @@ public class StompSubscriptionInterceptor implements ChannelInterceptor {
private static final String CHANNEL_ID_DELIMITER = "\\.";
private static final long SESSION_EXPIRY_HOURS = 4;
private static final String DEFAULT_CHANNEL = "none";
private static final Long DEFAULT_ID = 1L;

private final RedisTemplate<String, String> stringOperRedisTemplate;
private final RedisTemplate<String, Object> objectOperRedisTemplate;

private final ChatMessageRepository chatMessageRepository;
private final UserChannelRepository userChannelRepository;

@Override
public void afterSendCompletion(Message<?> message, MessageChannel channel, boolean sent, Exception ex) {
if (!isValidMessage(sent, ex)) {
Expand All @@ -37,6 +45,24 @@ public void afterSendCompletion(Message<?> message, MessageChannel channel, bool
handleStompCommand(StompHeaderAccessor.wrap(message));
}

@Transactional
public void handleChatUnsubscription(StompHeaderAccessor accessor) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

해당 메서드 @transactional처리 안해줘도 될까요?
updateChannelFromSession, removeTabFromChannel, updateLastReadId 3개의 변경이 발생되고 있어서요!
하나라도 실패해도 될까요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

현재 Redis 트랜잭션, 보상 트랜잭션 등 다양한 방안을 검토 중입니다!
추후에 리팩토링하겠습니다!

String sessionId = accessor.getSessionId();
String userId = getUserIdFromSessionId(sessionId);
String channelId = getChannelIdFromSessionId(sessionId);

if (userId != null && !userId.trim().isEmpty()) {
updateChannelFromSession(sessionId);
removeTabFromChannel(channelId, userId, sessionId);
userChannelRepository.updateLastReadId(
userId,
channelId,
chatMessageRepository.findFirstByChannelIdOrderByThreadIdDesc(Long.valueOf(channelId))
.map(ChatMessage::getThreadId)
.orElse(DEFAULT_ID));
}
}

private boolean isValidMessage(boolean sent, Exception ex) {
return !hasException(ex) && sent;
}
Expand Down Expand Up @@ -127,17 +153,6 @@ private void saveUserTabs(String channelId, String userId, Set<String> userTabs)
);
}

private void handleChatUnsubscription(StompHeaderAccessor accessor) {
String sessionId = accessor.getSessionId();
String userId = getUserIdFromSessionId(sessionId);
String channelId = getChannelIdFromSessionId(sessionId);

if (userId != null && !userId.trim().isEmpty()) {
updateChannelFromSession(sessionId);
removeTabFromChannel(channelId, userId, sessionId);
}
}

private String getUserIdFromSessionId(String sessionId) {
return stringOperRedisTemplate.opsForValue().get(RedisKeys.sessionUser(sessionId));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.jootalkpia.chat_server.domain;

import lombok.Builder;
import lombok.Getter;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.index.CompoundIndex;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.Field;

import java.util.List;

@Getter
@Builder
@Document(collection = "chat_messages")
@CompoundIndex(name = "channel_thread_idx", def = "{'channel_id': 1, 'thread_id': -1}")
public class ChatMessage extends BaseTimeEntity {

@Id
private String id; // MongoDB의 ObjectId 자동 생성

@Field("channel_id")
private Long channelId;

@Field("thread_id")
private Long threadId;

@Field("thread_date_time")
private String threadDateTime;

@Field("user_id")
private Long userId;

@Field("user_nickname")
private String userNickname;

@Field("user_profile_image")
private String userProfileImage;

@Field("messages")
private List<Message> messages;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.jootalkpia.chat_server.domain;

import lombok.*;
import org.springframework.data.mongodb.core.mapping.Field;

@Getter
@Builder
public class Message {
@Field("type")
private String type;

@Field("text")
private String text;

@Field("image_id")
private Long imageId;

@Field("image_url")
private String imageUrl;

@Field("video_id")
private Long videoId;

@Field("video_thumbnail_id")
private Long videoThumbnailId;

@Field("thumbnail_url")
private String thumbnailUrl;

@Field("video_url")
private String videoUrl;
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,7 @@
package com.jootalkpia.chat_server.domain;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Table;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import jakarta.persistence.Id;
import jakarta.persistence.*;
import lombok.*;

@Entity
@Getter
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.jootalkpia.chat_server.repository;

import lombok.RequiredArgsConstructor;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

@Repository
@RequiredArgsConstructor
public class UserChannelRepository {
private final JdbcTemplate jdbcTemplate;

public void updateLastReadId(String userId, String channelId, Long lastReadId) {
String sql = "UPDATE user_channel SET last_read_id = ? " +
"WHERE user_id = ? AND channel_id = ?";

jdbcTemplate.update(sql, lastReadId, Long.valueOf(userId), Long.valueOf(channelId));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updateLastReadId가 실행될 때 해당 row가 DB에 존재하는지 보장되지 않아서 존재하지 않으면 아무 일도 안 일어날텐데 예외처리까지는 너무 과할까요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optional 추가했고 없을 시 Default 값으로 0L 등록 로직 구현 완료했습니다!

}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.jootalkpia.chat_server.repository;
package com.jootalkpia.chat_server.repository.jpa;

import com.jootalkpia.chat_server.domain.Files;
import org.springframework.data.jpa.repository.JpaRepository;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.jootalkpia.chat_server.repository;
package com.jootalkpia.chat_server.repository.jpa;

import com.jootalkpia.chat_server.domain.Thread;
import org.springframework.data.jpa.repository.JpaRepository;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.jootalkpia.chat_server.repository;
package com.jootalkpia.chat_server.repository.jpa;

import com.jootalkpia.chat_server.domain.User;
import org.springframework.data.jpa.repository.JpaRepository;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.jootalkpia.chat_server.repository.mongo;

import com.jootalkpia.chat_server.domain.ChatMessage;
import org.springframework.data.mongodb.repository.MongoRepository;

import java.util.Optional;

public interface ChatMessageRepository extends MongoRepository<ChatMessage,String> {
Optional<ChatMessage> findFirstByChannelIdOrderByThreadIdDesc(Long channelId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@
import com.jootalkpia.chat_server.dto.messgaeDto.MessageResponse;
import com.jootalkpia.chat_server.dto.messgaeDto.TextResponse;
import com.jootalkpia.chat_server.dto.messgaeDto.VideoResponse;
import com.jootalkpia.chat_server.repository.FileRepository;
import com.jootalkpia.chat_server.repository.ThreadRepository;
import com.jootalkpia.chat_server.repository.UserRepository;
import com.jootalkpia.chat_server.repository.jpa.FileRepository;
import com.jootalkpia.chat_server.repository.jpa.ThreadRepository;
import com.jootalkpia.chat_server.repository.jpa.UserRepository;
import com.jootalkpia.chat_server.util.DateTimeUtil;
import jakarta.transaction.Transactional;
import java.util.ArrayList;
Expand Down
4 changes: 4 additions & 0 deletions src/backend/chat_server/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ spring:
redis:
host: ${REDIS_HOST}
port: ${REDIS_PORT}
mongodb:
uri: ${MONGO_URI}
main:
allow-bean-definition-overriding: true

logging:
level:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
package com.jootalkpia.history_server.domain;

import lombok.*;
import lombok.Builder;
import lombok.Getter;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.index.CompoundIndex;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.Field;

import java.util.List;

@Getter
@Builder
@Document(collection = "chat_messages")
public class ChatMessage extends BaseTimeEntity {
@CompoundIndex(name = "channel_thread_idx", def = "{'channel_id': 1, 'thread_id': -1}")
public class ChatMessage extends BaseTimeEntity {

@Id
private String id; // MongoDB의 ObjectId 자동 생성
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public class MinutePrice {
@Id
private ObjectId minutePriceId;

@Indexed(background = true)
@Indexed
private String code;

private String stockName;
Expand Down