feat: 유저 행동 기록 및 포인트 지급 로직 구현 + 회원가입 연동

This commit is contained in:
Klaus 2025-04-21 22:03:58 +09:00
parent d94418067f
commit e2c70de2e0
6 changed files with 176 additions and 1 deletions

View File

@ -13,6 +13,8 @@ import kr.co.vividnext.sodalive.member.notification.UpdateNotificationSettingReq
import kr.co.vividnext.sodalive.member.signUp.SignUpRequestV2
import kr.co.vividnext.sodalive.member.social.google.GoogleAuthService
import kr.co.vividnext.sodalive.member.social.kakao.KakaoAuthService
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.security.core.userdetails.User
@ -34,7 +36,8 @@ class MemberController(
private val service: MemberService,
private val kakaoAuthService: KakaoAuthService,
private val googleAuthService: GoogleAuthService,
private val trackingService: AdTrackingService
private val trackingService: AdTrackingService,
private val userActionService: UserActionService
) {
@GetMapping("/check/email")
fun checkEmail(@RequestParam email: String) = service.duplicateCheckEmail(email)
@ -60,6 +63,11 @@ class MemberController(
)
}
userActionService.recordAction(
memberId = response.memberId,
actionType = ActionType.SIGN_UP
)
return ApiResponse.ok(message = "회원가입을 축하드립니다.", data = response.loginResponse)
}

View File

@ -0,0 +1,5 @@
package kr.co.vividnext.sodalive.point
import org.springframework.data.jpa.repository.JpaRepository
interface MemberPointRepository : JpaRepository<MemberPoint, Long>

View File

@ -0,0 +1,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
interface PointGrantLogRepository : JpaRepository<PointGrantLog, Long>, PointGrantLogQueryRepository
interface PointGrantLogQueryRepository {
fun existsByMemberIdAndPolicyId(memberId: Long, policyId: Long): Boolean
}
class PointGrantLogQueryRepositoryImpl(
private val queryFactory: JPAQueryFactory
) : PointGrantLogQueryRepository {
override fun existsByMemberIdAndPolicyId(memberId: Long, policyId: Long): Boolean {
return queryFactory
.select(pointGrantLog.id)
.from(pointGrantLog)
.where(
pointGrantLog.memberId.eq(memberId),
pointGrantLog.policyId.eq(policyId)
)
.fetch()
.isNotEmpty()
}
}

View File

@ -0,0 +1,34 @@
package kr.co.vividnext.sodalive.point
import com.querydsl.jpa.impl.JPAQueryFactory
import kr.co.vividnext.sodalive.point.QPointRewardPolicy.pointRewardPolicy
import kr.co.vividnext.sodalive.useraction.ActionType
import org.springframework.data.jpa.repository.JpaRepository
import java.time.LocalDateTime
interface PointRewardPolicyRepository : JpaRepository<PointRewardPolicy, Long>, PointRewardPolicyQueryRepository
interface PointRewardPolicyQueryRepository {
fun findByActionTypeAndIsActiveTrue(actionType: ActionType, nowDateTime: LocalDateTime): PointRewardPolicy?
}
class PointRewardPolicyQueryRepositoryImpl(
private val queryFactory: JPAQueryFactory
) : PointRewardPolicyQueryRepository {
override fun findByActionTypeAndIsActiveTrue(
actionType: ActionType,
nowDateTime: LocalDateTime
): PointRewardPolicy? {
return queryFactory
.selectFrom(pointRewardPolicy)
.where(
pointRewardPolicy.isActive,
pointRewardPolicy.actionType.eq(actionType),
pointRewardPolicy.startDate.loe(nowDateTime),
pointRewardPolicy.endDate.goe(nowDateTime)
.or(pointRewardPolicy.endDate.isNull)
)
.orderBy(pointRewardPolicy.endDate.asc())
.fetchFirst()
}
}

View File

@ -0,0 +1,39 @@
package kr.co.vividnext.sodalive.useraction
import com.querydsl.jpa.impl.JPAQueryFactory
import kr.co.vividnext.sodalive.useraction.QUserActionLog.userActionLog
import org.springframework.data.jpa.repository.JpaRepository
import java.time.LocalDateTime
interface UserActionLogRepository : JpaRepository<UserActionLog, Long>, UserActionLogQueryRepository
interface UserActionLogQueryRepository {
fun countByMemberIdAndActionTypeAndCreatedAtBetween(
memberId: Long,
actionType: ActionType,
startDate: LocalDateTime,
endDate: LocalDateTime
): Int
}
class UserActionLogQueryRepositoryImpl(
private val queryFactory: JPAQueryFactory
) : UserActionLogQueryRepository {
override fun countByMemberIdAndActionTypeAndCreatedAtBetween(
memberId: Long,
actionType: ActionType,
startDate: LocalDateTime,
endDate: LocalDateTime
): Int {
return queryFactory
.select(userActionLog.id)
.from(userActionLog)
.where(
userActionLog.memberId.eq(memberId)
.and(userActionLog.actionType.eq(actionType))
.and(userActionLog.createdAt.between(startDate, endDate))
)
.fetch()
.size
}
}

View File

@ -0,0 +1,62 @@
package kr.co.vividnext.sodalive.useraction
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
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.springframework.stereotype.Service
import java.time.LocalDateTime
@Service
class UserActionService(
private val repository: UserActionLogRepository,
private val policyRepository: PointRewardPolicyRepository,
private val grantLogRepository: PointGrantLogRepository,
private val memberPointRepository: MemberPointRepository
) {
private val coroutineScope = CoroutineScope(Dispatchers.IO)
fun recordAction(memberId: Long, actionType: ActionType) {
coroutineScope.launch {
val now = LocalDateTime.now()
repository.save(UserActionLog(memberId, actionType))
val policy = policyRepository.findByActionTypeAndIsActiveTrue(actionType, now)
if (policy != null) {
val actionCount = repository.countByMemberIdAndActionTypeAndCreatedAtBetween(
memberId = memberId,
actionType = actionType,
startDate = policy.startDate,
endDate = policy.endDate ?: now
)
if (actionCount < policy.threshold) return@launch
val alreadyGranted = grantLogRepository.existsByMemberIdAndPolicyId(memberId, policy.id!!)
if (alreadyGranted) return@launch
memberPointRepository.save(
MemberPoint(
memberId = memberId,
point = policy.pointAmount,
actionType = actionType,
expiresAt = now.plusDays(3)
)
)
grantLogRepository.save(
PointGrantLog(
memberId = memberId,
point = policy.pointAmount,
actionType = actionType,
policyId = policy.id!!
)
)
}
}
}
}