feat(chat): 이미지 메시지 조회 시 CloudFront 서명 URL 적용 및 DTO 변환 로직 공통화

- 조회 가능한(보유/무료/결제완료) 이미지 메시지의 이미지 URL을 ImageContentCloudFront.generateSignedURL(만료 5분)로 생성
- 접근 불가(미보유, 유료 미구매) 이미지 메시지는 기존 공개 호스트 URL(블러/스냅샷 경로) 유지
- ChatRoomService에 ImageContentCloudFront를 주입하고, toChatMessageItemDto에서 이미지 URL/hasAccess 결정 로직 단일화
- enterChatRoom, getChatMessages, sendMessage 경로의 중복된 DTO 매핑 로직 제거
- purchaseMessage 결제 완료 시 forceHasAccess=true로 접근 가능 DTO 반환
This commit is contained in:
Klaus 2025-08-25 14:28:11 +09:00
parent 12574dbe46
commit 5a58fe9077
1 changed files with 22 additions and 82 deletions

View File

@ -48,6 +48,7 @@ class ChatRoomService(
private val characterService: ChatCharacterService,
private val characterImageService: CharacterImageService,
private val canPaymentService: kr.co.vividnext.sodalive.can.payment.CanPaymentService,
private val imageCloudFront: kr.co.vividnext.sodalive.aws.cloudfront.ImageContentCloudFront,
@Value("\${weraser.api-key}")
private val apiKey: String,
@ -332,40 +333,7 @@ class ChatRoomService(
}
val messagesAsc = fetched.sortedBy { it.createdAt }
val items = messagesAsc.map { msg ->
val sender = msg.participant
val profilePath = when (sender.participantType) {
ParticipantType.USER -> sender.member?.profileImage
ParticipantType.CHARACTER -> sender.character?.imagePath
}
val senderImageUrl = "$imageHost/${profilePath ?: "profile/default-profile.png"}"
val createdAtMillis = msg.createdAt?.atZone(ZoneId.systemDefault())?.toInstant()?.toEpochMilli()
?: 0L
ChatMessageItemDto(
messageId = msg.id!!,
message = msg.message,
profileImageUrl = senderImageUrl,
mine = sender.member?.id == member.id,
createdAt = createdAtMillis,
messageType = msg.messageType.name,
imageUrl = msg.imagePath?.let { "$imageHost/$it" },
price = msg.price,
hasAccess = if (msg.messageType == ChatMessageType.IMAGE) {
if (msg.price == null) {
true
} else {
msg.characterImage?.id?.let {
characterImageService.isOwnedImageByMember(
it,
member.id!!
)
} ?: true
}
} else {
true
}
)
}
val items = messagesAsc.map { toChatMessageItemDto(it, member) }
return ChatRoomEnterResponse(
roomId = room.id!!,
@ -512,40 +480,7 @@ class ChatRoomService(
// createdAt 오름차순으로 정렬하여 반환
val messagesAsc = fetched.sortedBy { it.createdAt }
val items = messagesAsc.map { msg ->
val sender = msg.participant
val profilePath = when (sender.participantType) {
ParticipantType.USER -> sender.member?.profileImage
ParticipantType.CHARACTER -> sender.character?.imagePath
}
val senderImageUrl = "$imageHost/${profilePath ?: "profile/default-profile.png"}"
val createdAtMillis = msg.createdAt?.atZone(ZoneId.systemDefault())?.toInstant()?.toEpochMilli()
?: 0L
ChatMessageItemDto(
messageId = msg.id!!,
message = msg.message,
profileImageUrl = senderImageUrl,
mine = sender.member?.id == member.id,
createdAt = createdAtMillis,
messageType = msg.messageType.name,
imageUrl = msg.imagePath?.let { "$imageHost/$it" },
price = msg.price,
hasAccess = if (msg.messageType == ChatMessageType.IMAGE) {
if (msg.price == null) {
true
} else {
msg.characterImage?.id?.let {
characterImageService.isOwnedImageByMember(
it,
member.id!!
)
} ?: true
}
} else {
true
}
)
}
val items = messagesAsc.map { toChatMessageItemDto(it, member) }
return ChatMessagesPageResponse(
messages = items,
@ -643,19 +578,7 @@ class ChatRoomService(
)
)
val imageDto = ChatMessageItemDto(
messageId = imageMsg.id!!,
message = imageMsg.message,
profileImageUrl = senderImageUrl,
mine = false,
createdAt = imageMsg.createdAt?.atZone(ZoneId.systemDefault())?.toInstant()
?.toEpochMilli()
?: 0L,
messageType = ChatMessageType.IMAGE.name,
imageUrl = imageMsg.imagePath?.let { "$imageHost/$it" },
price = imageMsg.price,
hasAccess = owned || imageMsg.price == null
)
val imageDto = toChatMessageItemDto(imageMsg, member)
return listOf(textDto, imageDto)
}
@ -687,6 +610,23 @@ class ChatRoomService(
} else {
true
}
val expirationMs = 5L * 60L * 1000L
val resolvedImageUrl: String? = if (msg.messageType == ChatMessageType.IMAGE) {
val path = if (hasAccess) {
msg.characterImage?.imagePath ?: msg.imagePath
} else {
msg.imagePath
}
path?.let { p ->
if (hasAccess) {
imageCloudFront.generateSignedURL(p, expirationMs)
} else {
"$imageHost/$p"
}
}
} else {
null
}
return ChatMessageItemDto(
messageId = msg.id!!,
message = msg.message,
@ -694,7 +634,7 @@ class ChatRoomService(
mine = sender.member?.id == member.id,
createdAt = createdAtMillis,
messageType = msg.messageType.name,
imageUrl = msg.imagePath?.let { "$imageHost/$it" },
imageUrl = resolvedImageUrl,
price = msg.price,
hasAccess = hasAccess
)