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 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 class UserActionService( private val repository: UserActionLogRepository, private val orderRepository: OrderRepository, private val policyRepository: PointRewardPolicyRepository, private val grantLogRepository: PointGrantLogRepository, private val memberPointRepository: MemberPointRepository, private val transactionTemplate: TransactionTemplate, private val fcmService: FcmService ) { private val coroutineScope = CoroutineScope(Dispatchers.IO) fun recordAction( memberId: Long, isAuth: Boolean, actionType: ActionType, contentId: Long? = null, contentCommentId: Long? = null, pushTokenList: List = emptyList() ) { coroutineScope.launch { val now = LocalDateTime.now() transactionTemplate.execute { repository.save( UserActionLog( memberId = memberId, actionType = actionType, contentCommentId = contentCommentId ) ) repository.flush() } 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( 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, 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}") } } } } companion object { private val logger = LoggerFactory.getLogger(UserActionService::class.java) } }