캐릭터 챗봇 #338
| @@ -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.CharacterPersonalityResponse | ||||
| 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.service.ChatCharacterBannerService | ||||
| 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( | ||||
|             CharacterDetailResponse( | ||||
| @@ -137,7 +152,11 @@ class ChatCharacterController( | ||||
|                 imageUrl = "$imageHost/${character.imagePath ?: "profile/default-profile.png"}", | ||||
|                 personalities = personality, | ||||
|                 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 | ||||
|  | ||||
| import kr.co.vividnext.sodalive.chat.character.CharacterType | ||||
|  | ||||
| data class CharacterDetailResponse( | ||||
|     val characterId: Long, | ||||
|     val name: String, | ||||
| @@ -8,6 +10,17 @@ data class CharacterDetailResponse( | ||||
|     val imageUrl: String, | ||||
|     val personalities: CharacterPersonalityResponse?, | ||||
|     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 | ||||
| ) | ||||
|  | ||||
|   | ||||
| @@ -66,4 +66,25 @@ interface ChatCharacterRepository : JpaRepository<ChatCharacter, Long> { | ||||
|         @Param("member") member: Member, | ||||
|         pageable: Pageable | ||||
|     ): 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)) | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 특정 캐릭터와 태그를 공유하는 다른 캐릭터를 무작위로 조회 (현재 캐릭터 제외) | ||||
|      */ | ||||
|     @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 | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 태그를 찾거나 생성하여 캐릭터에 연결 | ||||
|      */ | ||||
|   | ||||
		Reference in New Issue
	
	Block a user