Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: 알림 Entity 구조 변경 #42

Merged
merged 54 commits into from
Sep 22, 2024
Merged
Show file tree
Hide file tree
Changes from 50 commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
e28c69d
chore: local Dockerfile 추가
junest66 Sep 9, 2024
84716ee
refactor: 알림 Entity 상속관계 매핑으로 리팩토링
junest66 Sep 10, 2024
8fe537e
refactor: 엑세스 토큰 만료시간 임시로 30분으로 수정
junest66 Sep 10, 2024
a8cec4f
style: SSE 성공 로그 추가
junest66 Sep 10, 2024
23e097c
test: 알림 Entity 변경으로 인한 알림 서비스 테스트 코드 수정
junest66 Sep 10, 2024
1276590
refactor: NotificationEvent 에 auctionId 필드 추가
junest66 Sep 10, 2024
28113de
refactor: NotificationRealMessage auctionId 필드 추가
junest66 Sep 10, 2024
17da848
refactor: NotificationSseResponse auctionId 필드 추가
junest66 Sep 10, 2024
7cdada5
refactor: 상품의 좋아요 누른 사용자의 id를 찾는 도메인 함수 추가
junest66 Sep 10, 2024
d536a52
refactor: NotificationType 에 타입마다 알림객체를 만드는 추상메서드 추가
junest66 Sep 10, 2024
fb043e9
refactor: 알림조회 응답 DTO 수정
junest66 Sep 10, 2024
5e25c14
refactor: 알림조회 상속관계 구조로 인한 쿼리 일부 수정
junest66 Sep 10, 2024
f0de96e
test: 알림조회 상속관계 구조로 인한 테스트 코드 수정
junest66 Sep 10, 2024
31ac6e9
chore: 테스트 프론트 도커 이미지 제거
junest66 Sep 10, 2024
7f1c897
refactor: getFirstImage 함수 null 처리 리팩토링
junest66 Sep 11, 2024
1eff9f5
refactor: 알림 생성시 공통된 필드 외에 추가된 필드를 Map 필드로 리팩토링
junest66 Sep 11, 2024
5af2a10
refactor: 공통된 필드 외에 추가된 필드의 Map에서 특정 키의 값을 가져오는 메서드 호출
junest66 Sep 11, 2024
7fd7e6d
refactor: NotificationEvent 객체 생성 메서드 이름 변경으로 인한 코드 수정
junest66 Sep 11, 2024
1940ec1
style: Redis pub/sub 메세지 수신시 발생하는 에러의 로그 명확하게 수정
junest66 Sep 11, 2024
51ee4ae
refactor: 사전 등록 취소 알림 클래스 이름 변경
junest66 Sep 11, 2024
ccb2455
refactor: 불필요한 Column 어노테이션 제거
junest66 Sep 11, 2024
0fff8a3
refactor: 사전등록 상수 이름 변경으로 인한 코드 수정
junest66 Sep 11, 2024
e42f869
refactor: NotificationEvent 객체 생성 메서드 변경으로 인한 코드 수정
junest66 Sep 11, 2024
cce14b9
refactor: 사전경매취소 클래스 이름 수정 및 상수 이름 변경
junest66 Sep 11, 2024
9fda2c6
style: 주석 제거
junest66 Sep 11, 2024
d73e174
style: 알림 메세지 수정
junest66 Sep 11, 2024
d9a49ce
refactor: 쿠키 sameSite, secure 설정 추가
junest66 Sep 12, 2024
3654a40
chore: CORS allowedMethods 명시적으로 설정
junest66 Sep 12, 2024
60df121
refactor: SSE 구독 요청 시 NGINX 버퍼링 비활성화를 위해 X-Accel-Buffering 헤더 추가
junest66 Sep 12, 2024
cd098c6
chore: SSE 관련 nginx 설정 추가
junest66 Sep 12, 2024
eee2501
refactor: JWT 필터를 재발행 URL에서 제외
junest66 Sep 12, 2024
0593dd3
refactor: 커스텀 필터 빈에서 제외
junest66 Sep 12, 2024
d9eb14f
refactor: 경매 상세 조회 쿼리 결과가 null 일시 에러코드 404로 변경
junest66 Sep 13, 2024
78c8a47
refactor: ThousandMultipleValidator Long 도 가능하게 타입 변경
junest66 Sep 13, 2024
9d8bb32
refactor: 입찰 요청 DTO 유효성 검사 추가
junest66 Sep 13, 2024
3a2b940
refactor: BankName Enum value 수정 및 from 메서드 추가
junest66 Sep 13, 2024
f6815ad
refactor: createUser 시 이미 회원이 경우를 예외 처리
junest66 Sep 13, 2024
8716c76
refactor: @PathVariable 유효성 검사 제외
junest66 Sep 13, 2024
c462e93
refactor: 이미 회원이 경우 예외코드 추가
junest66 Sep 13, 2024
526744c
refactor: 최종 회원가입 요청시에만 임시토큰 검증으로 수정
junest66 Sep 13, 2024
ce936a0
feat: 문자열로 들어온 enum를 유효한 Enum인지 검증하는 커스텀 어노테이션 추가
junest66 Sep 13, 2024
f1c37de
refactor: 요청 dto 은행이름 필드 Enum -> String 으로 수정
junest66 Sep 13, 2024
99dd6f0
test: 요청 dto 은행이름 필드 Enum -> String 으로 수정으로 인한 테스트 코드 수정
junest66 Sep 13, 2024
585dfc6
test: 회원가입 테스트 코드 수정
junest66 Sep 13, 2024
23d935e
feat: 존재하는 API URI인지 확인하는 필터 추가
junest66 Sep 14, 2024
bb3c001
feat: 존재하는 API URI인지 확인하는 필터 등록
junest66 Sep 14, 2024
4dc8224
chore: PR 템플릿 수정
junest66 Sep 19, 2024
910a6e1
chore: PR 템플릿 수정
junest66 Sep 19, 2024
b5276a0
chore: PR 템플릿 수정
junest66 Sep 19, 2024
ab69353
chore: PR 템플릿 수정
junest66 Sep 19, 2024
4d58ef4
refactor: SseEmitter 타임아웃 시간 AccessToken 만료시간과 동일하게 변경
junest66 Sep 20, 2024
23dffc0
chore: nginx sse proxy_read_timeout 시간 32분으로 변경
junest66 Sep 20, 2024
b0d17ab
Merge branch 'develop' into feat/refactor-notification with conflict …
junest66 Sep 22, 2024
f0616a0
Merge branch 'develop' into feat/refactor-notification with conflict …
junest66 Sep 22, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
## #️⃣연관된 이슈

#이슈번호 #done
[CHZZ-이슈번호] #done

## 📝작업 내용

Expand Down
17 changes: 17 additions & 0 deletions compose-local.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
version: '3.8'
services:
chzzdb:
image: mysql:latest
container_name: chzzdb
environment:
MYSQL_ROOT_PASSWORD: qal,1
MYSQL_DATABASE: chzzdb
ports:
- "3306:3306"

chzz-redis:
container_name: chzz-redis
image: redis:latest
restart: always
ports:
- "6379:6379"
9 changes: 9 additions & 0 deletions nginx.conf
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,15 @@
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}

location /api/v1/notifications/subscribe {
proxy_pass http://app;
proxy_http_version 1.1;
proxy_set_header Connection '';
proxy_set_header Cache_Control 'no-cache';
chunked_transfer_encoding on;
proxy_read_timeout 3600s; // Nginx가 백엔드 서버로부터 데이터를 받지 못하는 동안 대기할 최대 시간, 이 시간 동안 데이터가 안오면 연결 끊음 (클라이언트에서 재연결 요청)
}
}
}

17 changes: 12 additions & 5 deletions src/main/java/org/chzz/market/common/config/SecurityConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,16 @@
import static org.springframework.http.HttpMethod.GET;
import static org.springframework.http.HttpMethod.POST;

import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.Collections;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.chzz.market.common.error.handler.CustomAccessDeniedHandler;
import org.chzz.market.common.error.handler.CustomAuthenticationEntryPoint;
import org.chzz.market.common.error.handler.ExceptionHandlingFilter;
import org.chzz.market.common.filter.NotFoundFilter;
import org.chzz.market.common.filter.JWTFilter;
import org.chzz.market.common.util.JWTUtil;
import org.chzz.market.domain.user.oauth2.CustomFailureHandler;
import org.chzz.market.domain.user.oauth2.CustomSuccessHandler;
import org.chzz.market.domain.user.service.CustomOAuth2UserService;
Expand All @@ -25,6 +29,7 @@
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.servlet.HandlerMapping;

@Configuration
@EnableWebSecurity
Expand All @@ -40,8 +45,9 @@ public class SecurityConfig {
private final CustomAuthenticationEntryPoint customAuthenticationEntryPoint;
private final CustomSuccessHandler customSuccessHandler;
private final CustomFailureHandler customFailureHandler;
private final JWTFilter jwtFilter;
private final ExceptionHandlingFilter exceptionHandlingFilter;
private final JWTUtil jwtUtil;
private final ObjectMapper objectMapper;
private final List<HandlerMapping> handlerMappings;

@Bean
public SecurityFilterChain filterChain(final HttpSecurity http) throws Exception {
Expand Down Expand Up @@ -79,8 +85,9 @@ public SecurityFilterChain filterChain(final HttpSecurity http) throws Exception
.successHandler(customSuccessHandler)
.failureHandler(customFailureHandler)
)
.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(exceptionHandlingFilter, JWTFilter.class)
.addFilterBefore(new JWTFilter(jwtUtil), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(new NotFoundFilter(handlerMappings), JWTFilter.class)
.addFilterBefore(new ExceptionHandlingFilter(objectMapper), NotFoundFilter.class)
YeaChan05 marked this conversation as resolved.
Show resolved Hide resolved
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.build();
Expand All @@ -90,7 +97,7 @@ public SecurityFilterChain filterChain(final HttpSecurity http) throws Exception
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Collections.singletonList(clientUrl));
configuration.setAllowedMethods(Collections.singletonList("*"));
configuration.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"));
configuration.setAllowCredentials(true);
configuration.setAllowedHeaders(Collections.singletonList("*"));
configuration.setExposedHeaders(Collections.singletonList("Authorization"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,8 @@
import org.chzz.market.common.error.GlobalErrorCode;
import org.chzz.market.common.error.GlobalException;
import org.chzz.market.common.error.exception.BusinessException;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

@Component
@RequiredArgsConstructor
@Slf4j
public class ExceptionHandlingFilter extends OncePerRequestFilter {
Expand Down
17 changes: 14 additions & 3 deletions src/main/java/org/chzz/market/common/filter/JWTFilter.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package org.chzz.market.common.filter;

import static org.springframework.http.HttpMethod.POST;

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.Cookie;
Expand All @@ -17,10 +19,8 @@
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

@Component
@RequiredArgsConstructor
@Slf4j
public class JWTFilter extends OncePerRequestFilter {
Expand All @@ -29,6 +29,13 @@ public class JWTFilter extends OncePerRequestFilter {

private final JWTUtil jwtUtil;

@Override
protected boolean shouldNotFilter(HttpServletRequest request)
throws ServletException { // 해당 url에 대해서 필터를 적용 제외 (토큰 재발행)
return request.getRequestURI().equals("/api/v1/users/tokens/reissue") && request.getMethod()
.equals(POST.name());
}

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
Expand All @@ -40,7 +47,7 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse
jwtUtil.validateToken(accessToken, TokenType.ACCESS);
setAuthentication(accessToken);
filterChain.doFilter(request, response);
} else if (tempCookie != null) {
} else if (isSignUpRequest(request) && tempCookie != null) {
String tempToken = tempCookie.getValue();
jwtUtil.validateToken(tempToken, TokenType.TEMP);
setAuthentication(tempToken);
Expand All @@ -63,4 +70,8 @@ private void setAuthentication(String token) {
customUserDetails.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authToken);
}

private boolean isSignUpRequest(HttpServletRequest request) {
return request.getRequestURI().equals("/api/v1/users") && request.getMethod().equals(POST.name());
}
}
69 changes: 69 additions & 0 deletions src/main/java/org/chzz/market/common/filter/NotFoundFilter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package org.chzz.market.common.filter;

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.chzz.market.common.error.GlobalErrorCode;
import org.chzz.market.common.error.GlobalException;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.servlet.HandlerExecutionChain;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.resource.ResourceHttpRequestHandler;

/**
*
*/
public class NotFoundFilter extends OncePerRequestFilter {

private final List<HandlerMapping> handlerMappings;
private final Map<String, HandlerExecutionChain> handlerCache = new ConcurrentHashMap<>();

public NotFoundFilter(List<HandlerMapping> handlerMappings) {
this.handlerMappings = handlerMappings;
}

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {

String requestUri = request.getRequestURI();

// 캐시에서 HandlerExecutionChain 검색 후 없으면 캐시 추가 (null 일 경우 map 추가 안함)
HandlerExecutionChain handlerChain = handlerCache.computeIfAbsent(requestUri, uri -> findHandler(request));

// 핸들러가 없으면 404 예외 처리
if (handlerChain == null) {
throw new GlobalException(GlobalErrorCode.RESOURCE_NOT_FOUND);
}

filterChain.doFilter(request, response);
}

/**
* 핸들러 매핑에서 HandlerExecutionChain을 검색하는 스트림 메서드
*/
private HandlerExecutionChain findHandler(HttpServletRequest request) {
return handlerMappings.stream()
.map(handlerMapping -> getHandlerExecutionChain(handlerMapping, request))
.filter(chain -> chain != null
&& !(chain.getHandler() instanceof ResourceHttpRequestHandler)) // 정적 리소스 핸들러 제외
.findFirst()
.orElse(null);
}

/**
* 핸들러 매핑에서 HandlerExecutionChain을 가져옴
*/
private HandlerExecutionChain getHandlerExecutionChain(HandlerMapping handlerMapping, HttpServletRequest request) {
try {
return handlerMapping.getHandler(request);
} catch (Exception e) {
return null;
}
}
}
32 changes: 20 additions & 12 deletions src/main/java/org/chzz/market/common/util/CookieUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,20 @@

import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.chzz.market.domain.token.entity.TokenType;
import org.springframework.http.ResponseCookie;

public class CookieUtil {
public static Cookie createTokenCookie(String token, TokenType tokenType) {
Cookie cookie = new Cookie(tokenType.name(), token);
cookie.setHttpOnly(true);
cookie.setMaxAge(tokenType.getExpirationTime());
cookie.setPath("/");
return cookie;
public static void createTokenCookie(HttpServletResponse response, String token, TokenType tokenType) {
ResponseCookie cookie = ResponseCookie.from(tokenType.name(), token)
.path("/")
.sameSite("None")
.httpOnly(true)
.secure(true)
.maxAge(tokenType.getExpirationTime())
.build();
response.addHeader("Set-Cookie", cookie.toString());
}

public static Cookie getCookieByName(HttpServletRequest request, String cookieName) {
Expand All @@ -25,11 +30,14 @@ public static Cookie getCookieByName(HttpServletRequest request, String cookieNa
return null;
}

public static Cookie expireCookie(String cookieName) {
Cookie cookie = new Cookie(cookieName, null);
cookie.setHttpOnly(true);
cookie.setMaxAge(0);
cookie.setPath("/");
return cookie;
public static void expireCookie(HttpServletResponse response, String cookieName) {
ResponseCookie cookie = ResponseCookie.from(cookieName, null)
.path("/")
.sameSite("None")
.httpOnly(true)
.secure(true)
.maxAge(0)
.build();
response.addHeader("Set-Cookie", cookie.toString());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package org.chzz.market.common.validation.annotation;

import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.chzz.market.common.validation.validator.EnumValidator;

@Constraint(validatedBy = EnumValidator.class)
@Target({ ElementType.FIELD, ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
public @interface EnumValue {
Class<? extends Enum<?>> enumClass();
String message() default "Invalid value for enum field";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package org.chzz.market.common.validation.validator;

import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
import java.util.Arrays;
import org.chzz.market.common.validation.annotation.EnumValue;

public class EnumValidator implements ConstraintValidator<EnumValue, String> {
private EnumValue enumValue;

@Override
public void initialize(final EnumValue constraintAnnotation) {
this.enumValue = constraintAnnotation;
}

@Override
public boolean isValid(final String value, final ConstraintValidatorContext context) {
final Enum<?>[] enumConstants = this.enumValue.enumClass().getEnumConstants();
if (enumConstants == null) {
return false;
}

// Enum의 모든 상수들과 대소문자 구분 없이 비교
return Arrays.stream(enumValue.enumClass().getEnumConstants())
.anyMatch(enumConstant -> value.trim().equalsIgnoreCase(enumConstant.name()));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
import jakarta.validation.ConstraintValidatorContext;
import org.chzz.market.common.validation.annotation.ThousandMultiple;

public class ThousandMultipleValidator implements ConstraintValidator<ThousandMultiple, Integer> {
public class ThousandMultipleValidator implements ConstraintValidator<ThousandMultiple, Number> {

@Override
public boolean isValid(Integer value, ConstraintValidatorContext context) {
return value != null && value % 1000 == 0 && value > 0;
public boolean isValid(Number value, ConstraintValidatorContext context) {
return value != null && value.longValue() > 0 && value.longValue() % 1000 == 0;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
@Getter
@AllArgsConstructor
public enum AuctionErrorCode implements ErrorCode {
AUCTION_NOT_ACCESSIBLE(BAD_REQUEST, "해당 경매를 조회할 수 없습니다. "),
AUCTION_ENDED(BAD_REQUEST, "경매가 종료되었습니다."),
AUCTION_NOT_FOUND(NOT_FOUND, "경매를 찾을 수 없습니다."),
INVALID_AUCTION_STATE(BAD_REQUEST, "경매 상태가 유효하지 않습니다."),
Expand Down
Loading
Loading