From 7f4de67d671d8090efd5595c12455ffaf9a8c276 Mon Sep 17 00:00:00 2001 From: Klaus Date: Wed, 27 Nov 2024 01:59:01 +0900 Subject: [PATCH 01/13] =?UTF-8?q?=ED=94=8C=EB=A0=88=EC=9D=B4=20=EB=A6=AC?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20API=20-=20=EC=A1=B0=ED=9A=8C,=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1,=20=EC=82=AD=EC=A0=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../content/AudioContentRepository.kt | 36 ++++++- .../sodalive/content/order/OrderRepository.kt | 17 ++++ .../content/playlist/AudioContentPlaylist.kt | 33 +++++++ .../AudioContentPlaylistController.kt | 48 ++++++++++ .../AudioContentPlaylistRedisRepository.kt | 9 ++ .../playlist/AudioContentPlaylistService.kt | 93 +++++++++++++++++++ .../content/playlist/CreatePlaylistRequest.kt | 7 ++ .../content/playlist/GetPlaylistsResponse.kt | 14 +++ 8 files changed, 256 insertions(+), 1 deletion(-) create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/AudioContentPlaylist.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/AudioContentPlaylistController.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/AudioContentPlaylistRedisRepository.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/AudioContentPlaylistService.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/CreatePlaylistRequest.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/GetPlaylistsResponse.kt diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentRepository.kt index 979ceb8..2ab8471 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentRepository.kt @@ -17,10 +17,13 @@ import kr.co.vividnext.sodalive.content.main.curation.AudioContentCuration import kr.co.vividnext.sodalive.content.main.curation.QAudioContentCuration.audioContentCuration import kr.co.vividnext.sodalive.content.order.QOrder.order import kr.co.vividnext.sodalive.content.pin.QPinContent.pinContent +import kr.co.vividnext.sodalive.content.playlist.AudioContentPlaylistContent +import kr.co.vividnext.sodalive.content.playlist.QAudioContentPlaylistContent import kr.co.vividnext.sodalive.content.theme.QAudioContentTheme.audioContentTheme import kr.co.vividnext.sodalive.event.QEvent.event import kr.co.vividnext.sodalive.member.MemberRole import kr.co.vividnext.sodalive.member.QMember.member +import org.springframework.beans.factory.annotation.Value import org.springframework.data.jpa.repository.JpaRepository import org.springframework.stereotype.Repository import java.time.LocalDateTime @@ -120,10 +123,18 @@ interface AudioContentQueryRepository { fun getNotReleaseContentId(): List fun isContentCreator(contentId: Long, memberId: Long): Boolean + + fun fetchContentForPlaylist(contentIdList: List): List + fun getCoverImageById(id: Long): String? } @Repository -class AudioContentQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) : AudioContentQueryRepository { +class AudioContentQueryRepositoryImpl( + private val queryFactory: JPAQueryFactory, + + @Value("\${cloud.aws.cloud-front.host}") + private val imageHost: String +) : AudioContentQueryRepository { override fun findByIdAndActive(contentId: Long): AudioContent? { return queryFactory .selectFrom(audioContent) @@ -778,4 +789,27 @@ class AudioContentQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) return foundedContentId != null && foundedContentId == contentId } + + override fun fetchContentForPlaylist(contentIdList: List): List { + return queryFactory + .select( + QAudioContentPlaylistContent( + audioContent.id, + audioContent.title, + audioContentTheme.theme, + audioContent.coverImage.prepend("/").prepend(imageHost), + audioContent.duration + ) + ) + .from(audioContent) + .innerJoin(audioContent.theme, audioContentTheme) + .fetch() + } + + override fun getCoverImageById(id: Long): String? { + return queryFactory + .select(audioContent.coverImage.prepend("/").prepend(imageHost)) + .from(audioContent) + .fetchFirst() + } } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/order/OrderRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/order/OrderRepository.kt index 42ede81..fb6022a 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/order/OrderRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/order/OrderRepository.kt @@ -35,6 +35,8 @@ interface OrderQueryRepository { offset: Long = 0, limit: Long = 20 ): List + + fun findOrderedContent(contentIdList: List, memberId: Long): List } @Repository @@ -218,4 +220,19 @@ class OrderQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) : Orde .orderBy(order.createdAt.desc()) .fetch() } + + override fun findOrderedContent(contentIdList: List, memberId: Long): List { + return queryFactory + .select(audioContent.id) + .from(order) + .innerJoin(order.member, member) + .innerJoin(order.audioContent, audioContent) + .where( + order.isActive.isTrue, + order.endDate.isNull, + member.id.eq(memberId), + audioContent.id.`in`(contentIdList) + ) + .fetch() + } } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/AudioContentPlaylist.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/AudioContentPlaylist.kt new file mode 100644 index 0000000..62badd8 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/AudioContentPlaylist.kt @@ -0,0 +1,33 @@ +package kr.co.vividnext.sodalive.content.playlist + +import com.querydsl.core.annotations.QueryProjection +import org.springframework.data.annotation.Id +import org.springframework.data.redis.core.RedisHash +import org.springframework.data.redis.core.index.Indexed +import java.time.LocalDateTime +import java.time.format.DateTimeFormatter + +@RedisHash("AudioContentPlaylist") +data class AudioContentPlaylist( + @Id + val id: Long, + @Indexed + val memberId: Long, + var title: String, + var desc: String? = null, + var contentIdList: MutableList = mutableListOf(), + + // ISO 8601 형식의 String + private val _createdAt: String = LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME) +) { + val createdAt: LocalDateTime + get() = LocalDateTime.parse(_createdAt, DateTimeFormatter.ISO_LOCAL_DATE_TIME) +} + +data class AudioContentPlaylistContent @QueryProjection constructor( + val id: Long, + val title: String, + val category: String, + val coverUrl: String, + val duration: String +) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/AudioContentPlaylistController.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/AudioContentPlaylistController.kt new file mode 100644 index 0000000..a5d5865 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/AudioContentPlaylistController.kt @@ -0,0 +1,48 @@ +package kr.co.vividnext.sodalive.content.playlist + +import kr.co.vividnext.sodalive.common.ApiResponse +import kr.co.vividnext.sodalive.common.SodaException +import kr.co.vividnext.sodalive.member.Member +import org.springframework.security.core.annotation.AuthenticationPrincipal +import org.springframework.web.bind.annotation.DeleteMapping +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.PathVariable +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.RequestBody +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController + +@RestController +@RequestMapping("/audio-content/playlist") +class AudioContentPlaylistController(private val service: AudioContentPlaylistService) { + @PostMapping + fun createPlaylist( + @RequestBody request: CreatePlaylistRequest, + @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? + ) = run { + if (member == null) throw SodaException("로그인 정보를 확인해주세요.") + + ApiResponse.ok( + service.createPlaylist(request, member) + ) + } + + @DeleteMapping("/{id}") + fun deletePlaylist( + @PathVariable id: Long, + @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? + ) = run { + if (member == null) throw SodaException("로그인 정보를 확인해주세요.") + + ApiResponse.ok(service.deletePlaylist(playlistId = id, member)) + } + + @GetMapping + fun getPlaylists( + @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? + ) = run { + if (member == null) throw SodaException("로그인 정보를 확인해주세요.") + + ApiResponse.ok(service.getPlaylists(member)) + } +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/AudioContentPlaylistRedisRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/AudioContentPlaylistRedisRepository.kt new file mode 100644 index 0000000..6dedba0 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/AudioContentPlaylistRedisRepository.kt @@ -0,0 +1,9 @@ +package kr.co.vividnext.sodalive.content.playlist + +import org.springframework.data.repository.CrudRepository +import org.springframework.stereotype.Repository + +@Repository +interface AudioContentPlaylistRedisRepository : CrudRepository { + fun findByMemberId(memberId: Long): List +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/AudioContentPlaylistService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/AudioContentPlaylistService.kt new file mode 100644 index 0000000..37802c8 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/AudioContentPlaylistService.kt @@ -0,0 +1,93 @@ +package kr.co.vividnext.sodalive.content.playlist + +import kr.co.vividnext.sodalive.common.SodaException +import kr.co.vividnext.sodalive.content.AudioContentRepository +import kr.co.vividnext.sodalive.content.order.OrderRepository +import kr.co.vividnext.sodalive.live.roulette.RedisIdGenerator +import kr.co.vividnext.sodalive.member.Member +import org.springframework.data.repository.findByIdOrNull +import org.springframework.stereotype.Service + +@Service +class AudioContentPlaylistService( + private val idGenerator: RedisIdGenerator, + private val orderRepository: OrderRepository, + private val audioContentRepository: AudioContentRepository, + private val redisRepository: AudioContentPlaylistRedisRepository +) { + fun createPlaylist(request: CreatePlaylistRequest, member: Member) { + if (request.contentIdList.size >= 30) { + throw SodaException("플레이 리스트에는 최대 30개의 콘텐츠를 저장할 수 있습니다.") + } + + val playlistCount = redisRepository.findByMemberId(member.id!!).size + if (playlistCount >= 10) { + throw SodaException("플레이 리스트는 최대 10개까지 생성할 수 있습니다.") + } + + // 콘텐츠 유효성 검사 (소장으로 구매한 콘텐츠 인가?) + checkOrderedContent( + contentIdList = request.contentIdList, + memberId = member.id!! + ) + + val playlist = AudioContentPlaylist( + id = idGenerator.generateId(SEQUENCE_NAME), + memberId = member.id!!, + title = request.title, + desc = request.desc + ) + playlist.contentIdList.addAll(request.contentIdList) + + redisRepository.save(playlist) + } + + private fun checkOrderedContent(contentIdList: List, memberId: Long) { + val orderedContentIdList = orderRepository.findOrderedContent(contentIdList, memberId).toSet() + val orderedContentMap = contentIdList.associateWith { it in orderedContentIdList } + val notOrderedContentList = orderedContentMap.filterValues { !it }.keys + + if (notOrderedContentList.isNotEmpty()) { + throw SodaException("소장하지 않은 콘텐츠는 재생목록에 추가할 수 없습니다.") + } + } + + fun getPlaylists(member: Member): GetPlaylistsResponse { + val playlists = redisRepository.findByMemberId(memberId = member.id!!) + + return GetPlaylistsResponse( + totalCount = playlists.size, + items = playlists.map { + val contentCount = it.contentIdList.size + val coverImageUrl = if (contentCount > 0) { + audioContentRepository.getCoverImageById(id = it.contentIdList[0]) + ?: "" + } else { + "" + } + GetPlaylistsItem( + id = it.id, + title = it.title, + desc = it.desc ?: "", + contentCount = contentCount, + coverImageUrl = coverImageUrl + ) + } + ) + } + + fun deletePlaylist(playlistId: Long, member: Member) { + val playlist = redisRepository.findByIdOrNull(id = playlistId) + ?: throw SodaException("잘못된 요청입니다.") + + if (playlist.memberId != member.id) { + throw SodaException("잘못된 요청입니다.") + } + + redisRepository.delete(playlist) + } + + companion object { + const val SEQUENCE_NAME = "AudioContentPlaylist:sequence" + } +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/CreatePlaylistRequest.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/CreatePlaylistRequest.kt new file mode 100644 index 0000000..914cb90 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/CreatePlaylistRequest.kt @@ -0,0 +1,7 @@ +package kr.co.vividnext.sodalive.content.playlist + +data class CreatePlaylistRequest( + val title: String, + val desc: String? = null, + val contentIdList: List = emptyList() +) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/GetPlaylistsResponse.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/GetPlaylistsResponse.kt new file mode 100644 index 0000000..b292c01 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/GetPlaylistsResponse.kt @@ -0,0 +1,14 @@ +package kr.co.vividnext.sodalive.content.playlist + +data class GetPlaylistsResponse( + val totalCount: Int, + val items: List +) + +data class GetPlaylistsItem( + val id: Long, + val title: String, + val desc: String, + val contentCount: Int, + val coverImageUrl: String +) -- 2.40.1 From 22c5c5be25cde49aae2b75d81ef1273b61be9b9b Mon Sep 17 00:00:00 2001 From: Klaus Date: Wed, 27 Nov 2024 17:15:33 +0900 Subject: [PATCH 02/13] =?UTF-8?q?=ED=94=8C=EB=A0=88=EC=9D=B4=20=EB=A6=AC?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=83=9D=EC=84=B1=20API=20-=20=EA=B8=B0?= =?UTF-8?q?=EC=A1=B4:=20=ED=94=8C=EB=A0=88=EC=9D=B4=20=EB=A6=AC=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=EC=97=90=20=EC=BD=98=ED=85=90=EC=B8=A0=20ID=EB=A7=8C?= =?UTF-8?q?=20=EC=A0=80=EC=9E=A5=EB=90=A8=20-=20=EB=B3=80=EA=B2=BD:=20?= =?UTF-8?q?=EC=BD=98=ED=85=90=EC=B8=A0=20ID=EC=99=80=20=EC=A0=95=EB=A0=AC?= =?UTF-8?q?=EC=88=9C=EC=84=9C=EB=8F=84=20=EA=B0=99=EC=9D=B4=20=EC=A0=80?= =?UTF-8?q?=EC=9E=A5=EB=90=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sodalive/content/playlist/AudioContentPlaylist.kt | 2 +- .../content/playlist/AudioContentPlaylistService.kt | 10 +++++----- .../sodalive/content/playlist/CreatePlaylistRequest.kt | 2 +- .../content/playlist/PlaylistContentIdAndOrder.kt | 6 ++++++ 4 files changed, 13 insertions(+), 7 deletions(-) create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/PlaylistContentIdAndOrder.kt diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/AudioContentPlaylist.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/AudioContentPlaylist.kt index 62badd8..d33247a 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/AudioContentPlaylist.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/AudioContentPlaylist.kt @@ -15,7 +15,7 @@ data class AudioContentPlaylist( val memberId: Long, var title: String, var desc: String? = null, - var contentIdList: MutableList = mutableListOf(), + var contentIdAndOrderList: MutableList = mutableListOf(), // ISO 8601 형식의 String private val _createdAt: String = LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/AudioContentPlaylistService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/AudioContentPlaylistService.kt index 37802c8..65fce07 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/AudioContentPlaylistService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/AudioContentPlaylistService.kt @@ -16,7 +16,7 @@ class AudioContentPlaylistService( private val redisRepository: AudioContentPlaylistRedisRepository ) { fun createPlaylist(request: CreatePlaylistRequest, member: Member) { - if (request.contentIdList.size >= 30) { + if (request.contentIdAndOrderList.size >= 30) { throw SodaException("플레이 리스트에는 최대 30개의 콘텐츠를 저장할 수 있습니다.") } @@ -27,7 +27,7 @@ class AudioContentPlaylistService( // 콘텐츠 유효성 검사 (소장으로 구매한 콘텐츠 인가?) checkOrderedContent( - contentIdList = request.contentIdList, + contentIdList = request.contentIdAndOrderList.map { it.contentId }, memberId = member.id!! ) @@ -37,7 +37,7 @@ class AudioContentPlaylistService( title = request.title, desc = request.desc ) - playlist.contentIdList.addAll(request.contentIdList) + playlist.contentIdAndOrderList.addAll(request.contentIdAndOrderList) redisRepository.save(playlist) } @@ -58,9 +58,9 @@ class AudioContentPlaylistService( return GetPlaylistsResponse( totalCount = playlists.size, items = playlists.map { - val contentCount = it.contentIdList.size + val contentCount = it.contentIdAndOrderList.size val coverImageUrl = if (contentCount > 0) { - audioContentRepository.getCoverImageById(id = it.contentIdList[0]) + audioContentRepository.getCoverImageById(id = it.contentIdAndOrderList[0].contentId) ?: "" } else { "" diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/CreatePlaylistRequest.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/CreatePlaylistRequest.kt index 914cb90..1cdc09e 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/CreatePlaylistRequest.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/CreatePlaylistRequest.kt @@ -3,5 +3,5 @@ package kr.co.vividnext.sodalive.content.playlist data class CreatePlaylistRequest( val title: String, val desc: String? = null, - val contentIdList: List = emptyList() + val contentIdAndOrderList: List = emptyList() ) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/PlaylistContentIdAndOrder.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/PlaylistContentIdAndOrder.kt new file mode 100644 index 0000000..2e0d558 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/PlaylistContentIdAndOrder.kt @@ -0,0 +1,6 @@ +package kr.co.vividnext.sodalive.content.playlist + +data class PlaylistContentIdAndOrder( + val contentId: Long, + val order: Int +) -- 2.40.1 From 10a65294cea15931064ef3d66dad465e3e99db95 Mon Sep 17 00:00:00 2001 From: Klaus Date: Wed, 27 Nov 2024 17:37:22 +0900 Subject: [PATCH 03/13] =?UTF-8?q?=ED=94=8C=EB=A0=88=EC=9D=B4=20=EB=A6=AC?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=83=9D=EC=84=B1=20API=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20-=20=EA=B8=B0=EC=A1=B4:=20=ED=94=8C=EB=A0=88?= =?UTF-8?q?=EC=9D=B4=20=EB=A6=AC=EC=8A=A4=ED=8A=B8=EC=97=90=20=EC=BD=98?= =?UTF-8?q?=ED=85=90=EC=B8=A0=20ID=EC=99=80=20=EC=A0=95=EB=A0=AC=EC=88=9C?= =?UTF-8?q?=EC=84=9C=EB=8F=84=20=EA=B0=99=EC=9D=B4=20=EC=A0=80=EC=9E=A5?= =?UTF-8?q?=EB=90=A8=20-=20=EB=B3=80=EA=B2=BD:=20=EC=BD=98=ED=85=90?= =?UTF-8?q?=EC=B8=A0=20ID=EB=A7=8C=20=EC=A0=80=EC=9E=A5=EB=90=A8=20-=20?= =?UTF-8?q?=EC=9D=B4=EC=9C=A0:=20List=20=EC=82=AC=EC=9A=A9=ED=95=98?= =?UTF-8?q?=EA=B8=B0=EC=97=90=20=EC=A0=95=EB=A0=AC=EC=88=9C=EC=84=9C?= =?UTF-8?q?=EB=A5=BC=20=EB=B3=84=EB=8F=84=EB=A1=9C=20=EC=A0=80=EC=9E=A5?= =?UTF-8?q?=ED=95=A0=20=ED=95=84=EC=9A=94=EA=B0=80=20=EC=97=86=EB=8B=A4?= =?UTF-8?q?=EA=B3=A0=20=ED=8C=90=EB=8B=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../content/playlist/AudioContentPlaylist.kt | 2 +- .../content/playlist/AudioContentPlaylistService.kt | 12 ++++++------ .../content/playlist/CreatePlaylistRequest.kt | 2 +- .../content/playlist/PlaylistContentIdAndOrder.kt | 6 ------ 4 files changed, 8 insertions(+), 14 deletions(-) delete mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/PlaylistContentIdAndOrder.kt diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/AudioContentPlaylist.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/AudioContentPlaylist.kt index d33247a..aac2ee7 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/AudioContentPlaylist.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/AudioContentPlaylist.kt @@ -15,7 +15,7 @@ data class AudioContentPlaylist( val memberId: Long, var title: String, var desc: String? = null, - var contentIdAndOrderList: MutableList = mutableListOf(), + var contentIdList: List, // ISO 8601 형식의 String private val _createdAt: String = LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/AudioContentPlaylistService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/AudioContentPlaylistService.kt index 65fce07..64c2504 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/AudioContentPlaylistService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/AudioContentPlaylistService.kt @@ -16,7 +16,7 @@ class AudioContentPlaylistService( private val redisRepository: AudioContentPlaylistRedisRepository ) { fun createPlaylist(request: CreatePlaylistRequest, member: Member) { - if (request.contentIdAndOrderList.size >= 30) { + if (request.contentIdList.size >= 30) { throw SodaException("플레이 리스트에는 최대 30개의 콘텐츠를 저장할 수 있습니다.") } @@ -27,7 +27,7 @@ class AudioContentPlaylistService( // 콘텐츠 유효성 검사 (소장으로 구매한 콘텐츠 인가?) checkOrderedContent( - contentIdList = request.contentIdAndOrderList.map { it.contentId }, + contentIdList = request.contentIdList, memberId = member.id!! ) @@ -35,9 +35,9 @@ class AudioContentPlaylistService( id = idGenerator.generateId(SEQUENCE_NAME), memberId = member.id!!, title = request.title, - desc = request.desc + desc = request.desc, + contentIdList = request.contentIdList ) - playlist.contentIdAndOrderList.addAll(request.contentIdAndOrderList) redisRepository.save(playlist) } @@ -58,9 +58,9 @@ class AudioContentPlaylistService( return GetPlaylistsResponse( totalCount = playlists.size, items = playlists.map { - val contentCount = it.contentIdAndOrderList.size + val contentCount = it.contentIdList.size val coverImageUrl = if (contentCount > 0) { - audioContentRepository.getCoverImageById(id = it.contentIdAndOrderList[0].contentId) + audioContentRepository.getCoverImageById(id = it.contentIdList[0]) ?: "" } else { "" diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/CreatePlaylistRequest.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/CreatePlaylistRequest.kt index 1cdc09e..914cb90 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/CreatePlaylistRequest.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/CreatePlaylistRequest.kt @@ -3,5 +3,5 @@ package kr.co.vividnext.sodalive.content.playlist data class CreatePlaylistRequest( val title: String, val desc: String? = null, - val contentIdAndOrderList: List = emptyList() + val contentIdList: List = emptyList() ) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/PlaylistContentIdAndOrder.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/PlaylistContentIdAndOrder.kt deleted file mode 100644 index 2e0d558..0000000 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/PlaylistContentIdAndOrder.kt +++ /dev/null @@ -1,6 +0,0 @@ -package kr.co.vividnext.sodalive.content.playlist - -data class PlaylistContentIdAndOrder( - val contentId: Long, - val order: Int -) -- 2.40.1 From d6e5a45be9384c12e3fb729e2e17a3ceac55e211 Mon Sep 17 00:00:00 2001 From: Klaus Date: Wed, 27 Nov 2024 18:07:17 +0900 Subject: [PATCH 04/13] =?UTF-8?q?=ED=94=8C=EB=A0=88=EC=9D=B4=20=EB=A6=AC?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=88=98=EC=A0=95=20API=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AudioContentPlaylistController.kt | 16 +++++++++--- .../playlist/AudioContentPlaylistService.kt | 26 +++++++++++++++++++ .../content/playlist/UpdatePlaylistRequest.kt | 7 +++++ 3 files changed, 46 insertions(+), 3 deletions(-) create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/UpdatePlaylistRequest.kt diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/AudioContentPlaylistController.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/AudioContentPlaylistController.kt index a5d5865..ef934a1 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/AudioContentPlaylistController.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/AudioContentPlaylistController.kt @@ -8,6 +8,7 @@ import org.springframework.web.bind.annotation.DeleteMapping import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PathVariable import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.PutMapping import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController @@ -22,9 +23,18 @@ class AudioContentPlaylistController(private val service: AudioContentPlaylistSe ) = run { if (member == null) throw SodaException("로그인 정보를 확인해주세요.") - ApiResponse.ok( - service.createPlaylist(request, member) - ) + ApiResponse.ok(service.createPlaylist(request, member)) + } + + @PutMapping("/{id}") + fun updatePlaylist( + @PathVariable id: Long, + @RequestBody request: UpdatePlaylistRequest, + @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? + ) = run { + if (member == null) throw SodaException("로그인 정보를 확인해주세요.") + + ApiResponse.ok(service.updatePlaylist(playlistId = id, request = request, member = member)) } @DeleteMapping("/{id}") diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/AudioContentPlaylistService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/AudioContentPlaylistService.kt index 64c2504..14f3a0b 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/AudioContentPlaylistService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/AudioContentPlaylistService.kt @@ -52,6 +52,32 @@ class AudioContentPlaylistService( } } + fun updatePlaylist(playlistId: Long, request: UpdatePlaylistRequest, member: Member) { + if (request.contentIdList.size >= 30) { + throw SodaException("플레이 리스트에는 최대 30개의 콘텐츠를 저장할 수 있습니다.") + } + + val playlist = redisRepository.findByIdOrNull(id = playlistId) + ?: throw SodaException("잘못된 요청입니다.") + + if (playlist.memberId != member.id) { + throw SodaException("잘못된 요청입니다.") + } + + checkOrderedContent( + contentIdList = request.contentIdList, + memberId = member.id!! + ) + + val updatePlaylist = playlist.copy( + title = request.title ?: playlist.title, + desc = request.desc ?: playlist.desc, + contentIdList = request.contentIdList + ) + + redisRepository.save(updatePlaylist) + } + fun getPlaylists(member: Member): GetPlaylistsResponse { val playlists = redisRepository.findByMemberId(memberId = member.id!!) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/UpdatePlaylistRequest.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/UpdatePlaylistRequest.kt new file mode 100644 index 0000000..2fd2300 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/UpdatePlaylistRequest.kt @@ -0,0 +1,7 @@ +package kr.co.vividnext.sodalive.content.playlist + +data class UpdatePlaylistRequest( + val title: String? = null, + val desc: String? = null, + val contentIdList: List = emptyList() +) -- 2.40.1 From 3093d2159d87ecd9139b83b094fef7e6eae89ebc Mon Sep 17 00:00:00 2001 From: Klaus Date: Wed, 27 Nov 2024 19:09:18 +0900 Subject: [PATCH 05/13] =?UTF-8?q?=ED=94=8C=EB=A0=88=EC=9D=B4=20=EB=A6=AC?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=83=81=EC=84=B8=20API=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AudioContentPlaylistController.kt | 10 +++++++ .../playlist/AudioContentPlaylistService.kt | 29 +++++++++++++++++++ .../playlist/GetPlaylistDetailResponse.kt | 11 +++++++ 3 files changed, 50 insertions(+) create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/GetPlaylistDetailResponse.kt diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/AudioContentPlaylistController.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/AudioContentPlaylistController.kt index ef934a1..d368441 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/AudioContentPlaylistController.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/AudioContentPlaylistController.kt @@ -55,4 +55,14 @@ class AudioContentPlaylistController(private val service: AudioContentPlaylistSe ApiResponse.ok(service.getPlaylists(member)) } + + @GetMapping("/{id}") + fun getPlaylistDetail( + @PathVariable id: Long, + @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? + ) = run { + if (member == null) throw SodaException("로그인 정보를 확인해주세요.") + + ApiResponse.ok(service.getPlaylistDetail(playlistId = id, member = member)) + } } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/AudioContentPlaylistService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/AudioContentPlaylistService.kt index 14f3a0b..550e366 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/AudioContentPlaylistService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/AudioContentPlaylistService.kt @@ -7,6 +7,8 @@ import kr.co.vividnext.sodalive.live.roulette.RedisIdGenerator import kr.co.vividnext.sodalive.member.Member import org.springframework.data.repository.findByIdOrNull import org.springframework.stereotype.Service +import java.time.ZoneId +import java.time.format.DateTimeFormatter @Service class AudioContentPlaylistService( @@ -113,6 +115,33 @@ class AudioContentPlaylistService( redisRepository.delete(playlist) } + fun getPlaylistDetail(playlistId: Long, member: Member): GetPlaylistDetailResponse { + val playlist = redisRepository.findByIdOrNull(id = playlistId) + ?: throw SodaException("잘못된 요청입니다.") + + if (playlist.memberId != member.id) { + throw SodaException("잘못된 요청입니다.") + } + + val dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd") + val createdDate = playlist.createdAt + .atZone(ZoneId.of("UTC")) + .withZoneSameInstant(ZoneId.of("Asia/Seoul")) + .format(dateTimeFormatter) + + val contentList = audioContentRepository.fetchContentForPlaylist(contentIdList = playlist.contentIdList) + + return GetPlaylistDetailResponse( + playlistId = playlist.id, + title = playlist.title, + desc = playlist.desc ?: "", + createdDate = createdDate, + contentCount = contentList.size, + playlistCoverImageList = contentList.take(4).map { it.coverUrl }, + contentList = contentList + ) + } + companion object { const val SEQUENCE_NAME = "AudioContentPlaylist:sequence" } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/GetPlaylistDetailResponse.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/GetPlaylistDetailResponse.kt new file mode 100644 index 0000000..1d89d53 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/GetPlaylistDetailResponse.kt @@ -0,0 +1,11 @@ +package kr.co.vividnext.sodalive.content.playlist + +data class GetPlaylistDetailResponse( + val playlistId: Long, + val title: String, + val desc: String, + val createdDate: String, + val contentCount: Int, + val playlistCoverImageList: List, + val contentList: List +) -- 2.40.1 From 4097e5a13318bee58e926dfd09f0d169acf85bc3 Mon Sep 17 00:00:00 2001 From: Klaus Date: Wed, 27 Nov 2024 19:31:59 +0900 Subject: [PATCH 06/13] =?UTF-8?q?=ED=94=8C=EB=A0=88=EC=9D=B4=20=EB=A6=AC?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=EC=97=90=20=EC=A0=80=EC=9E=A5=ED=95=98?= =?UTF-8?q?=EB=8A=94=20=EC=BD=98=ED=85=90=EC=B8=A0ID=EC=97=90=20=EC=A0=95?= =?UTF-8?q?=EB=A0=AC=EC=88=9C=EC=84=9C=20=EA=B0=92=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?-=20=EC=9D=B4=EC=9C=A0:=20=ED=94=8C=EB=A0=88=EC=9D=B4=20?= =?UTF-8?q?=EB=A6=AC=EC=8A=A4=ED=8A=B8=EC=97=90=20=EC=A0=80=EC=9E=A5?= =?UTF-8?q?=ED=95=98=EB=8A=94=20=EC=BD=98=ED=85=90=EC=B8=A0ID=EC=97=90?= =?UTF-8?q?=EB=8A=94=20=EC=88=9C=EC=84=9C=EA=B0=80=20=EC=9E=88=EC=A7=80?= =?UTF-8?q?=EB=A7=8C=20=ED=95=B4=EB=8B=B9=20=EA=B0=92=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=ED=95=9C=20=EC=BD=98=ED=85=90=EC=B8=A0=20?= =?UTF-8?q?=EC=83=81=EC=84=B8=EB=82=B4=EC=9A=A9=EC=9D=98=20=EC=A0=95?= =?UTF-8?q?=EB=A0=AC=EC=9D=B4=20=EC=BD=98=ED=85=90=EC=B8=A0ID=EA=B0=80=20?= =?UTF-8?q?=EC=A0=80=EC=9E=A5=EB=90=9C=20=EC=88=9C=EC=84=9C=EB=8C=80?= =?UTF-8?q?=EB=A1=9C=20=EB=82=98=EC=98=A8=EB=8B=A4=EB=8A=94=20=EB=B3=B4?= =?UTF-8?q?=EC=9E=A5=EC=9D=B4=20=EC=97=86=EC=9D=8C,=20=EC=A1=B0=ED=9A=8C?= =?UTF-8?q?=ED=95=9C=20=EC=BD=98=ED=85=90=EC=B8=A0=20=EC=83=81=EC=84=B8?= =?UTF-8?q?=EC=9D=98=20=EC=A0=95=EB=A0=AC=EC=9D=84=20=EC=9C=84=ED=95=B4=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../content/playlist/AudioContentPlaylist.kt | 2 +- .../playlist/AudioContentPlaylistService.kt | 32 ++++++++++++------- .../content/playlist/CreatePlaylistRequest.kt | 2 +- .../playlist/PlaylistContentIdAndOrder.kt | 6 ++++ .../content/playlist/UpdatePlaylistRequest.kt | 2 +- 5 files changed, 29 insertions(+), 15 deletions(-) create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/PlaylistContentIdAndOrder.kt diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/AudioContentPlaylist.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/AudioContentPlaylist.kt index aac2ee7..d9dd2ec 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/AudioContentPlaylist.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/AudioContentPlaylist.kt @@ -15,7 +15,7 @@ data class AudioContentPlaylist( val memberId: Long, var title: String, var desc: String? = null, - var contentIdList: List, + var contentIdAndOrderList: List, // ISO 8601 형식의 String private val _createdAt: String = LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/AudioContentPlaylistService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/AudioContentPlaylistService.kt index 550e366..4bc76ce 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/AudioContentPlaylistService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/AudioContentPlaylistService.kt @@ -18,7 +18,7 @@ class AudioContentPlaylistService( private val redisRepository: AudioContentPlaylistRedisRepository ) { fun createPlaylist(request: CreatePlaylistRequest, member: Member) { - if (request.contentIdList.size >= 30) { + if (request.contentIdAndOrderList.size >= 30) { throw SodaException("플레이 리스트에는 최대 30개의 콘텐츠를 저장할 수 있습니다.") } @@ -29,7 +29,7 @@ class AudioContentPlaylistService( // 콘텐츠 유효성 검사 (소장으로 구매한 콘텐츠 인가?) checkOrderedContent( - contentIdList = request.contentIdList, + contentIdList = request.contentIdAndOrderList.map { it.contentId }, memberId = member.id!! ) @@ -38,7 +38,7 @@ class AudioContentPlaylistService( memberId = member.id!!, title = request.title, desc = request.desc, - contentIdList = request.contentIdList + contentIdAndOrderList = request.contentIdAndOrderList ) redisRepository.save(playlist) @@ -55,7 +55,7 @@ class AudioContentPlaylistService( } fun updatePlaylist(playlistId: Long, request: UpdatePlaylistRequest, member: Member) { - if (request.contentIdList.size >= 30) { + if (request.contentIdAndOrderList.size >= 30) { throw SodaException("플레이 리스트에는 최대 30개의 콘텐츠를 저장할 수 있습니다.") } @@ -67,14 +67,14 @@ class AudioContentPlaylistService( } checkOrderedContent( - contentIdList = request.contentIdList, + contentIdList = request.contentIdAndOrderList.map { it.contentId }, memberId = member.id!! ) val updatePlaylist = playlist.copy( title = request.title ?: playlist.title, desc = request.desc ?: playlist.desc, - contentIdList = request.contentIdList + contentIdAndOrderList = request.contentIdAndOrderList ) redisRepository.save(updatePlaylist) @@ -86,9 +86,9 @@ class AudioContentPlaylistService( return GetPlaylistsResponse( totalCount = playlists.size, items = playlists.map { - val contentCount = it.contentIdList.size + val contentCount = it.contentIdAndOrderList.size val coverImageUrl = if (contentCount > 0) { - audioContentRepository.getCoverImageById(id = it.contentIdList[0]) + audioContentRepository.getCoverImageById(id = it.contentIdAndOrderList[0].contentId) ?: "" } else { "" @@ -129,16 +129,24 @@ class AudioContentPlaylistService( .withZoneSameInstant(ZoneId.of("Asia/Seoul")) .format(dateTimeFormatter) - val contentList = audioContentRepository.fetchContentForPlaylist(contentIdList = playlist.contentIdList) + val contentList = audioContentRepository.fetchContentForPlaylist( + contentIdList = playlist.contentIdAndOrderList.map { it.contentId } + ) + + val orderMap = playlist.contentIdAndOrderList.sortedBy { it.order } + .mapIndexed { index, item -> item.contentId to index } + .toMap() + + val sortedContentList = contentList.sortedBy { orderMap[it.id] } return GetPlaylistDetailResponse( playlistId = playlist.id, title = playlist.title, desc = playlist.desc ?: "", createdDate = createdDate, - contentCount = contentList.size, - playlistCoverImageList = contentList.take(4).map { it.coverUrl }, - contentList = contentList + contentCount = sortedContentList.size, + playlistCoverImageList = sortedContentList.take(4).map { it.coverUrl }, + contentList = sortedContentList ) } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/CreatePlaylistRequest.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/CreatePlaylistRequest.kt index 914cb90..1cdc09e 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/CreatePlaylistRequest.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/CreatePlaylistRequest.kt @@ -3,5 +3,5 @@ package kr.co.vividnext.sodalive.content.playlist data class CreatePlaylistRequest( val title: String, val desc: String? = null, - val contentIdList: List = emptyList() + val contentIdAndOrderList: List = emptyList() ) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/PlaylistContentIdAndOrder.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/PlaylistContentIdAndOrder.kt new file mode 100644 index 0000000..2e0d558 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/PlaylistContentIdAndOrder.kt @@ -0,0 +1,6 @@ +package kr.co.vividnext.sodalive.content.playlist + +data class PlaylistContentIdAndOrder( + val contentId: Long, + val order: Int +) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/UpdatePlaylistRequest.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/UpdatePlaylistRequest.kt index 2fd2300..2c739c1 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/UpdatePlaylistRequest.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/UpdatePlaylistRequest.kt @@ -3,5 +3,5 @@ package kr.co.vividnext.sodalive.content.playlist data class UpdatePlaylistRequest( val title: String? = null, val desc: String? = null, - val contentIdList: List = emptyList() + val contentIdAndOrderList: List = emptyList() ) -- 2.40.1 From 05592f94b9632f7c6c743d8036a74ca8114a1c99 Mon Sep 17 00:00:00 2001 From: Klaus Date: Mon, 2 Dec 2024 08:22:16 +0900 Subject: [PATCH 07/13] =?UTF-8?q?=EC=8A=A4=ED=94=84=EB=A7=81=20=EC=8A=A4?= =?UTF-8?q?=EC=BC=80=EC=A4=84=EB=9F=AC=EB=A5=BC=20=EC=9D=B4=EC=9A=A9?= =?UTF-8?q?=ED=95=98=EC=97=AC=20=EC=BD=98=ED=85=90=EC=B8=A0=20=EC=98=88?= =?UTF-8?q?=EC=95=BD=20=EC=98=A4=ED=94=88=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle.kts | 2 ++ .../vividnext/sodalive/SodaLiveApplication.kt | 2 ++ .../common/annotation/SchedulerOnly.kt | 26 +++++++++++++++ .../vividnext/sodalive/configs/RedisConfig.kt | 14 ++++++++ .../sodalive/content/AudioContentService.kt | 2 ++ .../AudioContentReleaseSchedulerService.kt | 32 +++++++++++++++++++ 6 files changed, 78 insertions(+) create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/common/annotation/SchedulerOnly.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/scheduler/AudioContentReleaseSchedulerService.kt diff --git a/build.gradle.kts b/build.gradle.kts index 2d3a3e5..2e02f9e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -26,6 +26,7 @@ repositories { } dependencies { + implementation("org.springframework.boot:spring-boot-starter-aop") implementation("org.springframework.boot:spring-boot-starter-data-jpa") implementation("org.springframework.boot:spring-boot-starter-data-redis") implementation("org.springframework.boot:spring-boot-starter-security") @@ -33,6 +34,7 @@ dependencies { implementation("com.fasterxml.jackson.module:jackson-module-kotlin") implementation("org.springframework.retry:spring-retry") implementation("org.jetbrains.kotlin:kotlin-reflect") + implementation("org.redisson:redisson-spring-boot-starter:3.17.7") // jwt implementation("io.jsonwebtoken:jjwt-api:0.11.5") diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/SodaLiveApplication.kt b/src/main/kotlin/kr/co/vividnext/sodalive/SodaLiveApplication.kt index 78fe613..d160bf6 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/SodaLiveApplication.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/SodaLiveApplication.kt @@ -4,8 +4,10 @@ import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.runApplication import org.springframework.retry.annotation.EnableRetry import org.springframework.scheduling.annotation.EnableAsync +import org.springframework.scheduling.annotation.EnableScheduling @SpringBootApplication +@EnableScheduling @EnableAsync @EnableRetry class SodaLiveApplication diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/common/annotation/SchedulerOnly.kt b/src/main/kotlin/kr/co/vividnext/sodalive/common/annotation/SchedulerOnly.kt new file mode 100644 index 0000000..2c29ee8 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/common/annotation/SchedulerOnly.kt @@ -0,0 +1,26 @@ +package kr.co.vividnext.sodalive.common.annotation + +import org.aspectj.lang.annotation.Aspect +import org.aspectj.lang.annotation.Before +import org.springframework.stereotype.Component + +@Target(AnnotationTarget.FUNCTION) +@Retention(AnnotationRetention.RUNTIME) +annotation class SchedulerOnly + +@Aspect +@Component +class SchedulerOnlyAspect { + + @Before("@annotation(SchedulerOnly)") + fun checkSchedulerAccess() { + if (!isSchedulerThread()) { + throw IllegalStateException("잘못된 접근입니다.") + } + } + + private fun isSchedulerThread(): Boolean { + // 스케줄러 스레드 여부를 판단하는 간단한 로직 + return Thread.currentThread().name.contains("scheduler") + } +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/configs/RedisConfig.kt b/src/main/kotlin/kr/co/vividnext/sodalive/configs/RedisConfig.kt index 09989bd..0e98856 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/configs/RedisConfig.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/configs/RedisConfig.kt @@ -1,5 +1,8 @@ package kr.co.vividnext.sodalive.configs +import org.redisson.Redisson +import org.redisson.api.RedissonClient +import org.redisson.config.Config import org.springframework.beans.factory.annotation.Value import org.springframework.cache.annotation.EnableCaching import org.springframework.context.annotation.Bean @@ -26,6 +29,17 @@ class RedisConfig( @Value("\${spring.redis.port}") private val port: Int ) { + @Bean + fun redissonClient(): RedissonClient { + val config = Config() + config.useSingleServer() + .setAddress("redis://$host:$port") + .setConnectionMinimumIdleSize(1) // 최소 유휴 연결: 1 + .setConnectionPoolSize(5) // 최대 연결 풀 크기: 5 + + return Redisson.create(config) + } + @Bean fun redisConnectionFactory(): RedisConnectionFactory { val clientConfiguration = LettuceClientConfiguration.builder() diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentService.kt index d971e56..888bd31 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentService.kt @@ -5,6 +5,7 @@ import com.fasterxml.jackson.databind.ObjectMapper import kr.co.vividnext.sodalive.aws.cloudfront.AudioContentCloudFront import kr.co.vividnext.sodalive.aws.s3.S3Uploader import kr.co.vividnext.sodalive.common.SodaException +import kr.co.vividnext.sodalive.common.annotation.SchedulerOnly import kr.co.vividnext.sodalive.content.comment.AudioContentCommentRepository import kr.co.vividnext.sodalive.content.hashtag.AudioContentHashTag import kr.co.vividnext.sodalive.content.hashtag.HashTag @@ -402,6 +403,7 @@ class AudioContentService( } } + @SchedulerOnly @Transactional fun releaseContent() { val contentIdList = repository.getNotReleaseContentId() diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/scheduler/AudioContentReleaseSchedulerService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/scheduler/AudioContentReleaseSchedulerService.kt new file mode 100644 index 0000000..fdd62bd --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/scheduler/AudioContentReleaseSchedulerService.kt @@ -0,0 +1,32 @@ +package kr.co.vividnext.sodalive.scheduler + +import kr.co.vividnext.sodalive.content.AudioContentService +import org.redisson.api.RedissonClient +import org.springframework.scheduling.annotation.Scheduled +import org.springframework.stereotype.Service +import java.util.concurrent.TimeUnit + +@Service +class AudioContentReleaseSchedulerService( + private val redissonClient: RedissonClient, + private val audioContentService: AudioContentService +) { + @Scheduled(fixedRate = 1000 * 60 * 5) + fun release() { + val lock = redissonClient.getLock("lock:audioContentRelease") + + if (lock.tryLock(10, TimeUnit.SECONDS)) { + try { + println("락을 획득하여 배포를 시작합니다.") + audioContentService.releaseContent() + } finally { + if (lock.isHeldByCurrentThread) { + lock.unlock() + println("락 해제") + } + } + } else { + println("락을 획득하지 못해서 배포를 건너뜁니다") + } + } +} -- 2.40.1 From e0d48712acd37422fb0a927f3ca29781cc293972 Mon Sep 17 00:00:00 2001 From: Klaus Date: Mon, 2 Dec 2024 08:25:55 +0900 Subject: [PATCH 08/13] =?UTF-8?q?=EC=BD=98=ED=85=90=EC=B8=A0=20=EC=98=88?= =?UTF-8?q?=EC=95=BD=20=EC=98=A4=ED=94=88=20=EC=84=A4=EC=A0=95=20-=20?= =?UTF-8?q?=EC=BD=98=ED=85=90=EC=B8=A0=20id=EB=BF=90=20=EC=95=84=EB=8B=88?= =?UTF-8?q?=EB=9D=BC=20=EC=BD=98=ED=85=90=EC=B8=A0=20=EC=A0=84=EC=B2=B4?= =?UTF-8?q?=EB=A5=BC=20=EB=B6=88=EB=9F=AC=EC=99=80=EC=84=9C=20=EC=A4=91?= =?UTF-8?q?=EB=B3=B5=ED=98=B8=EC=B6=9C=20=ED=95=98=EC=A7=80=20=EC=95=8A?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sodalive/content/AudioContentRepository.kt | 7 +++---- .../vividnext/sodalive/content/AudioContentService.kt | 11 ++++------- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentRepository.kt index 2ab8471..628d5b3 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentRepository.kt @@ -120,7 +120,7 @@ interface AudioContentQueryRepository { fun getAudioContentCurationList(isAdult: Boolean, offset: Long, limit: Long): List - fun getNotReleaseContentId(): List + fun getNotReleaseContent(): List fun isContentCreator(contentId: Long, memberId: Long): Boolean @@ -762,15 +762,14 @@ class AudioContentQueryRepositoryImpl( .fetch() } - override fun getNotReleaseContentId(): List { + override fun getNotReleaseContent(): List { val where = audioContent.isActive.isFalse .and(audioContent.releaseDate.isNotNull) .and(audioContent.releaseDate.loe(LocalDateTime.now())) .and(audioContent.duration.isNotNull) return queryFactory - .select(audioContent.id) - .from(audioContent) + .selectFrom(audioContent) .where(where) .fetch() } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentService.kt index 888bd31..1902782 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentService.kt @@ -406,12 +406,9 @@ class AudioContentService( @SchedulerOnly @Transactional fun releaseContent() { - val contentIdList = repository.getNotReleaseContentId() - - for (contentId in contentIdList) { - val audioContent = repository.findByIdOrNull(contentId) - ?: throw SodaException("잘못된 요청입니다.") + val notReleasedAudioContent = repository.getNotReleaseContent() + for (audioContent in notReleasedAudioContent) { audioContent.isActive = true applicationEventPublisher.publishEvent( @@ -420,7 +417,7 @@ class AudioContentService( title = audioContent.member!!.nickname, message = "콘텐츠를 업로드 하였습니다. - ${audioContent.title}", isAuth = audioContent.isAdult, - contentId = contentId, + contentId = audioContent.id!!, creatorId = audioContent.member!!.id, container = "ios" ) @@ -432,7 +429,7 @@ class AudioContentService( title = audioContent.member!!.nickname, message = "콘텐츠를 업로드 하였습니다. - ${audioContent.title}", isAuth = audioContent.isAdult, - contentId = contentId, + contentId = audioContent.id!!, creatorId = audioContent.member!!.id, container = "aos" ) -- 2.40.1 From e470e706123cd91968b4f77aa68df4658f5ab043 Mon Sep 17 00:00:00 2001 From: Klaus Date: Mon, 2 Dec 2024 08:40:38 +0900 Subject: [PATCH 09/13] =?UTF-8?q?=EC=BD=98=ED=85=90=EC=B8=A0=20=EC=98=88?= =?UTF-8?q?=EC=95=BD=20=EC=98=A4=ED=94=88=20=EC=84=A4=EC=A0=95=20-=20?= =?UTF-8?q?=EB=B6=84=EC=82=B0=EB=9D=BD=20=EC=A0=9C=EA=B1=B0,=20=EC=84=9C?= =?UTF-8?q?=EB=B2=84=EA=B0=80=20=EC=97=AC=EB=9F=AC=EB=8C=80=EB=9D=BC?= =?UTF-8?q?=EB=A9=B4=20=EC=97=AC=EB=9F=AC=EB=B2=88=20=ED=98=B8=EC=B6=9C?= =?UTF-8?q?=EB=90=A0=20=EC=88=98=20=EC=9E=88=EC=9D=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle.kts | 1 - .../vividnext/sodalive/configs/RedisConfig.kt | 14 ------------- .../AudioContentReleaseSchedulerService.kt | 21 ++----------------- 3 files changed, 2 insertions(+), 34 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 2e02f9e..b0ee365 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -34,7 +34,6 @@ dependencies { implementation("com.fasterxml.jackson.module:jackson-module-kotlin") implementation("org.springframework.retry:spring-retry") implementation("org.jetbrains.kotlin:kotlin-reflect") - implementation("org.redisson:redisson-spring-boot-starter:3.17.7") // jwt implementation("io.jsonwebtoken:jjwt-api:0.11.5") diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/configs/RedisConfig.kt b/src/main/kotlin/kr/co/vividnext/sodalive/configs/RedisConfig.kt index 0e98856..09989bd 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/configs/RedisConfig.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/configs/RedisConfig.kt @@ -1,8 +1,5 @@ package kr.co.vividnext.sodalive.configs -import org.redisson.Redisson -import org.redisson.api.RedissonClient -import org.redisson.config.Config import org.springframework.beans.factory.annotation.Value import org.springframework.cache.annotation.EnableCaching import org.springframework.context.annotation.Bean @@ -29,17 +26,6 @@ class RedisConfig( @Value("\${spring.redis.port}") private val port: Int ) { - @Bean - fun redissonClient(): RedissonClient { - val config = Config() - config.useSingleServer() - .setAddress("redis://$host:$port") - .setConnectionMinimumIdleSize(1) // 최소 유휴 연결: 1 - .setConnectionPoolSize(5) // 최대 연결 풀 크기: 5 - - return Redisson.create(config) - } - @Bean fun redisConnectionFactory(): RedisConnectionFactory { val clientConfiguration = LettuceClientConfiguration.builder() diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/scheduler/AudioContentReleaseSchedulerService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/scheduler/AudioContentReleaseSchedulerService.kt index fdd62bd..a6b847f 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/scheduler/AudioContentReleaseSchedulerService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/scheduler/AudioContentReleaseSchedulerService.kt @@ -1,32 +1,15 @@ package kr.co.vividnext.sodalive.scheduler import kr.co.vividnext.sodalive.content.AudioContentService -import org.redisson.api.RedissonClient import org.springframework.scheduling.annotation.Scheduled import org.springframework.stereotype.Service -import java.util.concurrent.TimeUnit @Service class AudioContentReleaseSchedulerService( - private val redissonClient: RedissonClient, private val audioContentService: AudioContentService ) { - @Scheduled(fixedRate = 1000 * 60 * 5) + @Scheduled(cron = "0 0/15 * * * *") fun release() { - val lock = redissonClient.getLock("lock:audioContentRelease") - - if (lock.tryLock(10, TimeUnit.SECONDS)) { - try { - println("락을 획득하여 배포를 시작합니다.") - audioContentService.releaseContent() - } finally { - if (lock.isHeldByCurrentThread) { - lock.unlock() - println("락 해제") - } - } - } else { - println("락을 획득하지 못해서 배포를 건너뜁니다") - } + audioContentService.releaseContent() } } -- 2.40.1 From c1748001d5f2fc77647ac7d8aef13f25433362be Mon Sep 17 00:00:00 2001 From: Klaus Date: Mon, 2 Dec 2024 08:58:54 +0900 Subject: [PATCH 10/13] =?UTF-8?q?=EC=BD=98=ED=85=90=EC=B8=A0=20=EC=98=88?= =?UTF-8?q?=EC=95=BD=20=EC=98=A4=ED=94=88=20=EC=84=A4=EC=A0=95=20-=20?= =?UTF-8?q?=EC=8A=A4=EC=BC=80=EC=A4=84=EB=9F=AC=20=EC=84=A4=EC=A0=95=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../vividnext/sodalive/SodaLiveApplication.kt | 2 -- ....kt => AudioContentReleaseSchedulerOnly.kt} | 6 +++--- .../sodalive/configs/SchedulerConfig.kt | 18 ++++++++++++++++++ .../sodalive/content/AudioContentService.kt | 4 ++-- 4 files changed, 23 insertions(+), 7 deletions(-) rename src/main/kotlin/kr/co/vividnext/sodalive/common/annotation/{SchedulerOnly.kt => AudioContentReleaseSchedulerOnly.kt} (75%) create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/configs/SchedulerConfig.kt diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/SodaLiveApplication.kt b/src/main/kotlin/kr/co/vividnext/sodalive/SodaLiveApplication.kt index d160bf6..78fe613 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/SodaLiveApplication.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/SodaLiveApplication.kt @@ -4,10 +4,8 @@ import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.runApplication import org.springframework.retry.annotation.EnableRetry import org.springframework.scheduling.annotation.EnableAsync -import org.springframework.scheduling.annotation.EnableScheduling @SpringBootApplication -@EnableScheduling @EnableAsync @EnableRetry class SodaLiveApplication diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/common/annotation/SchedulerOnly.kt b/src/main/kotlin/kr/co/vividnext/sodalive/common/annotation/AudioContentReleaseSchedulerOnly.kt similarity index 75% rename from src/main/kotlin/kr/co/vividnext/sodalive/common/annotation/SchedulerOnly.kt rename to src/main/kotlin/kr/co/vividnext/sodalive/common/annotation/AudioContentReleaseSchedulerOnly.kt index 2c29ee8..28340bc 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/common/annotation/SchedulerOnly.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/common/annotation/AudioContentReleaseSchedulerOnly.kt @@ -6,13 +6,13 @@ import org.springframework.stereotype.Component @Target(AnnotationTarget.FUNCTION) @Retention(AnnotationRetention.RUNTIME) -annotation class SchedulerOnly +annotation class AudioContentReleaseSchedulerOnly @Aspect @Component class SchedulerOnlyAspect { - @Before("@annotation(SchedulerOnly)") + @Before("@annotation(AudioContentReleaseSchedulerOnly)") fun checkSchedulerAccess() { if (!isSchedulerThread()) { throw IllegalStateException("잘못된 접근입니다.") @@ -21,6 +21,6 @@ class SchedulerOnlyAspect { private fun isSchedulerThread(): Boolean { // 스케줄러 스레드 여부를 판단하는 간단한 로직 - return Thread.currentThread().name.contains("scheduler") + return Thread.currentThread().name.contains("AudioContentRelease-Scheduler") } } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/configs/SchedulerConfig.kt b/src/main/kotlin/kr/co/vividnext/sodalive/configs/SchedulerConfig.kt new file mode 100644 index 0000000..116acc6 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/configs/SchedulerConfig.kt @@ -0,0 +1,18 @@ +package kr.co.vividnext.sodalive.configs + +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.scheduling.annotation.EnableScheduling +import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler + +@Configuration +@EnableScheduling +class SchedulerConfig { + @Bean + fun taskScheduler(): ThreadPoolTaskScheduler { + val scheduler = ThreadPoolTaskScheduler() + scheduler.poolSize = 2 + scheduler.setThreadNamePrefix("AudioContentRelease-Scheduler-") + return scheduler + } +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentService.kt index 1902782..05f9738 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentService.kt @@ -5,7 +5,7 @@ import com.fasterxml.jackson.databind.ObjectMapper import kr.co.vividnext.sodalive.aws.cloudfront.AudioContentCloudFront import kr.co.vividnext.sodalive.aws.s3.S3Uploader import kr.co.vividnext.sodalive.common.SodaException -import kr.co.vividnext.sodalive.common.annotation.SchedulerOnly +import kr.co.vividnext.sodalive.common.annotation.AudioContentReleaseSchedulerOnly import kr.co.vividnext.sodalive.content.comment.AudioContentCommentRepository import kr.co.vividnext.sodalive.content.hashtag.AudioContentHashTag import kr.co.vividnext.sodalive.content.hashtag.HashTag @@ -403,7 +403,7 @@ class AudioContentService( } } - @SchedulerOnly + @AudioContentReleaseSchedulerOnly @Transactional fun releaseContent() { val notReleasedAudioContent = repository.getNotReleaseContent() -- 2.40.1 From a1ef9a497086805e55298341bbbb7d612bf98548 Mon Sep 17 00:00:00 2001 From: Klaus Date: Mon, 2 Dec 2024 10:46:48 +0900 Subject: [PATCH 11/13] =?UTF-8?q?=EC=BD=98=ED=85=90=EC=B8=A0=20=EC=98=88?= =?UTF-8?q?=EC=95=BD=20=EC=98=A4=ED=94=88=20=EC=84=A4=EC=A0=95=20-=20?= =?UTF-8?q?=EC=8A=A4=EC=BC=80=EC=A4=84=EB=9F=AC=20=EC=84=A4=EC=A0=95=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20-=20=EC=99=B8=EB=B6=80=EC=97=90=EC=84=9C?= =?UTF-8?q?=20=EC=8B=A4=ED=96=89=EB=90=98=EB=8A=94=20endpoint=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sodalive/configs/SchedulerConfig.kt | 14 ++++++++- .../content/AudioContentController.kt | 6 ---- .../AudioContentReleaseScheduledTask.kt | 31 +++++++++++++++++++ .../AudioContentReleaseSchedulerService.kt | 15 --------- 4 files changed, 44 insertions(+), 22 deletions(-) create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/scheduler/AudioContentReleaseScheduledTask.kt delete mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/scheduler/AudioContentReleaseSchedulerService.kt diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/configs/SchedulerConfig.kt b/src/main/kotlin/kr/co/vividnext/sodalive/configs/SchedulerConfig.kt index 116acc6..a6a982a 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/configs/SchedulerConfig.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/configs/SchedulerConfig.kt @@ -8,11 +8,23 @@ import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler @Configuration @EnableScheduling class SchedulerConfig { - @Bean + @Bean(name = ["taskScheduler"]) fun taskScheduler(): ThreadPoolTaskScheduler { + val scheduler = ThreadPoolTaskScheduler() + scheduler.poolSize = 5 + scheduler.setThreadNamePrefix("DefaultScheduler-") + scheduler.setWaitForTasksToCompleteOnShutdown(true) // 종료 시 대기 설정 + scheduler.setAwaitTerminationSeconds(10) // 최대 10초 대기 + return scheduler + } + + @Bean(name = ["audioContentReleaseScheduler"]) + fun audioContentReleaseScheduler(): ThreadPoolTaskScheduler { val scheduler = ThreadPoolTaskScheduler() scheduler.poolSize = 2 scheduler.setThreadNamePrefix("AudioContentRelease-Scheduler-") + scheduler.setWaitForTasksToCompleteOnShutdown(true) // 종료 시 대기 설정 + scheduler.setAwaitTerminationSeconds(10) // 최대 10초 대기 return scheduler } } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentController.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentController.kt index 750a882..2a2a983 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentController.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentController.kt @@ -193,12 +193,6 @@ class AudioContentController(private val service: AudioContentService) { ) } - @PostMapping("/release") - @PreAuthorize("hasRole('BOT')") - fun releaseContent() = run { - ApiResponse.ok(service.releaseContent()) - } - @PostMapping("/pin-to-the-top/{id}") @PreAuthorize("hasRole('CREATOR')") fun pinToTheTop( diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/scheduler/AudioContentReleaseScheduledTask.kt b/src/main/kotlin/kr/co/vividnext/sodalive/scheduler/AudioContentReleaseScheduledTask.kt new file mode 100644 index 0000000..503b028 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/scheduler/AudioContentReleaseScheduledTask.kt @@ -0,0 +1,31 @@ +package kr.co.vividnext.sodalive.scheduler + +import kr.co.vividnext.sodalive.content.AudioContentService +import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler +import org.springframework.scheduling.support.CronTrigger +import org.springframework.stereotype.Component +import java.util.concurrent.ScheduledFuture +import javax.annotation.PostConstruct +import javax.annotation.PreDestroy + +@Component +class AudioContentReleaseScheduledTask( + private val audioContentService: AudioContentService, + private val audioContentReleaseScheduler: ThreadPoolTaskScheduler +) { + private var scheduledTask: ScheduledFuture<*>? = null + + @PostConstruct + fun release() { + scheduledTask = audioContentReleaseScheduler.schedule( + { audioContentService.releaseContent() }, + CronTrigger("0 0/15 * * * *") + ) + } + + @PreDestroy + fun stopReleaseScheduler() { + scheduledTask?.cancel(false) + audioContentReleaseScheduler.shutdown() + } +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/scheduler/AudioContentReleaseSchedulerService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/scheduler/AudioContentReleaseSchedulerService.kt deleted file mode 100644 index a6b847f..0000000 --- a/src/main/kotlin/kr/co/vividnext/sodalive/scheduler/AudioContentReleaseSchedulerService.kt +++ /dev/null @@ -1,15 +0,0 @@ -package kr.co.vividnext.sodalive.scheduler - -import kr.co.vividnext.sodalive.content.AudioContentService -import org.springframework.scheduling.annotation.Scheduled -import org.springframework.stereotype.Service - -@Service -class AudioContentReleaseSchedulerService( - private val audioContentService: AudioContentService -) { - @Scheduled(cron = "0 0/15 * * * *") - fun release() { - audioContentService.releaseContent() - } -} -- 2.40.1 From 9039a7a2d0c33210eae017521ed6684fff55f560 Mon Sep 17 00:00:00 2001 From: Klaus Date: Mon, 2 Dec 2024 11:02:02 +0900 Subject: [PATCH 12/13] =?UTF-8?q?taskScheduler=EC=97=90=20Primary=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kotlin/kr/co/vividnext/sodalive/configs/SchedulerConfig.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/configs/SchedulerConfig.kt b/src/main/kotlin/kr/co/vividnext/sodalive/configs/SchedulerConfig.kt index a6a982a..09cbe14 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/configs/SchedulerConfig.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/configs/SchedulerConfig.kt @@ -2,12 +2,14 @@ package kr.co.vividnext.sodalive.configs import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration +import org.springframework.context.annotation.Primary import org.springframework.scheduling.annotation.EnableScheduling import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler @Configuration @EnableScheduling class SchedulerConfig { + @Primary @Bean(name = ["taskScheduler"]) fun taskScheduler(): ThreadPoolTaskScheduler { val scheduler = ThreadPoolTaskScheduler() -- 2.40.1 From c54105e65b6f42f543d2dbcb37409467b2f3b5ee Mon Sep 17 00:00:00 2001 From: Klaus Date: Mon, 2 Dec 2024 11:12:31 +0900 Subject: [PATCH 13/13] =?UTF-8?q?AudioContentReleaseScheduledTask=20-=20Qu?= =?UTF-8?q?alifier=EB=A1=9C=20audioContentReleaseScheduler=20=EC=84=A0?= =?UTF-8?q?=ED=83=9D=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sodalive/scheduler/AudioContentReleaseScheduledTask.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/scheduler/AudioContentReleaseScheduledTask.kt b/src/main/kotlin/kr/co/vividnext/sodalive/scheduler/AudioContentReleaseScheduledTask.kt index 503b028..b07c6fe 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/scheduler/AudioContentReleaseScheduledTask.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/scheduler/AudioContentReleaseScheduledTask.kt @@ -1,6 +1,7 @@ package kr.co.vividnext.sodalive.scheduler import kr.co.vividnext.sodalive.content.AudioContentService +import org.springframework.beans.factory.annotation.Qualifier import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler import org.springframework.scheduling.support.CronTrigger import org.springframework.stereotype.Component @@ -11,7 +12,7 @@ import javax.annotation.PreDestroy @Component class AudioContentReleaseScheduledTask( private val audioContentService: AudioContentService, - private val audioContentReleaseScheduler: ThreadPoolTaskScheduler + @Qualifier("audioContentReleaseScheduler") private val audioContentReleaseScheduler: ThreadPoolTaskScheduler ) { private var scheduledTask: ScheduledFuture<*>? = null -- 2.40.1