캐릭터 챗봇 #338
|
@ -40,30 +40,73 @@ class CharacterImageController(
|
||||||
if (member.auth == null) throw SodaException("본인인증을 하셔야 합니다.")
|
if (member.auth == null) throw SodaException("본인인증을 하셔야 합니다.")
|
||||||
|
|
||||||
val pageSize = if (size <= 0) 20 else minOf(size, 20)
|
val pageSize = if (size <= 0) 20 else minOf(size, 20)
|
||||||
val pageable = PageRequest.of(page, pageSize)
|
|
||||||
|
|
||||||
val pageResult = imageService.pageActiveByCharacter(characterId, pageable)
|
|
||||||
val totalCount = pageResult.totalElements
|
|
||||||
|
|
||||||
|
// 전체 활성 이미지 수(프로필 제외) 파악을 위해 최소 페이지 조회
|
||||||
|
val totalActiveElements = imageService.pageActiveByCharacter(characterId, PageRequest.of(0, 1)).totalElements
|
||||||
val ownedCount = imageService.countOwnedActiveByCharacterForMember(characterId, member.id!!)
|
val ownedCount = imageService.countOwnedActiveByCharacterForMember(characterId, member.id!!)
|
||||||
|
|
||||||
val expiration = 5L * 60L * 1000L // 5분
|
val totalCount = totalActiveElements + 1 // 프로필 포함
|
||||||
val items = pageResult.content.map { img ->
|
|
||||||
val isOwned = (img.imagePriceCan == 0L) || imageService.isOwnedImageByMember(img.id!!, member.id!!)
|
val startIndex = page * pageSize
|
||||||
val url = if (isOwned) {
|
if (startIndex >= totalCount) {
|
||||||
imageCloudFront.generateSignedURL(img.imagePath, expiration)
|
return@run ApiResponse.ok(
|
||||||
} else {
|
CharacterImageListResponse(
|
||||||
"$imageHost/${img.blurImagePath}"
|
totalCount = totalCount,
|
||||||
}
|
ownedCount = ownedCount,
|
||||||
CharacterImageListItemResponse(
|
items = emptyList()
|
||||||
id = img.id!!,
|
)
|
||||||
imageUrl = url,
|
|
||||||
isOwned = isOwned,
|
|
||||||
imagePriceCan = img.imagePriceCan,
|
|
||||||
sortOrder = img.sortOrder
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val endExclusive = kotlin.math.min(startIndex + pageSize, totalCount.toInt())
|
||||||
|
val pageLength = endExclusive - startIndex
|
||||||
|
|
||||||
|
// 프로필 이미지 구성(맨 앞)
|
||||||
|
val profilePath = imageService.getCharacterImagePath(characterId) ?: "profile/default-profile.png"
|
||||||
|
val profileItem = CharacterImageListItemResponse(
|
||||||
|
id = 0L,
|
||||||
|
imageUrl = "$imageHost/$profilePath",
|
||||||
|
isOwned = true,
|
||||||
|
imagePriceCan = 0L,
|
||||||
|
sortOrder = 0
|
||||||
|
)
|
||||||
|
|
||||||
|
// 활성 이미지 offset/limit 계산 (결합 리스트 [프로필] + activeImages)
|
||||||
|
val activeOffset = if (startIndex == 0) 0L else (startIndex - 1).toLong()
|
||||||
|
val activeLimit = if (startIndex == 0) (pageLength - 1).toLong() else pageLength.toLong()
|
||||||
|
|
||||||
|
val expiration = 5L * 60L * 1000L // 5분
|
||||||
|
val activeImages = if (activeLimit > 0) {
|
||||||
|
imageService.pageActiveByCharacterOffset(
|
||||||
|
characterId,
|
||||||
|
activeOffset,
|
||||||
|
activeLimit
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
val items = buildList {
|
||||||
|
if (startIndex == 0 && pageLength > 0) add(profileItem)
|
||||||
|
activeImages.forEach { img ->
|
||||||
|
val isOwned = (img.imagePriceCan == 0L) || imageService.isOwnedImageByMember(img.id!!, member.id!!)
|
||||||
|
val url = if (isOwned) {
|
||||||
|
imageCloudFront.generateSignedURL(img.imagePath, expiration)
|
||||||
|
} else {
|
||||||
|
"$imageHost/${img.blurImagePath}"
|
||||||
|
}
|
||||||
|
add(
|
||||||
|
CharacterImageListItemResponse(
|
||||||
|
id = img.id!!,
|
||||||
|
imageUrl = url,
|
||||||
|
isOwned = isOwned,
|
||||||
|
imagePriceCan = img.imagePriceCan,
|
||||||
|
sortOrder = img.sortOrder
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ApiResponse.ok(
|
ApiResponse.ok(
|
||||||
CharacterImageListResponse(
|
CharacterImageListResponse(
|
||||||
totalCount = totalCount,
|
totalCount = totalCount,
|
||||||
|
|
|
@ -35,6 +35,12 @@ interface CharacterImageQueryRepository {
|
||||||
offset: Long,
|
offset: Long,
|
||||||
limit: Long
|
limit: Long
|
||||||
): List<CharacterImage>
|
): List<CharacterImage>
|
||||||
|
|
||||||
|
fun findActiveImagesByCharacterPaged(
|
||||||
|
characterId: Long,
|
||||||
|
offset: Long,
|
||||||
|
limit: Long
|
||||||
|
): List<CharacterImage>
|
||||||
}
|
}
|
||||||
|
|
||||||
class CharacterImageQueryRepositoryImpl(
|
class CharacterImageQueryRepositoryImpl(
|
||||||
|
@ -73,4 +79,22 @@ class CharacterImageQueryRepositoryImpl(
|
||||||
.limit(limit)
|
.limit(limit)
|
||||||
.fetch()
|
.fetch()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun findActiveImagesByCharacterPaged(
|
||||||
|
characterId: Long,
|
||||||
|
offset: Long,
|
||||||
|
limit: Long
|
||||||
|
): List<CharacterImage> {
|
||||||
|
val ci = QCharacterImage.characterImage
|
||||||
|
return queryFactory
|
||||||
|
.selectFrom(ci)
|
||||||
|
.where(
|
||||||
|
ci.chatCharacter.id.eq(characterId)
|
||||||
|
.and(ci.isActive.isTrue)
|
||||||
|
)
|
||||||
|
.orderBy(ci.sortOrder.asc(), ci.id.asc())
|
||||||
|
.offset(offset)
|
||||||
|
.limit(limit)
|
||||||
|
.fetch()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,16 @@ class CharacterImageService(
|
||||||
return imageRepository.findByChatCharacterIdAndIsActiveTrueOrderBySortOrderAsc(characterId, pageable)
|
return imageRepository.findByChatCharacterIdAndIsActiveTrueOrderBySortOrderAsc(characterId, pageable)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 오프셋/리밋 조회(활성 이미지)
|
||||||
|
fun pageActiveByCharacterOffset(
|
||||||
|
characterId: Long,
|
||||||
|
offset: Long,
|
||||||
|
limit: Long
|
||||||
|
): List<CharacterImage> {
|
||||||
|
if (limit <= 0L) return emptyList()
|
||||||
|
return imageRepository.findActiveImagesByCharacterPaged(characterId, offset, limit)
|
||||||
|
}
|
||||||
|
|
||||||
// 구매 이력 + 무료로 계산된 보유 수
|
// 구매 이력 + 무료로 계산된 보유 수
|
||||||
fun countOwnedActiveByCharacterForMember(characterId: Long, memberId: Long): Long {
|
fun countOwnedActiveByCharacterForMember(characterId: Long, memberId: Long): Long {
|
||||||
val freeCount = imageRepository.countByChatCharacterIdAndIsActiveTrueAndImagePriceCan(characterId, 0L)
|
val freeCount = imageRepository.countByChatCharacterIdAndIsActiveTrueAndImagePriceCan(characterId, 0L)
|
||||||
|
|
Loading…
Reference in New Issue