Compare commits

...

2 Commits

Author SHA1 Message Date
7f3589dcfb fix(original): 인기 캐릭터 조회
- 캐시 키 변경
2025-09-15 05:20:46 +09:00
b134c28c10 feat(original): 관리자 캐릭터 상세 조회
- 원작 데이터 추가
2025-09-15 05:18:01 +09:00
3 changed files with 34 additions and 5 deletions

View File

@@ -2,6 +2,10 @@ package kr.co.vividnext.sodalive.admin.chat.character.dto
import kr.co.vividnext.sodalive.chat.character.ChatCharacter import kr.co.vividnext.sodalive.chat.character.ChatCharacter
/**
* 관리자 캐릭터 상세 응답 DTO
* - 원작이 연결되어 있으면 원작 요약 정보(originalWork)를 함께 반환한다.
*/
data class ChatCharacterDetailResponse( data class ChatCharacterDetailResponse(
val id: Long, val id: Long,
val characterUUID: String, val characterUUID: String,
@@ -24,7 +28,8 @@ data class ChatCharacterDetailResponse(
val relationships: List<RelationshipResponse>, val relationships: List<RelationshipResponse>,
val personalities: List<PersonalityResponse>, val personalities: List<PersonalityResponse>,
val backgrounds: List<BackgroundResponse>, val backgrounds: List<BackgroundResponse>,
val memories: List<MemoryResponse> val memories: List<MemoryResponse>,
val originalWork: OriginalWorkBriefResponse? // 추가: 원작 요약 정보
) { ) {
companion object { companion object {
fun from(chatCharacter: ChatCharacter, imageHost: String = ""): ChatCharacterDetailResponse { fun from(chatCharacter: ChatCharacter, imageHost: String = ""): ChatCharacterDetailResponse {
@@ -34,6 +39,20 @@ data class ChatCharacterDetailResponse(
chatCharacter.imagePath ?: "" chatCharacter.imagePath ?: ""
} }
val ow = chatCharacter.originalWork
val originalWorkBrief = ow?.let {
val owImage = if (it.imagePath != null && imageHost.isNotEmpty()) {
"$imageHost/${it.imagePath}"
} else {
it.imagePath
}
OriginalWorkBriefResponse(
id = it.id!!,
imageUrl = owImage,
title = it.title
)
}
return ChatCharacterDetailResponse( return ChatCharacterDetailResponse(
id = chatCharacter.id!!, id = chatCharacter.id!!,
characterUUID = chatCharacter.characterUUID, characterUUID = chatCharacter.characterUUID,
@@ -71,7 +90,8 @@ data class ChatCharacterDetailResponse(
}, },
memories = chatCharacter.memories.map { memories = chatCharacter.memories.map {
MemoryResponse(it.title, it.content, it.emotion) MemoryResponse(it.title, it.content, it.emotion)
} },
originalWork = originalWorkBrief
) )
} }
} }
@@ -101,3 +121,12 @@ data class RelationshipResponse(
val relationshipType: String, val relationshipType: String,
val currentStatus: String val currentStatus: String
) )
/**
* 원작 요약 응답 DTO(관리자 캐릭터 상세용)
*/
data class OriginalWorkBriefResponse(
val id: Long,
val imageUrl: String?,
val title: String
)

View File

@@ -45,10 +45,10 @@ class ChatCharacterService(
@Transactional(readOnly = true) @Transactional(readOnly = true)
@Cacheable( @Cacheable(
cacheNames = ["popularCharacters_24h"], cacheNames = ["popularCharacters_24h"],
key = "T(kr.co.vividnext.sodalive.chat.character.service.RankingWindowCalculator).now('popular-chat-character').cacheKey" key = "T(kr.co.vividnext.sodalive.chat.character.service.RankingWindowCalculator).now('popular-character').cacheKey"
) )
fun getPopularCharacters(limit: Long = 20): List<Character> { fun getPopularCharacters(limit: Long = 20): List<Character> {
val window = RankingWindowCalculator.now("popular-chat-character") val window = RankingWindowCalculator.now("popular-character")
val topIds = popularCharacterQuery.findPopularCharacterIds(window.windowStart, window.nextBoundary, limit) val topIds = popularCharacterQuery.findPopularCharacterIds(window.windowStart, window.nextBoundary, limit)
val list = loadCharactersInOrder(topIds) val list = loadCharactersInOrder(topIds)
return list.map { return list.map {

View File

@@ -20,7 +20,7 @@ object RankingWindowCalculator {
private const val BOUNDARY_HOUR = 20 // 20:00:00 UTC private const val BOUNDARY_HOUR = 20 // 20:00:00 UTC
@JvmStatic @JvmStatic
fun now(prefix: String = "popular-chat-character"): RankingWindow { fun now(prefix: String = "popular-character"): RankingWindow {
val now = ZonedDateTime.now(ZONE) val now = ZonedDateTime.now(ZONE)
val todayBoundary = now.toLocalDate().atTime(BOUNDARY_HOUR, 0, 0).atZone(ZONE) val todayBoundary = now.toLocalDate().atTime(BOUNDARY_HOUR, 0, 0).atZone(ZONE)