캐릭터 챗봇 #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.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 | ||||||
|  |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * 태그를 찾거나 생성하여 캐릭터에 연결 |      * 태그를 찾거나 생성하여 캐릭터에 연결 | ||||||
|      */ |      */ | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user