Skip to content

Commit 66a101a

Browse files
feat: 대기열 도메인 앞 순위 사람 입장시키는 개발용 API 추가
* feat: top n명 입장처리 * feat: 본인 순서 앞까지 사용자 모두 입장 처리 구현 * feat: queueEntry.http 추가 * feat: testcode 추가 & TestRedisConfig에도 비밀번호 명시적으로 없다고 추가
1 parent 6e073f8 commit 66a101a

11 files changed

Lines changed: 437 additions & 4 deletions

File tree

backend/src/main/java/com/back/api/queue/controller/AdminQueueEntryApi.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,4 +73,5 @@ ApiResponse<Void> resetQueue(
7373
@Parameter(description = "이벤트 ID", example = "1")
7474
@PathVariable Long eventId
7575
);
76+
7677
}

backend/src/main/java/com/back/api/queue/controller/AdminQueueEntryController.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,4 +79,5 @@ public ApiResponse<Void> resetQueue(
7979
queueEntryRedisRepository.clearAll(eventId);
8080
return ApiResponse.ok("대기열이 초기화되었습니다.", null);
8181
}
82+
8283
}

backend/src/main/java/com/back/api/queue/controller/QueueEntryApi.java

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
11
package com.back.api.queue.controller;
22

33
import org.springframework.web.bind.annotation.PathVariable;
4+
import org.springframework.web.bind.annotation.RequestBody;
45

6+
import com.back.api.queue.dto.request.ProcessEntriesRequest;
7+
import com.back.api.queue.dto.response.ProcessEntriesResponse;
58
import com.back.api.queue.dto.response.QueueEntryStatusResponse;
69
import com.back.global.config.swagger.ApiErrorCode;
710
import com.back.global.response.ApiResponse;
811

912
import io.swagger.v3.oas.annotations.Operation;
1013
import io.swagger.v3.oas.annotations.Parameter;
1114
import io.swagger.v3.oas.annotations.tags.Tag;
15+
import jakarta.validation.Valid;
1216

1317
@Tag(name = "QueueEntry API", description = "사용자 대기열 API")
1418
public interface QueueEntryApi {
@@ -35,4 +39,40 @@ ApiResponse<Boolean> existsInQueue(
3539
@Parameter(description = "이벤트 ID", example = "1")
3640
@PathVariable Long eventId
3741
);
42+
43+
@Operation(
44+
summary = "[테스트용] 상위 N명 입장 처리",
45+
description = "대기열 상위 N명을 즉시 입장 처리합니다."
46+
)
47+
48+
@ApiErrorCode({
49+
"NOT_FOUND_QUEUE_ENTRY",
50+
"ALREADY_ENTERED",
51+
"ALREADY_EXPIRED",
52+
"ALREADY_COMPLETED",
53+
"NOT_WAITING_STATUS"
54+
})
55+
ApiResponse<ProcessEntriesResponse> processTopEntries(
56+
@Parameter(description = "이벤트 ID", example = "1")
57+
@PathVariable Long eventId,
58+
59+
@RequestBody(required = false) @Valid ProcessEntriesRequest request
60+
);
61+
62+
@Operation(
63+
summary = "[테스트용] 본인 제외 상위 사용자 모두 입장 처리",
64+
description = "내 앞 순번의 모든 사람을 즉시 입장 처리합니다."
65+
)
66+
67+
@ApiErrorCode({
68+
"NOT_FOUND_QUEUE_ENTRY",
69+
"ALREADY_ENTERED",
70+
"ALREADY_EXPIRED",
71+
"ALREADY_COMPLETED",
72+
"NOT_WAITING_STATUS"
73+
})
74+
ApiResponse<ProcessEntriesResponse> processUntilMe(
75+
@Parameter(description = "이벤트 ID", example = "1")
76+
@PathVariable Long eventId
77+
);
3878
}

backend/src/main/java/com/back/api/queue/controller/QueueEntryController.java

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,20 @@
22

33
import org.springframework.web.bind.annotation.GetMapping;
44
import org.springframework.web.bind.annotation.PathVariable;
5+
import org.springframework.web.bind.annotation.PostMapping;
6+
import org.springframework.web.bind.annotation.RequestBody;
57
import org.springframework.web.bind.annotation.RequestMapping;
68
import org.springframework.web.bind.annotation.RestController;
79

10+
import com.back.api.queue.dto.request.ProcessEntriesRequest;
11+
import com.back.api.queue.dto.response.ProcessEntriesResponse;
812
import com.back.api.queue.dto.response.QueueEntryStatusResponse;
13+
import com.back.api.queue.service.QueueEntryProcessService;
914
import com.back.api.queue.service.QueueEntryReadService;
1015
import com.back.global.http.HttpRequestContext;
1116
import com.back.global.response.ApiResponse;
1217

18+
import jakarta.validation.Valid;
1319
import lombok.RequiredArgsConstructor;
1420

1521

@@ -20,13 +26,14 @@ public class QueueEntryController implements QueueEntryApi {
2026

2127
private final QueueEntryReadService queueEntryReadService;
2228
private final HttpRequestContext httpRequestContext;
29+
private final QueueEntryProcessService queueEntryProcessService;
2330

2431
@Override
2532
@GetMapping("/{eventId}/status")
2633
public ApiResponse<QueueEntryStatusResponse> getMyQueueEntryStatus(
2734
@PathVariable Long eventId
2835
) {
29-
Long userId = httpRequestContext.getUser().getId();
36+
Long userId = httpRequestContext.getUserId();
3037
QueueEntryStatusResponse response = queueEntryReadService.getMyQueueStatus(eventId, userId);
3138
return ApiResponse.ok("대기열 상태를 조회했습니다.", response);
3239

@@ -37,9 +44,41 @@ public ApiResponse<QueueEntryStatusResponse> getMyQueueEntryStatus(
3744
public ApiResponse<Boolean> existsInQueue(
3845
@PathVariable Long eventId
3946
) {
40-
Long userId = httpRequestContext.getUser().getId();
47+
Long userId = httpRequestContext.getUserId();
4148
boolean exists = queueEntryReadService.existsInWaitingQueue(eventId, userId);
4249
return ApiResponse.ok("대기열 진입 여부를 확인했습니다.", exists);
4350
}
4451

52+
@Override
53+
@PostMapping("/{eventId}/process-entries")
54+
public ApiResponse<ProcessEntriesResponse> processTopEntries(
55+
@PathVariable Long eventId,
56+
@RequestBody(required = false) @Valid ProcessEntriesRequest request
57+
) {
58+
Long userId = httpRequestContext.getUserId();
59+
int entryCount = (request != null) ? request.getCountOrDefault() : 1;
60+
61+
ProcessEntriesResponse response = queueEntryProcessService.processTopEntriesForTest(
62+
eventId,
63+
entryCount
64+
);
65+
66+
return ApiResponse.ok("입장 처리가 완료되었습니다.", response);
67+
}
68+
69+
@Override
70+
@PostMapping("/{eventId}/process-until-me")
71+
public ApiResponse<ProcessEntriesResponse> processUntilMe(
72+
@PathVariable Long eventId
73+
) {
74+
Long userId = httpRequestContext.getUserId();
75+
76+
ProcessEntriesResponse response = queueEntryProcessService.processTopEntriesUntilMeForTest(
77+
eventId,
78+
userId
79+
);
80+
81+
return ApiResponse.ok("내 앞 사용들이 모두 입장 처리가 완료되었습니다.", response);
82+
}
83+
4584
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package com.back.api.queue.dto.request;
2+
3+
import com.fasterxml.jackson.annotation.JsonIgnore;
4+
5+
import io.swagger.v3.oas.annotations.media.Schema;
6+
import jakarta.validation.constraints.Min;
7+
8+
@Schema(description = "[테스트용] 대기열 입장 처리 요청 DTO")
9+
public record ProcessEntriesRequest(
10+
@Schema(
11+
description = "입장 처리할 인원 수",
12+
example = "5",
13+
defaultValue = "1"
14+
)
15+
@Min(value = 1, message = "최소 1명 이상이어야 합니다.")
16+
Integer count
17+
) {
18+
19+
@JsonIgnore
20+
public int getCountOrDefault() {
21+
return count != null ? count : 1;
22+
}
23+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package com.back.api.queue.dto.response;
2+
3+
import io.swagger.v3.oas.annotations.media.Schema;
4+
5+
@Schema(description = "[테스트용] 대기열 입장 처리 응답 DTO")
6+
public record ProcessEntriesResponse(
7+
8+
@Schema(description = "이벤트 ID", example = "1")
9+
Long eventId,
10+
11+
@Schema(description = "입장 처리된 인원 수", example = "5")
12+
int processedCount,
13+
14+
@Schema(description = "남은 대기 인원 수", example = "50")
15+
long remainingWaitingCount
16+
) {
17+
public static ProcessEntriesResponse from(
18+
Long eventId,
19+
int processedCount,
20+
long remainingWaitingCount
21+
) {
22+
return new ProcessEntriesResponse(
23+
eventId,
24+
processedCount,
25+
remainingWaitingCount
26+
);
27+
}
28+
}

backend/src/main/java/com/back/api/queue/service/QueueEntryProcessService.java

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import com.back.api.queue.dto.response.CompletedQueueResponse;
1414
import com.back.api.queue.dto.response.EnteredQueueResponse;
1515
import com.back.api.queue.dto.response.ExpiredQueueResponse;
16+
import com.back.api.queue.dto.response.ProcessEntriesResponse;
1617
import com.back.api.queue.dto.response.WaitingQueueBatchEventResponse;
1718
import com.back.api.queue.dto.response.WaitingQueueResponse;
1819
import com.back.domain.event.entity.Event;
@@ -156,6 +157,86 @@ public boolean canEnterEntry(Long eventId, Long userId) {
156157
.orElse(false);
157158
}
158159

160+
/* ==================== 테스트용 상위 N명 입장 처리 ==================== */
161+
162+
@Transactional
163+
public ProcessEntriesResponse processTopEntriesForTest(Long eventId, int count) {
164+
165+
Long totalWaitingCount = queueEntryRedisRepository.getTotalWaitingCount(eventId);
166+
167+
if(totalWaitingCount == 0) {
168+
return ProcessEntriesResponse.from(eventId, 0, 0L);
169+
}
170+
171+
int actualCount = Math.min(count, totalWaitingCount.intValue());
172+
173+
Set<Object> topWaitingUsers = queueEntryRedisRepository.getTopWaitingUsers(eventId, actualCount);
174+
175+
if(topWaitingUsers.isEmpty()) {
176+
throw new ErrorException(QueueEntryErrorCode.NOT_INVALID_COUNT);
177+
}
178+
179+
List<Long> userIds = new ArrayList<>();
180+
181+
for(Object userId : topWaitingUsers) {
182+
userIds.add(Long.parseLong(userId.toString()));
183+
}
184+
185+
processBatchEntry(eventId, userIds);
186+
publishWaitingUpdateEvents(eventId);
187+
188+
Long remainingCount = queueEntryRedisRepository.getTotalWaitingCount(eventId);
189+
return ProcessEntriesResponse.from(eventId, userIds.size(), remainingCount);
190+
}
191+
192+
/* ==================== 테스트용 내 앞에 사람 모두 입장 처리 ==================== */
193+
194+
@Transactional
195+
public ProcessEntriesResponse processTopEntriesUntilMeForTest(Long eventId, Long userId) {
196+
197+
QueueEntry myEntry = queueEntryRepository.findByEvent_IdAndUser_Id(eventId, userId)
198+
.orElseThrow(() -> new ErrorException(QueueEntryErrorCode.NOT_FOUND_QUEUE_ENTRY));
199+
200+
if(myEntry.getQueueEntryStatus() != QueueEntryStatus.WAITING) {
201+
throw new ErrorException(QueueEntryErrorCode.NOT_WAITING_STATUS);
202+
}
203+
204+
Long myRank = queueEntryRedisRepository.getMyRankInWaitingQueue(eventId, userId);
205+
206+
if (myRank == null || myRank <= 1) {
207+
return ProcessEntriesResponse.from(eventId, 0,
208+
queueEntryRedisRepository.getTotalWaitingCount(eventId));
209+
}
210+
211+
int countToProcess = myRank.intValue() - 1;
212+
213+
Set<Object> topWaitingUsers = queueEntryRedisRepository.getTopWaitingUsers(eventId, countToProcess);
214+
215+
if(topWaitingUsers.isEmpty()) {
216+
throw new ErrorException(QueueEntryErrorCode.NOT_INVALID_COUNT);
217+
}
218+
219+
List<Long> userIds = new ArrayList<>();
220+
for (Object userIdObj : topWaitingUsers) {
221+
Long targetUserId = Long.parseLong(userIdObj.toString());
222+
if (!targetUserId.equals(userId)) {
223+
userIds.add(targetUserId);
224+
}
225+
}
226+
227+
if (userIds.isEmpty()) {
228+
return ProcessEntriesResponse.from(eventId, 0,
229+
queueEntryRedisRepository.getTotalWaitingCount(eventId));
230+
}
231+
232+
processBatchEntry(eventId, userIds);
233+
publishWaitingUpdateEvents(eventId);
234+
235+
Long remainingCount = queueEntryRedisRepository.getTotalWaitingCount(eventId);
236+
return ProcessEntriesResponse.from(eventId, userIds.size(), remainingCount);
237+
}
238+
239+
159240
/* ==================== 만료 처리 ==================== */
160241

161242
@Transactional

backend/src/main/java/com/back/global/error/code/QueueEntryErrorCode.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,9 @@ public enum QueueEntryErrorCode implements ErrorCode {
2525

2626
ALREADY_COMPLETED(HttpStatus.BAD_REQUEST, "이미 결제가 완료되었습니다."),
2727
NOT_ENTERED_STATUS(HttpStatus.BAD_REQUEST, "입장 완료 상태가 아닙니다."),
28-
CANNOT_COMPLETE_PAYMENT(HttpStatus.BAD_REQUEST, "결제 완료 처리를 할 수 없는 상태입니다.");
28+
CANNOT_COMPLETE_PAYMENT(HttpStatus.BAD_REQUEST, "결제 완료 처리를 할 수 없는 상태입니다."),
2929

30+
NOT_INVALID_COUNT(HttpStatus.BAD_REQUEST, "입장시킬 사용자가 없습니다.");
3031
private final HttpStatus httpStatus;
3132
private final String message;
3233
}

0 commit comments

Comments
 (0)