feat(chat): 채팅방 메시지 조회 API 구현
This commit is contained in:
parent
1509ee0729
commit
002f2c2834
|
@ -11,6 +11,7 @@ import org.springframework.web.bind.annotation.PathVariable
|
||||||
import org.springframework.web.bind.annotation.PostMapping
|
import org.springframework.web.bind.annotation.PostMapping
|
||||||
import org.springframework.web.bind.annotation.RequestBody
|
import org.springframework.web.bind.annotation.RequestBody
|
||||||
import org.springframework.web.bind.annotation.RequestMapping
|
import org.springframework.web.bind.annotation.RequestMapping
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam
|
||||||
import org.springframework.web.bind.annotation.RestController
|
import org.springframework.web.bind.annotation.RestController
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
|
@ -62,6 +63,24 @@ class ChatRoomController(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 채팅방 메시지 조회 API
|
||||||
|
* - 참여 여부 검증(미참여시 "잘못된 접근입니다")
|
||||||
|
* - messageId가 있으면 해당 ID 이전 20개, 없으면 최신 20개
|
||||||
|
*/
|
||||||
|
@GetMapping("/{chatRoomId}/messages")
|
||||||
|
fun getChatMessages(
|
||||||
|
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?,
|
||||||
|
@PathVariable chatRoomId: Long,
|
||||||
|
@RequestParam(required = false) messageId: Long?
|
||||||
|
) = run {
|
||||||
|
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
|
||||||
|
if (member.auth == null) throw SodaException("본인인증을 하셔야 합니다.")
|
||||||
|
|
||||||
|
val response = chatRoomService.getChatMessages(member, chatRoomId, messageId)
|
||||||
|
ApiResponse.ok(response)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 세션 상태 조회 API
|
* 세션 상태 조회 API
|
||||||
* - 채팅방 참여 여부 검증
|
* - 채팅방 참여 여부 검증
|
||||||
|
|
|
@ -24,6 +24,16 @@ data class ChatRoomListItemDto(
|
||||||
val lastMessagePreview: String?
|
val lastMessagePreview: String?
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 채팅방 메시지 아이템 DTO (API 응답용)
|
||||||
|
*/
|
||||||
|
data class ChatMessageItemDto(
|
||||||
|
val messageId: Long,
|
||||||
|
val message: String,
|
||||||
|
val profileImageUrl: String,
|
||||||
|
val mine: Boolean
|
||||||
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 채팅방 목록 쿼리 DTO (레포지토리 투영용)
|
* 채팅방 목록 쿼리 DTO (레포지토리 투영용)
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -8,4 +8,11 @@ import org.springframework.stereotype.Repository
|
||||||
@Repository
|
@Repository
|
||||||
interface CharacterChatMessageRepository : JpaRepository<CharacterChatMessage, Long> {
|
interface CharacterChatMessageRepository : JpaRepository<CharacterChatMessage, Long> {
|
||||||
fun findTopByChatRoomAndIsActiveTrueOrderByCreatedAtDesc(chatRoom: CharacterChatRoom): CharacterChatMessage?
|
fun findTopByChatRoomAndIsActiveTrueOrderByCreatedAtDesc(chatRoom: CharacterChatRoom): CharacterChatMessage?
|
||||||
|
|
||||||
|
fun findTop20ByChatRoomAndIsActiveTrueOrderByIdDesc(chatRoom: CharacterChatRoom): List<CharacterChatMessage>
|
||||||
|
|
||||||
|
fun findTop20ByChatRoomAndIdLessThanAndIsActiveTrueOrderByIdDesc(
|
||||||
|
chatRoom: CharacterChatRoom,
|
||||||
|
id: Long
|
||||||
|
): List<CharacterChatMessage>
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import kr.co.vividnext.sodalive.chat.character.service.ChatCharacterService
|
||||||
import kr.co.vividnext.sodalive.chat.room.CharacterChatParticipant
|
import kr.co.vividnext.sodalive.chat.room.CharacterChatParticipant
|
||||||
import kr.co.vividnext.sodalive.chat.room.CharacterChatRoom
|
import kr.co.vividnext.sodalive.chat.room.CharacterChatRoom
|
||||||
import kr.co.vividnext.sodalive.chat.room.ParticipantType
|
import kr.co.vividnext.sodalive.chat.room.ParticipantType
|
||||||
|
import kr.co.vividnext.sodalive.chat.room.dto.ChatMessageItemDto
|
||||||
import kr.co.vividnext.sodalive.chat.room.dto.ChatRoomListItemDto
|
import kr.co.vividnext.sodalive.chat.room.dto.ChatRoomListItemDto
|
||||||
import kr.co.vividnext.sodalive.chat.room.dto.ChatRoomListQueryDto
|
import kr.co.vividnext.sodalive.chat.room.dto.ChatRoomListQueryDto
|
||||||
import kr.co.vividnext.sodalive.chat.room.dto.CreateChatRoomResponse
|
import kr.co.vividnext.sodalive.chat.room.dto.CreateChatRoomResponse
|
||||||
|
@ -324,4 +325,36 @@ class ChatRoomService(
|
||||||
// 최종 실패 로그 (예외 미전파)
|
// 최종 실패 로그 (예외 미전파)
|
||||||
log.error("[chat] 외부 세션 종료 최종 실패: sessionId={}, attempts={}", sessionId, maxAttempts)
|
log.error("[chat] 외부 세션 종료 최종 실패: sessionId={}, attempts={}", sessionId, maxAttempts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
fun getChatMessages(member: Member, chatRoomId: Long, beforeMessageId: Long?): List<ChatMessageItemDto> {
|
||||||
|
val room = chatRoomRepository.findById(chatRoomId).orElseThrow {
|
||||||
|
SodaException("채팅방을 찾을 수 없습니다.")
|
||||||
|
}
|
||||||
|
val participant = participantRepository.findByChatRoomAndMemberAndIsActiveTrue(room, member)
|
||||||
|
if (participant == null) {
|
||||||
|
throw SodaException("잘못된 접근입니다")
|
||||||
|
}
|
||||||
|
|
||||||
|
val messages = if (beforeMessageId != null) {
|
||||||
|
messageRepository.findTop20ByChatRoomAndIdLessThanAndIsActiveTrueOrderByIdDesc(room, beforeMessageId)
|
||||||
|
} else {
|
||||||
|
messageRepository.findTop20ByChatRoomAndIsActiveTrueOrderByIdDesc(room)
|
||||||
|
}
|
||||||
|
|
||||||
|
return messages.map { msg ->
|
||||||
|
val sender = msg.participant
|
||||||
|
val profilePath = when (sender.participantType) {
|
||||||
|
ParticipantType.USER -> sender.member?.profileImage
|
||||||
|
ParticipantType.CHARACTER -> sender.character?.imagePath
|
||||||
|
}
|
||||||
|
val imageUrl = "$imageHost/${profilePath ?: "profile/default-profile.png"}"
|
||||||
|
ChatMessageItemDto(
|
||||||
|
messageId = msg.id!!,
|
||||||
|
message = msg.message,
|
||||||
|
profileImageUrl = imageUrl,
|
||||||
|
mine = sender.member?.id == member.id
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue