이번 단계에서는 접속 대기열 시스템에서 사용자의 이탈을 관리하고, 이를 테스트하는 방법을 다룹니다. 이를 통해 사용자가 대기열에서 벗어나거나 접속을 유지하는 로직을 구현하고 검증할 수 있습니다.
1. UserQueueController
UserQueueController
는 사용자의 대기열 정보를 처리하고, 쿠키를 사용해 접속 정보를 유지합니다.
touch
메서드
@GetMapping("/touch")
Mono<?> touch(
@RequestParam(name = "queue", defaultValue = "default") String queue,
@RequestParam(name = "user_id") Long userId,
ServerWebExchange exchange
) {
return Mono.defer(() -> userQueueService.generateToken(queue, userId))
.map(token -> {
exchange.getResponse().addCookie(
ResponseCookie.from("user-queue-%s-token".formatted(queue), token)
.maxAge(Duration.ofSeconds(300))
.path("/")
.build()
);
return token;
});
}
1. 기능 설명:
- 사용자가 대기열에 접속할 때 호출되는 메서드입니다.
generateToken
메서드를 호출하여 사용자의 토큰을 생성하고, 이를 쿠키에 저장합니다.
2. 쿠키 설정:
ResponseCookie
를 사용해"user-queue-%s-token"
형식의 쿠키를 설정합니다. 쿠키는 300초(5분) 동안 유효하며, 대기열 접속 상태를 유지합니다.
2. WaitingRoomController
WaitingRoomController
는 대기 중인 사용자의 접속을 확인하고, 입장이 허용되었는지 여부를 검사합니다.
waitingRoomPage
메서드
public class WaitingRoomController {
private final UserQueueService userQueueService;
@GetMapping("/waiting-room")
public Mono<Rendering> waitingRoomPage(
@RequestParam(name = "queue", defaultValue = "default") String queue,
@RequestParam(name = "user_id") Long userId,
@RequestParam(name = "redirect_url") String redirectUrl,
ServerWebExchange exchange
) {
var key = "user-queue-%s-token".formatted(queue);
var cookieValue = exchange.getRequest().getCookies().getFirst(key);
var token = (cookieValue == null) ? "" : cookieValue.getValue();
return checkIfUserIsAllowed(queue, userId, token, redirectUrl)
.switchIfEmpty(registerUserAndShowWaitingPage(queue, userId));
}
private Mono<Rendering> checkIfUserIsAllowed(String queue, Long userId, String token, String redirectUrl) {
return userQueueService.isAllowedByToken(queue, userId, token)
.filter(Boolean::booleanValue)
.flatMap(allowed -> Mono.just(Rendering.redirectTo(redirectUrl).build()));
}
}
1. 기능 설명:
- 사용자가 대기 중인지 확인하고, 쿠키를 통해 접속 상태를 검증합니다.
- 입장이 허용된 경우,
redirectUrl
로 리다이렉트합니다.
2. 쿠키 검증:
- 대기열 토큰이 쿠키에 저장되어 있는지 확인합니다. 만약 토큰이 없다면, 사용자는 등록되지 않은 상태로 간주됩니다.
3. 허용 여부 검사:
checkIfUserIsAllowed
메서드를 통해 사용자가 접속 허용 상태인지 검증합니다. 허용된 경우 리다이렉트 응답을 반환합니다.
3. UserQueueService
UserQueueService
는 대기열 시스템의 비즈니스 로직을 처리하며, 토큰을 생성하고 검증합니다.
isAllowedByToken
메서드
public Mono<Boolean> isAllowedByToken(final String queue, final Long userId, final String token) {
return this.generateToken(queue, userId)
.filter(gen -> gen.equalsIgnoreCase(token))
.map(i -> true)
.defaultIfEmpty(false);
}
1. 기능 설명:
- 사용자가 제공한 토큰이 서버에서 생성한 토큰과 일치하는지 검사합니다.
- 토큰이 일치하면
true
, 그렇지 않으면false
를 반환합니다.
2. 토큰 검증 로직:
generateToken
메서드를 통해 생성된 토큰과 사용자가 보낸 토큰을 비교하여 일치 여부를 확인합니다.
generateToken
메서드
public Mono<String> generateToken(final String queue, final Long userId) {
MessageDigest digest = null;
try {
digest = MessageDigest.getInstance("SHA-256");
var input = "user-queue-%s-%d".formatted(queue, userId);
byte[] encodeHash = digest.digest(input.getBytes(StandardCharsets.UTF_8));
StringBuilder hexString = new StringBuilder();
for (byte aByte : encodeHash) {
hexString.append(String.format("%02x", aByte));
}
return Mono.just(hexString.toString());
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
1. 기능 설명:
- SHA-256 해시 알고리즘을 사용해 토큰을 생성합니다.
- 토큰은
"user-queue-<queue_name>-<user_id>"
형식의 문자열을 해싱하여 생성됩니다.
2. 토큰의 용도:
- 생성된 토큰은 사용자가 대기열에 등록된 상태를 검증하는 데 사용됩니다.
4. UserQueueServiceTest
테스트 클래스는 UserQueueService
의 토큰 생성 및 검증 로직을 테스트합니다.
isAllowedByToken
테스트
@Test
void isNotAllowedByToken() {
StepVerifier.create(userQueueService.isAllowedByToken("default", 100L, ""))
.expectNext(false)
.verifyComplete();
}
@Test
void isAllowedByToken() {
StepVerifier.create(userQueueService.isAllowedByToken("default", 100L, "d333a5d4eb24f3f5cdd767d79b8c01aad3cd73d3537c70dec430455d37afe4b8"))
.expectNext(true)
.verifyComplete();
}
1. 설명:
isNotAllowedByToken
테스트는 빈 토큰이 제공된 경우 허용되지 않음을 확인합니다.isAllowedByToken
테스트는 올바른 토큰이 제공된 경우 허용됨을 확인합니다.
generateToken
테스트
@Test
void generateToken() {
StepVerifier.create(userQueueService.generateToken("default", 100L))
.expectNext("d333a5d4eb24f3f5cdd767d79b8c01aad3cd73d3537c70dec430455d37afe4b8")
.verifyComplete();
}
1. 설명:
generateToken
테스트는queue
와userId
값에 따라 일관된 토큰이 생성되는지 검증합니다.
5. waiting-room.html
fetchWaitingRank
함수
응답이 성공적으로 반환되면 JSON 형식으로 파싱된 데이터를 사용해 대기열 정보를 업데이트합니다.
function fetchWaitingRank() {
const queue = `[[${queue}]]`;
const userId = `[[${userId}]]`;
const queryParam = new URLSearchParams({queue: queue, user_id: userId});
fetch('/api/v1/queue/rank?' + queryParam)
.then(response => response.json())
.then(data => {
const numberElement = document.querySelector('#number');
const timeElement = document.querySelector('#waiting-time');
if (data.rank < 0) {
fetch('/api/v1/queue/touch?' + queryParam)
.then(() => {
window.location.reload();
})
.catch(error => console.error(error));
return;
}
// 대기 인원 업데이트
if (numberElement) {
numberElement.innerHTML = data.rank + '명 이상';
}
// 예상 대기 시간 업데이트
if (timeElement) {
timeElement.innerHTML = calculateEstimatedTime(data.rank);
}
})
.catch(error => console.error(error));
}
1. 순위가 0보다 작은 경우 처리:
- 만약
data.rank
가 0보다 작으면, 사용자는 아직 대기열에 등록되지 않은 상태로 간주합니다. - 이 경우
/api/v1/queue/touch
엔드포인트에 요청을 보내어 접속 상태를 유지하고 페이지를 새로고침합니다.
2. 대기 인원 업데이트:
numberElement
가 존재하면, 대기 인원 숫자를 업데이트합니다. 예를 들어,100명 이상
과 같이 표시합니다.
3. 예상 대기 시간 업데이트:
timeElement
가 존재하면,calculateEstimatedTime
함수를 호출하여 계산된 예상 대기 시간을 업데이트합니다.
calculateEstimatedTime
함수
이 함수는 현재 대기열의 순위를 기반으로 예상 대기 시간을 계산합니다.
function calculateEstimatedTime(rank) {
// 대기열에서 10초마다 100명씩 제거
const removalRatePerSecond = 100 / 10;
const estimatedTimeInSeconds = rank / removalRatePerSecond;
const minutes = Math.floor(estimatedTimeInSeconds / 60);
const seconds = Math.floor(estimatedTimeInSeconds % 60);
return `${minutes}분 ${seconds < 10 ? '0' : ''}${seconds}초`;
}
1. 제거 속도 설정:
- 대기열에서 10초마다 100명이 제거된다고 가정합니다. 따라서 초당 제거 속도는
100 / 10 = 10명
입니다.
2. 예상 대기 시간 계산:
rank
값을 제거 속도로 나누어 총 예상 대기 시간을 초 단위로 계산합니다.
3. 분과 초로 변환:
- 계산된 초를 분(
minutes
)과 나머지 초(seconds
)로 변환하여"분 초"
형식의 문자열로 반환합니다.
주기적인 fetchWaitingRank
호출
setInterval(fetchWaitingRank, 3000);
1. setInterval
함수를 사용하여 fetchWaitingRank
함수를 3초마다 호출합니다.
2. 이를 통해 3초마다 대기열 순위와 예상 대기 시간을 업데이트하여 사용자에게 실시간 정보를 제공합니다.
6. QnA
1. UserQueueController
Q1. touch
메서드의 역할은 무엇인가요?
A1. touch
메서드는 사용자가 대기열에 접속할 때 호출되어 사용자의 접속 상태를 유지합니다. 이 메서드는 사용자 ID와 대기열 이름을 기반으로 토큰을 생성하고, 이를 쿠키에 저장하여 5분 동안 유효하게 합니다.
Q2. 쿠키에 저장된 토큰의 유효기간은 어떻게 설정되나요?
A2. ResponseCookie
를 사용하여 쿠키의 maxAge
를 300초(5분)로 설정합니다. 이 기간 동안 사용자는 대기 상태를 유지하며, 이후에는 토큰이 만료됩니다.
Q3. 쿠키를 사용하여 대기열 접속 정보를 유지하는 이유는 무엇인가요?
A3. 쿠키를 사용하면 서버가 사용자 상태를 관리하기 쉬워지며, 사용자가 새로고침하거나 재접속할 때에도 동일한 접속 상태를 유지할 수 있습니다.
2. WaitingRoomController
Q1. waitingRoomPage
메서드의 기능은 무엇인가요?
A1. waitingRoomPage
메서드는 사용자가 대기 중인지 확인하고, 접속이 허용된 경우에는 지정된 리다이렉트 URL로 이동시키는 역할을 합니다. 접속이 허용되지 않은 경우 대기열 페이지를 표시합니다.
Q2. 대기열 토큰은 어디에서 확인하나요?
A2. 대기열 토큰은 ServerWebExchange
에서 가져온 쿠키 값에서 확인합니다. 쿠키의 이름은 "user-queue-<queue_name>-token"
형식으로 설정됩니다.
Q3. checkIfUserIsAllowed
메서드는 어떤 경우에 리다이렉트를 하나요?
A3. checkIfUserIsAllowed
메서드는 사용자가 접속이 허용된 상태일 때, 지정된 redirectUrl
로 리다이렉트합니다. 허용되지 않은 경우, 대기열 페이지를 표시합니다.
3. UserQueueService
Q1. isAllowedByToken
메서드는 어떤 기능을 하나요?
A1. isAllowedByToken
메서드는 사용자가 제공한 토큰이 서버에서 생성한 토큰과 일치하는지 검증합니다. 일치하면 true
, 그렇지 않으면 false
를 반환하여 접속 허용 여부를 결정합니다.
Q2. generateToken
메서드는 어떤 방식으로 토큰을 생성하나요?
A2. generateToken
메서드는 SHA-256 해시 알고리즘을 사용하여 토큰을 생성합니다. "user-queue-<queue_name>-<user_id>"
형식의 문자열을 해싱하여 고유한 토큰을 만듭니다.
Q3. 토큰 검증 시 대소문자 구분은 어떻게 하나요?
A3. isAllowedByToken
메서드는 equalsIgnoreCase
를 사용하여 대소문자 구분 없이 토큰을 비교합니다.
4. UserQueueServiceTest
Q1. 테스트에서 StepVerifier
는 어떤 역할을 하나요?
A1. StepVerifier
는 리액티브 스트림의 결과를 검증하는 도구로, 예상되는 결과와 실제 결과가 일치하는지 단계별로 확인합니다. 예를 들어, expectNext
와 같은 메서드를 사용하여 반환값을 검증할 수 있습니다.
Q2. isAllowedByToken
테스트에서 사용된 토큰 값은 어떻게 생성되었나요?
A2. 테스트에서는 generateToken
메서드를 사용하여 미리 생성된 토큰 값을 사용합니다. 이를 통해 예측 가능한 결과로 검증할 수 있습니다.
Q3. generateToken
메서드의 테스트는 무엇을 검증하나요?
A3. generateToken
테스트는 특정 queue
와 userId
값에 대해 일관된 토큰이 생성되는지 확인합니다. 이를 통해 토큰 생성 로직이 예상대로 작동하는지 검증할 수 있습니다.
5. waiting-room.html
Q1. fetchWaitingRank
함수는 어떤 역할을 하나요?
A1. fetchWaitingRank
함수는 주기적으로 대기열의 순위를 확인하고, 대기 인원과 예상 대기 시간을 업데이트합니다. 이를 통해 실시간으로 사용자에게 정보를 제공합니다.
Q2. 순위가 0보다 작은 경우 어떻게 처리하나요?
A2. fetchWaitingRank
함수에서 순위가 0보다 작을 경우, /api/v1/queue/touch
를 호출하여 접속 상태를 유지하도록 합니다. 이후 페이지를 새로고침하여 사용자 정보를 갱신합니다.
Q3. calculateEstimatedTime
함수는 어떤 방식으로 예상 대기 시간을 계산하나요?
A3. calculateEstimatedTime
함수는 대기열의 제거 속도를 기반으로 남은 순위를 초 단위로 변환하여 예상 대기 시간을 계산합니다. 결과는 "분 초" 형식으로 반환됩니다.
6. index.html (waiting-web)
Q1. index
메서드의 역할은 무엇인가요?
A1. index
메서드는 사용자의 대기열 상태를 확인하고, 접속이 허용된 경우 메인 페이지로 이동합니다. 허용되지 않은 경우 대기 페이지로 리다이렉트합니다.
Q2. 쿠키에서 토큰을 추출하는 이유는 무엇인가요?
A2. 쿠키에서 토큰을 추출하여 사용자의 대기 상태를 인증합니다. 이를 통해 사용자가 대기열에 등록된 상태인지 확인할 수 있습니다.
Q3. RestTemplate
을 사용하여 API 요청을 보내는 이유는 무엇인가요?
A3. RestTemplate
을 사용하면 다른 서비스와 쉽게 HTTP 통신을 할 수 있습니다. 여기서는 사용자 대기열 상태를 확인하기 위해 외부 서비스에 API 요청을 보내는 데 사용됩니다.
'프레임워크 > 자바 스프링' 카테고리의 다른 글
접속자 대기열 시스템 #9- 테스트 (0) | 2024.10.10 |
---|---|
접속자 대기열 시스템 #7- 대기열 스케줄러 개발 (0) | 2024.10.10 |
접속자 대기열 시스템 #6- 접속 대기 웹페이지 개발 (1) | 2024.10.10 |
접속자 대기열 시스템 #5- Redis를 이용한 대기열 관리 및 웹페이지 진입 API 구현 (1) | 2024.10.10 |
접속자 대기열 시스템 #3- 셋업 (2) | 2024.10.10 |