feat(chat-character): 추천 캐릭터 조회 및 메인/새로고침 API 반영
This commit is contained in:
@@ -74,6 +74,13 @@ class ChatCharacterController(
|
||||
size = 50
|
||||
).content
|
||||
|
||||
// 추천 캐릭터 조회
|
||||
// 최근 대화한 캐릭터를 제외한 랜덤 20개 조회
|
||||
// Controller에서는 호출만
|
||||
// 세부로직은 추후에 변경될 수 있으므로 Service에 별도로 생성
|
||||
val excludeIds = recentCharacters.map { it.characterId }
|
||||
val recommendCharacters = service.getRecommendCharacters(excludeIds, 20)
|
||||
|
||||
// 큐레이션 섹션 (활성화된 큐레이션 + 캐릭터)
|
||||
val curationSections = curationQueryService.getActiveCurationsWithCharacters()
|
||||
.map { agg ->
|
||||
@@ -98,6 +105,7 @@ class ChatCharacterController(
|
||||
recentCharacters = recentCharacters,
|
||||
popularCharacters = popularCharacters,
|
||||
newCharacters = newCharacters,
|
||||
recommendCharacters = recommendCharacters,
|
||||
curationSections = curationSections
|
||||
)
|
||||
)
|
||||
@@ -193,4 +201,23 @@ class ChatCharacterController(
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 추천 캐릭터 새로고침 API
|
||||
* - 최근 대화한 캐릭터를 제외하고 랜덤 20개 반환
|
||||
* - 비회원 또는 본인인증되지 않은 경우: 최근 대화 목록 없음 → 전체 활성 캐릭터 중 랜덤 20개
|
||||
*/
|
||||
@GetMapping("/recommend")
|
||||
fun getRecommendCharacters(
|
||||
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
|
||||
) = run {
|
||||
val recent = if (member == null || member.auth == null) {
|
||||
emptyList()
|
||||
} else {
|
||||
chatRoomService
|
||||
.listMyChatRooms(member, 0, 50) // 최근 기록은 최대 50개까지만 제외 대상으로 고려
|
||||
.map { it.characterId }
|
||||
}
|
||||
ApiResponse.ok(service.getRecommendCharacters(recent, 20))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ data class CharacterMainResponse(
|
||||
val recentCharacters: List<RecentCharacter>,
|
||||
val popularCharacters: List<Character>,
|
||||
val newCharacters: List<Character>,
|
||||
val recommendCharacters: List<Character>,
|
||||
val curationSections: List<CurationSection>
|
||||
)
|
||||
|
||||
|
||||
@@ -74,5 +74,29 @@ interface ChatCharacterRepository : JpaRepository<ChatCharacter, Long> {
|
||||
pageable: Pageable
|
||||
): List<ChatCharacter>
|
||||
|
||||
/**
|
||||
* 활성 캐릭터 무작위 조회
|
||||
*/
|
||||
@Query(
|
||||
"""
|
||||
SELECT c FROM ChatCharacter c
|
||||
WHERE c.isActive = true
|
||||
ORDER BY function('RAND')
|
||||
"""
|
||||
)
|
||||
fun findRandomActive(pageable: Pageable): List<ChatCharacter>
|
||||
|
||||
/**
|
||||
* 제외할 캐릭터를 뺀 활성 캐릭터 무작위 조회
|
||||
*/
|
||||
@Query(
|
||||
"""
|
||||
SELECT c FROM ChatCharacter c
|
||||
WHERE c.isActive = true AND c.id NOT IN :excludeIds
|
||||
ORDER BY function('RAND')
|
||||
"""
|
||||
)
|
||||
fun findRandomActiveExcluding(@Param("excludeIds") excludeIds: List<Long>, pageable: Pageable): List<ChatCharacter>
|
||||
|
||||
fun findByIdInAndIsActiveTrue(ids: List<Long>): List<ChatCharacter>
|
||||
}
|
||||
|
||||
@@ -38,6 +38,24 @@ class ChatCharacterService(
|
||||
@Value("\${cloud.aws.cloud-front.host}")
|
||||
private val imageHost: String
|
||||
) {
|
||||
@Transactional(readOnly = true)
|
||||
fun getRecommendCharacters(excludeCharacterIds: List<Long> = emptyList(), limit: Int = 20): List<Character> {
|
||||
val safeLimit = if (limit <= 0) 20 else if (limit > 50) 50 else limit
|
||||
val chars = if (excludeCharacterIds.isNotEmpty()) {
|
||||
chatCharacterRepository.findRandomActiveExcluding(excludeCharacterIds, PageRequest.of(0, safeLimit))
|
||||
} else {
|
||||
chatCharacterRepository.findRandomActive(PageRequest.of(0, safeLimit))
|
||||
}
|
||||
return chars.map {
|
||||
Character(
|
||||
characterId = it.id!!,
|
||||
name = it.name,
|
||||
description = it.description,
|
||||
imageUrl = "$imageHost/${it.imagePath ?: "profile/default-profile.png"}"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* UTC 20:00 경계 기준 지난 윈도우의 메시지 수 상위 캐릭터 조회
|
||||
* Spring Cache(@Cacheable) + 동적 키 + 고정 TTL(24h) 사용
|
||||
|
||||
Reference in New Issue
Block a user