Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
2caff13
#225 feat(be): passport를 위한 common module 의존성 추가
bo-ram-bo-ram Feb 20, 2025
159aa6b
#225 feat(be): 과거 채팅 조회 api 구현 - controller
bo-ram-bo-ram Feb 20, 2025
3d28cce
#225 feat(be): 과거 채팅 조회 api 구현 - response dto
bo-ram-bo-ram Feb 20, 2025
6745e9f
#225 feat(be): 과거 채팅 조회 api 구현 - service(1차)
bo-ram-bo-ram Feb 20, 2025
4e84252
#225 feat(be): 과거 채팅 조회 api 구현 - repository
bo-ram-bo-ram Feb 20, 2025
427e1ab
#225 fix(be): size 기본값 로직 이동 (service -> controller)
bo-ram-bo-ram Feb 20, 2025
967c569
#225 refactor(be): 변수명 수정 (chatMessages -> chatMessageList)
bo-ram-bo-ram Feb 20, 2025
62ce423
#225 refactor(be): 주석 수정
bo-ram-bo-ram Feb 20, 2025
f8a2d60
#225 feat(be): 첫요청이면 db에 저장된 마지막 id로 조회하는 로직 추가 - userChannel 추가
bo-ram-bo-ram Feb 20, 2025
9517bba
#225 feat(be): 첫요청이면 db에 저장된 마지막 id로 조회하는 로직 추가 - userChannel reposi…
bo-ram-bo-ram Feb 20, 2025
58fabe5
#225 feat(be): 첫요청이면 db에 저장된 마지막 id로 조회하는 로직 추가 - yml 추가
bo-ram-bo-ram Feb 20, 2025
647bf3a
#225 feat(be): 첫요청이면 db에 저장된 마지막 id로 조회하는 로직 추가 - 의존성 추가
bo-ram-bo-ram Feb 20, 2025
c3ccd2c
#225 feat(be): 첫요청이면 db에 저장된 마지막 id로 조회하는 로직 추가 - 서비스 로직 반영
bo-ram-bo-ram Feb 20, 2025
8be9f77
#225 fix(be): message 조회 시 null 값 제거
bo-ram-bo-ram Feb 20, 2025
c24952c
#225 feat(be): ChatMessagePageResponse에 message의 불필요 컬럼 삭제하여 담을 mess…
bo-ram-bo-ram Feb 20, 2025
a217c62
#225 feat(be): ChatMessageDto 반환로직 구현
bo-ram-bo-ram Feb 20, 2025
ee98eb7
#225 fix(be): MessageDto 조회 시 null 값 제거
bo-ram-bo-ram Feb 20, 2025
6d5f59e
#225 feat(be): 첫 입장 예외처리 구현
bo-ram-bo-ram Feb 20, 2025
9075845
#225 fix(be): hasNext 로직에러 수정
bo-ram-bo-ram Feb 20, 2025
0426aef
#225 refactor(be): 역할별 메서드 분리
bo-ram-bo-ram Feb 20, 2025
be5de07
#225 refactor(be): determineHasNext메서드 적용
bo-ram-bo-ram Feb 20, 2025
945b92e
#225 refactor(be): 변수명 수정
bo-ram-bo-ram Feb 20, 2025
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
9 changes: 8 additions & 1 deletion src/backend/history_server/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,19 @@ repositories {
}

dependencies {
//common-module
implementation project(":common_module")

//spring boot
implementation 'org.springframework.boot:spring-boot-starter-web'

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

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

//test
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
Expand Down
3 changes: 3 additions & 0 deletions src/backend/history_server/settings.gradle
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
rootProject.name = 'history_server'

include(":common_module")
findProject(":common_module")?.projectDir = file("../common_module")
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@
import org.springframework.data.mongodb.config.EnableMongoAuditing;

@EnableMongoAuditing
@SpringBootApplication
@SpringBootApplication(scanBasePackages = {
"com.jootalkpia.history_server",
"com.jootalkpia.passport",
"com.jootalkpia.config",
})
public class HistoryServerApplication {

public static void main(String[] args) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.jootalkpia.history_server.controller;

import com.jootalkpia.history_server.dto.ChatMessagePageResponse;
import com.jootalkpia.history_server.service.HistoryQueryService;
import com.jootalkpia.passport.anotation.CurrentUser;
import com.jootalkpia.passport.component.UserInfo;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequiredArgsConstructor
public class HistoryController {

private static final String DEFAULT_PAGE_SIZE = "30";
private final HistoryQueryService historyQueryService;

@GetMapping("/api/v1/history/{channelId}")
public ChatMessagePageResponse getChatMessagesForward(
@PathVariable Long channelId,
//처음 요청시엔 서버 내에서 안읽은 메세지 값으로 설정하기 위해 false
// 이때 message의 objectId가 아닌 threadId가 기준
@RequestParam(required = false) Long cursorId,
@RequestParam(defaultValue = DEFAULT_PAGE_SIZE) int size,
@CurrentUser UserInfo userInfo) {
return historyQueryService.
getChatMessagesForward(channelId, cursorId, size, userInfo.userId());
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.jootalkpia.history_server.domain;

import jakarta.persistence.Column;
import java.time.LocalDateTime;
import lombok.Getter;
import org.springframework.data.annotation.CreatedDate;
Expand All @@ -9,6 +10,7 @@
public abstract class BaseTimeEntity {

@CreatedDate
@Column(updatable = false)
private LocalDateTime createdAt; // 생성 시간

@LastModifiedDate
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.jootalkpia.history_server.domain;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.Getter;
import lombok.RequiredArgsConstructor;

@Entity
@Table(name = "user_channel")
@Getter
@RequiredArgsConstructor
public class UserChannel extends BaseTimeEntity {

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

private Long userId;

private Long lastReadId;

private Long channelId;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.jootalkpia.history_server.dto;

import java.util.List;

public record ChatMessagePageResponse(
boolean hasNext,
Long lastCursorId,
List<ThreadDto> threads
) {}

Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package com.jootalkpia.history_server.dto;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.jootalkpia.history_server.domain.Message;

@JsonInclude(JsonInclude.Include.NON_NULL)
public record MessageDto(
String type,
String text,
Expand All @@ -24,5 +26,17 @@ public Message toMessage() {
.videoUrl(this.videoUrl)
.build();
}
public static MessageDto documentToDto(Message message) {
return new MessageDto(
message.getType(),
message.getText(),
message.getImageId(),
message.getImageUrl(),
message.getVideoId(),
message.getVideoThumbnailId(),
message.getThumbnailUrl(),
message.getVideoUrl()
);
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.jootalkpia.history_server.dto;

import com.jootalkpia.history_server.domain.ChatMessage;
import java.util.List;
import java.util.stream.Collectors;

public record ThreadDto(
Long channelId,
Long threadId,
String threadDateTime,
Long userId,
String userNickname,
String userProfileImage,
List<MessageDto> messages
) {
public static ThreadDto from(ChatMessage chatMessage) {
return new ThreadDto(
chatMessage.getChannelId(),
chatMessage.getThreadId(),
chatMessage.getThreadDateTime(),
chatMessage.getUserId(),
chatMessage.getUserNickname(),
chatMessage.getUserProfileImage(),
chatMessage.getMessages().stream()
.map(MessageDto::documentToDto) // Message 변환
.collect(Collectors.toList())
);
}
}

Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
package com.jootalkpia.history_server.repository;

import com.jootalkpia.history_server.domain.ChatMessage;
import org.springframework.data.domain.Pageable;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.stereotype.Repository;

public interface ChatMessageRepository extends MongoRepository<ChatMessage,String> {
import java.util.List;

@Repository
public interface ChatMessageRepository extends MongoRepository<ChatMessage, String> {
// cursorId 이후의 메시지 조회 (페이징)
List<ChatMessage> findByChannelIdAndThreadIdGreaterThanOrderByThreadIdAsc(Long channelId, Long threadId, Pageable pageable);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.jootalkpia.history_server.repository;

import com.jootalkpia.history_server.domain.UserChannel;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;

@Repository
public interface UserChannelRepository extends JpaRepository<UserChannel, Long> {

@Query("SELECT u.lastReadId FROM UserChannel u WHERE u.userId = :userId AND u.channelId = :channelId")
Long findLastReadIdByUserIdAndChannelId(@Param("userId") Long userId, @Param("channelId") Long channelId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package com.jootalkpia.history_server.service;

import com.jootalkpia.history_server.domain.ChatMessage;
import com.jootalkpia.history_server.dto.ThreadDto;
import com.jootalkpia.history_server.dto.ChatMessagePageResponse;
import com.jootalkpia.history_server.repository.ChatMessageRepository;
import com.jootalkpia.history_server.repository.UserChannelRepository;
import java.util.Collections;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class HistoryQueryService {

private final ChatMessageRepository chatMessageRepository;
private final UserChannelRepository userChannelRepository;

public ChatMessagePageResponse getChatMessagesForward(Long channelId, Long cursorId, int size, Long userId) {
List<ChatMessage> chatMessageList = fetchMessagesForward(channelId, cursorId, size, userId);

if (chatMessageList.isEmpty()) {
return new ChatMessagePageResponse(false, null, Collections.emptyList());
}

List<ThreadDto> responseMessages = convertToDtoList(chatMessageList);
boolean hasNext = determineHasNext(responseMessages, size);

// size + 1로 조회했으므로, 초과한 1개 데이터 제거
if (hasNext) {
responseMessages = responseMessages.subList(0, size);
}

Long lastThreadId = getLastThreadId(responseMessages);

return new ChatMessagePageResponse(hasNext, lastThreadId, responseMessages);
}


/**
* DB에서 채팅 메시지를 조회하는 메서드
*/
private List<ChatMessage> fetchMessagesForward(Long channelId, Long cursorId, int size, Long userId) {
if (cursorId == null) {
Long lastReadId = userChannelRepository.findLastReadIdByUserIdAndChannelId(userId, channelId);

if (lastReadId == null) {
return Collections.emptyList(); // 첫 입장 시 빈 응답
}

return chatMessageRepository.findByChannelIdAndThreadIdGreaterThanOrderByThreadIdAsc(
channelId, lastReadId, PageRequest.of(0, size + 1));
}

return chatMessageRepository.findByChannelIdAndThreadIdGreaterThanOrderByThreadIdAsc(
channelId, cursorId, PageRequest.of(0, size + 1));
}

/**
* ChatMessage 리스트를 ChatMessageDto 리스트로 변환하는 메서드
*/
private List<ThreadDto> convertToDtoList(List<ChatMessage> chatMessageList) {
return chatMessageList.stream()
.map(ThreadDto::from)
.toList();
}

/**
* hasNext(다음 페이지 여부)를 판별하는 메서드
*/
private boolean determineHasNext(List<ThreadDto> responseMessages, int size) {
return responseMessages.size() > size;
}

/**
* 마지막 threadId를 반환하는 메서드
*/
private Long getLastThreadId(List<ThreadDto> responseMessages) {
if (responseMessages.isEmpty()) {
return null;
}
return responseMessages.get(responseMessages.size() - 1).threadId();
}
}
15 changes: 15 additions & 0 deletions src/backend/history_server/src/main/resources/application.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,19 @@
spring:
datasource:
driver-class-name: org.postgresql.Driver
url: jdbc:postgresql://${DB_HOST}:${DB_PORT}/${DB_NAME}
username: ${DB_USER}
password: ${DB_PASSWORD}

jpa:
show-sql: true
hibernate:
ddl-auto: none
properties:
hibernate:
format_sql: true
show_sql: true

data:
mongodb:
uri: ${MONGO_URI}
Expand Down