-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: 상품의 조회수 기능 및 치팅 방지 기능 구현 (#29)
* feat: 조회수를 증가를 위한 Resolver 생성 (쿠키를 통해 조회수 치팅 방지) * refactor: 조회수 동시성 문제 해결 * refactor: 쿠키 관련 문서화 추가 * refactor: 토큰 추출 로직 인터페이스 분리 및 테스트 추가 * refactor: 변수명 변경 * refactor: 변수명 변경
- Loading branch information
Showing
18 changed files
with
316 additions
and
14 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
package com.market.market.config; | ||
|
||
import com.market.market.ui.support.resolver.ViewCountArgumentResolver; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.context.annotation.Configuration; | ||
import org.springframework.web.method.support.HandlerMethodArgumentResolver; | ||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; | ||
|
||
import java.util.List; | ||
|
||
@RequiredArgsConstructor | ||
@Configuration | ||
public class MarketConfig implements WebMvcConfigurer { | ||
|
||
private final ViewCountArgumentResolver viewCountArgumentResolver; | ||
|
||
@Override | ||
public void addArgumentResolvers(final List<HandlerMethodArgumentResolver> resolvers) { | ||
resolvers.add(viewCountArgumentResolver); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
11 changes: 11 additions & 0 deletions
11
src/main/java/com/market/market/ui/support/ViewCountChecker.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package com.market.market.ui.support; | ||
|
||
import java.lang.annotation.ElementType; | ||
import java.lang.annotation.Retention; | ||
import java.lang.annotation.RetentionPolicy; | ||
import java.lang.annotation.Target; | ||
|
||
@Target(ElementType.PARAMETER) | ||
@Retention(RetentionPolicy.RUNTIME) | ||
public @interface ViewCountChecker { | ||
} |
14 changes: 14 additions & 0 deletions
14
src/main/java/com/market/market/ui/support/resolver/ProductCookieHelper.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package com.market.market.ui.support.resolver; | ||
|
||
import jakarta.servlet.http.Cookie; | ||
import jakarta.servlet.http.HttpServletRequest; | ||
import jakarta.servlet.http.HttpServletResponse; | ||
|
||
public interface ProductCookieHelper { | ||
|
||
Cookie findCookie(final HttpServletRequest request); | ||
|
||
boolean hasAlreadyVisitedProduct(final Cookie cookie, final String productId); | ||
|
||
void updateCookie(final HttpServletResponse response, final Cookie cookie, final String productId); | ||
} |
65 changes: 65 additions & 0 deletions
65
src/main/java/com/market/market/ui/support/resolver/ProductCookieHelperImpl.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
package com.market.market.ui.support.resolver; | ||
|
||
import jakarta.servlet.http.Cookie; | ||
import jakarta.servlet.http.HttpServletRequest; | ||
import jakarta.servlet.http.HttpServletResponse; | ||
import org.springframework.stereotype.Component; | ||
|
||
import java.util.Arrays; | ||
|
||
@Component | ||
public class ProductCookieHelperImpl implements ProductCookieHelper { | ||
|
||
private static final String COOKIE_NAME = "productView"; | ||
private static final int COOKIE_VALID_TIME = 60 * 60 * 24; | ||
|
||
@Override | ||
public Cookie findCookie(final HttpServletRequest request) { | ||
Cookie[] cookies = request.getCookies(); | ||
|
||
if (cookies != null) { | ||
return Arrays.stream(cookies) | ||
.filter(it -> it.getName().equals(COOKIE_NAME)) | ||
.findAny() | ||
.orElseGet(null); | ||
} | ||
|
||
return null; | ||
} | ||
|
||
@Override | ||
public boolean hasAlreadyVisitedProduct(final Cookie cookie, final String productId) { | ||
return cookie != null && hasProductIdInCookie(cookie, productId); | ||
} | ||
|
||
@Override | ||
public void updateCookie(final HttpServletResponse response, final Cookie cookie, final String productId) { | ||
if (cookie != null && !hasProductIdInCookie(cookie, productId)) { | ||
addProductIdInCookie(cookie, productId, response); | ||
} | ||
|
||
if (cookie == null) { | ||
createProductIdCookie(productId, response); | ||
} | ||
} | ||
|
||
private boolean hasProductIdInCookie(final Cookie cookie, final String productId) { | ||
return cookie.getValue().contains("[" + productId + "]"); | ||
} | ||
|
||
private void addProductIdInCookie(final Cookie productViewCookie, final String productId, final HttpServletResponse response) { | ||
productViewCookie.setValue(productViewCookie.getValue() + "_[" + productId + "]"); | ||
addCookie(productViewCookie, response); | ||
} | ||
|
||
private void createProductIdCookie(final String productId, final HttpServletResponse response) { | ||
Cookie newCookie = new Cookie(COOKIE_NAME, "[" + productId + "]"); | ||
addCookie(newCookie, response); | ||
} | ||
|
||
private void addCookie(final Cookie cookie, final HttpServletResponse response) { | ||
cookie.setPath("/"); | ||
cookie.setMaxAge(COOKIE_VALID_TIME); | ||
response.addCookie(cookie); | ||
} | ||
} |
48 changes: 48 additions & 0 deletions
48
src/main/java/com/market/market/ui/support/resolver/ViewCountArgumentResolver.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
package com.market.market.ui.support.resolver; | ||
|
||
import com.market.market.ui.support.ViewCountChecker; | ||
import jakarta.servlet.http.Cookie; | ||
import jakarta.servlet.http.HttpServletRequest; | ||
import jakarta.servlet.http.HttpServletResponse; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.core.MethodParameter; | ||
import org.springframework.stereotype.Component; | ||
import org.springframework.web.bind.support.WebDataBinderFactory; | ||
import org.springframework.web.context.request.NativeWebRequest; | ||
import org.springframework.web.method.support.HandlerMethodArgumentResolver; | ||
import org.springframework.web.method.support.ModelAndViewContainer; | ||
|
||
@RequiredArgsConstructor | ||
@Component | ||
public class ViewCountArgumentResolver implements HandlerMethodArgumentResolver { | ||
|
||
private static final String REQUEST_URL_SEPARATOR = "/"; | ||
|
||
private final ProductCookieHelper productCookieHelper; | ||
|
||
@Override | ||
public boolean supportsParameter(final MethodParameter parameter) { | ||
return parameter.hasParameterAnnotation(ViewCountChecker.class) && | ||
parameter.getParameterType().equals(Boolean.class); | ||
} | ||
|
||
@Override | ||
public Object resolveArgument(final MethodParameter parameter, | ||
final ModelAndViewContainer mavContainer, | ||
final NativeWebRequest webRequest, | ||
final WebDataBinderFactory binderFactory) { | ||
HttpServletResponse response = (HttpServletResponse) webRequest.getNativeResponse(); | ||
HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest(); | ||
String requestURI = request.getRequestURI(); | ||
String productId = requestURI.substring(requestURI.lastIndexOf(REQUEST_URL_SEPARATOR) + 1); | ||
|
||
Cookie cookie = productCookieHelper.findCookie(request); | ||
|
||
if (productCookieHelper.hasAlreadyVisitedProduct(cookie, productId)) { | ||
return false; | ||
} | ||
|
||
productCookieHelper.updateCookie(response, cookie, productId); | ||
return true; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.