package kr.co.vividnext.sodalive.useraction import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.cancel import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import kr.co.vividnext.sodalive.content.order.Order import kr.co.vividnext.sodalive.content.order.OrderRepository import kr.co.vividnext.sodalive.member.auth.AuthRepository 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 import javax.annotation.PreDestroy @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 authRepository: AuthRepository ) { private val coroutineScope = CoroutineScope( Dispatchers.IO + CoroutineExceptionHandler { _, e -> logger.error("포인트 지급 또는 알림 실패: ${e.message}") } ) fun recordAction( memberId: Long, isAuth: Boolean, actionType: ActionType, contentId: Long? = null, contentCommentId: Long? = null ) { coroutineScope.launch { var actionCount = 0 var grantedCount = 0 val order: Order? var orderId: Long? = null var point = 0 val now = LocalDateTime.now() 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 order = if (contentId != null) { orderRepository.findByMemberIdAndContentId( memberId = memberId, contentId = contentId, createdAt = if (isValidPolicyTypeDailyAndDailyStartDateAfterPolicyStartDate) { policyTypeDailyStartDate } else { policy.startDate } ) } else { null } orderId = order?.id if (actionType == ActionType.ORDER_CONTENT_COMMENT && order == null) return@launch point = if (actionType == ActionType.ORDER_CONTENT_COMMENT && order != null) { order.can } else { policy.pointAmount } actionCount = repository.countByMemberIdAndActionTypeAndCreatedAtBetween( memberId = memberId, actionType = actionType, startDate = if (isValidPolicyTypeDailyAndDailyStartDateAfterPolicyStartDate) { policyTypeDailyStartDate } else { policy.startDate }, endDate = policy.endDate ?: LocalDateTime.now() ) grantedCount = grantLogRepository.countByMemberIdAndPolicyIdAndStartDate( memberId, policy.id!!, startDate = if (isValidPolicyTypeDailyAndDailyStartDateAfterPolicyStartDate) { policyTypeDailyStartDate } else { policy.startDate }, orderId = order?.id ) } withContext(Dispatchers.IO) { transactionTemplate.execute { repository.save( UserActionLog( memberId = memberId, actionType = actionType, contentCommentId = contentCommentId ) ) repository.flush() } } withContext(Dispatchers.IO) { if (isAuth) { try { transactionTemplate.execute { if (policy != null) { if (actionCount + 1 < policy.threshold) return@execute if (grantedCount >= policy.availableCount) return@execute if (point > 0) { grantLogRepository.save( PointGrantLog( memberId = memberId, point = point, actionType = actionType, policyId = policy.id!!, orderId = orderId ) ) memberPointRepository.save( MemberPoint( memberId = memberId, point = point, actionType = actionType, expiresAt = now.plusDays(3) ) ) } } } } catch (e: Exception) { logger.warn("포인트 지급 또는 알림 실패: ${e.message}") } } } } } fun recordAction( memberId: Long, actionType: ActionType, contentId: Long? = null, contentCommentId: Long? = null ) { recordAction( memberId = memberId, isAuth = authRepository.getAuthIdByMemberId(memberId) != null, actionType = actionType, contentId = contentId, contentCommentId = contentCommentId ) } @PreDestroy fun onDestroy() { coroutineScope.cancel("UserActionService 종료") } companion object { private val logger = LoggerFactory.getLogger(UserActionService::class.java) } }