fix: 유저 행동 데이터 기록시 포인트 지급 조건 수정

- 지급유형(매일, 전체) 추가
- 참여가능 횟수 추가
- 주문한 콘텐츠에 댓글을 쓰면 포인트 지급을 위해 포인트 지급 이력에 orderId 추가
This commit is contained in:
Klaus 2025-05-16 15:01:33 +09:00
parent 73c9a90ae3
commit f23251f5bb
7 changed files with 45 additions and 11 deletions

View File

@ -2,14 +2,17 @@ package kr.co.vividnext.sodalive.admin.point
import kr.co.vividnext.sodalive.point.PointRewardPolicy import kr.co.vividnext.sodalive.point.PointRewardPolicy
import kr.co.vividnext.sodalive.useraction.ActionType import kr.co.vividnext.sodalive.useraction.ActionType
import kr.co.vividnext.sodalive.useraction.PolicyType
import java.time.LocalDateTime import java.time.LocalDateTime
import java.time.ZoneId import java.time.ZoneId
import java.time.format.DateTimeFormatter import java.time.format.DateTimeFormatter
data class CreatePointRewardPolicyRequest( data class CreatePointRewardPolicyRequest(
val title: String, val title: String,
val policyType: PolicyType,
val actionType: ActionType, val actionType: ActionType,
val threshold: Int, val threshold: Int,
val availableCount: Int,
val pointAmount: Int, val pointAmount: Int,
val startDate: String, val startDate: String,
val endDate: String val endDate: String
@ -19,8 +22,10 @@ data class CreatePointRewardPolicyRequest(
return PointRewardPolicy( return PointRewardPolicy(
title = title, title = title,
policyType = policyType,
actionType = actionType, actionType = actionType,
threshold = threshold, threshold = threshold,
availableCount = availableCount,
pointAmount = pointAmount, pointAmount = pointAmount,
startDate = LocalDateTime.parse(startDate, dateTimeFormatter) startDate = LocalDateTime.parse(startDate, dateTimeFormatter)
.atZone(ZoneId.of("Asia/Seoul")) .atZone(ZoneId.of("Asia/Seoul"))

View File

@ -12,5 +12,6 @@ data class PointGrantLog(
val point: Int, val point: Int,
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
val actionType: ActionType, val actionType: ActionType,
val policyId: Long? val policyId: Long?,
val orderId: Long?
) : BaseEntity() ) : BaseEntity()

View File

@ -3,25 +3,27 @@ package kr.co.vividnext.sodalive.point
import com.querydsl.jpa.impl.JPAQueryFactory import com.querydsl.jpa.impl.JPAQueryFactory
import kr.co.vividnext.sodalive.point.QPointGrantLog.pointGrantLog import kr.co.vividnext.sodalive.point.QPointGrantLog.pointGrantLog
import org.springframework.data.jpa.repository.JpaRepository import org.springframework.data.jpa.repository.JpaRepository
import java.time.LocalDateTime
interface PointGrantLogRepository : JpaRepository<PointGrantLog, Long>, PointGrantLogQueryRepository interface PointGrantLogRepository : JpaRepository<PointGrantLog, Long>, PointGrantLogQueryRepository
interface PointGrantLogQueryRepository { interface PointGrantLogQueryRepository {
fun existsByMemberIdAndPolicyId(memberId: Long, policyId: Long): Boolean fun countByMemberIdAndPolicyIdAndStartDate(memberId: Long, policyId: Long, startDate: LocalDateTime): Int
} }
class PointGrantLogQueryRepositoryImpl( class PointGrantLogQueryRepositoryImpl(
private val queryFactory: JPAQueryFactory private val queryFactory: JPAQueryFactory
) : PointGrantLogQueryRepository { ) : PointGrantLogQueryRepository {
override fun existsByMemberIdAndPolicyId(memberId: Long, policyId: Long): Boolean { override fun countByMemberIdAndPolicyIdAndStartDate(memberId: Long, policyId: Long, startDate: LocalDateTime): Int {
return queryFactory return queryFactory
.select(pointGrantLog.id) .select(pointGrantLog.id)
.from(pointGrantLog) .from(pointGrantLog)
.where( .where(
pointGrantLog.memberId.eq(memberId), pointGrantLog.memberId.eq(memberId),
pointGrantLog.policyId.eq(policyId) pointGrantLog.policyId.eq(policyId),
pointGrantLog.createdAt.goe(startDate)
) )
.fetch() .fetch()
.isNotEmpty() .size
} }
} }

View File

@ -2,6 +2,7 @@ package kr.co.vividnext.sodalive.point
import kr.co.vividnext.sodalive.common.BaseEntity import kr.co.vividnext.sodalive.common.BaseEntity
import kr.co.vividnext.sodalive.useraction.ActionType import kr.co.vividnext.sodalive.useraction.ActionType
import kr.co.vividnext.sodalive.useraction.PolicyType
import java.time.LocalDateTime import java.time.LocalDateTime
import javax.persistence.Entity import javax.persistence.Entity
import javax.persistence.EnumType import javax.persistence.EnumType
@ -11,8 +12,11 @@ import javax.persistence.Enumerated
data class PointRewardPolicy( data class PointRewardPolicy(
var title: String, var title: String,
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
val policyType: PolicyType,
@Enumerated(EnumType.STRING)
val actionType: ActionType, val actionType: ActionType,
val threshold: Int, val threshold: Int,
val availableCount: Int,
val pointAmount: Int, val pointAmount: Int,
var startDate: LocalDateTime, var startDate: LocalDateTime,
var endDate: LocalDateTime? = null, var endDate: LocalDateTime? = null,

View File

@ -2,5 +2,8 @@ package kr.co.vividnext.sodalive.useraction
enum class ActionType(val displayName: String) { enum class ActionType(val displayName: String) {
SIGN_UP("회원가입"), SIGN_UP("회원가입"),
USER_AUTHENTICATION("본인인증") USER_AUTHENTICATION("본인인증"),
CONTENT_COMMENT("콘텐츠 댓글"),
ORDER_CONTENT_COMMENT("구매한 콘텐츠 댓글"),
LIVE_CONTINUOUS_LISTEN_30("라이브 연속 청취 30분")
} }

View File

@ -0,0 +1,6 @@
package kr.co.vividnext.sodalive.useraction
enum class PolicyType(val displayName: String) {
DAILY("기간 내 매일"),
TOTAL("기간 내 전체")
}

View File

@ -24,23 +24,35 @@ class UserActionService(
private val coroutineScope = CoroutineScope(Dispatchers.IO) 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 { coroutineScope.launch {
val now = LocalDateTime.now() val now = LocalDateTime.now()
repository.save(UserActionLog(memberId, actionType)) repository.save(UserActionLog(memberId, actionType))
val policy = policyRepository.findByActionTypeAndIsActiveTrue(actionType, now) val policy = policyRepository.findByActionTypeAndIsActiveTrue(actionType, now)
if (policy != null) { 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( val actionCount = repository.countByMemberIdAndActionTypeAndCreatedAtBetween(
memberId = memberId, memberId = memberId,
actionType = actionType, actionType = actionType,
startDate = policy.startDate, startDate = if (policyType == PolicyType.DAILY) policyTypeDailyStartDate else policy.startDate,
endDate = policy.endDate ?: now endDate = policy.endDate ?: now
) )
if (actionCount < policy.threshold) return@launch if (actionCount < policy.threshold) return@launch
val alreadyGranted = grantLogRepository.existsByMemberIdAndPolicyId(memberId, policy.id!!) val grantedCount = grantLogRepository.countByMemberIdAndPolicyIdAndStartDate(
if (alreadyGranted) return@launch memberId,
policy.id!!,
startDate = if (policyType == PolicyType.DAILY) policyTypeDailyStartDate else policy.startDate
)
if (grantedCount >= policy.availableCount) return@launch
memberPointRepository.save( memberPointRepository.save(
MemberPoint( MemberPoint(
@ -56,7 +68,8 @@ class UserActionService(
memberId = memberId, memberId = memberId,
point = policy.pointAmount, point = policy.pointAmount,
actionType = actionType, actionType = actionType,
policyId = policy.id!! policyId = policy.id!!,
orderId = orderId
) )
) )