From 5d21fef77840262f15556c872c361ee627b5dc9d Mon Sep 17 00:00:00 2001 From: thguss Date: Tue, 10 Sep 2024 00:43:23 +0900 Subject: [PATCH] add: added aop for duplicated request --- .../smeem/common/exception/ExceptionCode.java | 1 + smeem-input-http/build.gradle | 3 + .../http/aspect/DuplicateRequestAspect.java | 57 +++++++++++++++++++ ...rBadgeApiV3.java => MemberBadgeV3Api.java} | 4 +- ...iV3Docs.java => MemberBadgeV3ApiDocs.java} | 2 +- 5 files changed, 64 insertions(+), 3 deletions(-) create mode 100644 smeem-input-http/src/main/java/com/smeem/http/aspect/DuplicateRequestAspect.java rename smeem-input-http/src/main/java/com/smeem/http/controller/{MemberBadgeApiV3.java => MemberBadgeV3Api.java} (90%) rename smeem-input-http/src/main/java/com/smeem/http/controller/docs/{MemberBadgeApiV3Docs.java => MemberBadgeV3ApiDocs.java} (96%) diff --git a/smeem-common/src/main/java/com/smeem/common/exception/ExceptionCode.java b/smeem-common/src/main/java/com/smeem/common/exception/ExceptionCode.java index 66e728bf..2f4e95ac 100644 --- a/smeem-common/src/main/java/com/smeem/common/exception/ExceptionCode.java +++ b/smeem-common/src/main/java/com/smeem/common/exception/ExceptionCode.java @@ -9,6 +9,7 @@ public enum ExceptionCode { // 4xx UNAUTHORIZED(401, "유효하지 않은 토큰 "), NOT_FOUND(404, "존재하지 않음 "), + TOO_MANY_REQUESTS(429, "너무 많은 요청"), // 5xx SERVICE_AVAILABLE(503, "서비스에 접근할 수 없음 "), diff --git a/smeem-input-http/build.gradle b/smeem-input-http/build.gradle index c5ab80e6..7344ef6a 100644 --- a/smeem-input-http/build.gradle +++ b/smeem-input-http/build.gradle @@ -13,6 +13,9 @@ project(':smeem-input-http') { implementation group: 'io.jsonwebtoken', name: 'jjwt-api', version: '0.11.5' implementation group: 'io.jsonwebtoken', name: 'jjwt-impl', version: '0.11.5' implementation group: 'io.jsonwebtoken', name: 'jjwt-jackson', version: '0.11.5' + + // AOP + implementation 'org.springframework.boot:spring-boot-starter-aop' } } diff --git a/smeem-input-http/src/main/java/com/smeem/http/aspect/DuplicateRequestAspect.java b/smeem-input-http/src/main/java/com/smeem/http/aspect/DuplicateRequestAspect.java new file mode 100644 index 00000000..7b50d59e --- /dev/null +++ b/smeem-input-http/src/main/java/com/smeem/http/aspect/DuplicateRequestAspect.java @@ -0,0 +1,57 @@ +package com.smeem.http.aspect; + +import com.smeem.common.exception.ExceptionCode; +import com.smeem.http.controller.dto.ExceptionResponse; +import lombok.val; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Component; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +@Aspect +@Component +public class DuplicateRequestAspect { + private final Set requestSet = Collections.synchronizedSet(new HashSet<>()); + + @Pointcut("within(*..*Api)") + public void onRequest() { + } + + @Around("onRequest()") + public Object duplicateRequestCheck(ProceedingJoinPoint joinPoint) throws Throwable { + val request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest(); + val httpMethod = request.getMethod(); + + // GET 요청일 경우 중복 체크 생략 + if ("GET".equalsIgnoreCase(httpMethod)) { + return joinPoint.proceed(); + } + + val requestId = joinPoint.getSignature().toLongString(); + if (requestSet.contains(requestId)) { + // 중복 요청인 경우 + return handleDuplicateRequest(); + } + requestSet.add(requestId); + + try { + return joinPoint.proceed(); + } finally { // 요청 후 요청값 삭제 + requestSet.remove(requestId); + } + } + + private ResponseEntity handleDuplicateRequest() { + return ResponseEntity + .status(ExceptionCode.TOO_MANY_REQUESTS.getStatusCode()) + .body(ExceptionResponse.of(ExceptionCode.TOO_MANY_REQUESTS.getMessage() + ": 중복된 요청")); + } +} diff --git a/smeem-input-http/src/main/java/com/smeem/http/controller/MemberBadgeApiV3.java b/smeem-input-http/src/main/java/com/smeem/http/controller/MemberBadgeV3Api.java similarity index 90% rename from smeem-input-http/src/main/java/com/smeem/http/controller/MemberBadgeApiV3.java rename to smeem-input-http/src/main/java/com/smeem/http/controller/MemberBadgeV3Api.java index f171e0bc..7435f3aa 100644 --- a/smeem-input-http/src/main/java/com/smeem/http/controller/MemberBadgeApiV3.java +++ b/smeem-input-http/src/main/java/com/smeem/http/controller/MemberBadgeV3Api.java @@ -4,7 +4,7 @@ import com.smeem.application.port.input.BadgeUseCase; import com.smeem.http.controller.dto.SmeemResponse; import com.smeem.application.port.input.dto.response.badge.RetrieveMemberBadgesResponse; -import com.smeem.http.controller.docs.MemberBadgeApiV3Docs; +import com.smeem.http.controller.docs.MemberBadgeV3ApiDocs; import com.smeem.common.util.SmeemConverter; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; @@ -18,7 +18,7 @@ @RestController @RequiredArgsConstructor @RequestMapping("/api/v3/members/badges") -public class MemberBadgeApiV3 implements MemberBadgeApiV3Docs { +public class MemberBadgeV3Api implements MemberBadgeV3ApiDocs { private final BadgeUseCase badgeUseCase; private final SmeemConverter smeemConverter; diff --git a/smeem-input-http/src/main/java/com/smeem/http/controller/docs/MemberBadgeApiV3Docs.java b/smeem-input-http/src/main/java/com/smeem/http/controller/docs/MemberBadgeV3ApiDocs.java similarity index 96% rename from smeem-input-http/src/main/java/com/smeem/http/controller/docs/MemberBadgeApiV3Docs.java rename to smeem-input-http/src/main/java/com/smeem/http/controller/docs/MemberBadgeV3ApiDocs.java index 300b9de1..f3cf4df2 100644 --- a/smeem-input-http/src/main/java/com/smeem/http/controller/docs/MemberBadgeApiV3Docs.java +++ b/smeem-input-http/src/main/java/com/smeem/http/controller/docs/MemberBadgeV3ApiDocs.java @@ -13,7 +13,7 @@ import java.security.Principal; @Tag(name = "MemberBadgeApi (v3)", description = "회원의 배지(Badge) 관련 Api 입니다.") -public interface MemberBadgeApiV3Docs { +public interface MemberBadgeV3ApiDocs { @Operation(summary = "배지 조회 api", description = "회원의 배지 정보를 조회합니다.") @ApiResponses(value = {