feat(chat-character): 캐릭터 상세 조회 응답 확장 및 ‘다른 캐릭터’ 추천 추가
- 상세 페이지 정보 강화 및 탐색성 향상을 위해 응답 필드를 확장 - CharacterDetailResponse에 originalTitle, originalLink, characterType, others 추가 - OtherCharacter DTO 추가 (characterId, name, imageUrl, tags) - 공유 태그 기반으로 현재 캐릭터를 제외한 랜덤 10개 캐릭터 조회 JPA 쿼리 추가 - ChatCharacterRepository.findRandomBySharedTags(@Query, RAND 정렬, 페이징) - 서비스 계층에 getOtherCharactersBySharedTags 추가 및 태그 지연 로딩 초기화 - 컨트롤러에서: - others 리스트를 조회/매핑하여 응답에 포함 - originalTitle, originalLink, characterType을 응답에 포함
This commit is contained in:
parent
423cbe7315
commit
01ef738d31
|
@ -7,6 +7,7 @@ import kr.co.vividnext.sodalive.chat.character.dto.CharacterDetailResponse
|
||||||
import kr.co.vividnext.sodalive.chat.character.dto.CharacterMainResponse
|
import kr.co.vividnext.sodalive.chat.character.dto.CharacterMainResponse
|
||||||
import kr.co.vividnext.sodalive.chat.character.dto.CharacterPersonalityResponse
|
import kr.co.vividnext.sodalive.chat.character.dto.CharacterPersonalityResponse
|
||||||
import kr.co.vividnext.sodalive.chat.character.dto.CurationSection
|
import kr.co.vividnext.sodalive.chat.character.dto.CurationSection
|
||||||
|
import kr.co.vividnext.sodalive.chat.character.dto.OtherCharacter
|
||||||
import kr.co.vividnext.sodalive.chat.character.dto.RecentCharacter
|
import kr.co.vividnext.sodalive.chat.character.dto.RecentCharacter
|
||||||
import kr.co.vividnext.sodalive.chat.character.service.ChatCharacterBannerService
|
import kr.co.vividnext.sodalive.chat.character.service.ChatCharacterBannerService
|
||||||
import kr.co.vividnext.sodalive.chat.character.service.ChatCharacterService
|
import kr.co.vividnext.sodalive.chat.character.service.ChatCharacterService
|
||||||
|
@ -127,6 +128,20 @@ class ChatCharacterController(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 다른 캐릭터 조회 (태그 기반, 랜덤 10개, 현재 캐릭터 제외)
|
||||||
|
val others = service.getOtherCharactersBySharedTags(characterId, 10)
|
||||||
|
.map { other ->
|
||||||
|
val otherTags = other.tagMappings
|
||||||
|
.map { it.tag.tag }
|
||||||
|
.joinToString(" ") { if (it.startsWith("#")) it else "#$it" }
|
||||||
|
OtherCharacter(
|
||||||
|
characterId = other.id!!,
|
||||||
|
name = other.name,
|
||||||
|
imageUrl = "$imageHost/${other.imagePath ?: "profile/default-profile.png"}",
|
||||||
|
tags = otherTags
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// 응답 생성
|
// 응답 생성
|
||||||
ApiResponse.ok(
|
ApiResponse.ok(
|
||||||
CharacterDetailResponse(
|
CharacterDetailResponse(
|
||||||
|
@ -137,7 +152,11 @@ class ChatCharacterController(
|
||||||
imageUrl = "$imageHost/${character.imagePath ?: "profile/default-profile.png"}",
|
imageUrl = "$imageHost/${character.imagePath ?: "profile/default-profile.png"}",
|
||||||
personalities = personality,
|
personalities = personality,
|
||||||
backgrounds = background,
|
backgrounds = background,
|
||||||
tags = tags
|
tags = tags,
|
||||||
|
originalTitle = character.originalTitle,
|
||||||
|
originalLink = character.originalLink,
|
||||||
|
characterType = character.characterType,
|
||||||
|
others = others
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package kr.co.vividnext.sodalive.chat.character.dto
|
package kr.co.vividnext.sodalive.chat.character.dto
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.chat.character.CharacterType
|
||||||
|
|
||||||
data class CharacterDetailResponse(
|
data class CharacterDetailResponse(
|
||||||
val characterId: Long,
|
val characterId: Long,
|
||||||
val name: String,
|
val name: String,
|
||||||
|
@ -8,6 +10,17 @@ data class CharacterDetailResponse(
|
||||||
val imageUrl: String,
|
val imageUrl: String,
|
||||||
val personalities: CharacterPersonalityResponse?,
|
val personalities: CharacterPersonalityResponse?,
|
||||||
val backgrounds: CharacterBackgroundResponse?,
|
val backgrounds: CharacterBackgroundResponse?,
|
||||||
|
val tags: String,
|
||||||
|
val originalTitle: String?,
|
||||||
|
val originalLink: String?,
|
||||||
|
val characterType: CharacterType,
|
||||||
|
val others: List<OtherCharacter>
|
||||||
|
)
|
||||||
|
|
||||||
|
data class OtherCharacter(
|
||||||
|
val characterId: Long,
|
||||||
|
val name: String,
|
||||||
|
val imageUrl: String,
|
||||||
val tags: String
|
val tags: String
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -66,4 +66,25 @@ interface ChatCharacterRepository : JpaRepository<ChatCharacter, Long> {
|
||||||
@Param("member") member: Member,
|
@Param("member") member: Member,
|
||||||
pageable: Pageable
|
pageable: Pageable
|
||||||
): List<ChatCharacter>
|
): List<ChatCharacter>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 특정 캐릭터와 태그를 공유하는 다른 캐릭터를 무작위로 조회 (현재 캐릭터 제외)
|
||||||
|
*/
|
||||||
|
@Query(
|
||||||
|
"""
|
||||||
|
SELECT DISTINCT c FROM ChatCharacter c
|
||||||
|
JOIN c.tagMappings tm
|
||||||
|
JOIN tm.tag t
|
||||||
|
WHERE c.isActive = true
|
||||||
|
AND c.id <> :characterId
|
||||||
|
AND t.id IN (
|
||||||
|
SELECT t2.id FROM ChatCharacterTagMapping tm2 JOIN tm2.tag t2 WHERE tm2.chatCharacter.id = :characterId
|
||||||
|
)
|
||||||
|
ORDER BY function('RAND')
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
fun findRandomBySharedTags(
|
||||||
|
@Param("characterId") characterId: Long,
|
||||||
|
pageable: Pageable
|
||||||
|
): List<ChatCharacter>
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,6 +53,20 @@ class ChatCharacterService(
|
||||||
return chatCharacterRepository.findByIsActiveTrueOrderByCreatedAtDesc(PageRequest.of(0, limit))
|
return chatCharacterRepository.findByIsActiveTrueOrderByCreatedAtDesc(PageRequest.of(0, limit))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 특정 캐릭터와 태그를 공유하는 다른 캐릭터를 무작위로 조회 (현재 캐릭터 제외)
|
||||||
|
*/
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
fun getOtherCharactersBySharedTags(characterId: Long, limit: Int = 10): List<ChatCharacter> {
|
||||||
|
val others = chatCharacterRepository.findRandomBySharedTags(
|
||||||
|
characterId,
|
||||||
|
PageRequest.of(0, limit)
|
||||||
|
)
|
||||||
|
// 태그 초기화 (지연 로딩 문제 방지)
|
||||||
|
others.forEach { it.tagMappings.size }
|
||||||
|
return others
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 태그를 찾거나 생성하여 캐릭터에 연결
|
* 태그를 찾거나 생성하여 캐릭터에 연결
|
||||||
*/
|
*/
|
||||||
|
|
Loading…
Reference in New Issue