Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BE] Blackhole 유저 대여시 재확인 로직 #1304 & 밴 유저가 만료시간 볼 수 있도록 변경 #1294 #1305

Merged
merged 23 commits into from
Aug 18, 2023
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
0179af6
[BE] REFACTOR: 매직넘버 수정
enaenen Aug 12, 2023
2d3a701
[BE] REFACTOR: 매직넘버 수정 코멘트
enaenen Aug 12, 2023
5dd877a
[BE] FIX: 로거 수정
enaenen Aug 12, 2023
af42fbf
Merge branch 'be/dev/refactor_blackhole_scheduler/#1294' of github.co…
enaenen Aug 14, 2023
a6a0cd1
[BE] REFACTOR: handlePolicyStatus method moved to service
enaenen Aug 14, 2023
36bd42f
[BE] WIP: banned user 잔여날짜표기 & blackhole 근접유저 대여시 체크 로직 수정
enaenen Aug 14, 2023
1451102
[BE] REFACTOR: BAN 상태일때, unban 일자 에러메세지에 표기
enaenen Aug 14, 2023
465f437
[BE] FIX: Logger 오타 수정
enaenen Aug 14, 2023
99f4e43
[BE] FIX: BanHistory 매개변수 List로 변경
enaenen Aug 14, 2023
9810343
[BE] REFACTOR & FIX: 블랙홀 유저 대여시 체크 후 블랙홀 업데이트
enaenen Aug 14, 2023
be4b5d9
[BE] REFACTOR: BlackholeManager 수정에 따른 TEST Mock 수정
enaenen Aug 14, 2023
8a0032b
[BE] REFACTOR: DELAY_TIME static
enaenen Aug 15, 2023
8fde2a1
[BE] REFACTOR: isBlackholed 함수 이름 명확히 변경
enaenen Aug 15, 2023
52c0c55
[BE] REFACTOR: isBlackholed 함수 이름 명확히 변경
enaenen Aug 15, 2023
ba0a3ac
[BE] REFACTOR: BlackholeUserInfo 변수명 변경, 블랙홀 날짜 확인 전달인자 변경
enaenen Aug 16, 2023
dc27bdc
[BE] REFACTOR: 주석 및 Import 변경
enaenen Aug 16, 2023
d5a8fa9
[BE] REFACTOR: Event Lister 를 통한 순환 참조 해결
enaenen Aug 16, 2023
f3bb117
[BE] REFACTOR: 메세지 수정
enaenen Aug 16, 2023
acefae0
[BE] REFACTOR: refector Blackholemanager
enaenen Aug 16, 2023
ec325a1
[BE] REFACTOR: 블랙홀매니저 최종 - 자동갱신형태로
enaenen Aug 18, 2023
8c39cfa
[BE] FEAT: UTIL Exception 추가
enaenen Aug 18, 2023
6ec842c
[BE] REFACTOR: Exception Wrapping
enaenen Aug 18, 2023
8e35441
Merge branch 'dev' of github.com:innovationacademy-kr/42cabi into be/…
enaenen Aug 18, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.ToString;
import org.ftclub.cabinet.user.domain.User;

/**
* 유저의 식별자, 이름, 이메일, 블랙홀 날짜를 반환하는 DTO입니다.
Expand All @@ -22,4 +23,7 @@ public static UserBlackholeInfoDto of(Long userId, String name, String email,
LocalDateTime blackHoledAt) {
return new UserBlackholeInfoDto(userId, name, email, blackHoledAt);
}
public static UserBlackholeInfoDto of(User user) {
return new UserBlackholeInfoDto(user.getUserId(), user.getName(), user.getEmail(), user.getBlackholedAt());
}
enaenen marked this conversation as resolved.
Show resolved Hide resolved
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package org.ftclub.cabinet.event;

import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.ftclub.cabinet.dto.UserBlackholeInfoDto;
import org.ftclub.cabinet.utils.blackhole.manager.BlackholeManagerV2;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.stereotype.Component;

@Log4j2
@Component
@EnableAsync
@RequiredArgsConstructor
public class BlackholedUserLentEventListener {

private final BlackholeManagerV2 blackholeManager;

@Async
@EventListener
public void handleBlackholedUserLentAttemptingEvent(UserBlackholeInfoDto userBlackholeInfoDto) {
log.info("Called handleBlackholedUserLentAttemptingEvent");
blackholeManager.handleBlackhole(userBlackholeInfoDto);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package org.ftclub.cabinet.exception;

import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;

/**
* {@link CustomServiceException}을 위한 exception 클래스. 생성할 exception에 대한 정보를 담고있다.
*/
@JsonFormat(shape = JsonFormat.Shape.OBJECT)
@RequiredArgsConstructor
@Getter
public class CustomExceptionStatus {
private final int statusCode;
private final String message;
private final String error;

public CustomExceptionStatus(HttpStatus status, String message) {
this.statusCode = status.value();
this.message = message;
this.error = status.getReasonPhrase();
}

public CustomExceptionStatus(ExceptionStatus status, String message) {
this.statusCode = status.getStatusCode();
this.message = status.getMessage() + "\n" + message;
this.error = status.getError();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package org.ftclub.cabinet.exception;

/**
* Service에서 throw 하는 Exception 중 오류메세지를 커스텀 가능한 Exception
* @see CustomExceptionStatus
*/
public class CustomServiceException extends RuntimeException {

final CustomExceptionStatus status;

/**
* @param status exception에 대한 정보에 대한 enum
*/
public CustomServiceException(CustomExceptionStatus status) {
this.status = status;
}

public CustomExceptionStatus getStatus() {
return status;
}
}
enaenen marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,14 @@ public ResponseEntity<?> serviceExceptionHandler(ServiceException e) {
.body(e.status);
}

@ExceptionHandler(CustomServiceException.class)
public ResponseEntity<?> customServiceExceptionHandler(CustomServiceException e) {
log.info("[CustomServiceException] {} : {}", e.status.getError(), e.status.getMessage());
return ResponseEntity
.status(e.status.getStatusCode())
.body(e.status);
}

@ExceptionHandler(DomainException.class)
public ResponseEntity<?> domainExceptionHandler(DomainException e) {
log.warn("[DomainException] {} : {}", e.status.getError(), e.status.getMessage());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,14 @@ public enum ExceptionStatus {
SHARE_BANNED_USER(HttpStatus.BAD_REQUEST, "SHARE 밴 상태의 유저입니다."),
NOT_FOUND_BAN_HISTORY(HttpStatus.NOT_FOUND, "현재 정지 상태인 유저가 아닙니다."),
BLACKHOLED_USER(HttpStatus.BAD_REQUEST, "블랙홀 상태의 유저입니다."),
BLACKHOLE_REFRESHING(HttpStatus.BAD_REQUEST, "블랙홀 갱신 중 입니다.\n잠시 후에 다시 시도해주세요."),
UNAUTHORIZED_ADMIN(HttpStatus.UNAUTHORIZED, "관리자 로그인 정보가 유효하지 않습니다\n다시 로그인해주세요"),
UNAUTHORIZED_USER(HttpStatus.UNAUTHORIZED, "사용자 로그인 정보가 유효하지 않습니다\n다시 로그인해주세요"),
EXTERNAL_API_EXCEPTION(HttpStatus.BAD_REQUEST, "외부 API와 통신 중 에러가 발생했습니다"),
EXISTED_CLUB_USER(HttpStatus.CONFLICT, "이미 존재하는 동아리 유저입니다"),
CLUB_HAS_LENT_CABINET(HttpStatus.NOT_ACCEPTABLE, "대여 중인 사물함을 반납 후 삭제할 수 있습니다."),
;


final private int statusCode;
final private String message;
final private String error;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,30 +1,32 @@
package org.ftclub.cabinet.lent.domain;

import java.time.LocalDate;
import java.util.Date;
import java.time.LocalDateTime;
import java.util.List;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.ftclub.cabinet.cabinet.domain.Cabinet;
import org.ftclub.cabinet.cabinet.domain.CabinetStatus;
import org.ftclub.cabinet.cabinet.domain.LentType;
import org.ftclub.cabinet.config.CabinetProperties;
import org.ftclub.cabinet.dto.UserBlackholeInfoDto;
import org.ftclub.cabinet.exception.DomainException;
import org.ftclub.cabinet.exception.ExceptionStatus;
import org.ftclub.cabinet.user.domain.BanHistory;
import org.ftclub.cabinet.user.domain.User;
import org.ftclub.cabinet.user.domain.UserRole;
import org.ftclub.cabinet.utils.DateUtil;
import org.ftclub.cabinet.utils.blackhole.manager.BlackholeRefresher;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.util.List;

@Component
@RequiredArgsConstructor
@Log4j2
public class LentPolicyImpl implements LentPolicy {

private final CabinetProperties cabinetProperties;
private final ApplicationEventPublisher publisher;


private LocalDateTime generateSharedCabinetExpirationDate(LocalDateTime now,
CabinetStatus cabinetStatus, LentHistory activeLentHistory) {
Expand Down Expand Up @@ -78,10 +80,11 @@ public LocalDateTime generateExpirationDate(LocalDateTime now, Cabinet cabinet,
@Override
public void applyExpirationDate(LentHistory curHistory, List<LentHistory> beforeActiveHistories,
LocalDateTime expiredAt) {
log.info("Called applyExpirationDate curHistory: {}, beforeActiveHistories: {}, expiredAt: {}",
log.info(
"Called applyExpirationDate curHistory: {}, beforeActiveHistories: {}, expiredAt: {}",
curHistory, beforeActiveHistories, expiredAt);

if (expiredAt == null){
if (expiredAt == null) {
throw new DomainException(ExceptionStatus.INVALID_ARGUMENT);
}

Expand All @@ -105,17 +108,18 @@ public LentPolicyStatus verifyUserForLent(User user, Cabinet cabinet, int userAc
if (userActiveLentCount >= 1) {
return LentPolicyStatus.ALREADY_LENT_USER;
}
// TODO: 현재 구조에서는 DB 정합성 문제를 일으키는 코드입니다.
// 블랙홀에 빠진 유저 대여 로직을 막는다고 하면, BlackholeManager.handleBlackhole()을 통해
// 실제 DB에 반영되기 전에 블랙홀에 빠진 유저를 걸러낼 수 있습니다.
// 하지만, 현재는 BlackholeManager <-> LentService 간의 순환 참조가 발생하는데,
// BlackholeManager는 스케줄러에 의해 빈에 등록되는 컴포넌트이므로
// 현재 구조상으로는 @Lazy 어노테이션을 통해 순환 참조 문제를 해결할 수 없습니다.
// 추후 다른 방식으로 구조적인 리팩토링이 필요한 부분입니다..!
// if (user.getBlackholedAt() != null && user.getBlackholedAt()
// .isBefore(LocalDateTime.now())) {
// return LentPolicyStatus.BLACKHOLED_USER;
// }
if (user.getBlackholedAt() != null && user.getBlackholedAt()
.isBefore(LocalDateTime.now())) {
// 다시 돌고 돌고 돌고~
publisher.publishEvent(UserBlackholeInfoDto.of(user));
throw new DomainException(ExceptionStatus.BLACKHOLE_REFRESHING);

// if(user.getBlackholedAt() != null && user.getBlackholedAt().isBefore(LocalDateTime.now()))
// return LentPolicyStatus.BLACKHOLED_USER;
// if(blackholeRefresher.isBlackholedAndUpdateBlackhole(UserBlackholeInfoDto.of(user)))
// return LentPolicyStatus.BLACKHOLED_USER;
}

// 유저가 페널티 2 종류 이상 받을 수 있나? <- 실제로 그럴리 없지만 lentPolicy 객체는 그런 사실을 모르고, 유연하게 구현?
if (userActiveBanList == null || userActiveBanList.size() == 0) {
return LentPolicyStatus.FINE;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
import org.ftclub.cabinet.exception.ExceptionStatus;
import org.ftclub.cabinet.exception.ServiceException;
import org.ftclub.cabinet.lent.domain.LentHistory;
import org.ftclub.cabinet.lent.domain.LentPolicyStatus;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
Expand Down Expand Up @@ -86,41 +85,6 @@ public LentHistory getActiveLentHistoryWithUserId(Long userId) {
.orElseThrow(() -> new ServiceException(ExceptionStatus.NO_LENT_CABINET));
}

/**
* 정책에 대한 결과 상태({@link LentPolicyStatus})에 맞는 적절한 {@link ServiceException}을 throw합니다.
*
* @param status 정책에 대한 결과 상태
* @throws ServiceException 정책에 따라 다양한 exception이 throw될 수 있습니다.
*/
public void handlePolicyStatus(LentPolicyStatus status) {
log.info("Called handlePolicyStatus status: {}", status);
switch (status) {
case FINE:
break;
case BROKEN_CABINET:
throw new ServiceException(ExceptionStatus.LENT_BROKEN);
case FULL_CABINET:
throw new ServiceException(ExceptionStatus.LENT_FULL);
case OVERDUE_CABINET:
throw new ServiceException(ExceptionStatus.LENT_EXPIRED);
case LENT_CLUB:
throw new ServiceException(ExceptionStatus.LENT_CLUB);
case IMMINENT_EXPIRATION:
throw new ServiceException(ExceptionStatus.LENT_EXPIRE_IMMINENT);
case ALREADY_LENT_USER:
throw new ServiceException(ExceptionStatus.LENT_ALREADY_EXISTED);
case ALL_BANNED_USER:
throw new ServiceException(ExceptionStatus.ALL_BANNED_USER);
case SHARE_BANNED_USER:
throw new ServiceException(ExceptionStatus.SHARE_BANNED_USER);
case BLACKHOLED_USER:
throw new ServiceException(ExceptionStatus.BLACKHOLED_USER);
case NOT_USER:
case INTERNAL_ERROR:
default:
throw new ServiceException(ExceptionStatus.INTERNAL_SERVER_ERROR);
}
}

/**
* 사물함에 남은 자리가 있는 지 확인합니다. 남은 자리가 없으면 throw합니다.
Expand Down
Loading
Loading