fix(charge): 충전 이벤트 보너스 지급을 안정화한다

This commit is contained in:
2026-05-18 13:34:12 +09:00
parent acd0393a0e
commit 810b143c9e
15 changed files with 929 additions and 102 deletions

View File

@@ -0,0 +1,25 @@
package kr.co.vividnext.sodalive.admin.event.charge
import kr.co.vividnext.sodalive.common.ApiResponse
import org.springframework.security.access.prepost.PreAuthorize
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
@RestController
@RequestMapping("/admin/charge/event-jobs")
@PreAuthorize("hasRole('ADMIN')")
class AdminChargeEventJobController(
private val service: AdminChargeEventJobService
) {
@GetMapping
fun getJobs() = ApiResponse.ok(service.getJobs())
@PostMapping("/{jobId}/retry")
fun retry(@PathVariable jobId: Long): ApiResponse<Unit> {
service.retry(jobId)
return ApiResponse.ok(Unit)
}
}

View File

@@ -0,0 +1,42 @@
package kr.co.vividnext.sodalive.admin.event.charge
import kr.co.vividnext.sodalive.can.charge.event.ChargeEventJob
import kr.co.vividnext.sodalive.can.charge.event.ChargeEventJobStatus
import kr.co.vividnext.sodalive.can.charge.event.ChargeEventJobType
import java.time.LocalDateTime
data class AdminChargeEventJobResponse(
val id: Long,
val sourceChargeId: Long,
val resultChargeId: Long?,
val memberId: Long,
val chargeEventId: Long?,
val jobType: ChargeEventJobType,
val additionalCan: Int,
val status: ChargeEventJobStatus,
val retryCount: Int,
val nextRetryAt: LocalDateTime?,
val lastError: String?,
val createdAt: LocalDateTime?,
val updatedAt: LocalDateTime?
) {
companion object {
fun from(job: ChargeEventJob): AdminChargeEventJobResponse {
return AdminChargeEventJobResponse(
id = job.id!!,
sourceChargeId = job.sourceChargeId,
resultChargeId = job.resultChargeId,
memberId = job.memberId,
chargeEventId = job.chargeEventId,
jobType = job.jobType,
additionalCan = job.additionalCan,
status = job.status,
retryCount = job.retryCount,
nextRetryAt = job.nextRetryAt,
lastError = job.lastError,
createdAt = job.createdAt,
updatedAt = job.updatedAt
)
}
}
}

View File

@@ -0,0 +1,28 @@
package kr.co.vividnext.sodalive.admin.event.charge
import kr.co.vividnext.sodalive.can.charge.event.ChargeEventJobRepository
import kr.co.vividnext.sodalive.can.charge.event.ChargeEventJobStatus
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import java.time.LocalDateTime
@Service
@Transactional(readOnly = true)
class AdminChargeEventJobService(
private val repository: ChargeEventJobRepository
) {
fun getJobs(): List<AdminChargeEventJobResponse> {
return repository.findVisibleAdminJobs().map(AdminChargeEventJobResponse::from)
}
@Transactional
fun retry(jobId: Long) {
val job = repository.findByIdForUpdate(jobId) ?: return
if (job.status != ChargeEventJobStatus.FAILED) return
job.status = ChargeEventJobStatus.PENDING
job.retryCount = 0
job.nextRetryAt = LocalDateTime.now()
job.lastError = null
}
}