From f23251f5bb109f766ede13bee8e4e6e5b1515126 Mon Sep 17 00:00:00 2001 From: Klaus Date: Fri, 16 May 2025 15:01:33 +0900 Subject: [PATCH 01/20] =?UTF-8?q?fix:=20=EC=9C=A0=EC=A0=80=20=ED=96=89?= =?UTF-8?q?=EB=8F=99=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EA=B8=B0=EB=A1=9D?= =?UTF-8?q?=EC=8B=9C=20=ED=8F=AC=EC=9D=B8=ED=8A=B8=20=EC=A7=80=EA=B8=89=20?= =?UTF-8?q?=EC=A1=B0=EA=B1=B4=20=EC=88=98=EC=A0=95=20-=20=EC=A7=80?= =?UTF-8?q?=EA=B8=89=EC=9C=A0=ED=98=95(=EB=A7=A4=EC=9D=BC,=20=EC=A0=84?= =?UTF-8?q?=EC=B2=B4)=20=EC=B6=94=EA=B0=80=20-=20=EC=B0=B8=EC=97=AC?= =?UTF-8?q?=EA=B0=80=EB=8A=A5=20=ED=9A=9F=EC=88=98=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?-=20=EC=A3=BC=EB=AC=B8=ED=95=9C=20=EC=BD=98=ED=85=90=EC=B8=A0?= =?UTF-8?q?=EC=97=90=20=EB=8C=93=EA=B8=80=EC=9D=84=20=EC=93=B0=EB=A9=B4=20?= =?UTF-8?q?=ED=8F=AC=EC=9D=B8=ED=8A=B8=20=EC=A7=80=EA=B8=89=EC=9D=84=20?= =?UTF-8?q?=EC=9C=84=ED=95=B4=20=ED=8F=AC=EC=9D=B8=ED=8A=B8=20=EC=A7=80?= =?UTF-8?q?=EA=B8=89=20=EC=9D=B4=EB=A0=A5=EC=97=90=20orderId=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 --- .../point/CreatePointRewardPolicyRequest.kt | 5 ++++ .../vividnext/sodalive/point/PointGrantLog.kt | 3 ++- .../sodalive/point/PointGrantLogRepository.kt | 10 ++++---- .../sodalive/point/PointRewardPolicy.kt | 4 ++++ .../sodalive/useraction/ActionType.kt | 5 +++- .../sodalive/useraction/PolicyType.kt | 6 +++++ .../sodalive/useraction/UserActionService.kt | 23 +++++++++++++++---- 7 files changed, 45 insertions(+), 11 deletions(-) create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/useraction/PolicyType.kt diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/point/CreatePointRewardPolicyRequest.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/point/CreatePointRewardPolicyRequest.kt index 4c2cf73..0f80f8a 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/admin/point/CreatePointRewardPolicyRequest.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/point/CreatePointRewardPolicyRequest.kt @@ -2,14 +2,17 @@ package kr.co.vividnext.sodalive.admin.point import kr.co.vividnext.sodalive.point.PointRewardPolicy import kr.co.vividnext.sodalive.useraction.ActionType +import kr.co.vividnext.sodalive.useraction.PolicyType import java.time.LocalDateTime import java.time.ZoneId import java.time.format.DateTimeFormatter data class CreatePointRewardPolicyRequest( val title: String, + val policyType: PolicyType, val actionType: ActionType, val threshold: Int, + val availableCount: Int, val pointAmount: Int, val startDate: String, val endDate: String @@ -19,8 +22,10 @@ data class CreatePointRewardPolicyRequest( return PointRewardPolicy( title = title, + policyType = policyType, actionType = actionType, threshold = threshold, + availableCount = availableCount, pointAmount = pointAmount, startDate = LocalDateTime.parse(startDate, dateTimeFormatter) .atZone(ZoneId.of("Asia/Seoul")) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/point/PointGrantLog.kt b/src/main/kotlin/kr/co/vividnext/sodalive/point/PointGrantLog.kt index 780eccc..83666c6 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/point/PointGrantLog.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/point/PointGrantLog.kt @@ -12,5 +12,6 @@ data class PointGrantLog( val point: Int, @Enumerated(EnumType.STRING) val actionType: ActionType, - val policyId: Long? + val policyId: Long?, + val orderId: Long? ) : BaseEntity() diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/point/PointGrantLogRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/point/PointGrantLogRepository.kt index 361a873..8862ab6 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/point/PointGrantLogRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/point/PointGrantLogRepository.kt @@ -3,25 +3,27 @@ package kr.co.vividnext.sodalive.point import com.querydsl.jpa.impl.JPAQueryFactory import kr.co.vividnext.sodalive.point.QPointGrantLog.pointGrantLog import org.springframework.data.jpa.repository.JpaRepository +import java.time.LocalDateTime interface PointGrantLogRepository : JpaRepository, PointGrantLogQueryRepository interface PointGrantLogQueryRepository { - fun existsByMemberIdAndPolicyId(memberId: Long, policyId: Long): Boolean + fun countByMemberIdAndPolicyIdAndStartDate(memberId: Long, policyId: Long, startDate: LocalDateTime): Int } class PointGrantLogQueryRepositoryImpl( private val queryFactory: JPAQueryFactory ) : PointGrantLogQueryRepository { - override fun existsByMemberIdAndPolicyId(memberId: Long, policyId: Long): Boolean { + override fun countByMemberIdAndPolicyIdAndStartDate(memberId: Long, policyId: Long, startDate: LocalDateTime): Int { return queryFactory .select(pointGrantLog.id) .from(pointGrantLog) .where( pointGrantLog.memberId.eq(memberId), - pointGrantLog.policyId.eq(policyId) + pointGrantLog.policyId.eq(policyId), + pointGrantLog.createdAt.goe(startDate) ) .fetch() - .isNotEmpty() + .size } } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/point/PointRewardPolicy.kt b/src/main/kotlin/kr/co/vividnext/sodalive/point/PointRewardPolicy.kt index a898e1c..2fd9bd9 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/point/PointRewardPolicy.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/point/PointRewardPolicy.kt @@ -2,6 +2,7 @@ package kr.co.vividnext.sodalive.point import kr.co.vividnext.sodalive.common.BaseEntity import kr.co.vividnext.sodalive.useraction.ActionType +import kr.co.vividnext.sodalive.useraction.PolicyType import java.time.LocalDateTime import javax.persistence.Entity import javax.persistence.EnumType @@ -11,8 +12,11 @@ import javax.persistence.Enumerated data class PointRewardPolicy( var title: String, @Enumerated(EnumType.STRING) + val policyType: PolicyType, + @Enumerated(EnumType.STRING) val actionType: ActionType, val threshold: Int, + val availableCount: Int, val pointAmount: Int, var startDate: LocalDateTime, var endDate: LocalDateTime? = null, diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/useraction/ActionType.kt b/src/main/kotlin/kr/co/vividnext/sodalive/useraction/ActionType.kt index a9365c4..5933be9 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/useraction/ActionType.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/useraction/ActionType.kt @@ -2,5 +2,8 @@ package kr.co.vividnext.sodalive.useraction enum class ActionType(val displayName: String) { SIGN_UP("회원가입"), - USER_AUTHENTICATION("본인인증") + USER_AUTHENTICATION("본인인증"), + CONTENT_COMMENT("콘텐츠 댓글"), + ORDER_CONTENT_COMMENT("구매한 콘텐츠 댓글"), + LIVE_CONTINUOUS_LISTEN_30("라이브 연속 청취 30분") } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/useraction/PolicyType.kt b/src/main/kotlin/kr/co/vividnext/sodalive/useraction/PolicyType.kt new file mode 100644 index 0000000..81f4982 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/useraction/PolicyType.kt @@ -0,0 +1,6 @@ +package kr.co.vividnext.sodalive.useraction + +enum class PolicyType(val displayName: String) { + DAILY("기간 내 매일"), + TOTAL("기간 내 전체") +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/useraction/UserActionService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/useraction/UserActionService.kt index b3f0236..3f1a33d 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/useraction/UserActionService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/useraction/UserActionService.kt @@ -24,23 +24,35 @@ class UserActionService( private val coroutineScope = CoroutineScope(Dispatchers.IO) - fun recordAction(memberId: Long, actionType: ActionType, pushToken: String?) { + fun recordAction(memberId: Long, actionType: ActionType, orderId: Long? = null, pushToken: String? = null) { coroutineScope.launch { val now = LocalDateTime.now() repository.save(UserActionLog(memberId, actionType)) val policy = policyRepository.findByActionTypeAndIsActiveTrue(actionType, now) if (policy != null) { + val policyType = policy.policyType + val todayAt15 = now.toLocalDate().atTime(15, 0) + val policyTypeDailyStartDate = if (now.toLocalTime().isBefore(todayAt15.toLocalTime())) { + now.toLocalDate().minusDays(1).atTime(15, 0) + } else { + todayAt15 + } + val actionCount = repository.countByMemberIdAndActionTypeAndCreatedAtBetween( memberId = memberId, actionType = actionType, - startDate = policy.startDate, + startDate = if (policyType == PolicyType.DAILY) policyTypeDailyStartDate else policy.startDate, endDate = policy.endDate ?: now ) if (actionCount < policy.threshold) return@launch - val alreadyGranted = grantLogRepository.existsByMemberIdAndPolicyId(memberId, policy.id!!) - if (alreadyGranted) return@launch + val grantedCount = grantLogRepository.countByMemberIdAndPolicyIdAndStartDate( + memberId, + policy.id!!, + startDate = if (policyType == PolicyType.DAILY) policyTypeDailyStartDate else policy.startDate + ) + if (grantedCount >= policy.availableCount) return@launch memberPointRepository.save( MemberPoint( @@ -56,7 +68,8 @@ class UserActionService( memberId = memberId, point = policy.pointAmount, actionType = actionType, - policyId = policy.id!! + policyId = policy.id!!, + orderId = orderId ) ) From affc0cc2357532234c6b5f61f1e5af00107b5026 Mon Sep 17 00:00:00 2001 From: Klaus Date: Fri, 16 May 2025 17:31:28 +0900 Subject: [PATCH 02/20] =?UTF-8?q?fix:=20=EA=B4=80=EB=A6=AC=EC=9E=90=20-=20?= =?UTF-8?q?=ED=8F=AC=EC=9D=B8=ED=8A=B8=20=EC=A0=95=EC=B1=85=20=EB=A6=AC?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EA=B0=92=20=EC=B6=94=EA=B0=80=20-=20?= =?UTF-8?q?=EC=A7=80=EA=B8=89=EC=9C=A0=ED=98=95(=EB=A7=A4=EC=9D=BC,=20?= =?UTF-8?q?=EC=A0=84=EC=B2=B4)=20=EC=B6=94=EA=B0=80=20-=20=EC=B0=B8?= =?UTF-8?q?=EC=97=AC=EA=B0=80=EB=8A=A5=20=ED=9A=9F=EC=88=98=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 --- .../sodalive/admin/point/GetPointRewardPolicyListResponse.kt | 3 +++ .../co/vividnext/sodalive/admin/point/PointPolicyRepository.kt | 2 ++ 2 files changed, 5 insertions(+) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/point/GetPointRewardPolicyListResponse.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/point/GetPointRewardPolicyListResponse.kt index 33dd64e..8654c19 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/admin/point/GetPointRewardPolicyListResponse.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/point/GetPointRewardPolicyListResponse.kt @@ -2,6 +2,7 @@ package kr.co.vividnext.sodalive.admin.point import com.querydsl.core.annotations.QueryProjection import kr.co.vividnext.sodalive.useraction.ActionType +import kr.co.vividnext.sodalive.useraction.PolicyType data class GetPointRewardPolicyListResponse( val totalCount: Int, @@ -11,8 +12,10 @@ data class GetPointRewardPolicyListResponse( data class GetPointRewardPolicyListItem @QueryProjection constructor( val id: Long, val title: String, + val policyType: PolicyType, val actionType: ActionType, val threshold: Int, + val availableCount: Int, val pointAmount: Int, val startDate: String, val endDate: String, diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/point/PointPolicyRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/point/PointPolicyRepository.kt index 2cd63db..0c66df7 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/admin/point/PointPolicyRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/point/PointPolicyRepository.kt @@ -33,8 +33,10 @@ class PointPolicyQueryRepositoryImpl( QGetPointRewardPolicyListItem( pointRewardPolicy.id, pointRewardPolicy.title, + pointRewardPolicy.policyType, pointRewardPolicy.actionType, pointRewardPolicy.threshold, + pointRewardPolicy.availableCount, pointRewardPolicy.pointAmount, getFormattedDate(pointRewardPolicy.startDate), getFormattedDate(pointRewardPolicy.endDate), From eb8c8c14e812d3e66c791b63790f1c9b70f33f62 Mon Sep 17 00:00:00 2001 From: Klaus Date: Fri, 16 May 2025 17:57:37 +0900 Subject: [PATCH 03/20] =?UTF-8?q?fix:=20=EC=9C=A0=EC=A0=80=20=ED=96=89?= =?UTF-8?q?=EB=8F=99=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EA=B8=B0=EB=A1=9D?= =?UTF-8?q?=EC=8B=9C=20=ED=8F=AC=EC=9D=B8=ED=8A=B8=20=EC=A7=80=EA=B8=89?= =?UTF-8?q?=EA=B3=BC=20=EB=A1=9C=EA=B7=B8=20=EA=B8=B0=EB=A1=9D=20=EC=88=9C?= =?UTF-8?q?=EC=84=9C=20=EB=B3=80=EA=B2=BD=20-=20=EA=B8=B0=EC=A1=B4:=20?= =?UTF-8?q?=ED=8F=AC=EC=9D=B8=ED=8A=B8=20=EC=A7=80=EA=B8=89=20=ED=9B=84=20?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=20=EA=B8=B0=EB=A1=9D=20-=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD:=20=EB=A1=9C=EA=B7=B8=20=EA=B8=B0=EB=A1=9D=20?= =?UTF-8?q?=ED=9B=84=20=ED=8F=AC=EC=9D=B8=ED=8A=B8=20=EC=A7=80=EA=B8=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sodalive/useraction/UserActionService.kt | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/useraction/UserActionService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/useraction/UserActionService.kt index 3f1a33d..6881d59 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/useraction/UserActionService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/useraction/UserActionService.kt @@ -54,15 +54,6 @@ class UserActionService( ) if (grantedCount >= policy.availableCount) return@launch - memberPointRepository.save( - MemberPoint( - memberId = memberId, - point = policy.pointAmount, - actionType = actionType, - expiresAt = now.plusDays(3) - ) - ) - grantLogRepository.save( PointGrantLog( memberId = memberId, @@ -73,6 +64,15 @@ class UserActionService( ) ) + memberPointRepository.save( + MemberPoint( + memberId = memberId, + point = policy.pointAmount, + actionType = actionType, + expiresAt = now.plusDays(3) + ) + ) + if (pushToken != null) { fcmService.sendPointGranted(pushToken, policy.pointAmount) } From ddcd54d3b914c9241d837b4e6be8c0579f1fdecd Mon Sep 17 00:00:00 2001 From: Klaus Date: Fri, 16 May 2025 20:32:48 +0900 Subject: [PATCH 04/20] =?UTF-8?q?feat:=20=EC=9C=A0=EC=A0=80=20=ED=96=89?= =?UTF-8?q?=EB=8F=99=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EA=B8=B0=EB=A1=9D=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20-=20=EC=BD=98=ED=85=90=EC=B8=A0=EC=97=90?= =?UTF-8?q?=20=EB=8C=93=EA=B8=80=20=EC=93=B0=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../comment/AudioContentCommentController.kt | 37 ++++++--- .../co/vividnext/sodalive/fcm/FcmService.kt | 80 +++++++++++-------- .../sodalive/member/MemberController.kt | 18 ++++- .../sodalive/member/MemberRepository.kt | 15 ++++ .../sodalive/member/MemberService.kt | 6 ++ .../sodalive/member/auth/AuthController.kt | 17 ++-- .../sodalive/useraction/UserActionService.kt | 11 ++- 7 files changed, 132 insertions(+), 52 deletions(-) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/comment/AudioContentCommentController.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/comment/AudioContentCommentController.kt index a68faa2..c7b5834 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/comment/AudioContentCommentController.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/comment/AudioContentCommentController.kt @@ -3,6 +3,9 @@ package kr.co.vividnext.sodalive.content.comment import kr.co.vividnext.sodalive.common.ApiResponse import kr.co.vividnext.sodalive.common.SodaException import kr.co.vividnext.sodalive.member.Member +import kr.co.vividnext.sodalive.member.MemberService +import kr.co.vividnext.sodalive.useraction.ActionType +import kr.co.vividnext.sodalive.useraction.UserActionService import org.springframework.data.domain.Pageable import org.springframework.security.core.annotation.AuthenticationPrincipal import org.springframework.web.bind.annotation.GetMapping @@ -14,7 +17,11 @@ import org.springframework.web.bind.annotation.RequestParam import org.springframework.web.bind.annotation.RestController @RestController -class AudioContentCommentController(private val service: AudioContentCommentService) { +class AudioContentCommentController( + private val service: AudioContentCommentService, + private val memberService: MemberService, + private val userActionService: UserActionService +) { @PostMapping("/audio-content/comment") fun registerComment( @RequestBody request: RegisterCommentRequest, @@ -22,15 +29,27 @@ class AudioContentCommentController(private val service: AudioContentCommentServ ) = run { if (member == null) throw SodaException("로그인 정보를 확인해주세요.") - ApiResponse.ok( - service.registerComment( - comment = request.comment, - audioContentId = request.contentId, - parentId = request.parentId, - isSecret = request.isSecret, - member = member - ) + service.registerComment( + comment = request.comment, + audioContentId = request.contentId, + parentId = request.parentId, + isSecret = request.isSecret, + member = member ) + + try { + val memberId = member.id!! + val pushTokenList = memberService.getPushTokenList(recipient = memberId) + + userActionService.recordAction( + memberId = member.id!!, + actionType = ActionType.CONTENT_COMMENT, + pushTokenList = pushTokenList + ) + } catch (_: Exception) { + } + + ApiResponse.ok(Unit, "") } @PutMapping("/audio-content/comment") diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/fcm/FcmService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/fcm/FcmService.kt index 997b2f5..68e0570 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/fcm/FcmService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/fcm/FcmService.kt @@ -4,8 +4,6 @@ import com.google.firebase.messaging.AndroidConfig import com.google.firebase.messaging.ApnsConfig import com.google.firebase.messaging.Aps import com.google.firebase.messaging.FirebaseMessaging -import com.google.firebase.messaging.FirebaseMessagingException -import com.google.firebase.messaging.Message import com.google.firebase.messaging.MessagingErrorCode import com.google.firebase.messaging.MulticastMessage import com.google.firebase.messaging.Notification @@ -38,7 +36,7 @@ class FcmService(private val pushTokenService: PushTokenService) { while (attempt <= maxAttempts && targets.isNotEmpty()) { val multicastMessage = MulticastMessage.builder() - .addAllTokens(tokens) + .addAllTokens(targets) multicastMessage.setAndroidConfig( AndroidConfig.builder() @@ -117,51 +115,69 @@ class FcmService(private val pushTokenService: PushTokenService) { } } - fun sendPointGranted(token: String, point: Int) { + fun sendPointGranted(tokens: List, point: Int) { + if (tokens.isEmpty()) return val data = mapOf( "type" to "POINT_GRANTED", "point" to point.toString(), "message" to "${point}포인트가 지급되었습니다!" ) + var targets = tokens var attempts = 0 val maxAttempts = 3 - while (attempts < maxAttempts) { - try { - val message = Message.builder() - .setToken(token) - .putAllData(data) + while (attempts <= maxAttempts && targets.isNotEmpty()) { + val multicastMessage = MulticastMessage.builder() + .addAllTokens(targets) + .putAllData(data) + + multicastMessage.setAndroidConfig( + AndroidConfig.builder() + .setPriority(AndroidConfig.Priority.HIGH) .build() + ) - val response = FirebaseMessaging.getInstance().send(message) - logger.info("[FCM] ✅ 성공 (attempt ${attempts + 1}): messageId=$response") - return // 성공 시 즉시 종료 - } catch (e: FirebaseMessagingException) { - attempts++ + multicastMessage.setApnsConfig( + ApnsConfig.builder() + .setAps( + Aps.builder() + .setSound("default") + .build() + ) + .build() + ) - // "registration-token-not-registered" 예외 코드 확인 - if (e.messagingErrorCode == MessagingErrorCode.UNREGISTERED) { - logger.error("[FCM] ❌ 실패: 토큰이 등록되지 않음 (등록 해제됨) → 재시도 안함") - // DB에서 삭제 - pushTokenService.unregisterInvalidToken(token) - return - } + val response = FirebaseMessaging.getInstance().sendEachForMulticast(multicastMessage.build()) + val failedTokens = mutableListOf() - logger.error("[FCM] ❌ 실패 (attempt $attempts): ${e.errorCode} - ${e.message}") + response.responses.forEachIndexed { index, res -> + if (!res.isSuccessful) { + val exception = res.exception + val token = targets[index] - if (attempts >= maxAttempts) { - logger.error("[FCM] ❌ 최종 실패: 전송 불가") - } - } catch (e: Exception) { - // Firebase 이외의 예외도 잡기 - attempts++ - logger.error("[FCM] ❌ 실패 (attempt $attempts): ${e.message}") - - if (attempts >= maxAttempts) { - logger.error("[FCM] ❌ 최종 실패: 알 수 없는 오류") + if (exception?.messagingErrorCode == MessagingErrorCode.UNREGISTERED) { + logger.error("[FCM] ❌ UNREGISTERED → $token") + // DB에서 삭제 + pushTokenService.unregisterInvalidToken(token) + } else { + logger.error("[FCM] ❌ 실패: $token / ${exception?.messagingErrorCode}") + failedTokens.add(token) + } } } + + if (failedTokens.isEmpty()) { + logger.info("[FCM] ✅ 전체 전송 성공") + return + } + + targets = failedTokens + attempts++ + } + + if (targets.isNotEmpty()) { + logger.error("[FCM] ❌ 최종 실패 대상 ${targets.size}명 → $targets") } } } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberController.kt b/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberController.kt index 1a1b2e4..11c893c 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberController.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberController.kt @@ -66,7 +66,11 @@ class MemberController( userActionService.recordAction( memberId = response.memberId, actionType = ActionType.SIGN_UP, - pushToken = request.pushToken + pushTokenList = if (request.pushToken != null) { + listOf(request.pushToken) + } else { + emptyList() + } ) return ApiResponse.ok(message = "회원가입을 축하드립니다.", data = response.loginResponse) @@ -355,7 +359,11 @@ class MemberController( userActionService.recordAction( memberId = response.memberId, actionType = ActionType.SIGN_UP, - pushToken = request.pushToken + pushTokenList = if (request.pushToken != null) { + listOf(request.pushToken) + } else { + emptyList() + } ) } @@ -386,7 +394,11 @@ class MemberController( userActionService.recordAction( memberId = response.memberId, actionType = ActionType.SIGN_UP, - pushToken = request.pushToken + pushTokenList = if (request.pushToken != null) { + listOf(request.pushToken) + } else { + emptyList() + } ) } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberRepository.kt index 2f14fea..2eb20ef 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberRepository.kt @@ -64,6 +64,8 @@ interface MemberQueryRepository { fun existsByNickname(nickname: String): Boolean fun findNicknamesWithPrefix(prefix: String): List + + fun getPushTokenList(memberId: Long): List } @Repository @@ -507,4 +509,17 @@ class MemberQueryRepositoryImpl( ) .fetch() } + + override fun getPushTokenList(memberId: Long): List { + val where = member.isActive.isTrue + .and(member.email.notIn("admin@sodalive.net")) + .and(member.id.eq(memberId)) + + return queryFactory + .select(pushToken.token) + .from(member) + .innerJoin(pushToken).on(member.id.eq(pushToken.member.id)) + .where(where) + .fetch() + } } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberService.kt index 49f3362..ad71bec 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberService.kt @@ -907,6 +907,12 @@ class MemberService( return MemberResolveResult(member = member, isNew = true) } + fun getPushTokenList(recipient: Long): List { + return repository.getPushTokenList(recipient) + .toSet() + .toList() + } + private fun checkEmail(email: String) { val member = repository.findByEmail(email) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/member/auth/AuthController.kt b/src/main/kotlin/kr/co/vividnext/sodalive/member/auth/AuthController.kt index f645797..17366f5 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/member/auth/AuthController.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/member/auth/AuthController.kt @@ -3,6 +3,7 @@ package kr.co.vividnext.sodalive.member.auth import kr.co.vividnext.sodalive.common.ApiResponse import kr.co.vividnext.sodalive.common.SodaException import kr.co.vividnext.sodalive.member.Member +import kr.co.vividnext.sodalive.member.MemberService import kr.co.vividnext.sodalive.useraction.ActionType import kr.co.vividnext.sodalive.useraction.UserActionService import org.springframework.security.core.annotation.AuthenticationPrincipal @@ -15,6 +16,7 @@ import org.springframework.web.bind.annotation.RestController @RequestMapping("/auth") class AuthController( private val service: AuthService, + private val memberService: MemberService, private val userActionService: UserActionService ) { @PostMapping @@ -33,11 +35,16 @@ class AuthController( val authResponse = service.authenticate(authenticateData, member.id!!) - userActionService.recordAction( - memberId = member.id!!, - actionType = ActionType.USER_AUTHENTICATION, - pushToken = member.pushToken - ) + try { + val memberId = member.id!! + val pushTokenList = memberService.getPushTokenList(recipient = memberId) + userActionService.recordAction( + memberId = member.id!!, + actionType = ActionType.USER_AUTHENTICATION, + pushTokenList = pushTokenList + ) + } catch (_: Exception) { + } ApiResponse.ok(authResponse) } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/useraction/UserActionService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/useraction/UserActionService.kt index 6881d59..f6919ac 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/useraction/UserActionService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/useraction/UserActionService.kt @@ -24,7 +24,12 @@ class UserActionService( private val coroutineScope = CoroutineScope(Dispatchers.IO) - fun recordAction(memberId: Long, actionType: ActionType, orderId: Long? = null, pushToken: String? = null) { + fun recordAction( + memberId: Long, + actionType: ActionType, + orderId: Long? = null, + pushTokenList: List = emptyList() + ) { coroutineScope.launch { val now = LocalDateTime.now() repository.save(UserActionLog(memberId, actionType)) @@ -73,8 +78,8 @@ class UserActionService( ) ) - if (pushToken != null) { - fcmService.sendPointGranted(pushToken, policy.pointAmount) + if (pushTokenList.isNotEmpty()) { + fcmService.sendPointGranted(pushTokenList, policy.pointAmount) } } } From 6fc372c898c0b08b4ec2291777ecc589f96c1eb1 Mon Sep 17 00:00:00 2001 From: Klaus Date: Fri, 16 May 2025 21:24:12 +0900 Subject: [PATCH 05/20] =?UTF-8?q?feat:=20=EC=9C=A0=EC=A0=80=20=ED=96=89?= =?UTF-8?q?=EB=8F=99=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EA=B8=B0=EB=A1=9D=20?= =?UTF-8?q?Controller=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../useraction/UserActionController.kt | 36 +++++++++++++++++++ .../sodalive/useraction/UserActionRequest.kt | 3 ++ 2 files changed, 39 insertions(+) create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/useraction/UserActionController.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/useraction/UserActionRequest.kt diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/useraction/UserActionController.kt b/src/main/kotlin/kr/co/vividnext/sodalive/useraction/UserActionController.kt new file mode 100644 index 0000000..91ae58c --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/useraction/UserActionController.kt @@ -0,0 +1,36 @@ +package kr.co.vividnext.sodalive.useraction + +import kr.co.vividnext.sodalive.common.ApiResponse +import kr.co.vividnext.sodalive.common.SodaException +import kr.co.vividnext.sodalive.member.Member +import kr.co.vividnext.sodalive.member.MemberService +import org.springframework.security.core.annotation.AuthenticationPrincipal +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("/user-action") +class UserActionController( + private val service: UserActionService, + private val memberService: MemberService +) { + @PostMapping + fun recordAction( + @RequestBody request: UserActionRequest, + @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? + ) = run { + if (member == null) throw SodaException("") + + val memberId = member.id!! + val pushTokenList = memberService.getPushTokenList(recipient = memberId) + service.recordAction( + memberId = memberId, + actionType = request.actionType, + pushTokenList = pushTokenList + ) + + ApiResponse.ok(Unit, "") + } +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/useraction/UserActionRequest.kt b/src/main/kotlin/kr/co/vividnext/sodalive/useraction/UserActionRequest.kt new file mode 100644 index 0000000..4d92eb7 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/useraction/UserActionRequest.kt @@ -0,0 +1,3 @@ +package kr.co.vividnext.sodalive.useraction + +data class UserActionRequest(val actionType: ActionType) From 1bca1b27ed97c2d69f39f6df61112bc442bc7776 Mon Sep 17 00:00:00 2001 From: Klaus Date: Sat, 17 May 2025 18:07:02 +0900 Subject: [PATCH 06/20] =?UTF-8?q?feat:=20=EA=B5=AC=EB=A7=A4=ED=95=9C=20?= =?UTF-8?q?=EC=BD=98=ED=85=90=EC=B8=A0=20=EB=8C=93=EA=B8=80=20=EC=9D=B4?= =?UTF-8?q?=EB=B2=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../comment/AudioContentCommentController.kt | 7 +++++++ .../sodalive/content/order/OrderRepository.kt | 13 +++++++++++++ .../sodalive/useraction/UserActionService.kt | 17 +++++++++++++++-- 3 files changed, 35 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/comment/AudioContentCommentController.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/comment/AudioContentCommentController.kt index c7b5834..b3e4ad3 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/comment/AudioContentCommentController.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/comment/AudioContentCommentController.kt @@ -46,6 +46,13 @@ class AudioContentCommentController( actionType = ActionType.CONTENT_COMMENT, pushTokenList = pushTokenList ) + + userActionService.recordAction( + memberId = member.id!!, + actionType = ActionType.ORDER_CONTENT_COMMENT, + contentId = request.contentId, + pushTokenList = pushTokenList + ) } catch (_: Exception) { } 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 566cc27..d66760d 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 @@ -44,6 +44,7 @@ interface OrderQueryRepository { fun findOrderedContent(contentIdList: List, memberId: Long): List fun findEndDateByContentId(contentIdList: List, memberId: Long): List fun findBuyerListByContentId(contentId: Long): List + fun findByMemberIdAndContentId(memberId: Long, contentId: Long, createdAt: LocalDateTime): Order? } @Repository @@ -280,4 +281,16 @@ class OrderQueryRepositoryImpl( ) .fetch() } + + override fun findByMemberIdAndContentId(memberId: Long, contentId: Long, createdAt: LocalDateTime): Order? { + return queryFactory + .selectFrom(order) + .where( + order.isActive.isTrue, + order.member.id.eq(memberId), + order.audioContent.id.eq(contentId), + order.createdAt.after(createdAt) + ) + .fetchFirst() + } } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/useraction/UserActionService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/useraction/UserActionService.kt index f6919ac..f9d3143 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/useraction/UserActionService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/useraction/UserActionService.kt @@ -3,6 +3,7 @@ package kr.co.vividnext.sodalive.useraction import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import kr.co.vividnext.sodalive.content.order.OrderRepository import kr.co.vividnext.sodalive.fcm.FcmService import kr.co.vividnext.sodalive.point.MemberPoint import kr.co.vividnext.sodalive.point.MemberPointRepository @@ -15,6 +16,7 @@ import java.time.LocalDateTime @Service class UserActionService( private val repository: UserActionLogRepository, + private val orderRepository: OrderRepository, private val policyRepository: PointRewardPolicyRepository, private val grantLogRepository: PointGrantLogRepository, private val memberPointRepository: MemberPointRepository, @@ -27,7 +29,7 @@ class UserActionService( fun recordAction( memberId: Long, actionType: ActionType, - orderId: Long? = null, + contentId: Long? = null, pushTokenList: List = emptyList() ) { coroutineScope.launch { @@ -44,6 +46,17 @@ class UserActionService( todayAt15 } + val order = if (contentId != null) { + orderRepository.findByMemberIdAndContentId( + memberId = memberId, + contentId = contentId, + createdAt = if (policyType == PolicyType.DAILY) policyTypeDailyStartDate else policy.startDate + ) + } else { + null + } + if (actionType == ActionType.ORDER_CONTENT_COMMENT && order == null) return@launch + val actionCount = repository.countByMemberIdAndActionTypeAndCreatedAtBetween( memberId = memberId, actionType = actionType, @@ -65,7 +78,7 @@ class UserActionService( point = policy.pointAmount, actionType = actionType, policyId = policy.id!!, - orderId = orderId + orderId = order?.id ) ) From e2d0ae558a2c7713445ee893b96dacb8762e063b Mon Sep 17 00:00:00 2001 From: Klaus Date: Sat, 17 May 2025 18:13:11 +0900 Subject: [PATCH 07/20] =?UTF-8?q?feat:=20=EA=B5=AC=EB=A7=A4=ED=95=9C=20?= =?UTF-8?q?=EC=BD=98=ED=85=90=EC=B8=A0=20=EB=8C=93=EA=B8=80=20=EC=9D=B4?= =?UTF-8?q?=EB=B2=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80=20-=20=EA=B5=AC?= =?UTF-8?q?=EB=A7=A4=ED=95=9C=20=EC=BD=98=ED=85=90=EC=B8=A0=20=EB=8C=93?= =?UTF-8?q?=EA=B8=80=20=EC=93=B0=EA=B8=B0=EC=8B=9C=20=EA=B5=AC=EB=A7=A4?= =?UTF-8?q?=ED=95=9C=20=EC=BA=94=EC=9D=84=20=ED=8F=AC=EC=9D=B8=ED=8A=B8?= =?UTF-8?q?=EB=A1=9C=20=EC=A7=80=EA=B8=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../co/vividnext/sodalive/useraction/UserActionService.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/useraction/UserActionService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/useraction/UserActionService.kt index f9d3143..8cc75c4 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/useraction/UserActionService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/useraction/UserActionService.kt @@ -75,7 +75,11 @@ class UserActionService( grantLogRepository.save( PointGrantLog( memberId = memberId, - point = policy.pointAmount, + point = if (actionType == ActionType.ORDER_CONTENT_COMMENT && order != null) { + order.can + } else { + policy.pointAmount + }, actionType = actionType, policyId = policy.id!!, orderId = order?.id From 3079998a5d76fb3fe979ea7e033eb8e09e2c916c Mon Sep 17 00:00:00 2001 From: Klaus Date: Sat, 17 May 2025 18:44:04 +0900 Subject: [PATCH 08/20] =?UTF-8?q?fix:=20=EA=B5=AC=EB=A7=A4=ED=95=9C=20?= =?UTF-8?q?=EC=BD=98=ED=85=90=EC=B8=A0=20=EB=8C=93=EA=B8=80=20=EC=9D=B4?= =?UTF-8?q?=EB=B2=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80=20-=20=EA=B5=AC?= =?UTF-8?q?=EB=A7=A4=ED=95=9C=20=EC=BD=98=ED=85=90=EC=B8=A0=20=EB=8C=93?= =?UTF-8?q?=EA=B8=80=20=EC=93=B0=EA=B8=B0=EC=8B=9C=20=EA=B5=AC=EB=A7=A4?= =?UTF-8?q?=ED=95=9C=20=EC=BA=94=EC=9D=84=20=ED=8F=AC=EC=9D=B8=ED=8A=B8?= =?UTF-8?q?=EB=A1=9C=20=EC=A7=80=EA=B8=89=20=ED=95=B4=EC=95=BC=20=EB=90=98?= =?UTF-8?q?=EB=8A=94=EB=8D=B0=20=EC=84=A4=EC=A0=95=ED=95=9C=20=ED=8F=AC?= =?UTF-8?q?=EC=9D=B8=ED=8A=B8=EB=A1=9C=20=EC=A7=80=EA=B8=89=EB=90=98?= =?UTF-8?q?=EB=8A=94=20=EB=B2=84=EA=B7=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sodalive/useraction/UserActionService.kt | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/useraction/UserActionService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/useraction/UserActionService.kt index 8cc75c4..210b47d 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/useraction/UserActionService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/useraction/UserActionService.kt @@ -89,14 +89,25 @@ class UserActionService( memberPointRepository.save( MemberPoint( memberId = memberId, - point = policy.pointAmount, + point = if (actionType == ActionType.ORDER_CONTENT_COMMENT && order != null) { + order.can + } else { + policy.pointAmount + }, actionType = actionType, expiresAt = now.plusDays(3) ) ) if (pushTokenList.isNotEmpty()) { - fcmService.sendPointGranted(pushTokenList, policy.pointAmount) + fcmService.sendPointGranted( + pushTokenList, + if (actionType == ActionType.ORDER_CONTENT_COMMENT && order != null) { + order.can + } else { + policy.pointAmount + } + ) } } } From 107e8fce5581d9bff413278367a1d0d6abb8fb24 Mon Sep 17 00:00:00 2001 From: Klaus Date: Mon, 19 May 2025 10:49:16 +0900 Subject: [PATCH 09/20] =?UTF-8?q?fix:=20=EC=9C=A0=EC=A0=80=EC=9D=98=20?= =?UTF-8?q?=ED=96=89=EB=8F=99=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EA=B8=B0?= =?UTF-8?q?=EB=A1=9D=EC=8B=9C=20=EC=A3=BC=EB=AC=B8=ED=95=9C=20=EC=BD=98?= =?UTF-8?q?=ED=85=90=EC=B8=A0=EC=97=90=20=EB=8C=93=EA=B8=80=EC=9D=84=20?= =?UTF-8?q?=EC=93=B0=EB=8A=94=20=EA=B2=83=EC=9D=84=20=ED=8C=90=EB=8B=A8?= =?UTF-8?q?=ED=95=98=EA=B8=B0=20=EC=9C=84=ED=95=B4=20=EC=A3=BC=EB=AC=B8=20?= =?UTF-8?q?=EC=A0=95=EB=B3=B4=20=EC=A1=B0=ED=9A=8C=EC=8B=9C=20id=20?= =?UTF-8?q?=EB=82=B4=EB=A6=BC=EC=B0=A8=EC=88=9C=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=ED=95=98=EC=97=AC=20=EA=B0=80=EC=9E=A5=20=EC=B5=9C=EA=B7=BC=20?= =?UTF-8?q?=EC=A3=BC=EB=AC=B8=EC=A0=95=EB=B3=B4=EB=A5=BC=20=EA=B0=80?= =?UTF-8?q?=EC=A0=B8=EC=98=A4=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 --- .../kr/co/vividnext/sodalive/content/order/OrderRepository.kt | 1 + 1 file changed, 1 insertion(+) 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 d66760d..d460d19 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 @@ -291,6 +291,7 @@ class OrderQueryRepositoryImpl( order.audioContent.id.eq(contentId), order.createdAt.after(createdAt) ) + .orderBy(order.id.desc()) .fetchFirst() } } From 0f48c71837c062ca5f24235f5117590b198576f5 Mon Sep 17 00:00:00 2001 From: Klaus Date: Mon, 19 May 2025 11:43:24 +0900 Subject: [PATCH 10/20] =?UTF-8?q?fix:=20transactionTemplate=20=EC=9D=84=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9=ED=95=98=EC=97=AC=20=ED=9A=9F=EC=88=98?= =?UTF-8?q?=EA=B0=80=20=EC=9E=98=EB=AA=BB=20=ED=8C=90=EB=8B=A8=EB=90=98?= =?UTF-8?q?=EB=8A=94=20=EA=B2=BD=EC=9A=B0=20=EC=B5=9C=EC=86=8C=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sodalive/useraction/UserActionService.kt | 170 ++++++++++-------- 1 file changed, 99 insertions(+), 71 deletions(-) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/useraction/UserActionService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/useraction/UserActionService.kt index 210b47d..240c8a7 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/useraction/UserActionService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/useraction/UserActionService.kt @@ -10,7 +10,9 @@ import kr.co.vividnext.sodalive.point.MemberPointRepository import kr.co.vividnext.sodalive.point.PointGrantLog import kr.co.vividnext.sodalive.point.PointGrantLogRepository import kr.co.vividnext.sodalive.point.PointRewardPolicyRepository +import org.slf4j.LoggerFactory import org.springframework.stereotype.Service +import org.springframework.transaction.support.TransactionTemplate import java.time.LocalDateTime @Service @@ -20,6 +22,7 @@ class UserActionService( private val policyRepository: PointRewardPolicyRepository, private val grantLogRepository: PointGrantLogRepository, private val memberPointRepository: MemberPointRepository, + private val transactionTemplate: TransactionTemplate, private val fcmService: FcmService ) { @@ -34,82 +37,107 @@ class UserActionService( ) { coroutineScope.launch { val now = LocalDateTime.now() - repository.save(UserActionLog(memberId, actionType)) + transactionTemplate.execute { + repository.save(UserActionLog(memberId, actionType)) + repository.flush() + } - val policy = policyRepository.findByActionTypeAndIsActiveTrue(actionType, now) - if (policy != null) { - val policyType = policy.policyType - val todayAt15 = now.toLocalDate().atTime(15, 0) - val policyTypeDailyStartDate = if (now.toLocalTime().isBefore(todayAt15.toLocalTime())) { - now.toLocalDate().minusDays(1).atTime(15, 0) - } else { - todayAt15 - } - - val order = if (contentId != null) { - orderRepository.findByMemberIdAndContentId( - memberId = memberId, - contentId = contentId, - createdAt = if (policyType == PolicyType.DAILY) policyTypeDailyStartDate else policy.startDate - ) - } else { - null - } - if (actionType == ActionType.ORDER_CONTENT_COMMENT && order == null) return@launch - - val actionCount = repository.countByMemberIdAndActionTypeAndCreatedAtBetween( - memberId = memberId, - actionType = actionType, - startDate = if (policyType == PolicyType.DAILY) policyTypeDailyStartDate else policy.startDate, - endDate = policy.endDate ?: now - ) - if (actionCount < policy.threshold) return@launch - - val grantedCount = grantLogRepository.countByMemberIdAndPolicyIdAndStartDate( - memberId, - policy.id!!, - startDate = if (policyType == PolicyType.DAILY) policyTypeDailyStartDate else policy.startDate - ) - if (grantedCount >= policy.availableCount) return@launch - - grantLogRepository.save( - PointGrantLog( - memberId = memberId, - point = if (actionType == ActionType.ORDER_CONTENT_COMMENT && order != null) { - order.can + try { + transactionTemplate.execute { + val policy = policyRepository.findByActionTypeAndIsActiveTrue(actionType, now) + if (policy != null) { + val policyType = policy.policyType + val todayAt15 = now.toLocalDate().atTime(15, 0) + val policyTypeDailyStartDate = if (now.toLocalTime().isBefore(todayAt15.toLocalTime())) { + now.toLocalDate().minusDays(1).atTime(15, 0) } else { - policy.pointAmount - }, - actionType = actionType, - policyId = policy.id!!, - orderId = order?.id - ) - ) - - memberPointRepository.save( - MemberPoint( - memberId = memberId, - point = if (actionType == ActionType.ORDER_CONTENT_COMMENT && order != null) { - order.can - } else { - policy.pointAmount - }, - actionType = actionType, - expiresAt = now.plusDays(3) - ) - ) - - if (pushTokenList.isNotEmpty()) { - fcmService.sendPointGranted( - pushTokenList, - if (actionType == ActionType.ORDER_CONTENT_COMMENT && order != null) { - order.can - } else { - policy.pointAmount + todayAt15 } - ) + + val order = if (contentId != null) { + orderRepository.findByMemberIdAndContentId( + memberId = memberId, + contentId = contentId, + createdAt = if (policyType == PolicyType.DAILY) { + policyTypeDailyStartDate + } else { + policy.startDate + } + ) + } else { + null + } + if (actionType == ActionType.ORDER_CONTENT_COMMENT && order == null) return@execute + + val actionCount = repository.countByMemberIdAndActionTypeAndCreatedAtBetween( + memberId = memberId, + actionType = actionType, + startDate = if (policyType == PolicyType.DAILY) { + policyTypeDailyStartDate + } else { + policy.startDate + }, + endDate = policy.endDate ?: now + ) + if (actionCount < policy.threshold) return@execute + + val grantedCount = grantLogRepository.countByMemberIdAndPolicyIdAndStartDate( + memberId, + policy.id!!, + startDate = if (policyType == PolicyType.DAILY) { + policyTypeDailyStartDate + } else { + policy.startDate + } + ) + if (grantedCount >= policy.availableCount) return@execute + + grantLogRepository.save( + PointGrantLog( + memberId = memberId, + point = if (actionType == ActionType.ORDER_CONTENT_COMMENT && order != null) { + order.can + } else { + policy.pointAmount + }, + actionType = actionType, + policyId = policy.id!!, + orderId = order?.id + ) + ) + + memberPointRepository.save( + MemberPoint( + memberId = memberId, + point = if (actionType == ActionType.ORDER_CONTENT_COMMENT && order != null) { + order.can + } else { + policy.pointAmount + }, + actionType = actionType, + expiresAt = now.plusDays(3) + ) + ) + + if (pushTokenList.isNotEmpty()) { + fcmService.sendPointGranted( + pushTokenList, + if (actionType == ActionType.ORDER_CONTENT_COMMENT && order != null) { + order.can + } else { + policy.pointAmount + } + ) + } + } } + } catch (e: Exception) { + logger.warn("포인트 지급 또는 알림 실패: ${e.message}") } } } + + companion object { + private val logger = LoggerFactory.getLogger(UserActionService::class.java) + } } From fe84292483b72c7e422775acd8cdf61b1f1974f1 Mon Sep 17 00:00:00 2001 From: Klaus Date: Mon, 19 May 2025 14:43:50 +0900 Subject: [PATCH 11/20] =?UTF-8?q?fix:=20=ED=8F=AC=EC=9D=B8=ED=8A=B8=20?= =?UTF-8?q?=EC=A7=80=EA=B8=89=20=EC=9A=94=EC=86=8C=20=EA=B3=84=EC=82=B0?= =?UTF-8?q?=EC=8B=9C=20=EC=A0=95=EC=B1=85=20=EC=8B=9C=EC=9E=91=20=EB=82=A0?= =?UTF-8?q?=EC=A7=9C=20=EC=9D=B4=ED=9B=84=EC=9D=98=20=EC=9C=A0=EC=A0=80=20?= =?UTF-8?q?=ED=96=89=EB=8F=99=EB=93=A4=EB=A7=8C=20=EB=B0=98=EC=98=81?= =?UTF-8?q?=ED=95=98=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 --- .../co/vividnext/sodalive/useraction/UserActionService.kt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/useraction/UserActionService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/useraction/UserActionService.kt index 240c8a7..0efefc5 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/useraction/UserActionService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/useraction/UserActionService.kt @@ -54,11 +54,13 @@ class UserActionService( todayAt15 } + val isValidPolicyTypeDailyAndDailyStartDateAfterPolicyStartDate = + policyType == PolicyType.DAILY && policyTypeDailyStartDate >= policy.startDate val order = if (contentId != null) { orderRepository.findByMemberIdAndContentId( memberId = memberId, contentId = contentId, - createdAt = if (policyType == PolicyType.DAILY) { + createdAt = if (isValidPolicyTypeDailyAndDailyStartDateAfterPolicyStartDate) { policyTypeDailyStartDate } else { policy.startDate @@ -72,7 +74,7 @@ class UserActionService( val actionCount = repository.countByMemberIdAndActionTypeAndCreatedAtBetween( memberId = memberId, actionType = actionType, - startDate = if (policyType == PolicyType.DAILY) { + startDate = if (isValidPolicyTypeDailyAndDailyStartDateAfterPolicyStartDate) { policyTypeDailyStartDate } else { policy.startDate @@ -84,7 +86,7 @@ class UserActionService( val grantedCount = grantLogRepository.countByMemberIdAndPolicyIdAndStartDate( memberId, policy.id!!, - startDate = if (policyType == PolicyType.DAILY) { + startDate = if (isValidPolicyTypeDailyAndDailyStartDateAfterPolicyStartDate) { policyTypeDailyStartDate } else { policy.startDate From 6df043dfac15f798469d768409773c60dfe1c959 Mon Sep 17 00:00:00 2001 From: Klaus Date: Mon, 19 May 2025 15:05:31 +0900 Subject: [PATCH 12/20] =?UTF-8?q?fix:=20=EC=BD=98=ED=85=90=EC=B8=A0=20?= =?UTF-8?q?=EB=8C=93=EA=B8=80=20=EC=9E=91=EC=84=B1=EC=8B=9C=20=EC=9C=A0?= =?UTF-8?q?=EC=A0=80=20=ED=96=89=EB=8F=99=20=EB=8D=B0=EC=9D=B4=ED=84=B0?= =?UTF-8?q?=EC=97=90=20=EB=8C=93=EA=B8=80=20ID=EB=A5=BC=20=EA=B0=99?= =?UTF-8?q?=EC=9D=B4=20=EA=B8=B0=EB=A1=9D=ED=95=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../content/comment/AudioContentCommentController.kt | 4 +++- .../content/comment/AudioContentCommentService.kt | 6 ++++-- .../kr/co/vividnext/sodalive/useraction/UserActionLog.kt | 3 ++- .../vividnext/sodalive/useraction/UserActionService.kt | 9 ++++++++- 4 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/comment/AudioContentCommentController.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/comment/AudioContentCommentController.kt index b3e4ad3..6adb6cc 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/comment/AudioContentCommentController.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/comment/AudioContentCommentController.kt @@ -29,7 +29,7 @@ class AudioContentCommentController( ) = run { if (member == null) throw SodaException("로그인 정보를 확인해주세요.") - service.registerComment( + val commentId = service.registerComment( comment = request.comment, audioContentId = request.contentId, parentId = request.parentId, @@ -44,6 +44,7 @@ class AudioContentCommentController( userActionService.recordAction( memberId = member.id!!, actionType = ActionType.CONTENT_COMMENT, + commentId = commentId, pushTokenList = pushTokenList ) @@ -51,6 +52,7 @@ class AudioContentCommentController( memberId = member.id!!, actionType = ActionType.ORDER_CONTENT_COMMENT, contentId = request.contentId, + commentId = commentId, pushTokenList = pushTokenList ) } catch (_: Exception) { diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/comment/AudioContentCommentService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/comment/AudioContentCommentService.kt index 9d885c1..90b9be7 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/comment/AudioContentCommentService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/comment/AudioContentCommentService.kt @@ -33,7 +33,7 @@ class AudioContentCommentService( audioContentId: Long, parentId: Long? = null, isSecret: Boolean = false - ) { + ): Long { val audioContent = audioContentRepository.findByIdOrNull(id = audioContentId) ?: throw SodaException("잘못된 콘텐츠 입니다.\n다시 시도해 주세요.") @@ -64,7 +64,7 @@ class AudioContentCommentService( audioContentComment.parent = parent } - repository.save(audioContentComment) + val savedContentComment = repository.save(audioContentComment) applicationEventPublisher.publishEvent( FcmEvent( @@ -84,6 +84,8 @@ class AudioContentCommentService( myMemberId = member.id ) ) + + return savedContentComment.id!! } @Transactional diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/useraction/UserActionLog.kt b/src/main/kotlin/kr/co/vividnext/sodalive/useraction/UserActionLog.kt index fc92561..c7ae5cc 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/useraction/UserActionLog.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/useraction/UserActionLog.kt @@ -9,5 +9,6 @@ import javax.persistence.Enumerated data class UserActionLog( val memberId: Long, @Enumerated(EnumType.STRING) - val actionType: ActionType + val actionType: ActionType, + val commentId: Long? = null ) : BaseEntity() diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/useraction/UserActionService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/useraction/UserActionService.kt index 0efefc5..8e8d913 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/useraction/UserActionService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/useraction/UserActionService.kt @@ -33,12 +33,19 @@ class UserActionService( memberId: Long, actionType: ActionType, contentId: Long? = null, + commentId: Long? = null, pushTokenList: List = emptyList() ) { coroutineScope.launch { val now = LocalDateTime.now() transactionTemplate.execute { - repository.save(UserActionLog(memberId, actionType)) + repository.save( + UserActionLog( + memberId = memberId, + actionType = actionType, + commentId = commentId + ) + ) repository.flush() } From aa23d6d50f042abdad6824e9df57cf73f18c5563 Mon Sep 17 00:00:00 2001 From: Klaus Date: Mon, 19 May 2025 15:08:21 +0900 Subject: [PATCH 13/20] =?UTF-8?q?fix:=20=EC=A3=BC=EB=AC=B8=ED=95=9C=20?= =?UTF-8?q?=EC=BD=98=ED=85=90=EC=B8=A0=EC=97=90=20=EB=8C=93=EA=B8=80=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1=20=EC=9D=B4=EB=B2=A4=ED=8A=B8=20-=20?= =?UTF-8?q?=ED=8F=AC=EC=9D=B8=ED=8A=B8=20=EB=B0=9B=EC=9D=80=20=ED=98=84?= =?UTF-8?q?=ED=99=A9=EC=9D=84=20=EC=A1=B0=ED=9A=8C=ED=95=A0=20=EB=95=8C=20?= =?UTF-8?q?=EC=A3=BC=EB=AC=B8=20ID=EB=A5=BC=20=EA=B0=99=EC=9D=B4=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=ED=95=98=EB=8F=84=EB=A1=9D=20=EB=A7=8C?= =?UTF-8?q?=EB=93=A4=EC=96=B4=EC=84=9C=20=EC=A3=BC=EB=AC=B8=ED=95=9C=20?= =?UTF-8?q?=EC=BD=98=ED=85=90=EC=B8=A0=EC=97=90=20=EB=8C=93=EA=B8=80=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1=20=EC=9D=B4=EB=B2=A4=ED=8A=B8=EC=9D=98=20?= =?UTF-8?q?=EA=B2=BD=EC=9A=B0=20=EC=A3=BC=EB=AC=B8=EB=B3=84=EB=A1=9C=20?= =?UTF-8?q?=EC=B0=B8=EC=97=AC=ED=95=A0=20=EC=88=98=20=EC=9E=88=EB=8F=84?= =?UTF-8?q?=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/point/PointGrantLogRepository.kt | 28 ++++++++++++++----- .../sodalive/useraction/UserActionService.kt | 3 +- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/point/PointGrantLogRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/point/PointGrantLogRepository.kt index 8862ab6..52a57c4 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/point/PointGrantLogRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/point/PointGrantLogRepository.kt @@ -8,21 +8,35 @@ import java.time.LocalDateTime interface PointGrantLogRepository : JpaRepository, PointGrantLogQueryRepository interface PointGrantLogQueryRepository { - fun countByMemberIdAndPolicyIdAndStartDate(memberId: Long, policyId: Long, startDate: LocalDateTime): Int + fun countByMemberIdAndPolicyIdAndStartDate( + memberId: Long, + policyId: Long, + startDate: LocalDateTime, + orderId: Long? = null + ): Int } class PointGrantLogQueryRepositoryImpl( private val queryFactory: JPAQueryFactory ) : PointGrantLogQueryRepository { - override fun countByMemberIdAndPolicyIdAndStartDate(memberId: Long, policyId: Long, startDate: LocalDateTime): Int { + override fun countByMemberIdAndPolicyIdAndStartDate( + memberId: Long, + policyId: Long, + startDate: LocalDateTime, + orderId: Long? + ): Int { + var where = pointGrantLog.memberId.eq(memberId) + .and(pointGrantLog.policyId.eq(policyId)) + .and(pointGrantLog.createdAt.goe(startDate)) + + if (orderId != null) { + where = where.and(pointGrantLog.orderId.eq(orderId)) + } + return queryFactory .select(pointGrantLog.id) .from(pointGrantLog) - .where( - pointGrantLog.memberId.eq(memberId), - pointGrantLog.policyId.eq(policyId), - pointGrantLog.createdAt.goe(startDate) - ) + .where(where) .fetch() .size } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/useraction/UserActionService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/useraction/UserActionService.kt index 8e8d913..a0e92f5 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/useraction/UserActionService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/useraction/UserActionService.kt @@ -97,7 +97,8 @@ class UserActionService( policyTypeDailyStartDate } else { policy.startDate - } + }, + orderId = order?.id ) if (grantedCount >= policy.availableCount) return@execute From f36010fefa20c9ed291a591760c8f68c21c7bb59 Mon Sep 17 00:00:00 2001 From: Klaus Date: Mon, 19 May 2025 15:17:44 +0900 Subject: [PATCH 14/20] =?UTF-8?q?fix:=20=EC=9C=A0=EC=A0=80=20=ED=96=89?= =?UTF-8?q?=EB=8F=99=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20-=20commentId=20->=20?= =?UTF-8?q?contentCommentId=20=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kotlin/kr/co/vividnext/sodalive/useraction/UserActionLog.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/useraction/UserActionLog.kt b/src/main/kotlin/kr/co/vividnext/sodalive/useraction/UserActionLog.kt index c7ae5cc..2261cc6 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/useraction/UserActionLog.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/useraction/UserActionLog.kt @@ -10,5 +10,5 @@ data class UserActionLog( val memberId: Long, @Enumerated(EnumType.STRING) val actionType: ActionType, - val commentId: Long? = null + val contentCommentId: Long? = null ) : BaseEntity() From b102241efd757371192a900211797e4f17c38970 Mon Sep 17 00:00:00 2001 From: Klaus Date: Mon, 19 May 2025 15:25:17 +0900 Subject: [PATCH 15/20] =?UTF-8?q?fix:=20=EC=9C=A0=EC=A0=80=20=ED=96=89?= =?UTF-8?q?=EB=8F=99=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20-=20commentId=20->=20?= =?UTF-8?q?contentCommentId=20=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sodalive/content/comment/AudioContentCommentController.kt | 4 ++-- .../kr/co/vividnext/sodalive/useraction/UserActionService.kt | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/comment/AudioContentCommentController.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/comment/AudioContentCommentController.kt index 6adb6cc..f603fd7 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/comment/AudioContentCommentController.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/comment/AudioContentCommentController.kt @@ -44,7 +44,7 @@ class AudioContentCommentController( userActionService.recordAction( memberId = member.id!!, actionType = ActionType.CONTENT_COMMENT, - commentId = commentId, + contentCommentId = commentId, pushTokenList = pushTokenList ) @@ -52,7 +52,7 @@ class AudioContentCommentController( memberId = member.id!!, actionType = ActionType.ORDER_CONTENT_COMMENT, contentId = request.contentId, - commentId = commentId, + contentCommentId = commentId, pushTokenList = pushTokenList ) } catch (_: Exception) { diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/useraction/UserActionService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/useraction/UserActionService.kt index a0e92f5..4ef7092 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/useraction/UserActionService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/useraction/UserActionService.kt @@ -33,7 +33,7 @@ class UserActionService( memberId: Long, actionType: ActionType, contentId: Long? = null, - commentId: Long? = null, + contentCommentId: Long? = null, pushTokenList: List = emptyList() ) { coroutineScope.launch { @@ -43,7 +43,7 @@ class UserActionService( UserActionLog( memberId = memberId, actionType = actionType, - commentId = commentId + contentCommentId = contentCommentId ) ) repository.flush() From 36b8e8169ebaed357b02ceb497bb21b627d8be55 Mon Sep 17 00:00:00 2001 From: Klaus Date: Mon, 19 May 2025 16:27:58 +0900 Subject: [PATCH 16/20] =?UTF-8?q?fix:=20=EC=9C=A0=EC=A0=80=20=ED=96=89?= =?UTF-8?q?=EB=8F=99=20=EB=8D=B0=EC=9D=B4=ED=84=B0=EC=97=90=20=EB=94=B0?= =?UTF-8?q?=EB=A5=B8=20=ED=8F=AC=EC=9D=B8=ED=8A=B8=20=EC=A7=80=EA=B8=89=20?= =?UTF-8?q?-=20=EC=9C=A0=EC=A0=80=EA=B0=80=20=EC=A7=80=EA=B8=89=20?= =?UTF-8?q?=EB=B0=9B=EC=9D=84=20=ED=8F=AC=EC=9D=B8=ED=8A=B8=EA=B0=80=200?= =?UTF-8?q?=20=EC=9D=B4=EC=83=81=EC=9D=B8=20=EA=B2=BD=EC=9A=B0=EC=97=90?= =?UTF-8?q?=EB=A7=8C=20=ED=8F=AC=EC=9D=B8=ED=8A=B8=20=EC=A7=80=EA=B8=89=20?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EB=A5=BC=20=EB=82=A8=EA=B8=B0=EA=B3=A0=20?= =?UTF-8?q?=ED=91=B8=EC=8B=9C=20=EB=B0=9C=EC=86=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sodalive/useraction/UserActionService.kt | 60 +++++++++---------- 1 file changed, 28 insertions(+), 32 deletions(-) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/useraction/UserActionService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/useraction/UserActionService.kt index 4ef7092..11697a6 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/useraction/UserActionService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/useraction/UserActionService.kt @@ -102,42 +102,38 @@ class UserActionService( ) if (grantedCount >= policy.availableCount) return@execute - grantLogRepository.save( - PointGrantLog( - memberId = memberId, - point = if (actionType == ActionType.ORDER_CONTENT_COMMENT && order != null) { - order.can - } else { - policy.pointAmount - }, - actionType = actionType, - policyId = policy.id!!, - orderId = order?.id - ) - ) + val point = if (actionType == ActionType.ORDER_CONTENT_COMMENT && order != null) { + order.can + } else { + policy.pointAmount + } - memberPointRepository.save( - MemberPoint( - memberId = memberId, - point = if (actionType == ActionType.ORDER_CONTENT_COMMENT && order != null) { - order.can - } else { - policy.pointAmount - }, - actionType = actionType, - expiresAt = now.plusDays(3) + if (point > 0) { + grantLogRepository.save( + PointGrantLog( + memberId = memberId, + point = point, + actionType = actionType, + policyId = policy.id!!, + orderId = order?.id + ) ) - ) - if (pushTokenList.isNotEmpty()) { - fcmService.sendPointGranted( - pushTokenList, - if (actionType == ActionType.ORDER_CONTENT_COMMENT && order != null) { - order.can - } else { - policy.pointAmount - } + memberPointRepository.save( + MemberPoint( + memberId = memberId, + point = point, + actionType = actionType, + expiresAt = now.plusDays(3) + ) ) + + if (pushTokenList.isNotEmpty()) { + fcmService.sendPointGranted( + pushTokenList, + point + ) + } } } } From 56542a7bf1f2412989276f44b19fe354773ef433 Mon Sep 17 00:00:00 2001 From: Klaus Date: Mon, 19 May 2025 20:49:16 +0900 Subject: [PATCH 17/20] =?UTF-8?q?fix:=20=ED=8F=AC=EC=9D=B8=ED=8A=B8=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9=EB=82=B4=EC=97=AD=20-=20=ED=8F=AC=EC=9D=B8?= =?UTF-8?q?=ED=8A=B8=EB=A5=BC=20=EC=96=B4=EB=94=94=EC=97=90=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=ED=96=88=EB=8A=94=EC=A7=80=20=EC=95=8C=EA=B8=B0=20?= =?UTF-8?q?=EC=9C=84=ED=95=B4=20=ED=8F=AC=EC=9D=B8=ED=8A=B8=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=EB=82=B4=EC=97=AD=20=EC=A0=80=EC=9E=A5=EC=8B=9C=20ord?= =?UTF-8?q?erId=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kr/co/vividnext/sodalive/content/order/OrderService.kt | 2 +- .../kr/co/vividnext/sodalive/point/PointUsageService.kt | 4 ++-- src/main/kotlin/kr/co/vividnext/sodalive/point/UsePoint.kt | 3 ++- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/order/OrderService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/order/OrderService.kt index 255da15..68de04a 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/order/OrderService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/order/OrderService.kt @@ -44,7 +44,7 @@ class OrderService( } val usedPoint = if (order.type == OrderType.RENTAL && content.isPointAvailable) { - pointUsageService.usePoint(member.id!!, order.can) + pointUsageService.usePoint(member.id!!, order.can, orderId = order.id) } else { 0 } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/point/PointUsageService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/point/PointUsageService.kt index ed02ca1..4aadd65 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/point/PointUsageService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/point/PointUsageService.kt @@ -8,7 +8,7 @@ class PointUsageService( private val memberPointRepository: MemberPointRepository, private val usePointRepository: UsePointRepository ) { - fun usePoint(memberId: Long, contentPrice: Int): Int { + fun usePoint(memberId: Long, contentPrice: Int, orderId: Long?): Int { val now = LocalDateTime.now() val maxUsablePoint = contentPrice * 10 @@ -33,7 +33,7 @@ class PointUsageService( if (used > 0) { memberPointRepository.saveAll(points) - usePointRepository.save(UsePoint(memberId = memberId, amount = used)) + usePointRepository.save(UsePoint(memberId = memberId, amount = used, orderId = orderId)) } return used diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/point/UsePoint.kt b/src/main/kotlin/kr/co/vividnext/sodalive/point/UsePoint.kt index afbeb7e..a2ba533 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/point/UsePoint.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/point/UsePoint.kt @@ -6,5 +6,6 @@ import javax.persistence.Entity @Entity data class UsePoint( val memberId: Long, - val amount: Int + val amount: Int, + val orderId: Long? = null ) : BaseEntity() From d6db862c9d6259ef3894c8954d27f9ec3e043283 Mon Sep 17 00:00:00 2001 From: Klaus Date: Mon, 19 May 2025 21:38:24 +0900 Subject: [PATCH 18/20] =?UTF-8?q?fix:=20=ED=8F=AC=EC=9D=B8=ED=8A=B8=20?= =?UTF-8?q?=EB=82=B4=EC=97=AD=20=EB=A6=AC=EC=8A=A4=ED=8A=B8=20-=20?= =?UTF-8?q?=EC=9C=A0=EC=A0=80=EC=9D=98=20=ED=8F=AC=EC=9D=B8=ED=8A=B8=20?= =?UTF-8?q?=EB=B3=B4=EC=83=81=EB=82=B4=EC=97=AD,=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=EB=82=B4=EC=97=AD=20API=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../point/GetPointRewardStatusResponse.kt | 9 ++++ .../sodalive/point/GetPointStatusResponse.kt | 3 ++ .../point/GetPointUseStatusResponse.kt | 9 ++++ .../sodalive/point/PointController.kt | 49 +++++++++++++++++++ .../sodalive/point/PointGrantLogRepository.kt | 30 ++++++++++++ .../vividnext/sodalive/point/PointService.kt | 29 +++++++++++ .../sodalive/point/UsePointRepository.kt | 44 ++++++++++++++++- 7 files changed, 172 insertions(+), 1 deletion(-) create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/point/GetPointRewardStatusResponse.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/point/GetPointStatusResponse.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/point/GetPointUseStatusResponse.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/point/PointController.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/point/PointService.kt diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/point/GetPointRewardStatusResponse.kt b/src/main/kotlin/kr/co/vividnext/sodalive/point/GetPointRewardStatusResponse.kt new file mode 100644 index 0000000..050f7a7 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/point/GetPointRewardStatusResponse.kt @@ -0,0 +1,9 @@ +package kr.co.vividnext.sodalive.point + +import com.querydsl.core.annotations.QueryProjection + +data class GetPointRewardStatusResponse @QueryProjection constructor( + val rewardPoint: String, + val date: String, + val method: String +) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/point/GetPointStatusResponse.kt b/src/main/kotlin/kr/co/vividnext/sodalive/point/GetPointStatusResponse.kt new file mode 100644 index 0000000..3f60f3c --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/point/GetPointStatusResponse.kt @@ -0,0 +1,3 @@ +package kr.co.vividnext.sodalive.point + +data class GetPointStatusResponse(val point: Int) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/point/GetPointUseStatusResponse.kt b/src/main/kotlin/kr/co/vividnext/sodalive/point/GetPointUseStatusResponse.kt new file mode 100644 index 0000000..bffec5f --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/point/GetPointUseStatusResponse.kt @@ -0,0 +1,9 @@ +package kr.co.vividnext.sodalive.point + +import com.querydsl.core.annotations.QueryProjection + +data class GetPointUseStatusResponse @QueryProjection constructor( + val title: String, + val date: String, + val point: Int +) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/point/PointController.kt b/src/main/kotlin/kr/co/vividnext/sodalive/point/PointController.kt new file mode 100644 index 0000000..04d20c0 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/point/PointController.kt @@ -0,0 +1,49 @@ +package kr.co.vividnext.sodalive.point + +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.GetMapping +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RequestParam +import org.springframework.web.bind.annotation.RestController + +@RestController +@RequestMapping("/point") +class PointController(private val service: PointService) { + @GetMapping("/status") + fun getPointStatus( + @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? + ) = run { + if (member == null) { + throw SodaException("로그인 정보를 확인해주세요.") + } + + ApiResponse.ok(service.getPointStatus(member)) + } + + @GetMapping("/status/use") + fun getPointUseStatus( + @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?, + @RequestParam("timezone") timezone: String + ) = run { + if (member == null) { + throw SodaException("로그인 정보를 확인해주세요.") + } + + ApiResponse.ok(service.getPointUseStatus(member, timezone)) + } + + @GetMapping("/status/reward") + fun getPointRewardStatus( + @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?, + @RequestParam("timezone") timezone: String + ) = run { + if (member == null) { + throw SodaException("로그인 정보를 확인해주세요.") + } + + ApiResponse.ok(service.getPointRewardStatus(member, timezone)) + } +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/point/PointGrantLogRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/point/PointGrantLogRepository.kt index 52a57c4..3ec8c3a 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/point/PointGrantLogRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/point/PointGrantLogRepository.kt @@ -1,7 +1,9 @@ package kr.co.vividnext.sodalive.point +import com.querydsl.core.types.dsl.Expressions import com.querydsl.jpa.impl.JPAQueryFactory import kr.co.vividnext.sodalive.point.QPointGrantLog.pointGrantLog +import kr.co.vividnext.sodalive.point.QPointRewardPolicy.pointRewardPolicy import org.springframework.data.jpa.repository.JpaRepository import java.time.LocalDateTime @@ -14,6 +16,8 @@ interface PointGrantLogQueryRepository { startDate: LocalDateTime, orderId: Long? = null ): Int + + fun getPointRewardStatusByMemberId(memberId: Long, timezone: String): List } class PointGrantLogQueryRepositoryImpl( @@ -40,4 +44,30 @@ class PointGrantLogQueryRepositoryImpl( .fetch() .size } + + override fun getPointRewardStatusByMemberId(memberId: Long, timezone: String): List { + val formattedDate = Expressions.stringTemplate( + "DATE_FORMAT({0}, {1})", + Expressions.dateTimeTemplate( + LocalDateTime::class.java, + "CONVERT_TZ({0},{1},{2})", + pointGrantLog.createdAt, + "UTC", + timezone + ), + "%Y.%m.%d | %H:%i:%s" + ) + + return queryFactory + .select( + QGetPointRewardStatusResponse( + pointGrantLog.point.stringValue().concat(" 포인트"), + formattedDate, + pointRewardPolicy.title + ) + ) + .from(pointGrantLog) + .innerJoin(pointRewardPolicy).on(pointGrantLog.policyId.eq(pointRewardPolicy.id)) + .fetch() + } } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/point/PointService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/point/PointService.kt new file mode 100644 index 0000000..92ec123 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/point/PointService.kt @@ -0,0 +1,29 @@ +package kr.co.vividnext.sodalive.point + +import kr.co.vividnext.sodalive.member.Member +import org.springframework.stereotype.Service +import java.time.LocalDateTime + +@Service +class PointService( + private val pointGrantLogRepository: PointGrantLogRepository, + private val memberPointRepository: MemberPointRepository, + private val usePointRepository: UsePointRepository +) { + fun getPointStatus(member: Member): GetPointStatusResponse { + return GetPointStatusResponse( + point = memberPointRepository.findByMemberIdAndExpiresAtAfterOrderByExpiresAtAsc( + memberId = member.id!!, + expiresAt = LocalDateTime.now() + ).sumOf { it.point } + ) + } + + fun getPointUseStatus(member: Member, timezone: String): List { + return usePointRepository.getPointUseStatusByMemberId(member.id!!, timezone) + } + + fun getPointRewardStatus(member: Member, timezone: String): List { + return pointGrantLogRepository.getPointRewardStatusByMemberId(member.id!!, timezone) + } +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/point/UsePointRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/point/UsePointRepository.kt index 49a5fcc..076b4d4 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/point/UsePointRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/point/UsePointRepository.kt @@ -1,5 +1,47 @@ package kr.co.vividnext.sodalive.point +import com.querydsl.core.types.dsl.Expressions +import com.querydsl.jpa.impl.JPAQueryFactory +import kr.co.vividnext.sodalive.content.QAudioContent.audioContent +import kr.co.vividnext.sodalive.content.order.QOrder.order +import kr.co.vividnext.sodalive.point.QUsePoint.usePoint import org.springframework.data.jpa.repository.JpaRepository +import java.time.LocalDateTime -interface UsePointRepository : JpaRepository +interface UsePointRepository : JpaRepository, UsePointQueryRepository + +interface UsePointQueryRepository { + fun getPointUseStatusByMemberId(memberId: Long, timezone: String): List +} + +class UsePointQueryRepositoryImpl( + private val queryFactory: JPAQueryFactory +) : UsePointQueryRepository { + override fun getPointUseStatusByMemberId(memberId: Long, timezone: String): List { + val formattedDate = Expressions.stringTemplate( + "DATE_FORMAT({0}, {1})", + Expressions.dateTimeTemplate( + LocalDateTime::class.java, + "CONVERT_TZ({0},{1},{2})", + usePoint.createdAt, + "UTC", + timezone + ), + "%Y.%m.%d | %H:%i:%s" + ) + + return queryFactory + .select( + QGetPointUseStatusResponse( + audioContent.title.prepend("[콘텐츠 대여] "), + formattedDate, + usePoint.amount + ) + ) + .from(usePoint) + .innerJoin(order).on(usePoint.orderId.eq(order.id)) + .innerJoin(audioContent).on(order.audioContent.id.eq(audioContent.id)) + .where(usePoint.memberId.eq(memberId)) + .fetch() + } +} From a36d9f02d8bf6cd96384aba46b77f15a5e21b727 Mon Sep 17 00:00:00 2001 From: Klaus Date: Tue, 20 May 2025 00:14:57 +0900 Subject: [PATCH 19/20] =?UTF-8?q?fix:=20=ED=8F=AC=EC=9D=B8=ED=8A=B8=20?= =?UTF-8?q?=EB=82=B4=EC=97=AD=20=EB=A6=AC=EC=8A=A4=ED=8A=B8=20-=20?= =?UTF-8?q?=EC=9C=A0=EC=A0=80=EC=9D=98=20=ED=8F=AC=EC=9D=B8=ED=8A=B8=20?= =?UTF-8?q?=EB=B3=B4=EC=83=81=EB=82=B4=EC=97=AD,=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=EB=82=B4=EC=97=AD=20id=20=EB=82=B4=EB=A6=BC=EC=B0=A8=EC=88=9C?= =?UTF-8?q?=20=EC=A0=95=EB=A0=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kr/co/vividnext/sodalive/point/PointGrantLogRepository.kt | 2 ++ .../kotlin/kr/co/vividnext/sodalive/point/UsePointRepository.kt | 1 + 2 files changed, 3 insertions(+) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/point/PointGrantLogRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/point/PointGrantLogRepository.kt index 3ec8c3a..6edc73a 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/point/PointGrantLogRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/point/PointGrantLogRepository.kt @@ -68,6 +68,8 @@ class PointGrantLogQueryRepositoryImpl( ) .from(pointGrantLog) .innerJoin(pointRewardPolicy).on(pointGrantLog.policyId.eq(pointRewardPolicy.id)) + .where(pointGrantLog.memberId.eq(memberId)) + .orderBy(pointGrantLog.id.desc()) .fetch() } } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/point/UsePointRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/point/UsePointRepository.kt index 076b4d4..054d175 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/point/UsePointRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/point/UsePointRepository.kt @@ -42,6 +42,7 @@ class UsePointQueryRepositoryImpl( .innerJoin(order).on(usePoint.orderId.eq(order.id)) .innerJoin(audioContent).on(order.audioContent.id.eq(audioContent.id)) .where(usePoint.memberId.eq(memberId)) + .orderBy(usePoint.id.desc()) .fetch() } } From d3ec13e6c04b99cc70e5a003618745ba96b81361 Mon Sep 17 00:00:00 2001 From: Klaus Date: Tue, 20 May 2025 00:51:04 +0900 Subject: [PATCH 20/20] =?UTF-8?q?fix:=20=EC=9C=A0=EC=A0=80=20=ED=96=89?= =?UTF-8?q?=EB=8F=99=20=EB=8D=B0=EC=9D=B4=ED=84=B0=EC=97=90=20=EB=94=B0?= =?UTF-8?q?=EB=A5=B8=20=ED=8F=AC=EC=9D=B8=ED=8A=B8=20=EC=A7=80=EA=B8=89=20?= =?UTF-8?q?-=20=EB=B3=B8=EC=9D=B8=EC=9D=B8=EC=A6=9D=EC=9D=84=20=ED=95=9C?= =?UTF-8?q?=20=EC=9C=A0=EC=A0=80=EB=A7=8C=20=ED=8F=AC=EC=9D=B8=ED=8A=B8=20?= =?UTF-8?q?=EC=A0=95=EC=B1=85=EC=97=90=20=EB=94=B0=EB=9D=BC=20=ED=8F=AC?= =?UTF-8?q?=EC=9D=B8=ED=8A=B8=EB=A5=BC=20=EC=A7=80=EA=B8=89=ED=95=98?= =?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 --- .../comment/AudioContentCommentController.kt | 2 + .../sodalive/member/MemberController.kt | 3 + .../sodalive/member/auth/AuthController.kt | 1 + .../useraction/UserActionController.kt | 1 + .../sodalive/useraction/UserActionService.kt | 163 +++++++++--------- 5 files changed, 90 insertions(+), 80 deletions(-) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/comment/AudioContentCommentController.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/comment/AudioContentCommentController.kt index f603fd7..593758f 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/comment/AudioContentCommentController.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/comment/AudioContentCommentController.kt @@ -43,6 +43,7 @@ class AudioContentCommentController( userActionService.recordAction( memberId = member.id!!, + isAuth = member.auth != null, actionType = ActionType.CONTENT_COMMENT, contentCommentId = commentId, pushTokenList = pushTokenList @@ -50,6 +51,7 @@ class AudioContentCommentController( userActionService.recordAction( memberId = member.id!!, + isAuth = member.auth != null, actionType = ActionType.ORDER_CONTENT_COMMENT, contentId = request.contentId, contentCommentId = commentId, diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberController.kt b/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberController.kt index 11c893c..e7cd05b 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberController.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberController.kt @@ -65,6 +65,7 @@ class MemberController( userActionService.recordAction( memberId = response.memberId, + isAuth = false, actionType = ActionType.SIGN_UP, pushTokenList = if (request.pushToken != null) { listOf(request.pushToken) @@ -358,6 +359,7 @@ class MemberController( if (response.isNew) { userActionService.recordAction( memberId = response.memberId, + isAuth = false, actionType = ActionType.SIGN_UP, pushTokenList = if (request.pushToken != null) { listOf(request.pushToken) @@ -393,6 +395,7 @@ class MemberController( if (response.isNew) { userActionService.recordAction( memberId = response.memberId, + isAuth = false, actionType = ActionType.SIGN_UP, pushTokenList = if (request.pushToken != null) { listOf(request.pushToken) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/member/auth/AuthController.kt b/src/main/kotlin/kr/co/vividnext/sodalive/member/auth/AuthController.kt index 17366f5..50f8cbb 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/member/auth/AuthController.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/member/auth/AuthController.kt @@ -40,6 +40,7 @@ class AuthController( val pushTokenList = memberService.getPushTokenList(recipient = memberId) userActionService.recordAction( memberId = member.id!!, + isAuth = true, actionType = ActionType.USER_AUTHENTICATION, pushTokenList = pushTokenList ) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/useraction/UserActionController.kt b/src/main/kotlin/kr/co/vividnext/sodalive/useraction/UserActionController.kt index 91ae58c..1a87e08 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/useraction/UserActionController.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/useraction/UserActionController.kt @@ -27,6 +27,7 @@ class UserActionController( val pushTokenList = memberService.getPushTokenList(recipient = memberId) service.recordAction( memberId = memberId, + isAuth = member.auth != null, actionType = request.actionType, pushTokenList = pushTokenList ) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/useraction/UserActionService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/useraction/UserActionService.kt index 11697a6..1025369 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/useraction/UserActionService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/useraction/UserActionService.kt @@ -31,6 +31,7 @@ class UserActionService( fun recordAction( memberId: Long, + isAuth: Boolean, actionType: ActionType, contentId: Long? = null, contentCommentId: Long? = null, @@ -49,96 +50,98 @@ class UserActionService( repository.flush() } - try { - transactionTemplate.execute { - val policy = policyRepository.findByActionTypeAndIsActiveTrue(actionType, now) - if (policy != null) { - val policyType = policy.policyType - val todayAt15 = now.toLocalDate().atTime(15, 0) - val policyTypeDailyStartDate = if (now.toLocalTime().isBefore(todayAt15.toLocalTime())) { - now.toLocalDate().minusDays(1).atTime(15, 0) - } else { - todayAt15 - } + if (isAuth) { + try { + transactionTemplate.execute { + val policy = policyRepository.findByActionTypeAndIsActiveTrue(actionType, now) + if (policy != null) { + val policyType = policy.policyType + val todayAt15 = now.toLocalDate().atTime(15, 0) + val policyTypeDailyStartDate = if (now.toLocalTime().isBefore(todayAt15.toLocalTime())) { + now.toLocalDate().minusDays(1).atTime(15, 0) + } else { + todayAt15 + } - val isValidPolicyTypeDailyAndDailyStartDateAfterPolicyStartDate = - policyType == PolicyType.DAILY && policyTypeDailyStartDate >= policy.startDate - val order = if (contentId != null) { - orderRepository.findByMemberIdAndContentId( + val isValidPolicyTypeDailyAndDailyStartDateAfterPolicyStartDate = + policyType == PolicyType.DAILY && policyTypeDailyStartDate >= policy.startDate + val order = if (contentId != null) { + orderRepository.findByMemberIdAndContentId( + memberId = memberId, + contentId = contentId, + createdAt = if (isValidPolicyTypeDailyAndDailyStartDateAfterPolicyStartDate) { + policyTypeDailyStartDate + } else { + policy.startDate + } + ) + } else { + null + } + if (actionType == ActionType.ORDER_CONTENT_COMMENT && order == null) return@execute + + val actionCount = repository.countByMemberIdAndActionTypeAndCreatedAtBetween( memberId = memberId, - contentId = contentId, - createdAt = if (isValidPolicyTypeDailyAndDailyStartDateAfterPolicyStartDate) { + actionType = actionType, + startDate = if (isValidPolicyTypeDailyAndDailyStartDateAfterPolicyStartDate) { policyTypeDailyStartDate } else { policy.startDate + }, + endDate = policy.endDate ?: now + ) + if (actionCount < policy.threshold) return@execute + + val grantedCount = grantLogRepository.countByMemberIdAndPolicyIdAndStartDate( + memberId, + policy.id!!, + startDate = if (isValidPolicyTypeDailyAndDailyStartDateAfterPolicyStartDate) { + policyTypeDailyStartDate + } else { + policy.startDate + }, + orderId = order?.id + ) + if (grantedCount >= policy.availableCount) return@execute + + val point = if (actionType == ActionType.ORDER_CONTENT_COMMENT && order != null) { + order.can + } else { + policy.pointAmount + } + + if (point > 0) { + grantLogRepository.save( + PointGrantLog( + memberId = memberId, + point = point, + actionType = actionType, + policyId = policy.id!!, + orderId = order?.id + ) + ) + + memberPointRepository.save( + MemberPoint( + memberId = memberId, + point = point, + actionType = actionType, + expiresAt = now.plusDays(3) + ) + ) + + if (pushTokenList.isNotEmpty()) { + fcmService.sendPointGranted( + pushTokenList, + point + ) } - ) - } else { - null - } - if (actionType == ActionType.ORDER_CONTENT_COMMENT && order == null) return@execute - - val actionCount = repository.countByMemberIdAndActionTypeAndCreatedAtBetween( - memberId = memberId, - actionType = actionType, - startDate = if (isValidPolicyTypeDailyAndDailyStartDateAfterPolicyStartDate) { - policyTypeDailyStartDate - } else { - policy.startDate - }, - endDate = policy.endDate ?: now - ) - if (actionCount < policy.threshold) return@execute - - val grantedCount = grantLogRepository.countByMemberIdAndPolicyIdAndStartDate( - memberId, - policy.id!!, - startDate = if (isValidPolicyTypeDailyAndDailyStartDateAfterPolicyStartDate) { - policyTypeDailyStartDate - } else { - policy.startDate - }, - orderId = order?.id - ) - if (grantedCount >= policy.availableCount) return@execute - - val point = if (actionType == ActionType.ORDER_CONTENT_COMMENT && order != null) { - order.can - } else { - policy.pointAmount - } - - if (point > 0) { - grantLogRepository.save( - PointGrantLog( - memberId = memberId, - point = point, - actionType = actionType, - policyId = policy.id!!, - orderId = order?.id - ) - ) - - memberPointRepository.save( - MemberPoint( - memberId = memberId, - point = point, - actionType = actionType, - expiresAt = now.plusDays(3) - ) - ) - - if (pushTokenList.isNotEmpty()) { - fcmService.sendPointGranted( - pushTokenList, - point - ) } } } + } catch (e: Exception) { + logger.warn("포인트 지급 또는 알림 실패: ${e.message}") } - } catch (e: Exception) { - logger.warn("포인트 지급 또는 알림 실패: ${e.message}") } } }