본문 바로가기
개발

[개발] 포인트 만료

by 공덕뉸나 2024. 6. 3.

포인트 테이블에는 endDt 라는 만료일이 지정되어 있다.

스케줄러를 사용하여 만료일이 지나면 포인트를 만료 시키는 기능을 구현하려고 한다.

 

우선 point_history 테이블에는 사용자별 만료되어야 할 총 포인트가 들어가야하며 point_history_detail에는 accumulateId와 userId를 가지고 amount의 합이 0보다 큰 리스트가 쌓여야 한다.

 

나는 사용자별 만료 총 포인트를 구하기 위하여 서브쿼리를 사용하였다.

 

ExpiredPoints

@Entity
@Subselect(
        "select " +
                "       a.user_id " +
                "     , sum(a.amount) as expired_point " +
                "  from point_history_detail a " +
                " group by a.point_accumulate_id " +
                " , a.end_dt " +
                "having sum(a.amount) > 0 " +
                "   and a.end_dt < current_timestamp "
)
@Immutable
@Synchronize("PointHistoryDetail")
@Getter
public class ExpiredPoints {
    @Id
    private Long userId;
    private Long expiredPoint;
    private Long amount;
    private LocalDateTime endDt;
    private Long pointAccumulateId;
}

 

PointHistoryDetailRepoSupport

public List<GetTotalExpiredPointDto> getTotalExpiredPoint() {
        List<GetTotalExpiredPointDto> pointList = jpaQueryFactory
                .select(Projections.bean(
                        GetTotalExpiredPointDto.class,
                        EXPIRED_POINTS.userId,
                        EXPIRED_POINTS.expiredPoint.sum().as("totalExpiredPoint")
                ))
                .from(EXPIRED_POINTS)
                .groupBy(EXPIRED_POINTS.userId)
                .fetch();
        return pointList;
    }

 

public List<GetExpiredPointDto> getExpiredPoint(Long userId) {
        List<GetExpiredPointDto> pointList = jpaQueryFactory
                .select(Projections.bean(
                        GetExpiredPointDto.class,
                        POINT_HISTORY_DETAIL.pointAccumulateId,
                        POINT_HISTORY_DETAIL.userId,
                        POINT_HISTORY_DETAIL.endDt,
                        POINT_HISTORY_DETAIL.amount.sum().as("totalExpiredPoint")
                ))
                .from(POINT_HISTORY_DETAIL)
                .where(POINT_HISTORY_DETAIL.userId.eq(userId))
                .groupBy(
                        POINT_HISTORY_DETAIL.pointAccumulateId,
                        POINT_HISTORY_DETAIL.endDt
                )
                .having(POINT_HISTORY_DETAIL.amount.sum().gt(0))
                .having(POINT_HISTORY_DETAIL.endDt.lt(LocalDateTime.now()))
                .fetch();
        return pointList;
    }

 

accumulateId와 endDt를 기준으로 amount의 합이 0보다 큰 리스트를 추출한다.

 

PointHistoryDetailService

public List<GetExpiredPointDto> getExpiredPointList(Long userId) {
        return pointHistoryDetailRepoSupport.getExpiredPoint(userId);
    }

    public List<GetTotalExpiredPointDto> getTotalExpiredPointList() {
        return pointHistoryDetailRepoSupport.getTotalExpiredPoint();
    }

    public void deadlinePoint() {
        List<GetTotalExpiredPointDto> totalExpiredPoint = getTotalExpiredPointList();
        totalExpiredPoint.forEach(
                p -> {
                    PointHistory insertPointHistory = PointHistory.init(p);
                    pointHistoryRepo.save(insertPointHistory);

                    List<GetExpiredPointDto> expiredPoint = getExpiredPointList(p.getUserId());
                    expiredPoint.forEach(e -> {
                        PointHistoryDetail insertPointHistoryDetail = PointHistoryDetail.init(e,
                                insertPointHistory.getPointHistoryId());
                        pointHistoryDetailRepo.save(insertPointHistoryDetail);
                    });
                }
        );
    }

 

두 개의 리스트를 추출한 후 PointHistoryDetail에는 historyId와 userId별로 데이터가 쌓여야하기 때문에 forEach문을 두 번 실행한다.

 

사용자별 만료 되어야 할 총 포인트 리스트.forEach(

         History 테이블 insert;

         적립 아이디별 만료되어야 할 포인트 리스트 조회 (파라미터: userId);

         적립 아이디별 만료되어야 할 포인트 리스트.forEach(

                      History Detail 테이블 insert;

         )

)

 

PointHistory

public static PointHistory init(GetTotalExpiredPointDto totalExpiredPoint) {
        return PointHistory.builder().userId(totalExpiredPoint.getUserId())
                .amount(totalExpiredPoint.getTotalExpiredPoint() * -1)
                .pointHistoryType(null)
                .pointStat(PointStat.EXPIRATION)
                .endDt(null)
                .remarks("만료소멸")
                .build();
    }

 

PointHistoryDetail

public static PointHistoryDetail init(GetExpiredPointDto expiredPoint, Long historyId) {
        return PointHistoryDetail.builder().userId(expiredPoint.getUserId())
                .amount(expiredPoint.getTotalExpiredPoint() * -1)
                .pointAccumulateId(expiredPoint.getPointAccumulateId())
                .endDt(expiredPoint.getEndDt())
                .pointHistoryId(historyId)
                .build();
    }

 

 

여기까지 구현하였으면 이제 매일 밤 12시에 돌아가도록 설정한다.

 

PointHistorySchedulerService

@Slf4j
@Component
@RequiredArgsConstructor
public class PointHistorySchedulerService {
    private final PointHistoryDetailService pointHistoryDetailService;

    @Scheduled(cron = "0 0 24 * * *")
    public void deadlineTicket() {
        try {
            pointHistoryDetailService.deadlinePoint();
        } catch (RuntimeException exception) {

        }
    }
}

 

구현을 마치고 테스트 코드를 짜서 제대로 값이 들어가는지 확인했다.

 

PointHistoryDetailServiceTest

@SpringBootTest
@Transactional
@ExtendWith(SpringExtension.class)
public class PointHistoryDetailServiceTest {

    @Autowired
    private PointHistoryDetailService pointHistoryDetailService;

    @Test
    void getTotalExpiredPoints() {
        List<GetTotalExpiredPointDto> list = pointHistoryDetailService.getTotalExpiredPointList();
        list.stream().forEach(System.out::println);
        list.stream().forEach(
                p -> {
                    List<GetExpiredPointDto> pointList = pointHistoryDetailService.getExpiredPointList(p.getUserId());
                    pointList.stream().forEach(System.out::println);
                }
        );
    }

    @Test
    @Rollback(value = false)
    void deadlinePoints() {
        pointHistoryDetailService.deadlinePoint();
    }
}

 

point_history (DB table)

point_history_detail (DB table)

 

각각 가장 마지막 줄을 보면 오늘 날짜에 맞춰서 데이터가 잘 들어간 것을 확인해 볼 수가 있다.

 

다른 기능 구현보다 포인트 기능 구현하는 것이 가장 헷갈렸던 것 같다.

스케줄러도 처음 사용해 봤고 아직 공부해야 할 게 많다고 느낀다.

 

'개발' 카테고리의 다른 글

[에러] swagger로 테스트 시 LocalDateTime typeMisMatch  (0) 2024.06.26
[개발] 내 포인트 내역 조회  (0) 2024.06.04
[개발] 포인트 차감  (0) 2024.05.30
[개발] Maven 의존성 추가  (0) 2024.05.28
[개발] 총 포인트 조회  (0) 2024.04.17