Skip to content

Commit c96c079

Browse files
feat: 티켓 QR JWT 토큰 발급 & 검증 & 사용 완료 처리
* feat: Qr 초반 클래스 설정 * feat: QrTokenService 완성 * feat: qr controller 완성 * feat: qr controller 완성 * feat: securityConfig 설정 * refactor: issuedAt 변경 * feat: Transactional * fix: redis bean 명시 * fix: 테스트코드 수정 * feat: qr url 변경 * feat: api 설명 수정 * feat: redis template 이름 변경 * feat: verfiy api 관리자 변경 고려 TODO 작성
1 parent ead5512 commit c96c079

16 files changed

Lines changed: 446 additions & 11 deletions

File tree

backend/src/main/java/com/back/api/auth/service/ActiveSessionCache.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ public class ActiveSessionCache {
3737
private final ActiveSessionRepository activeSessionRepository;
3838

3939
public ActiveSessionCache(
40-
@Qualifier("activeSessionRedisTemplate") RedisTemplate<String, String> redisTemplate,
40+
@Qualifier("stringTemplate") RedisTemplate<String, String> redisTemplate,
4141
ActiveSessionRepository activeSessionRepository
4242
) {
4343
this.redisTemplate = redisTemplate;

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ public WaitingQueueResponse buildWaitingQueueResponseFromRank(
103103
estimatedWaitTime = 1;
104104
progress = 99;
105105
} else {
106-
estimatedWaitTime = waitingAhead * 3;
106+
estimatedWaitTime = waitingAhead * 2;
107107
progress = totalWaitingCount > 0
108108
? (int)(((totalWaitingCount - waitingAhead) * 100) / totalWaitingCount)
109109
: 0;
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package com.back.api.ticket.controller;
2+
3+
import org.springframework.web.bind.annotation.PathVariable;
4+
import org.springframework.web.bind.annotation.RequestParam;
5+
6+
import com.back.api.ticket.dto.response.QrTokenResponse;
7+
import com.back.api.ticket.dto.response.QrValidationResponse;
8+
import com.back.global.config.swagger.ApiErrorCode;
9+
import com.back.global.response.ApiResponse;
10+
11+
import io.swagger.v3.oas.annotations.Operation;
12+
import io.swagger.v3.oas.annotations.Parameter;
13+
import io.swagger.v3.oas.annotations.tags.Tag;
14+
15+
@Tag(name = "QR API", description = "티켓 QR 발급 및 검증 API")
16+
public interface QrApi {
17+
18+
@Operation(
19+
summary = "QR 토큰 발급",
20+
description = "티켓에 대한 QR 토큰을 발급합니다. 이벤트 당일부터 발급이 됩니다."
21+
)
22+
@ApiErrorCode({
23+
"TICKET_NOT_FOUND",
24+
"UNAUTHORIZED_TICKET_ACCESS",
25+
"INVALID_TICKET_STATE",
26+
"EVENT_NOT_STARTED"
27+
})
28+
ApiResponse<QrTokenResponse> generateQrToken(
29+
@Parameter(description = "티켓 ID", example = "1")
30+
@PathVariable Long ticketId
31+
);
32+
33+
@Operation(
34+
summary = "QR 코드 검증",
35+
description = "QR을 스캔하고 입장 가능 여부를 검증합니다."
36+
)
37+
@ApiErrorCode({
38+
"INVALID_QR_TOKEN",
39+
"QR_TOKEN_EXPIRED",
40+
"TICKET_NOT_FOUND"
41+
})
42+
ApiResponse<QrValidationResponse> validateQrCode(
43+
@Parameter(description = "QR 토큰", example = "abc123xyz456")
44+
@RequestParam String token
45+
);
46+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package com.back.api.ticket.controller;
2+
3+
import org.springframework.web.bind.annotation.GetMapping;
4+
import org.springframework.web.bind.annotation.PathVariable;
5+
import org.springframework.web.bind.annotation.PostMapping;
6+
import org.springframework.web.bind.annotation.RequestMapping;
7+
import org.springframework.web.bind.annotation.RequestParam;
8+
import org.springframework.web.bind.annotation.RestController;
9+
10+
import com.back.api.ticket.dto.response.QrTokenResponse;
11+
import com.back.api.ticket.dto.response.QrValidationResponse;
12+
import com.back.api.ticket.service.QrService;
13+
import com.back.global.http.HttpRequestContext;
14+
import com.back.global.response.ApiResponse;
15+
16+
import lombok.RequiredArgsConstructor;
17+
18+
@RestController
19+
@RequestMapping("/api/v1/tickets")
20+
@RequiredArgsConstructor
21+
public class QrController implements QrApi {
22+
23+
private final QrService qrService;
24+
private final HttpRequestContext httpRequestContext;
25+
26+
@Override
27+
@PostMapping("/{ticketId}/qr-token")
28+
public ApiResponse<QrTokenResponse> generateQrToken(
29+
@PathVariable Long ticketId
30+
){
31+
Long userId = httpRequestContext.getUserId();
32+
QrTokenResponse response = qrService.generateQrTokenResponse(ticketId, userId);
33+
34+
return ApiResponse.ok("QR 토큰 발급 성공", response);
35+
}
36+
37+
//TODO 관리자 전용 API로 변경 고려
38+
@Override
39+
@GetMapping("/entry/verify")
40+
public ApiResponse<QrValidationResponse> validateQrCode(
41+
@RequestParam String token
42+
){
43+
QrValidationResponse response = qrService.validateAndProcessEntry(token);
44+
45+
return ApiResponse.ok("QR 코드 검증 & 사용 처리 성공", response);
46+
}
47+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package com.back.api.ticket.dto.response;
2+
3+
import io.swagger.v3.oas.annotations.media.Schema;
4+
5+
@Schema(description = "QR 토큰 응답 DTO")
6+
public record QrTokenResponse(
7+
@Schema(description = "QR에 포함될 JWT 토큰", example = "abc123xyz456")
8+
String qrToken,
9+
10+
@Schema(description = "토큰 만료 시간(초)", example = "60")
11+
int expirationSecond,
12+
13+
@Schema(description = "QR 코드 갱신 간격(초)", example = "30")
14+
int refreshIntervalSecond,
15+
16+
@Schema(description = "QR 코드 URL", example = "https://www.waitfair.com/tickets/verifiy?token=abc123xyz456")
17+
String qrUrl
18+
19+
) {
20+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package com.back.api.ticket.dto.response;
2+
3+
import java.time.LocalDateTime;
4+
5+
import io.swagger.v3.oas.annotations.media.Schema;
6+
7+
@Schema(description = "QR 검증 응답 DTO")
8+
public record QrValidationResponse(
9+
10+
@Schema(description = "QR 유효 여부", example = "true")
11+
boolean isValid,
12+
13+
@Schema(description = "상태 메세지", example = "QR 코드가 유효합니다.")
14+
String message,
15+
16+
@Schema(description = "티켓 ID", example = "1")
17+
Long ticketId,
18+
19+
@Schema(description = "이벤트 ID", example = "1")
20+
Long eventId,
21+
22+
@Schema(description = "이벤트 제목", example = "2024 콘서트")
23+
String eventTitle,
24+
25+
@Schema(description = "좌석 코드", example = "A1")
26+
String seatCode,
27+
28+
@Schema(description = "소유자 닉네임", example = "testnick")
29+
String ownerNickname,
30+
31+
@Schema(description = "이벤트 일시", example = "2026-01-31T20:00:00")
32+
LocalDateTime eventDate,
33+
34+
@Schema(description = "QR 코드 발급 시간", example = "2026-01-31T12:00:00")
35+
LocalDateTime qrIssuedAt
36+
37+
) {
38+
}

0 commit comments

Comments
 (0)