diff --git a/.github/workflows/docker-build-push.yml b/.github/workflows/docker-build-push.yml new file mode 100644 index 00000000..14cddf18 --- /dev/null +++ b/.github/workflows/docker-build-push.yml @@ -0,0 +1,55 @@ +name: CI/CD Workflow + +on: + pull_request: + types: [opened] + workflow_dispatch: + +env: + AWS_REGION: ap-northeast-2 + +jobs: + build-and-push: + name: Build and Push to ECR + runs-on: ubuntu-latest + strategy: + matrix: + service: + - name: 'eureka-server' + ecr_repository: 'ticketping/eureka-server' + dockerfile_path: './eureka-server/Dockerfile' + context_path: './eureka-server' + + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Set up JDK 17 + uses: actions/setup-java@v2 + with: + java-version: '17' + distribution: 'temurin' + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + + - name: Test with Gradle Wrapper + run: ./gradlew build -x test + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v1 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: ${{ env.AWS_REGION }} + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v1 + + - name: Build and push image to Amazon ECR + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + run: | + docker build -t $ECR_REGISTRY/${{ matrix.service.ecr_repository }} -f ${{ matrix.service.dockerfile_path }} ${{ matrix.service.context_path }} + docker push $ECR_REGISTRY/${{ matrix.service.ecr_repository }} diff --git a/.github/workflows/pull_request_notification.yml b/.github/workflows/pull_request_notification.yml deleted file mode 100644 index 8e0077d2..00000000 --- a/.github/workflows/pull_request_notification.yml +++ /dev/null @@ -1,39 +0,0 @@ -name: Notify Discord on Pull Request - -on: - pull_request: - types: [opened] - -jobs: - notify-discord: - runs-on: ubuntu-latest - - steps: - - name: Send Discord Notification - env: - DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }} - run: | - curl -H "Content-Type: application/json" \ - -d '{ - "username": "GitHub Bot", - "avatar_url": "https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png", - "embeds": [{ - "title": "New Pull Request", - "description": "A new pull request has been opened in the repository.", - "url": "${{ github.event.pull_request.html_url }}", - "color": 3066993, - "fields": [ - { - "name": "Title", - "value": "${{ github.event.pull_request.title }}", - "inline": true - }, - { - "name": "Author", - "value": "${{ github.event.pull_request.user.login }}", - "inline": true - } - ] - }] - }' \ - $DISCORD_WEBHOOK_URL diff --git a/.gitignore b/.gitignore index 8a5c29d2..88c0349a 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ build/ !gradle/wrapper/gradle-wrapper.jar !**/src/main/**/build/ !**/src/test/**/build/ +.run ### STS ### .apt_generated diff --git a/common/resilience4j/build.gradle b/common/circuit-breaker/build.gradle similarity index 58% rename from common/resilience4j/build.gradle rename to common/circuit-breaker/build.gradle index 9e2aad8d..b1ce6efe 100644 --- a/common/resilience4j/build.gradle +++ b/common/circuit-breaker/build.gradle @@ -31,10 +31,24 @@ jar { enabled = true } +ext { + set('springCloudVersion', "2023.0.3") +} + +dependencyManagement { + imports { + mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}" + } +} + dependencies { - api 'io.github.resilience4j:resilience4j-spring-boot3:2.2.0' + implementation project(':common:core') + implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework.cloud:spring-cloud-starter-openfeign' + // Resilience4j + api 'io.github.resilience4j:resilience4j-spring-boot3:2.2.0' } tasks.named('test') { diff --git a/common/resilience4j/src/main/java/resilience4j/config/CircuitBreakerEventConfig.java b/common/circuit-breaker/src/main/java/circuitbreaker/config/CircuitBreakerEventConfig.java similarity index 98% rename from common/resilience4j/src/main/java/resilience4j/config/CircuitBreakerEventConfig.java rename to common/circuit-breaker/src/main/java/circuitbreaker/config/CircuitBreakerEventConfig.java index 86183e36..977ef5dd 100644 --- a/common/resilience4j/src/main/java/resilience4j/config/CircuitBreakerEventConfig.java +++ b/common/circuit-breaker/src/main/java/circuitbreaker/config/CircuitBreakerEventConfig.java @@ -1,4 +1,4 @@ -package resilience4j.config; +package circuitbreaker.config; import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry; import io.github.resilience4j.circuitbreaker.event.CircuitBreakerOnErrorEvent; diff --git a/services/auth/src/main/java/com/ticketPing/auth/infrastructure/config/CustomFeignConfig.java b/common/circuit-breaker/src/main/java/circuitbreaker/config/CustomFeignConfig.java similarity index 85% rename from services/auth/src/main/java/com/ticketPing/auth/infrastructure/config/CustomFeignConfig.java rename to common/circuit-breaker/src/main/java/circuitbreaker/config/CustomFeignConfig.java index 07694870..b924f698 100644 --- a/services/auth/src/main/java/com/ticketPing/auth/infrastructure/config/CustomFeignConfig.java +++ b/common/circuit-breaker/src/main/java/circuitbreaker/config/CustomFeignConfig.java @@ -1,4 +1,4 @@ -package com.ticketPing.auth.infrastructure.config; +package circuitbreaker.config; import feign.Request; import org.springframework.context.annotation.Bean; @@ -6,7 +6,6 @@ @Configuration public class CustomFeignConfig { - @Bean public Request.Options requestOptions() { return new Request.Options(1000, 12000); diff --git a/common/resilience4j/src/main/java/resilience4j/config/RetryEventConfig.java b/common/circuit-breaker/src/main/java/circuitbreaker/config/RetryEventConfig.java similarity index 96% rename from common/resilience4j/src/main/java/resilience4j/config/RetryEventConfig.java rename to common/circuit-breaker/src/main/java/circuitbreaker/config/RetryEventConfig.java index 42f0dcc4..56529db6 100644 --- a/common/resilience4j/src/main/java/resilience4j/config/RetryEventConfig.java +++ b/common/circuit-breaker/src/main/java/circuitbreaker/config/RetryEventConfig.java @@ -1,4 +1,4 @@ -package resilience4j.config; +package circuitbreaker.config; import io.github.resilience4j.retry.RetryRegistry; import io.github.resilience4j.retry.event.RetryOnRetryEvent; diff --git a/services/auth/src/main/java/com/ticketPing/auth/common/exception/CircuitBreakerErrorCase.java b/common/circuit-breaker/src/main/java/circuitbreaker/exception/CircuitBreakerErrorCase.java similarity index 92% rename from services/auth/src/main/java/com/ticketPing/auth/common/exception/CircuitBreakerErrorCase.java rename to common/circuit-breaker/src/main/java/circuitbreaker/exception/CircuitBreakerErrorCase.java index 7ec866bf..74afbcdb 100644 --- a/services/auth/src/main/java/com/ticketPing/auth/common/exception/CircuitBreakerErrorCase.java +++ b/common/circuit-breaker/src/main/java/circuitbreaker/exception/CircuitBreakerErrorCase.java @@ -1,4 +1,4 @@ -package com.ticketPing.auth.common.exception; +package circuitbreaker.exception; import exception.ErrorCase; import lombok.Getter; @@ -8,6 +8,7 @@ @Getter @RequiredArgsConstructor public enum CircuitBreakerErrorCase implements ErrorCase { + SERVICE_UNAVAILABLE(HttpStatus.SERVICE_UNAVAILABLE, "서비스가 연결 불가능합니다. 잠시 후 다시 시도해주세요."), SERVICE_IS_OPEN(HttpStatus.SERVICE_UNAVAILABLE, "서비스가 연결 불가능합니다. 관리자에게 문의해주세요."); diff --git a/services/order/src/main/java/com/ticketPing/order/common/utils/FeignFallbackUtils.java b/common/circuit-breaker/src/main/java/circuitbreaker/utils/FeignFallbackUtils.java similarity index 91% rename from services/order/src/main/java/com/ticketPing/order/common/utils/FeignFallbackUtils.java rename to common/circuit-breaker/src/main/java/circuitbreaker/utils/FeignFallbackUtils.java index 0254f4c2..a9549f48 100644 --- a/services/order/src/main/java/com/ticketPing/order/common/utils/FeignFallbackUtils.java +++ b/common/circuit-breaker/src/main/java/circuitbreaker/utils/FeignFallbackUtils.java @@ -1,10 +1,10 @@ -package com.ticketPing.order.common.utils; +package circuitbreaker.utils; -import com.ticketPing.order.common.exception.CircuitBreakerErrorCase; +import circuitbreaker.exception.CircuitBreakerErrorCase; import exception.ApplicationException; import feign.FeignException; -import io.github.resilience4j.circuitbreaker.CallNotPermittedException; import feign.RetryableException; +import io.github.resilience4j.circuitbreaker.CallNotPermittedException; public class FeignFallbackUtils { @@ -30,4 +30,5 @@ else if (cause instanceof FeignException) { throw new ApplicationException(CircuitBreakerErrorCase.SERVICE_UNAVAILABLE); } } + } diff --git a/common/resilience4j/src/main/resources/application-circuit-breaker.yml b/common/circuit-breaker/src/main/resources/application-resilience4j.yml similarity index 100% rename from common/resilience4j/src/main/resources/application-circuit-breaker.yml rename to common/circuit-breaker/src/main/resources/application-resilience4j.yml diff --git a/common/rdb/src/main/java/audit/BaseEntity.java b/common/rdb/src/main/java/auditing/BaseEntity.java similarity index 86% rename from common/rdb/src/main/java/audit/BaseEntity.java rename to common/rdb/src/main/java/auditing/BaseEntity.java index aadad66c..56bb2138 100644 --- a/common/rdb/src/main/java/audit/BaseEntity.java +++ b/common/rdb/src/main/java/auditing/BaseEntity.java @@ -1,13 +1,11 @@ -package audit; +package auditing; import jakarta.persistence.Column; import jakarta.persistence.EntityListeners; import jakarta.persistence.MappedSuperclass; import java.time.LocalDateTime; import lombok.Getter; -import org.springframework.data.annotation.CreatedBy; import org.springframework.data.annotation.CreatedDate; -import org.springframework.data.annotation.LastModifiedBy; import org.springframework.data.annotation.LastModifiedDate; import org.springframework.data.jpa.domain.support.AuditingEntityListener; diff --git a/common/rdb/src/main/java/audit/JpaAuditingConfig.java b/common/rdb/src/main/java/auditing/JpaAuditingConfig.java similarity index 91% rename from common/rdb/src/main/java/audit/JpaAuditingConfig.java rename to common/rdb/src/main/java/auditing/JpaAuditingConfig.java index ce4e8114..ec1f915d 100644 --- a/common/rdb/src/main/java/audit/JpaAuditingConfig.java +++ b/common/rdb/src/main/java/auditing/JpaAuditingConfig.java @@ -1,4 +1,4 @@ -package audit; +package auditing; import org.springframework.context.annotation.Configuration; import org.springframework.data.jpa.repository.config.EnableJpaAuditing; diff --git a/docker-compose/docker-compose.yml b/docker-compose/docker-compose.yml index 413e566e..eba1c979 100644 --- a/docker-compose/docker-compose.yml +++ b/docker-compose/docker-compose.yml @@ -6,10 +6,6 @@ services: image: ticketping/eureka-server:latest ports: - "10000:10000" - env_file: - - .env - environment: - - EUREKA_SERVER=${EUREKA_SERVER} gateway-server: container_name: gateway-server @@ -93,7 +89,6 @@ services: - PERFORMANCE_POSTGRES_URL=${PERFORMANCE_POSTGRES_URL} - PERFORMANCE_POSTGRES_USERNAME=${PERFORMANCE_POSTGRES_USERNAME} - PERFORMANCE_POSTGRES_PASSWORD=${PERFORMANCE_POSTGRES_PASSWORD} - - DISCORD_WEBHOOK_URL=${GF_DISCORD_WEBHOOK_URL} - REDIS_NODE_1=${REDIS_NODE_1} - REDIS_NODE_2=${REDIS_NODE_2} - REDIS_NODE_3=${REDIS_NODE_3} @@ -101,6 +96,7 @@ services: - REDIS_NODE_5=${REDIS_NODE_5} - REDIS_NODE_6=${REDIS_NODE_6} - ZIPKIN=${ZIPKIN} + - DISCORD_WEBHOOK_URL=${GF_DISCORD_WEBHOOK_URL} depends_on: eureka-server: condition: service_started diff --git a/docker-compose/end_service.sh b/docker-compose/end_service.sh deleted file mode 100644 index e6d3c164..00000000 --- a/docker-compose/end_service.sh +++ /dev/null @@ -1,17 +0,0 @@ -echo "1. Closing monitoring services..." -docker compose -f docker-compose-monitoring.yml down - -echo "2. Closing Kafka services..." -docker compose -f docker-compose-kafka.yml down - -echo "3. Closing Postgres services..." -docker compose -f docker-compose-postgres.yml down - -echo "4. Closing Redis services..." -docker compose -f docker-compose-redis.yml down - -echo "5. Closing application services..." -docker compose down - -echo "6. Remove unusing docker volume" -docker volume ls -qf dangling=true | xargs -r docker volume rm \ No newline at end of file diff --git a/docker-compose/start_service.sh b/docker-compose/start_service.sh deleted file mode 100644 index 0e207be6..00000000 --- a/docker-compose/start_service.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/bin/bash - -echo "1. Starting monitoring services..." -docker compose -f docker-compose-monitoring.yml up -d -sleep 5 - -echo "2. Starting Kafka services..." -docker compose -f docker-compose-kafka.yml up -d -sleep 5 - -echo "3. Starting Postgres services..." -docker compose -f docker-compose-postgres.yml up -d -sleep 5 - -echo "4. Starting Redis services..." -docker compose -f docker-compose-redis.yml up -d -sleep 5 - -echo "5. Building Gradle project..." -cd .. -./gradlew build -x test -cd docker-compose - -echo "6. Building Docker images..." -docker compose -f docker-compose-build.yml build - -echo "7. Starting application services..." -docker compose up -d - -echo "All steps completed successfully!" -echo "Waiting for 10 seconds before shutting down..." -sleep 10 \ No newline at end of file diff --git a/eureka-server/src/main/java/com/ticketPing/eureka_server/EurekaServerApplication.java b/eureka-server/src/main/java/com/ticketPing/eureka_server/EurekaServerApplication.java index d6f82d45..9ddc61bd 100644 --- a/eureka-server/src/main/java/com/ticketPing/eureka_server/EurekaServerApplication.java +++ b/eureka-server/src/main/java/com/ticketPing/eureka_server/EurekaServerApplication.java @@ -7,9 +7,7 @@ @EnableEurekaServer @SpringBootApplication public class EurekaServerApplication { - public static void main(String[] args) { SpringApplication.run(EurekaServerApplication.class, args); } - } diff --git a/eureka-server/src/main/resources/application.yml b/eureka-server/src/main/resources/application.yml index 1bf394c3..1ec0308c 100644 --- a/eureka-server/src/main/resources/application.yml +++ b/eureka-server/src/main/resources/application.yml @@ -7,9 +7,7 @@ server: eureka: client: - register-with-eureka: true - fetch-registry: true - service-url: - defaultZone: ${EUREKA_SERVER} + register-with-eureka: false + fetch-registry: false instance: hostname: eureka-server \ No newline at end of file diff --git a/gateway/src/main/java/com/ticketPing/gateway/common/exception/CircuitBreakerErrorCase.java b/gateway/src/main/java/com/ticketPing/gateway/common/exception/CircuitBreakerErrorCase.java index f5470444..67d3bcac 100644 --- a/gateway/src/main/java/com/ticketPing/gateway/common/exception/CircuitBreakerErrorCase.java +++ b/gateway/src/main/java/com/ticketPing/gateway/common/exception/CircuitBreakerErrorCase.java @@ -8,6 +8,7 @@ @Getter @RequiredArgsConstructor public enum CircuitBreakerErrorCase implements ErrorCase { + SERVICE_UNAVAILABLE(HttpStatus.SERVICE_UNAVAILABLE, "서비스가 연결 불가능합니다. 잠시 후 다시 시도해주세요."), CONNECTION_TIMEOUT(HttpStatus.GATEWAY_TIMEOUT, "서비스 요청 시간이 초과되었습니다. 잠시 후 다시 시도해주세요."), SERVICE_IS_OPEN(HttpStatus.SERVICE_UNAVAILABLE, "서비스가 연결 불가능합니다. 관리자에게 문의해주세요."); diff --git a/gateway/src/main/java/com/ticketPing/gateway/common/exception/SecurityErrorCase.java b/gateway/src/main/java/com/ticketPing/gateway/common/exception/SecurityErrorCase.java index 3759975d..825c60af 100644 --- a/gateway/src/main/java/com/ticketPing/gateway/common/exception/SecurityErrorCase.java +++ b/gateway/src/main/java/com/ticketPing/gateway/common/exception/SecurityErrorCase.java @@ -9,7 +9,7 @@ @AllArgsConstructor public enum SecurityErrorCase implements ErrorCase { - USER_CACHE_IS_NULL(HttpStatus.INTERNAL_SERVER_ERROR, "내부 서버 오류"), + UNAUTHORIZED(HttpStatus.UNAUTHORIZED, "인증이 필요합니다."), EXPIRED_TOKEN(HttpStatus.UNAUTHORIZED, "토큰이 만료되었습니다."); private final HttpStatus httpStatus; diff --git a/gateway/src/main/java/com/ticketPing/gateway/infrastructure/client/AuthWebClient.java b/gateway/src/main/java/com/ticketPing/gateway/infrastructure/client/AuthWebClient.java index b8af3fd8..fd722305 100644 --- a/gateway/src/main/java/com/ticketPing/gateway/infrastructure/client/AuthWebClient.java +++ b/gateway/src/main/java/com/ticketPing/gateway/infrastructure/client/AuthWebClient.java @@ -2,63 +2,36 @@ import auth.UserCacheDto; import com.ticketPing.gateway.application.client.AuthClient; -import com.ticketPing.gateway.common.exception.CircuitBreakerErrorCase; +import com.ticketPing.gateway.common.exception.SecurityErrorCase; import exception.ApplicationException; -import io.github.resilience4j.circuitbreaker.CallNotPermittedException; -import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker; -import io.github.resilience4j.retry.annotation.Retry; -import io.netty.channel.ChannelOption; import org.springframework.core.ParameterizedTypeReference; -import org.springframework.http.client.reactive.ReactorClientHttpConnector; import org.springframework.stereotype.Component; import org.springframework.web.reactive.function.client.WebClient; -import org.springframework.web.reactive.function.client.WebClientResponseException; import reactor.core.publisher.Mono; -import reactor.netty.http.client.HttpClient; import response.CommonResponse; -import java.time.Duration; - @Component public class AuthWebClient implements AuthClient { private final WebClient webClient; public AuthWebClient(WebClient.Builder webClientBuilder) { - this.webClient = webClientBuilder - .clientConnector(new ReactorClientHttpConnector( - HttpClient.create() - .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 1000) - .responseTimeout(Duration.ofSeconds(15)))) - .build(); + this.webClient = webClientBuilder.build(); } - @Retry(name = "authServiceRetry") - @CircuitBreaker(name = "authServiceCircuitBreaker", fallbackMethod = "validateTokenFallback") public Mono validateToken(String token) { return webClient.post() .uri("http://auth/api/v1/auth/validate") .header("Authorization", token) .retrieve() .bodyToMono(new ParameterizedTypeReference>() {}) - .flatMap(response -> Mono.just(response.getData())); - } - - private Mono validateTokenFallback(String token, Throwable ex) { - if (ex instanceof CallNotPermittedException) { - return Mono.error(new ApplicationException(CircuitBreakerErrorCase.SERVICE_IS_OPEN)); - } else if ( - ex instanceof WebClientResponseException.BadGateway || - ex instanceof WebClientResponseException.GatewayTimeout || - ex instanceof WebClientResponseException.TooManyRequests || - ex instanceof WebClientResponseException.ServiceUnavailable - ) { - return Mono.error(new ApplicationException(CircuitBreakerErrorCase.SERVICE_UNAVAILABLE)); - } else if (ex instanceof WebClientResponseException) { - return Mono.error(ex); - } else { - return Mono.error(new ApplicationException(CircuitBreakerErrorCase.SERVICE_UNAVAILABLE)); - } + .flatMap(response -> { + if (response.getData() != null) { + return Mono.just(response.getData()); + } else { + return Mono.error(new ApplicationException(SecurityErrorCase.UNAUTHORIZED)); + } + }); } } diff --git a/gateway/src/main/java/com/ticketPing/gateway/infrastructure/config/CircuitBreakerEventConfig.java b/gateway/src/main/java/com/ticketPing/gateway/infrastructure/config/CircuitBreakerEventConfig.java index 983dcbc1..be9116da 100644 --- a/gateway/src/main/java/com/ticketPing/gateway/infrastructure/config/CircuitBreakerEventConfig.java +++ b/gateway/src/main/java/com/ticketPing/gateway/infrastructure/config/CircuitBreakerEventConfig.java @@ -1,7 +1,6 @@ package com.ticketPing.gateway.infrastructure.config; import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry; -import io.github.resilience4j.circuitbreaker.event.CircuitBreakerEvent; import io.github.resilience4j.circuitbreaker.event.CircuitBreakerOnErrorEvent; import io.github.resilience4j.circuitbreaker.event.CircuitBreakerOnFailureRateExceededEvent; import io.github.resilience4j.circuitbreaker.event.CircuitBreakerOnStateTransitionEvent; diff --git a/gateway/src/main/java/com/ticketPing/gateway/infrastructure/config/CustomAuthenticationEntryPoint.java b/gateway/src/main/java/com/ticketPing/gateway/infrastructure/config/CustomAuthenticationEntryPoint.java index 104eb7e5..b208111a 100644 --- a/gateway/src/main/java/com/ticketPing/gateway/infrastructure/config/CustomAuthenticationEntryPoint.java +++ b/gateway/src/main/java/com/ticketPing/gateway/infrastructure/config/CustomAuthenticationEntryPoint.java @@ -1,12 +1,11 @@ package com.ticketPing.gateway.infrastructure.config; -import static com.ticketPing.gateway.common.exception.SecurityErrorCase.EXPIRED_TOKEN; - +import com.ticketPing.gateway.common.exception.SecurityErrorCase; import java.nio.charset.StandardCharsets; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; import org.springframework.core.io.buffer.DataBuffer; -import org.springframework.http.MediaType; +import org.springframework.http.HttpStatus; import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.server.ServerAuthenticationEntryPoint; @@ -24,11 +23,10 @@ public class CustomAuthenticationEntryPoint implements ServerAuthenticationEntry @Override public Mono commence(ServerWebExchange exchange, AuthenticationException ex) { ServerHttpResponse response = exchange.getResponse(); - response.setStatusCode(EXPIRED_TOKEN.getHttpStatus()); - response.getHeaders().setContentType(MediaType.APPLICATION_JSON); + response.setStatusCode(HttpStatus.UNAUTHORIZED); response.getHeaders().setAccessControlAllowOrigin(clientUrl); response.getHeaders().setAccessControlAllowCredentials(true); - String responseBody = EXPIRED_TOKEN.getMessage(); + String responseBody = SecurityErrorCase.UNAUTHORIZED.getMessage(); DataBuffer dataBuffer = response.bufferFactory().wrap(responseBody.getBytes(StandardCharsets.UTF_8)); return response.writeWith(Mono.just(dataBuffer)); } diff --git a/gateway/src/main/java/com/ticketPing/gateway/infrastructure/config/RetryEventConfig.java b/gateway/src/main/java/com/ticketPing/gateway/infrastructure/config/RetryEventConfig.java deleted file mode 100644 index 6ff36fa9..00000000 --- a/gateway/src/main/java/com/ticketPing/gateway/infrastructure/config/RetryEventConfig.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.ticketPing.gateway.infrastructure.config; - -import io.github.resilience4j.retry.RetryRegistry; -import io.github.resilience4j.retry.event.RetryOnRetryEvent; -import jakarta.annotation.PostConstruct; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.context.annotation.Configuration; - -@Slf4j -@Configuration -@RequiredArgsConstructor -public class RetryEventConfig { - - private final RetryRegistry retryRegistry; - - @PostConstruct - public void registerRetryEventListeners() { - retryRegistry.getAllRetries().forEach(retry -> { - retry.getEventPublisher() - .onRetry(this::logRetry); - }); - } - - private void logRetry(RetryOnRetryEvent event) { - log.info("Retry for '{}' attempt number: {}", - event.getName(), - event.getNumberOfRetryAttempts()); - } -} diff --git a/gateway/src/main/java/com/ticketPing/gateway/infrastructure/config/SecurityConfig.java b/gateway/src/main/java/com/ticketPing/gateway/infrastructure/config/SecurityConfig.java index e2a9ef40..9f90fa3a 100644 --- a/gateway/src/main/java/com/ticketPing/gateway/infrastructure/config/SecurityConfig.java +++ b/gateway/src/main/java/com/ticketPing/gateway/infrastructure/config/SecurityConfig.java @@ -2,7 +2,6 @@ import com.ticketPing.gateway.infrastructure.filter.JwtFilter; import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; @@ -13,7 +12,6 @@ import org.springframework.security.config.web.server.ServerHttpSecurity.HttpBasicSpec; import org.springframework.security.web.server.SecurityWebFilterChain; -@Slf4j @Configuration @RequiredArgsConstructor @EnableWebFluxSecurity diff --git a/gateway/src/main/java/com/ticketPing/gateway/infrastructure/config/WebClientConfig.java b/gateway/src/main/java/com/ticketPing/gateway/infrastructure/config/WebClientConfig.java index bb3fe4ad..88be8a2b 100644 --- a/gateway/src/main/java/com/ticketPing/gateway/infrastructure/config/WebClientConfig.java +++ b/gateway/src/main/java/com/ticketPing/gateway/infrastructure/config/WebClientConfig.java @@ -7,7 +7,6 @@ @Configuration public class WebClientConfig { - @Bean @LoadBalanced public WebClient.Builder webClientBuilder() { diff --git a/gateway/src/main/java/com/ticketPing/gateway/infrastructure/filter/JwtFilter.java b/gateway/src/main/java/com/ticketPing/gateway/infrastructure/filter/JwtFilter.java index 2cf0eee7..f26c6909 100644 --- a/gateway/src/main/java/com/ticketPing/gateway/infrastructure/filter/JwtFilter.java +++ b/gateway/src/main/java/com/ticketPing/gateway/infrastructure/filter/JwtFilter.java @@ -1,13 +1,16 @@ package com.ticketPing.gateway.infrastructure.filter; +import static com.ticketPing.gateway.common.exception.CircuitBreakerErrorCase.SERVICE_UNAVAILABLE; + import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.ticketPing.gateway.application.client.AuthClient; -import exception.ApplicationException; import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; +import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; @@ -16,6 +19,7 @@ import org.springframework.security.core.context.SecurityContextImpl; import org.springframework.security.web.server.context.ServerSecurityContextRepository; import org.springframework.stereotype.Component; +import org.springframework.web.reactive.function.client.WebClientRequestException; import org.springframework.web.reactive.function.client.WebClientResponseException; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; @@ -27,6 +31,9 @@ @RequiredArgsConstructor public class JwtFilter implements ServerSecurityContextRepository { + @Value("${client.url}") + private String clientUrl; + private final AuthClient authClient; @Override @@ -43,7 +50,7 @@ public Mono load(ServerWebExchange exchange) { } return authClient.validateToken(authHeader) - .flatMap(response -> { + .map(response -> { List authorities = List.of(new SimpleGrantedAuthority(response.role())); Authentication authentication = new UsernamePasswordAuthenticationToken( response.userId(), null, authorities @@ -56,34 +63,36 @@ public Mono load(ServerWebExchange exchange) { })) .build(); - return Mono.just((SecurityContext) new SecurityContextImpl(authentication)); + return new SecurityContextImpl(authentication); }) - .onErrorResume(ApplicationException.class, e -> handleErrorResponse(exchange, e.getMessage(), HttpStatus.SERVICE_UNAVAILABLE)) - .onErrorResume(WebClientResponseException.Unauthorized.class, e -> { - exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED); - return extractMessageFromResponse(e) - .flatMap(message -> handleErrorResponse(exchange, message, HttpStatus.UNAUTHORIZED)); - }); + .cast(SecurityContext.class) + .onErrorResume(WebClientRequestException.class, e -> + setErrorResponse(exchange, HttpStatus.SERVICE_UNAVAILABLE, SERVICE_UNAVAILABLE.getMessage())) + .onErrorResume(WebClientResponseException.Unauthorized.class, e -> + extractMessageFromResponse(e) + .flatMap(message -> setErrorResponse(exchange, HttpStatus.UNAUTHORIZED, message)) + ); } - private Mono handleErrorResponse(ServerWebExchange exchange, String message, HttpStatus status) { - exchange.getResponse().setStatusCode(status); - DataBuffer buffer = exchange.getResponse() - .bufferFactory() - .wrap(message.getBytes(StandardCharsets.UTF_8)); - return exchange.getResponse().writeWith(Mono.just(buffer)).then(Mono.empty()); + private Mono setErrorResponse(ServerWebExchange exchange, HttpStatus status, String message) { + ServerHttpResponse response = exchange.getResponse(); + response.setStatusCode(status); + response.getHeaders().setAccessControlAllowOrigin(clientUrl); + response.getHeaders().setAccessControlAllowCredentials(true); + DataBuffer dataBuffer = response.bufferFactory().wrap(message.getBytes(StandardCharsets.UTF_8)); + return response.writeWith(Mono.just(dataBuffer)) + .then(Mono.empty()); } private Mono extractMessageFromResponse(WebClientResponseException e) { String responseBody = e.getResponseBodyAsString(); - try { ObjectMapper objectMapper = new ObjectMapper(); JsonNode jsonNode = objectMapper.readTree(responseBody); String message = jsonNode.path("message").asText(""); return Mono.just(message); } catch (Exception ex) { - return Mono.just(""); + return Mono.empty(); } } diff --git a/gateway/src/main/resources/application.yml b/gateway/src/main/resources/application.yml index 2495a4e1..8c4f336e 100644 --- a/gateway/src/main/resources/application.yml +++ b/gateway/src/main/resources/application.yml @@ -33,22 +33,10 @@ token-value: secret-key: ${USER_TOKEN_SECRET_KEY} resilience4j: - retry: - retry-aspect-order: 2 - configs: - default: - max-attempts: 3 - wait-duration: 500ms - authServiceRetry: - retry-exceptions: - - org.springframework.web.reactive.function.client.WebClientRequestException - - org.springframework.web.reactive.function.client.WebClientResponseException.ServiceUnavailable - timelimiter: configs: default: timeout-duration: 15s - circuitbreaker: circuit-breaker-aspect-order: 1 configs: diff --git a/gateway/src/main/resources/logback.xml b/gateway/src/main/resources/logback.xml index ac9912fd..32b077b6 100644 --- a/gateway/src/main/resources/logback.xml +++ b/gateway/src/main/resources/logback.xml @@ -6,7 +6,7 @@ - http://loki:3100/loki/api/v1/push + http://${LOKI_HOST:-localhost}:3100/loki/api/v1/push - http://loki:3100/loki/api/v1/push + http://${LOKI_HOST:-localhost}:3100/loki/api/v1/push - http://loki:3100/loki/api/v1/push + http://${LOKI_HOST:-localhost}:3100/loki/api/v1/push - http://loki:3100/loki/api/v1/push + http://${LOKI_HOST:-localhost}:3100/loki/api/v1/push - http://loki:3100/loki/api/v1/push + http://${LOKI_HOST:-localhost}:3100/loki/api/v1/push - http://loki:3100/loki/api/v1/push + http://${LOKI_HOST:-localhost}:3100/loki/api/v1/push - http://loki:3100/loki/api/v1/push + http://${LOKI_HOST:-localhost}:3100/loki/api/v1/push