feat(quota)!: AI 채팅 쿼터(무료/유료) 도입 및 입장/전송 응답에 상태 포함
- ChatQuota 엔티티/레포/서비스/컨트롤러 추가 - 입장 시 Lazy refill 적용, 전송 시 무료 우선 차감 및 잔여/리필 시간 응답 포함 - ChatRoomEnterResponse에 totalRemaining/nextRechargeAtEpoch 추가 - SendChatMessageResponse 신설 및 send API 응답 스키마 변경 - CanUsage에 CHAT_QUOTA_PURCHASE 추가, CanPaymentService/CanService에 결제 흐름 반영
This commit is contained in:
@@ -180,5 +180,16 @@ data class ChatRoomEnterResponse(
|
||||
val roomId: Long,
|
||||
val character: ChatRoomEnterCharacterDto,
|
||||
val messages: List<ChatMessageItemDto>,
|
||||
val hasMoreMessages: Boolean
|
||||
val hasMoreMessages: Boolean,
|
||||
val totalRemaining: Int,
|
||||
val nextRechargeAtEpoch: Long?
|
||||
)
|
||||
|
||||
/**
|
||||
* 채팅 메시지 전송 응답 DTO (메시지 + 쿼터 상태)
|
||||
*/
|
||||
data class SendChatMessageResponse(
|
||||
val messages: List<ChatMessageItemDto>,
|
||||
val totalRemaining: Int,
|
||||
val nextRechargeAtEpoch: Long?
|
||||
)
|
||||
|
||||
@@ -19,6 +19,7 @@ import kr.co.vividnext.sodalive.chat.room.dto.CreateChatRoomResponse
|
||||
import kr.co.vividnext.sodalive.chat.room.dto.ExternalChatSendResponse
|
||||
import kr.co.vividnext.sodalive.chat.room.dto.ExternalChatSessionCreateResponse
|
||||
import kr.co.vividnext.sodalive.chat.room.dto.ExternalChatSessionGetResponse
|
||||
import kr.co.vividnext.sodalive.chat.room.dto.SendChatMessageResponse
|
||||
import kr.co.vividnext.sodalive.chat.room.repository.ChatMessageRepository
|
||||
import kr.co.vividnext.sodalive.chat.room.repository.ChatParticipantRepository
|
||||
import kr.co.vividnext.sodalive.chat.room.repository.ChatRoomRepository
|
||||
@@ -49,6 +50,7 @@ class ChatRoomService(
|
||||
private val characterImageService: CharacterImageService,
|
||||
private val canPaymentService: kr.co.vividnext.sodalive.can.payment.CanPaymentService,
|
||||
private val imageCloudFront: kr.co.vividnext.sodalive.aws.cloudfront.ImageContentCloudFront,
|
||||
private val chatQuotaService: kr.co.vividnext.sodalive.chat.quota.ChatQuotaService,
|
||||
|
||||
@Value("\${weraser.api-key}")
|
||||
private val apiKey: String,
|
||||
@@ -335,11 +337,16 @@ class ChatRoomService(
|
||||
val messagesAsc = fetched.sortedBy { it.createdAt }
|
||||
val items = messagesAsc.map { toChatMessageItemDto(it, member) }
|
||||
|
||||
// 입장 시 Lazy refill 적용 후 상태 반환
|
||||
val quotaStatus = chatQuotaService.applyRefillOnEnterAndGetStatus(member.id!!)
|
||||
|
||||
return ChatRoomEnterResponse(
|
||||
roomId = room.id!!,
|
||||
character = characterDto,
|
||||
messages = items,
|
||||
hasMoreMessages = hasMore
|
||||
hasMoreMessages = hasMore,
|
||||
totalRemaining = quotaStatus.totalRemaining,
|
||||
nextRechargeAtEpoch = quotaStatus.nextRechargeAtEpochMillis
|
||||
)
|
||||
}
|
||||
|
||||
@@ -490,7 +497,7 @@ class ChatRoomService(
|
||||
}
|
||||
|
||||
@Transactional
|
||||
fun sendMessage(member: Member, chatRoomId: Long, message: String): List<ChatMessageItemDto> {
|
||||
fun sendMessage(member: Member, chatRoomId: Long, message: String): SendChatMessageResponse {
|
||||
// 1) 방 존재 확인
|
||||
val room = chatRoomRepository.findById(chatRoomId).orElseThrow {
|
||||
SodaException("채팅방을 찾을 수 없습니다.")
|
||||
@@ -512,7 +519,10 @@ class ChatRoomService(
|
||||
val sessionId = room.sessionId
|
||||
val characterUUID = character.characterUUID
|
||||
|
||||
// 5) 외부 API 호출 (최대 3회 재시도)
|
||||
// 5) 쿼터 확인 및 차감
|
||||
chatQuotaService.consumeOne(member.id!!)
|
||||
|
||||
// 6) 외부 API 호출 (최대 3회 재시도)
|
||||
val characterReply = callExternalApiForChatSendWithRetry(userId, characterUUID, message, sessionId)
|
||||
|
||||
// 6) 내 메시지 저장
|
||||
@@ -553,6 +563,8 @@ class ChatRoomService(
|
||||
hasAccess = true
|
||||
)
|
||||
|
||||
val status = chatQuotaService.getStatus(member.id!!)
|
||||
|
||||
// 8) 트리거 매칭 → 이미지 메시지 추가 저장(있을 경우)
|
||||
val matchedImage = findTriggeredCharacterImage(character.id!!, characterReply)
|
||||
if (matchedImage != null) {
|
||||
@@ -579,10 +591,18 @@ class ChatRoomService(
|
||||
)
|
||||
|
||||
val imageDto = toChatMessageItemDto(imageMsg, member)
|
||||
return listOf(textDto, imageDto)
|
||||
return SendChatMessageResponse(
|
||||
messages = listOf(textDto, imageDto),
|
||||
totalRemaining = status.totalRemaining,
|
||||
nextRechargeAtEpoch = status.nextRechargeAtEpochMillis
|
||||
)
|
||||
}
|
||||
|
||||
return listOf(textDto)
|
||||
return SendChatMessageResponse(
|
||||
messages = listOf(textDto),
|
||||
totalRemaining = status.totalRemaining,
|
||||
nextRechargeAtEpoch = status.nextRechargeAtEpochMillis
|
||||
)
|
||||
}
|
||||
|
||||
private fun toChatMessageItemDto(
|
||||
|
||||
Reference in New Issue
Block a user