feat(chat): 입장 라우팅 도입 및 라우팅 시 배경 이미지 URL 무시(null)
- baseRoom이 비활성/미참여면 동일 캐릭터의 내 활성 방으로 라우팅해 응답 구성 - 라우팅된 경우 bgImageUrl은 항상 null 처리; 대체 방 없으면 기존 예외 유지
This commit is contained in:
parent
2c3e12a42c
commit
a94cf8dad9
|
@ -298,17 +298,49 @@ class ChatRoomService(
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
fun enterChatRoom(member: Member, chatRoomId: Long, characterImageId: Long? = null): ChatRoomEnterResponse {
|
fun enterChatRoom(member: Member, chatRoomId: Long, characterImageId: Long? = null): ChatRoomEnterResponse {
|
||||||
val room = chatRoomRepository.findByIdAndIsActiveTrue(chatRoomId)
|
// 1) 활성 여부 무관하게 방 조회
|
||||||
?: throw SodaException("채팅방을 찾을 수 없습니다.")
|
val baseRoom = chatRoomRepository.findById(chatRoomId).orElseThrow {
|
||||||
// 참여 여부 검증
|
SodaException("채팅방을 찾을 수 없습니다.")
|
||||||
participantRepository.findByChatRoomAndMemberAndIsActiveTrue(room, member)
|
}
|
||||||
?: throw SodaException("잘못된 접근입니다")
|
|
||||||
|
|
||||||
// 캐릭터 참여자 조회
|
// 2) 기본 방 기준 참여/활성 여부 확인
|
||||||
val characterParticipant = participantRepository.findByChatRoomAndParticipantTypeAndIsActiveTrue(
|
val isActiveRoom = baseRoom.isActive
|
||||||
room,
|
val isMyActiveParticipation =
|
||||||
|
participantRepository.findByChatRoomAndMemberAndIsActiveTrue(baseRoom, member) != null
|
||||||
|
|
||||||
|
// 3) 기본 방의 캐릭터 식별 (활성 우선, 없으면 컬렉션에서 검색)
|
||||||
|
val baseCharacterParticipant = participantRepository.findByChatRoomAndParticipantTypeAndIsActiveTrue(
|
||||||
|
baseRoom,
|
||||||
ParticipantType.CHARACTER
|
ParticipantType.CHARACTER
|
||||||
) ?: throw SodaException("잘못된 접근입니다")
|
) ?: baseRoom.participants.firstOrNull {
|
||||||
|
it.participantType == ParticipantType.CHARACTER
|
||||||
|
} ?: throw SodaException("잘못된 접근입니다")
|
||||||
|
|
||||||
|
val baseCharacter = baseCharacterParticipant.character
|
||||||
|
?: throw SodaException("오류가 발생했습니다. 다시 시도해 주세요.")
|
||||||
|
|
||||||
|
// 4) 유효한 입장 대상 방 결정
|
||||||
|
val effectiveRoom: ChatRoom = if (isActiveRoom && isMyActiveParticipation) {
|
||||||
|
baseRoom
|
||||||
|
} else {
|
||||||
|
// 동일 캐릭터 + 내가 참여 중인 활성 방을 찾는다
|
||||||
|
val alt = chatRoomRepository.findActiveChatRoomByMemberAndCharacter(member, baseCharacter)
|
||||||
|
alt ?: ( // 대체 방이 없으면 기존과 동일하게 예외 처리
|
||||||
|
if (!isActiveRoom) {
|
||||||
|
throw SodaException("채팅방을 찾을 수 없습니다.")
|
||||||
|
} else {
|
||||||
|
throw SodaException("잘못된 접근입니다")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5) 응답 구성 시에는 effectiveRoom의 캐릭터(활성 우선) 사용
|
||||||
|
val characterParticipant = participantRepository.findByChatRoomAndParticipantTypeAndIsActiveTrue(
|
||||||
|
effectiveRoom,
|
||||||
|
ParticipantType.CHARACTER
|
||||||
|
) ?: effectiveRoom.participants.firstOrNull {
|
||||||
|
it.participantType == ParticipantType.CHARACTER
|
||||||
|
} ?: throw SodaException("잘못된 접근입니다")
|
||||||
|
|
||||||
val character = characterParticipant.character
|
val character = characterParticipant.character
|
||||||
?: throw SodaException("오류가 발생했습니다. 다시 시도해 주세요.")
|
?: throw SodaException("오류가 발생했습니다. 다시 시도해 주세요.")
|
||||||
|
@ -321,13 +353,13 @@ class ChatRoomService(
|
||||||
characterType = character.characterType.name
|
characterType = character.characterType.name
|
||||||
)
|
)
|
||||||
|
|
||||||
// 메시지 최신 20개 조회 후 createdAt 오름차순으로 반환
|
// 메시지 최신 20개 조회 후 createdAt 오름차순으로 반환 (effectiveRoom 기준)
|
||||||
val pageable = PageRequest.of(0, 20)
|
val pageable = PageRequest.of(0, 20)
|
||||||
val fetched = messageRepository.findByChatRoomAndIsActiveTrueOrderByIdDesc(room, pageable)
|
val fetched = messageRepository.findByChatRoomAndIsActiveTrueOrderByIdDesc(effectiveRoom, pageable)
|
||||||
|
|
||||||
val nextCursor: Long? = fetched.minByOrNull { it.id ?: Long.MAX_VALUE }?.id
|
val nextCursor: Long? = fetched.minByOrNull { it.id ?: Long.MAX_VALUE }?.id
|
||||||
val hasMore: Boolean = if (nextCursor != null) {
|
val hasMore: Boolean = if (nextCursor != null) {
|
||||||
messageRepository.existsByChatRoomAndIsActiveTrueAndIdLessThan(room, nextCursor)
|
messageRepository.existsByChatRoomAndIsActiveTrueAndIdLessThan(effectiveRoom, nextCursor)
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
@ -339,38 +371,47 @@ class ChatRoomService(
|
||||||
val quotaStatus = chatQuotaService.applyRefillOnEnterAndGetStatus(member.id!!)
|
val quotaStatus = chatQuotaService.applyRefillOnEnterAndGetStatus(member.id!!)
|
||||||
|
|
||||||
// 선택적 캐릭터 이미지 서명 URL 생성 처리
|
// 선택적 캐릭터 이미지 서명 URL 생성 처리
|
||||||
val signedUrl: String? = try {
|
// 요구사항: baseRoom이 조건 불만족으로 동일 캐릭터의 내 활성 방으로 라우팅된 경우(bg 이미지 요청 무시)에는 null로 처리
|
||||||
if (characterImageId != null) {
|
val signedUrl: String? =
|
||||||
val img = characterImageService.getById(characterImageId)
|
if (effectiveRoom.id != baseRoom.id) {
|
||||||
// 동일 캐릭터 소속 및 활성 검증
|
null
|
||||||
if (img.chatCharacter.id == character.id && img.isActive) {
|
} else {
|
||||||
val owned =
|
try {
|
||||||
(img.imagePriceCan == 0L) || characterImageService.isOwnedImageByMember(img.id!!, member.id!!)
|
if (characterImageId != null) {
|
||||||
if (owned) {
|
val img = characterImageService.getById(characterImageId)
|
||||||
val expiration = 5L * 60L * 1000L // 5분
|
// 동일 캐릭터 소속 및 활성 검증
|
||||||
imageCloudFront.generateSignedURL(img.imagePath, expiration)
|
if (img.chatCharacter.id == character.id && img.isActive) {
|
||||||
|
val owned =
|
||||||
|
(img.imagePriceCan == 0L) || characterImageService.isOwnedImageByMember(
|
||||||
|
img.id!!,
|
||||||
|
member.id!!
|
||||||
|
)
|
||||||
|
if (owned) {
|
||||||
|
val expiration = 5L * 60L * 1000L // 5분
|
||||||
|
imageCloudFront.generateSignedURL(img.imagePath, expiration)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
} else {
|
} catch (e: Exception) {
|
||||||
|
// 문제가 있어도 입장 자체는 가능해야 하므로 로그만 남기고 null 반환
|
||||||
|
log.warn(
|
||||||
|
"[chat] enter: signed url generation failed. roomId={}, imageId={}, reason={}",
|
||||||
|
effectiveRoom.id,
|
||||||
|
characterImageId,
|
||||||
|
e.message
|
||||||
|
)
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
|
||||||
// 문제가 있어도 입장 자체는 가능해야 하므로 로그만 남기고 null 반환
|
|
||||||
log.warn(
|
|
||||||
"[chat] enter: signed url generation failed. roomId={}, imageId={}, reason={}",
|
|
||||||
room.id,
|
|
||||||
characterImageId,
|
|
||||||
e.message
|
|
||||||
)
|
|
||||||
null
|
|
||||||
}
|
|
||||||
|
|
||||||
return ChatRoomEnterResponse(
|
return ChatRoomEnterResponse(
|
||||||
roomId = room.id!!,
|
roomId = effectiveRoom.id!!,
|
||||||
character = characterDto,
|
character = characterDto,
|
||||||
messages = items,
|
messages = items,
|
||||||
hasMoreMessages = hasMore,
|
hasMoreMessages = hasMore,
|
||||||
|
|
Loading…
Reference in New Issue