From 28fda71d3b085decf99502f7fb5f4e365482df15 Mon Sep 17 00:00:00 2001 From: Hyunmin Choi Date: Sun, 8 Sep 2024 16:47:47 +0900 Subject: [PATCH] =?UTF-8?q?[FEATURE]=20Github=20Action=20=EC=9D=84=20?= =?UTF-8?q?=ED=86=B5=ED=95=9C=20CI/CD=20=EA=B5=AC=EC=B6=95=20(#43)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: 스프링 프로필 환경변수로 설정 (#36) * feat: 운영, 개발 프로필 설정 (#36) * refactor: 프로필별 설정 (#36) * refactor: jpa properties 설정 (#36) * refactor: h2-console URL 수정 (#36) * refactor: JWT_SECRET 기본값 설정 (#36) * refactor: 아카이브 파일 제외 (#36) * chore: 깃허브 액션 ci/cd 작성 (#36) * feat: Redis 서버 연결 실패 오류 핸들링 (#36) * feat: Redis 서버 오류 핸들링 (#36) * feat: Redis 컨테이너 설정 추가 (#36) * feat: 스프링 프로필 설정 (#36) * test: 깃허브 액션 테스트 (#36) * test: 깃허브 액션 운영 테스트 (#36) * test: 깃허브 액션 운영 테스트 완료 (#36) * docs: README 수정 (#36) * chore: 버전 수정 (#36) --------- Co-authored-by: hyunmin0317 --- .github/workflows/dev_cicd.yml | 55 +++++++++++++++++++ .github/workflows/release_cicd.yml | 55 +++++++++++++++++++ .gitignore | 4 -- README.md | 26 +++++++++ build.gradle | 6 +- docker-compose.yml | 8 +++ .../jwt/global/exception/code/ErrorCode.java | 6 +- .../handler/GeneralExceptionHandler.java | 18 ++++++ .../security/config/SecurityConfig.java | 4 +- src/main/resources/application-dev.yml | 21 +++++++ src/main/resources/application-local.yml | 11 ++-- src/main/resources/application-release.yml | 21 +++++++ src/main/resources/application.yml | 4 +- 13 files changed, 223 insertions(+), 16 deletions(-) create mode 100644 .github/workflows/dev_cicd.yml create mode 100644 .github/workflows/release_cicd.yml create mode 100644 docker-compose.yml create mode 100644 src/main/resources/application-dev.yml create mode 100644 src/main/resources/application-release.yml diff --git a/.github/workflows/dev_cicd.yml b/.github/workflows/dev_cicd.yml new file mode 100644 index 0000000..68a375b --- /dev/null +++ b/.github/workflows/dev_cicd.yml @@ -0,0 +1,55 @@ +name: Dev CI/CD + +on: + push: + branches: + - develop + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + distribution: 'corretto' + java-version: '17' + + - name: Grant execute permission for gradlew + run: chmod +x gradlew + + - name: Build with Gradle + run: ./gradlew clean build + env: + SPRING_PROFILE: dev + JWT_SECRET: ${{ secrets.JWT_SECRET }} + DB_URL: ${{ secrets.DB_URL }} + DB_USERNAME: ${{ secrets.DB_USERNAME }} + DB_PASSWORD: ${{ secrets.DB_PASSWORD }} + REDIS_URL: ${{ secrets.REDIS_URL }} + REDIS_PORT: ${{ secrets.REDIS_PORT }} + + - name: Get current time + uses: josStorer/get-current-time@v2.0.2 + id: current-time + with: + format: YYYY-MM-DDTHH-mm-ss + utcOffset: "+09:00" + + - name: Set artifact + run: echo "artifact=$(ls ./build/libs)" >> $GITHUB_ENV + + - name: Beanstalk Deploy + uses: einaregilsson/beanstalk-deploy@v20 + with: + aws_access_key: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws_secret_key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + application_name: jwt-dev + environment_name: jwt-dev-env + version_label: github-action-${{steps.current-time.outputs.formattedTime}} + region: ap-northeast-2 + deployment_package: ./build/libs/${{env.artifact}} diff --git a/.github/workflows/release_cicd.yml b/.github/workflows/release_cicd.yml new file mode 100644 index 0000000..d109e41 --- /dev/null +++ b/.github/workflows/release_cicd.yml @@ -0,0 +1,55 @@ +name: Release CI/CD + +on: + push: + branches: + - main + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + distribution: 'corretto' + java-version: '17' + + - name: Grant execute permission for gradlew + run: chmod +x gradlew + + - name: Build with Gradle + run: ./gradlew clean build + env: + SPRING_PROFILE: release + JWT_SECRET: ${{ secrets.JWT_SECRET }} + DB_URL: ${{ secrets.DB_URL }} + DB_USERNAME: ${{ secrets.DB_USERNAME }} + DB_PASSWORD: ${{ secrets.DB_PASSWORD }} + REDIS_URL: ${{ secrets.REDIS_URL }} + REDIS_PORT: ${{ secrets.REDIS_PORT }} + + - name: Get current time + uses: josStorer/get-current-time@v2.0.2 + id: current-time + with: + format: YYYY-MM-DDTHH-mm-ss + utcOffset: "+09:00" + + - name: Set artifact + run: echo "artifact=$(ls ./build/libs)" >> $GITHUB_ENV + + - name: Beanstalk Deploy + uses: einaregilsson/beanstalk-deploy@v20 + with: + aws_access_key: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws_secret_key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + application_name: jwt-release + environment_name: jwt-release-env + version_label: github-action-${{steps.current-time.outputs.formattedTime}} + region: ap-northeast-2 + deployment_package: ./build/libs/${{env.artifact}} diff --git a/.gitignore b/.gitignore index c5d6ec2..05a74ea 100644 --- a/.gitignore +++ b/.gitignore @@ -483,8 +483,4 @@ gradle-app.setting # Java heap dump *.hprof -# yml files -application-*.yml -!application-local.yml - # End of https://www.toptal.com/developers/gitignore/api/macos \ No newline at end of file diff --git a/README.md b/README.md index e350c6e..70dfc9b 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,29 @@ # Spring Boot JWT > Spring Boot, Spring Security를 이용한 JWT 인증·인가 서버 템플릿 + +## ⚒️ Tech Stack + +* #### Backend + + + + + +* #### Build Tool + + +* #### DBMS + + +* #### CI/CD + + + + + +* #### Deploy + + + + diff --git a/build.gradle b/build.gradle index 507f8e9..051bc72 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ plugins { } group = 'com.hyunmin' -version = '0.0.1-SNAPSHOT' +version = '1.0.0' java { toolchain { @@ -13,6 +13,10 @@ java { } } +jar { + enabled = false +} + configurations { compileOnly { extendsFrom annotationProcessor diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..5bae62c --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,8 @@ +version: '3' + +services: + redis: + image: redis:latest + container_name: jwt-redis + ports: + - "6379:6379" diff --git a/src/main/java/com/hyunmin/jwt/global/exception/code/ErrorCode.java b/src/main/java/com/hyunmin/jwt/global/exception/code/ErrorCode.java index a38134d..4709157 100644 --- a/src/main/java/com/hyunmin/jwt/global/exception/code/ErrorCode.java +++ b/src/main/java/com/hyunmin/jwt/global/exception/code/ErrorCode.java @@ -30,7 +30,11 @@ public enum ErrorCode { // Member Errors MEMBER_FORBIDDEN(403, "MEMBER001", "사용자 권한이 없습니다."), - MEMBER_NOT_FOUND(404, "MEMBER002", "해당 사용자가 없습니다."); + MEMBER_NOT_FOUND(404, "MEMBER002", "해당 사용자가 없습니다."), + + // Redis Errors + REDIS_CONNECTION_FAILURE(500, "REDIS001", "Redis 서버에 연결할 수 없습니다."), + REDIS_SYSTEM_EXCEPTION(500, "REDIS002", "Redis 시스템 예외가 발생했습니다."); private final int value; private final String code; diff --git a/src/main/java/com/hyunmin/jwt/global/exception/handler/GeneralExceptionHandler.java b/src/main/java/com/hyunmin/jwt/global/exception/handler/GeneralExceptionHandler.java index 9b043b8..a215c79 100644 --- a/src/main/java/com/hyunmin/jwt/global/exception/handler/GeneralExceptionHandler.java +++ b/src/main/java/com/hyunmin/jwt/global/exception/handler/GeneralExceptionHandler.java @@ -6,6 +6,8 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.context.MessageSourceResolvable; import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.data.redis.RedisConnectionFailureException; +import org.springframework.data.redis.RedisSystemException; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; @@ -51,6 +53,22 @@ protected ResponseEntity> handleHandlerMethodValidationExcep return ErrorResponse.handle(errorCode); } + // Redis 서버 연결 실패(RedisConnectionFailureException) 처리 메서드 + @ExceptionHandler(RedisConnectionFailureException.class) + protected ResponseEntity> handleRedisConnectionFailureException(RedisConnectionFailureException ex) { + log.warn("[WARNING] {} : {}", ex.getClass(), ex.getMessage()); + ErrorCode errorCode = ErrorCode.REDIS_CONNECTION_FAILURE; + return ErrorResponse.handle(errorCode); + } + + // Redis 서버 오류(REDIS_SYSTEM_EXCEPTION) 처리 메서드 + @ExceptionHandler(RedisSystemException.class) + protected ResponseEntity> handleRedisSystemException(RedisSystemException ex) { + log.warn("[WARNING] {} : {}", ex.getClass(), ex.getMessage()); + ErrorCode errorCode = ErrorCode.REDIS_SYSTEM_EXCEPTION; + return ErrorResponse.handle(errorCode); + } + // 기타 모든 예외(Exception) 처리 메서드 @ExceptionHandler(Exception.class) protected ResponseEntity> handleException(Exception ex) { diff --git a/src/main/java/com/hyunmin/jwt/global/security/config/SecurityConfig.java b/src/main/java/com/hyunmin/jwt/global/security/config/SecurityConfig.java index 6de576d..4285945 100644 --- a/src/main/java/com/hyunmin/jwt/global/security/config/SecurityConfig.java +++ b/src/main/java/com/hyunmin/jwt/global/security/config/SecurityConfig.java @@ -6,7 +6,6 @@ import com.hyunmin.jwt.global.security.handler.JwtAuthenticationEntryPoint; import com.hyunmin.jwt.global.security.provider.JwtTokenProvider; import lombok.RequiredArgsConstructor; -import org.springframework.boot.autoconfigure.security.servlet.PathRequest; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; @@ -51,8 +50,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // 경로별 인가 작업 http.authorizeHttpRequests(authorize -> authorize // H2 콘솔과 Swagger UI 및 API 문서에 대한 접근 허용 - .requestMatchers(PathRequest.toH2Console()).permitAll() - .requestMatchers("/swagger-ui/**", "/v3/api-docs/**").permitAll() + .requestMatchers("/h2-console/**", "/swagger-ui/**", "/v3/api-docs/**").permitAll() // API 계정 관련 요청에 대한 접근 허용 .requestMatchers("/api/v1/accounts/**").permitAll() // 전체 사용자 정보 조회 API 관리자 권한만 접근 허용 diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml new file mode 100644 index 0000000..cb9ee70 --- /dev/null +++ b/src/main/resources/application-dev.yml @@ -0,0 +1,21 @@ +spring: + datasource: + url: ${DB_URL} + username: ${DB_USERNAME} + password: ${DB_PASSWORD} + driver-class-name: com.mysql.cj.jdbc.Driver + + data: + redis: + host: ${REDIS_URL} + port: ${REDIS_PORT} + + jpa: + properties: + hibernate: + dialect: org.hibernate.dialect.MySQLDialect + show_sql: true + format_sql: true + + hibernate: + ddl-auto: update diff --git a/src/main/resources/application-local.yml b/src/main/resources/application-local.yml index 610f140..ef5ede2 100644 --- a/src/main/resources/application-local.yml +++ b/src/main/resources/application-local.yml @@ -5,14 +5,15 @@ spring: password: driver-class-name: org.h2.Driver + h2: + console: + enabled: true + jpa: - hibernate: - ddl-auto: update properties: hibernate: show_sql: true format_sql: true - h2: - console: - enabled: true + hibernate: + ddl-auto: update diff --git a/src/main/resources/application-release.yml b/src/main/resources/application-release.yml new file mode 100644 index 0000000..8fdd59b --- /dev/null +++ b/src/main/resources/application-release.yml @@ -0,0 +1,21 @@ +spring: + datasource: + url: ${DB_URL} + username: ${DB_USERNAME} + password: ${DB_PASSWORD} + driver-class-name: com.mysql.cj.jdbc.Driver + + data: + redis: + host: ${REDIS_URL} + port: ${REDIS_PORT} + + jpa: + properties: + hibernate: + dialect: org.hibernate.dialect.MySQLDialect + show_sql: true + format_sql: true + + hibernate: + ddl-auto: none diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 055870b..0029987 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,9 +1,9 @@ spring: profiles: - active: local + active: ${SPRING_PROFILE:local} jwt: - secret: ${JWT_SECRET} + secret: ${JWT_SECRET:secret} token: access-expiration-time: 1800 # 30 minutes refresh-expiration-time: 1209600 # 14 days