From 932178d060b407c6f2d4c8f4c160fda26c3efbbc Mon Sep 17 00:00:00 2001 From: jun02160 Date: Sat, 10 Feb 2024 17:02:05 +0900 Subject: [PATCH] =?UTF-8?q?[MERGE]=20=EB=B3=91=ED=95=A9=20=EC=B6=A9?= =?UTF-8?q?=EB=8F=8C=20=EC=98=A4=EB=A5=98=20=ED=95=B4=EA=B2=B0=20#117?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/api-CD.yml | 105 ------------------ .github/workflows/api-CI.yml | 68 ------------ .github/workflows/notification-CD.yml | 105 ------------------ .github/workflows/notification-CI.yml | 68 ------------ .gitignore | 2 + scripts/umbba-api/appspec.yml | 22 ---- scripts/umbba-api/deploy.sh | 86 -------------- scripts/umbba-api/switch.sh | 30 ----- scripts/umbba-notification/appspec.yml | 19 ---- scripts/umbba-notification/deploy.sh | 19 ---- .../advice/ControllerExceptionAdvice.java | 12 +- .../api/controller/qna/QnAController.java | 8 ++ .../src/main/resources/application-set1.yml | 54 --------- .../src/main/resources/application-set2.yml | 54 --------- .../org/umbba/common/exception/ErrorType.java | 7 +- .../umbba/common/exception/SuccessType.java | 1 + .../domain/parentchild/Parentchild.java | 14 ++- .../qna/repository/QuestionRepository.java | 2 + .../service/fcm/FCMController.java | 3 +- .../notification/service/fcm/FCMService.java | 77 ++++++++++++- 20 files changed, 108 insertions(+), 648 deletions(-) delete mode 100644 .github/workflows/api-CD.yml delete mode 100644 .github/workflows/api-CI.yml delete mode 100644 .github/workflows/notification-CD.yml delete mode 100644 .github/workflows/notification-CI.yml delete mode 100644 scripts/umbba-api/appspec.yml delete mode 100644 scripts/umbba-api/deploy.sh delete mode 100644 scripts/umbba-api/switch.sh delete mode 100644 scripts/umbba-notification/appspec.yml delete mode 100644 scripts/umbba-notification/deploy.sh delete mode 100644 umbba-api/src/main/resources/application-set1.yml delete mode 100644 umbba-api/src/main/resources/application-set2.yml diff --git a/.github/workflows/api-CD.yml b/.github/workflows/api-CD.yml deleted file mode 100644 index 358142ad..00000000 --- a/.github/workflows/api-CD.yml +++ /dev/null @@ -1,105 +0,0 @@ -# 워크플로우의 이름 지정 -name: Umbba API Server CD - -# 해당 workflow가 언제 실행될 것인지에 대한 트리거를 지정 -on: - push: - branches: [ "develop" ] - paths: - - umbba-api/** - - umbba-domain/** - - umbba-common/** - - umbba-external/** - - .github/workflows/** - -env: - S3_BUCKET_NAME: umbba-storage - -jobs: - build: - name: Code deployment - - # 실행 환경 - runs-on: ubuntu-latest - - steps: - - # 1) 워크플로우 실행 전 기본적으로 체크아웃 필요 - - name: checkout - uses: actions/checkout@v3 - - # 2) JDK 11버전 설치, 다른 JDK 버전을 사용하다면 수정 - - name: Set up JDK 11 - uses: actions/setup-java@v3 - with: - java-version: '11' - distribution: 'temurin' - - - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@v2 - with: - aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY }} - aws-secret-access-key: ${{ secrets.AWS_SECRET_KEY }} - aws-region: ap-northeast-2 - - # 3) AWS Secrets Manger 환경변수 사용 - - name: Read secrets from AWS Secrets Manager into environment variables - uses: abhilash1in/aws-secrets-manager-action@v1.1.0 - with: - aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY }} - aws-secret-access-key: ${{ secrets.AWS_SECRET_KEY }} - aws-region: ap-northeast-2 - secrets: /secret/umbba-secret - parse-json: false - - # 4) FCM secret key 파일 생성 - - name: FCM secret key 파일 생성 - run: | - cd ./umbba-api/src/main/resources - - mkdir ./firebase - cd ./firebase - - aws s3 cp --region ap-northeast-2 s3://${{ secrets.S3_BUCKET_NAME }}/json/umbba-fcm-firebase-adminsdk.json . - - shell: bash - - # 이 워크플로우는 gradle build - - name: Grant execute permission for gradlew - run: chmod +x gradlew - - - name: Build with Gradle # 실제 application build(-x 옵션을 통해 test는 제외) - run: ./gradlew umbba-api:bootJar -x test - - # 디렉토리 생성 - - name: Make Directory - run: mkdir -p deploy - - # Jar 파일 복사 - - name: Copy Jar - run: cp ./umbba-api/build/libs/*.jar ./deploy - # run: cp -r ./umbba-api/src/main/* ./deploy - - # appspec.yml, script files 파일 복사 - - name: Copy files - run: cp ./scripts/umbba-api/* ./deploy - - - name: Make zip file - run: zip -r ./umbba-api.zip ./deploy - shell: bash - - - name: Upload to S3 - run: aws s3 cp --region ap-northeast-2 ./umbba-api.zip s3://$S3_BUCKET_NAME/ - - # Deploy - - name: Deploy - env: - AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_KEY }} - run: - aws deploy create-deployment - --application-name umbba-server-codedeploy - --deployment-group-name umbba-api-server-codedeploy-group - --file-exists-behavior OVERWRITE - --s3-location bucket=umbba-storage,bundleType=zip,key=umbba-api.zip - --region ap-northeast-2 diff --git a/.github/workflows/api-CI.yml b/.github/workflows/api-CI.yml deleted file mode 100644 index 32a1a18e..00000000 --- a/.github/workflows/api-CI.yml +++ /dev/null @@ -1,68 +0,0 @@ -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. -# This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time -# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-gradle -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. -# This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time -# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-gradle - -name: Umbba API Server CI - -on: - push: - branches: [ "develop" ] - paths: - - umbba-api/** - - umbba-domain/** - - umbba-common/** - - umbba-external/** - pull_request: - branches: [ "develop" ] - paths: - - umbba-api/** - - umbba-domain/** - - umbba-common/** - - umbba-external/** - -permissions: - contents: read - -jobs: - build: - - runs-on: ubuntu-latest - - steps: - - # 1) 워크플로우 실행 전 기본적으로 체크아웃 필요 - - name: checkout - uses: actions/checkout@v3 - - # 2) JDK 11버전 설치, 다른 JDK 버전을 사용하다면 수정 - - name: Set up JDK 11 - uses: actions/setup-java@v3 - with: - java-version: '11' - distribution: 'temurin' - - # 3) AWS Secrets Manger 환경변수 사용 - - name: Read secrets from AWS Secrets Manager into environment variables - uses: abhilash1in/aws-secrets-manager-action@v1.1.0 - with: - aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY }} - aws-secret-access-key: ${{ secrets.AWS_SECRET_KEY }} - aws-region: ap-northeast-2 - secrets: /secret/umbba-secret - parse-json: false - - # 이 워크플로우는 gradle build - - name: Grant execute permission for gradlew - run: chmod +x gradlew - - - name: Build with Gradle # 실제 application build(-x 옵션을 통해 test는 제외) - run: ./gradlew umbba-api:bootJar -x test diff --git a/.github/workflows/notification-CD.yml b/.github/workflows/notification-CD.yml deleted file mode 100644 index ea8b69c0..00000000 --- a/.github/workflows/notification-CD.yml +++ /dev/null @@ -1,105 +0,0 @@ -# 워크플로우의 이름 지정 -name: Umbba Notification Server CD - -# 해당 workflow가 언제 실행될 것인지에 대한 트리거를 지정 -on: - push: - branches: [ "develop" ] - paths: - - umbba-notification/** - - umbba-domain/** - - umbba-common/** - - umbba-external/** - - .github/workflows/** - -env: - S3_BUCKET_NAME: umbba-storage - -jobs: - build: - name: Code deployment - - # 실행 환경 - runs-on: ubuntu-latest - - steps: - - # 1) 워크플로우 실행 전 기본적으로 체크아웃 필요 - - name: checkout - uses: actions/checkout@v3 - - # 2) JDK 11버전 설치, 다른 JDK 버전을 사용하다면 수정 - - name: Set up JDK 11 - uses: actions/setup-java@v3 - with: - java-version: '11' - distribution: 'temurin' - - - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@v2 - with: - aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY }} - aws-secret-access-key: ${{ secrets.AWS_SECRET_KEY }} - aws-region: ap-northeast-2 - - # 3) AWS Secrets Manger 환경변수 사용 - - name: Read secrets from AWS Secrets Manager into environment variables - uses: abhilash1in/aws-secrets-manager-action@v1.1.0 - with: - aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY }} - aws-secret-access-key: ${{ secrets.AWS_SECRET_KEY }} - aws-region: ap-northeast-2 - secrets: /secret/umbba-secret - parse-json: false - - # 4) FCM secret key 파일 생성 - - name: FCM secret key 파일 생성 - run: | - cd ./umbba-notification/src/main/resources - - mkdir ./firebase - cd ./firebase - - aws s3 cp --region ap-northeast-2 s3://${{ secrets.S3_BUCKET_NAME }}/json/umbba-fcm-firebase-adminsdk.json . - - shell: bash - - # 이 워크플로우는 gradle build - - name: Grant execute permission for gradlew - run: chmod +x gradlew - - - name: Build with Gradle # 실제 application build(-x 옵션을 통해 test는 제외) - run: ./gradlew umbba-notification:bootJar -x test - - # 디렉토리 생성 - - name: Make Directory - run: mkdir -p deploy - - # Jar 파일 복사 - - name: Copy Jar - run: cp ./umbba-notification/build/libs/*.jar ./deploy - # run: cp -r src/main/* ./deploy - - # appspec.yml, script files 파일 복사 - - name: Copy files - run: cp ./scripts/umbba-notification/* ./deploy - - - name: Make zip file - run: zip -r ./umbba-notification.zip ./deploy - shell: bash - - - name: Upload to S3 - run: aws s3 cp --region ap-northeast-2 ./umbba-notification.zip s3://$S3_BUCKET_NAME/ - - # Deploy - - name: Deploy - env: - AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_KEY }} - run: - aws deploy create-deployment - --application-name umbba-server-codedeploy - --deployment-group-name umbba-notification-server-codedeploy-group - --file-exists-behavior OVERWRITE - --s3-location bucket=umbba-storage,bundleType=zip,key=umbba-notification.zip - --region ap-northeast-2 diff --git a/.github/workflows/notification-CI.yml b/.github/workflows/notification-CI.yml deleted file mode 100644 index 7e2cf6fd..00000000 --- a/.github/workflows/notification-CI.yml +++ /dev/null @@ -1,68 +0,0 @@ -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. -# This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time -# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-gradle -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. -# This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time -# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-gradle - -name: Umbba Notification Server CI - -on: - push: - branches: [ "develop" ] - paths: - - umbba-notification/** - - umbba-domain/** - - umbba-common/** - - umbba-external/** - pull_request: - branches: [ "develop" ] - paths: - - umbba-notification/** - - umbba-domain/** - - umbba-common/** - - umbba-external/** - -permissions: - contents: read - -jobs: - build: - - runs-on: ubuntu-latest - - steps: - - # 1) 워크플로우 실행 전 기본적으로 체크아웃 필요 - - name: checkout - uses: actions/checkout@v3 - - # 2) JDK 11버전 설치, 다른 JDK 버전을 사용하다면 수정 - - name: Set up JDK 11 - uses: actions/setup-java@v3 - with: - java-version: '11' - distribution: 'temurin' - - # 3) AWS Secrets Manger 환경변수 사용 - - name: Read secrets from AWS Secrets Manager into environment variables - uses: abhilash1in/aws-secrets-manager-action@v1.1.0 - with: - aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY }} - aws-secret-access-key: ${{ secrets.AWS_SECRET_KEY }} - aws-region: ap-northeast-2 - secrets: /secret/umbba-secret - parse-json: false - - # 이 워크플로우는 gradle build - - name: Grant execute permission for gradlew - run: chmod +x gradlew - - - name: Build with Gradle # 실제 application build(-x 옵션을 통해 test는 제외) - run: ./gradlew umbba-notification:bootJar -x test diff --git a/.gitignore b/.gitignore index 8ab1ea93..8ad08fda 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,8 @@ build/ !**/src/main/**/build/ !**/src/test/**/build/ +umbba-test.http + ## Querydsl umbba-domain/src/main/generated diff --git a/scripts/umbba-api/appspec.yml b/scripts/umbba-api/appspec.yml deleted file mode 100644 index 0386a216..00000000 --- a/scripts/umbba-api/appspec.yml +++ /dev/null @@ -1,22 +0,0 @@ -version: 0.0 -os: linux - -files: - - source: / - destination: /home/ubuntu/api-server - overwrite: yes - -permissions: - - object: / - pattern: "**" - owner: ubuntu - group: ubuntu - -hooks: - AfterInstall: - - location: deploy.sh - timeout: 180 - runas: ubuntu - - location: switch.sh - timeout: 180 - runas: ubuntu \ No newline at end of file diff --git a/scripts/umbba-api/deploy.sh b/scripts/umbba-api/deploy.sh deleted file mode 100644 index 70564ac0..00000000 --- a/scripts/umbba-api/deploy.sh +++ /dev/null @@ -1,86 +0,0 @@ -#!/bin/bash -NOW_TIME="$(date +%Y)-$(date +%m)-$(date +%d) $(date +%H):$(date +%M):$(date +%S)" - -BUILD_PATH=$(ls /home/ubuntu/api-server/umbba-api-0.0.1-SNAPSHOT.jar) -JAR_NAME=$(basename $BUILD_PATH) -echo "[$NOW_TIME] build 파일명: $JAR_NAME" - -echo "[$NOW_TIME] build 파일 복사" -DEPLOY_PATH=/home/ubuntu/api-server/nonstop/jar/ -cp $BUILD_PATH $DEPLOY_PATH - -echo "[$NOW_TIME] 현재 구동중인 Set 확인" -CURRENT_PROFILE=$(curl -s http://localhost/profile) -echo "[$NOW_TIME] $CURRENT_PROFILE" - -# 쉬고 있는 set 찾기: set1이 사용중이면 set2가 쉬고 있고, 반대면 set1이 쉬고 있음 -if [ $CURRENT_PROFILE == set1 ] -then - IDLE_PROFILE=set2 - IDLE_PORT=8082 -elif [ $CURRENT_PROFILE == set2 ] -then - IDLE_PROFILE=set1 - IDLE_PORT=8081 -else - echo "[$NOW_TIME] 일치하는 Profile이 없습니다. Profile: $CURRENT_PROFILE" - echo "[$NOW_TIME] set1을 할당합니다. IDLE_PROFILE: set1" - IDLE_PROFILE=set1 - IDLE_PORT=8081 -fi - -echo "[$NOW_TIME] application.jar 교체" -IDLE_APPLICATION=$IDLE_PROFILE-Umbba-API.jar -IDLE_APPLICATION_PATH=$DEPLOY_PATH$IDLE_APPLICATION - -ln -Tfs $DEPLOY_PATH$JAR_NAME $IDLE_APPLICATION_PATH - -echo "[$NOW_TIME] $IDLE_PROFILE 에서 구동중인 애플리케이션 pid 확인" -IDLE_PID=$(pgrep -f $IDLE_APPLICATION) - -if [ -z $IDLE_PID ] -then - echo "[$NOW_TIME] 현재 구동중인 애플리케이션이 없으므로 종료하지 않습니다." -else - echo "[$NOW_TIME] kill -15 $IDLE_PID" - kill -15 $IDLE_PID - - while ps -p $IDLE_PID > /dev/null; do - sleep 1 - done - echo "[$NOW_TIME] 애플리케이션이 정상 종료되었습니다." -fi - -echo "[$NOW_TIME] $IDLE_PROFILE 배포" -nohup java -jar -Duser.timezone=Asia/Seoul -Dspring.profiles.active=$IDLE_PROFILE $IDLE_APPLICATION_PATH >> /home/ubuntu/api-server/deploy.log 2>/home/ubuntu/api-server/deploy_err.log & - -################################################################## - -echo "[$NOW_TIME] $IDLE_PROFILE 10초 후 Health check 시작" -echo "[$NOW_TIME] curl -s http://localhost:$IDLE_PORT/health " -sleep 10 - -for retry_count in {1..10} -do - response=$(curl -s http://localhost:$IDLE_PORT/actuator/health) - up_count=$(echo $response | grep 'UP' | wc -l) - - if [ $up_count -ge 1 ] - then # $up_count >= 1 ("UP" 문자열이 있는지 검증) - echo "[$NOW_TIME] Health check 성공" - break - else - echo "[$NOW_TIME] Health check의 응답을 알 수 없거나 혹은 status가 UP이 아닙니다." - echo "[$NOW_TIME] Health check: ${response}" - fi - - if [ $retry_count -eq 10 ] - then - echo "[$NOW_TIME] Health check 실패. " - echo "[$NOW_TIME] Nginx에 연결하지 않고 배포를 종료합니다." - exit 1 - fi - - echo "[$NOW_TIME] Health check 연결 실패. 재시도..." - sleep 10 -done \ No newline at end of file diff --git a/scripts/umbba-api/switch.sh b/scripts/umbba-api/switch.sh deleted file mode 100644 index d2715800..00000000 --- a/scripts/umbba-api/switch.sh +++ /dev/null @@ -1,30 +0,0 @@ -#!/bin/bash -NOW_TIME="$(date +%Y)-$(date +%m)-$(date +%d) $(date +%H):$(date +%M):$(date +%S)" - -echo "[$NOW_TIME] 스위칭" -sleep 10 -echo "[$NOW_TIME] 현재 구동중인 Port 확인" -CURRENT_PROFILE=$(curl -s http://localhost/profile) - -# 쉬고 있는 set 찾기: set1이 사용중이면 set2가 쉬고 있고, 반대면 set1이 쉬고 있음 -if [ $CURRENT_PROFILE == set1 ] -then - IDLE_PORT=8082 -elif [ $CURRENT_PROFILE == set2 ] -then - IDLE_PORT=8081 -else - echo "[$NOW_TIME] 일치하는 Profile이 없습니다. Profile: $CURRENT_PROFILE" - echo "[$NOW_TIME] 8081을 할당합니다." - IDLE_PORT=8081 -fi - -echo "[$NOW_TIME] 전환할 Port: $IDLE_PORT" -echo "[$NOW_TIME] Port 전환" -echo "set \$service_url http://127.0.0.1:${IDLE_PORT};" | sudo tee /etc/nginx/conf.d/service-url.inc - -PROXY_PORT=$(curl -s http://localhost/profile) -echo "[$NOW_TIME] Nginx Current Proxy Port: $PROXY_PORT" - -echo "[$NOW_TIME] Nginx Reload" -sudo service nginx reload \ No newline at end of file diff --git a/scripts/umbba-notification/appspec.yml b/scripts/umbba-notification/appspec.yml deleted file mode 100644 index 4a0bf554..00000000 --- a/scripts/umbba-notification/appspec.yml +++ /dev/null @@ -1,19 +0,0 @@ -version: 0.0 -os: linux - -files: - - source: / - destination: /home/ubuntu/notification-server - overwrite: yes - -permissions: - - object: / - pattern: "**" - owner: ubuntu - group: ubuntu - -hooks: - AfterInstall: - - location: deploy.sh - timeout: 180 - runas: ubuntu \ No newline at end of file diff --git a/scripts/umbba-notification/deploy.sh b/scripts/umbba-notification/deploy.sh deleted file mode 100644 index 8024a227..00000000 --- a/scripts/umbba-notification/deploy.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/bash -NOW_TIME="$(date +%Y)-$(date +%m)-$(date +%d) $(date +%H):$(date +%M):$(date +%S)" - -BUILD_PATH=/home/ubuntu/notification-server/umbba-notification-0.0.1-SNAPSHOT.jar -TARGET_PORT=8083 -TARGET_PID=$(lsof -Fp -i TCP:${TARGET_PORT} | grep -Po 'p[0-9]+' | grep -Po '[0-9]+') - -if [ ! -z ${TARGET_PID} ]; then - echo "[$NOW_TIME] Kill WAS running at ${TARGET_PORT}." >> /home/ubuntu/notification-server/deploy.log - sudo kill -15 ${TARGET_PID} - while ps -p $TARGET_PID > /dev/null; do - sleep 1 - done - echo "[$NOW_TIME] 애플리케이션이 정상 종료되었습니다." -fi - -nohup java -jar -Duser.timezone=Asia/Seoul -Dspring.profiles.active=dev $BUILD_PATH >> /home/ubuntu/notification-server/deploy.log 2>/home/ubuntu/notification-server/deploy_err.log & -echo "[$NOW_TIME] Now new WAS runs at ${TARGET_PORT}." >> /home/ubuntu/notification-server/deploy.log -exit 0 \ No newline at end of file diff --git a/umbba-api/src/main/java/sopt/org/umbba/api/controller/advice/ControllerExceptionAdvice.java b/umbba-api/src/main/java/sopt/org/umbba/api/controller/advice/ControllerExceptionAdvice.java index 8f2dfe29..c4752718 100644 --- a/umbba-api/src/main/java/sopt/org/umbba/api/controller/advice/ControllerExceptionAdvice.java +++ b/umbba-api/src/main/java/sopt/org/umbba/api/controller/advice/ControllerExceptionAdvice.java @@ -192,11 +192,17 @@ public ApiResponse handlerNullPointerException(final NullPointerExcep * CUSTOM_ERROR */ @ExceptionHandler(CustomException.class) - protected ResponseEntity handleCustomException(CustomException e) { + protected ResponseEntity handleCustomException(CustomException e, final HttpServletRequest request) { log.error("CustomException occured: {}", e.getMessage(), e); - return ResponseEntity.status(e.getHttpStatus()) - .body(ApiResponse.error(e.getErrorType(), e.getMessage())); + if (e.getHttpStatus() == 501) { + notificationService.sendExceptionToSlack(e, request); + return ResponseEntity.status(e.getHttpStatus()) + .body(ApiResponse.error(ErrorType.NEED_MORE_QUESTION)); + } else { + return ResponseEntity.status(e.getHttpStatus()) + .body(ApiResponse.error(e.getErrorType(), e.getMessage())); + } } } diff --git a/umbba-api/src/main/java/sopt/org/umbba/api/controller/qna/QnAController.java b/umbba-api/src/main/java/sopt/org/umbba/api/controller/qna/QnAController.java index dd68046e..27918114 100644 --- a/umbba-api/src/main/java/sopt/org/umbba/api/controller/qna/QnAController.java +++ b/umbba-api/src/main/java/sopt/org/umbba/api/controller/qna/QnAController.java @@ -72,6 +72,14 @@ public ApiResponse getSingleQna( qnAService.getSingleQna(JwtProvider.getUserFromPrincial(principal), qnaId)); } + @PatchMapping("/qna/restart") + @ResponseStatus(HttpStatus.OK) + public ApiResponse restartQna(Principal principal) { + + qnAService.restartQna(JwtProvider.getUserFromPrincial(principal)); + return ApiResponse.success(SuccessType.RESTART_QNA_SUCCESS); + } + @GetMapping("/home") @ResponseStatus(HttpStatus.OK) public ApiResponse home(Principal principal) { diff --git a/umbba-api/src/main/resources/application-set1.yml b/umbba-api/src/main/resources/application-set1.yml deleted file mode 100644 index a924c93f..00000000 --- a/umbba-api/src/main/resources/application-set1.yml +++ /dev/null @@ -1,54 +0,0 @@ -spring: - config: - activate: - on-profile: set1 - - datasource: - driver-class-name: com.mysql.cj.jdbc.Driver - url: ${DB_URL_DEV} - username: ${DB_USER_DEV} - password: ${DB_PWD_DEV} - hikari: - pool-name: Hikari 커넥션 풀 # Pool - connection-timeout: 30000 # 30초(default: 30초) - maximum-pool-size: 10 # default: 10개 - max-lifetime: 600000 # 10분(default: 30분) - leak-detection-threshold: 3500 # default: 0(이용X) - - jpa: - show-sql: false - hibernate: - ddl-auto: update - ejb: - naming_strategy: org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy - properties: - hibernate: - format_sql: true - -cloud: - aws: - credentials: - accessKey: ${CLOUD_ACCESS_DEV} - secretKey: ${CLOUD_SECRET_DEV} - region: - static: ${CLOUD_REGION_DEV} - s3: - bucket: ${BUCKET_NAME_DEV} - stack: - auto: false - sqs: - notification: - name: ${SQS_NAME_DEV} - url: ${SQS_URL_DEV} - -server: - port: 8081 - -kakao: - client-id: ${KAKAO_ID} - authorization-grant-type: authorization_code - redirect-uri: ${KAKAO_REDIRECT_DEV} - -slack: - webhook: - url: ${SLACK_URL_DEV} \ No newline at end of file diff --git a/umbba-api/src/main/resources/application-set2.yml b/umbba-api/src/main/resources/application-set2.yml deleted file mode 100644 index 1d537832..00000000 --- a/umbba-api/src/main/resources/application-set2.yml +++ /dev/null @@ -1,54 +0,0 @@ -spring: - config: - activate: - on-profile: set2 - - datasource: - driver-class-name: com.mysql.cj.jdbc.Driver - url: ${DB_URL_DEV} - username: ${DB_USER_DEV} - password: ${DB_PWD_DEV} - hikari: - pool-name: Hikari 커넥션 풀 # Pool - connection-timeout: 30000 # 30초(default: 30초) - maximum-pool-size: 10 # default: 10개 - max-lifetime: 600000 # 10분(default: 30분) - leak-detection-threshold: 3500 # default: 0(이용X) - - jpa: - show-sql: false - hibernate: - ddl-auto: update - ejb: - naming_strategy: org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy - properties: - hibernate: - format_sql: true - -cloud: - aws: - credentials: - accessKey: ${CLOUD_ACCESS_DEV} - secretKey: ${CLOUD_SECRET_DEV} - region: - static: ${CLOUD_REGION_DEV} - s3: - bucket: ${BUCKET_NAME_DEV} - stack: - auto: false - sqs: - notification: - name: ${SQS_NAME_DEV} - url: ${SQS_URL_DEV} - -server: - port: 8082 - -kakao: - client-id: ${KAKAO_ID} - authorization-grant-type: authorization_code - redirect-uri: ${KAKAO_REDIRECT_DEV} - -slack: - webhook: - url: ${SLACK_URL_DEV} \ No newline at end of file diff --git a/umbba-common/src/main/java/sopt/org/umbba/common/exception/ErrorType.java b/umbba-common/src/main/java/sopt/org/umbba/common/exception/ErrorType.java index adc0ad08..37bb4846 100644 --- a/umbba-common/src/main/java/sopt/org/umbba/common/exception/ErrorType.java +++ b/umbba-common/src/main/java/sopt/org/umbba/common/exception/ErrorType.java @@ -62,7 +62,6 @@ public enum ErrorType { PARENTCHILD_HAVE_NO_OPPONENT(HttpStatus.NOT_FOUND, "부모자식 관계에 1명만 참여하고 있습니다."), NOT_FOUND_SECTION(HttpStatus.NOT_FOUND, "해당 아이디와 일치하는 섹션이 없습니다."), - /** * About Apple (HttpStatus 고민) */ @@ -82,7 +81,6 @@ public enum ErrorType { FIREBASE_CONNECTION_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "파이어베이스 서버와의 연결에 실패했습니다."), FAIL_TO_SEND_PUSH_ALARM(HttpStatus.INTERNAL_SERVER_ERROR, "푸시 알림 메세지 전송에 실패했습니다."), - // ETC INDEX_OUT_OF_BOUNDS(HttpStatus.INTERNAL_SERVER_ERROR, "인덱스 범위를 초과했습니다."), JWT_SERIALIZE(HttpStatus.INTERNAL_SERVER_ERROR, "JWT 라이브러리 직렬화에 실패했습니다."), @@ -93,7 +91,10 @@ public enum ErrorType { DATA_INTEGRITY_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "데이터 무결성 제약조건을 위반했습니다."), NULL_POINTER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "NULL 포인터를 참조했습니다."), - + /** + * 501 NOT_IMPLEMENTED + */ + NEED_MORE_QUESTION(HttpStatus.NOT_IMPLEMENTED, "남은 질문이 없습니다. 질문을 추가해주세요."), ; diff --git a/umbba-common/src/main/java/sopt/org/umbba/common/exception/SuccessType.java b/umbba-common/src/main/java/sopt/org/umbba/common/exception/SuccessType.java index b4421785..e68ab841 100644 --- a/umbba-common/src/main/java/sopt/org/umbba/common/exception/SuccessType.java +++ b/umbba-common/src/main/java/sopt/org/umbba/common/exception/SuccessType.java @@ -27,6 +27,7 @@ public enum SuccessType { REMIND_QUESTION_SUCCESS(HttpStatus.OK, "상대방에게 질문을 리마인드 하는 데 성공했습니다."), GET_MY_USER_INFO_SUCCESS(HttpStatus.OK, "마이페이지 내 정보 조회에 성공했습니다."), TEST_SUCCESS(HttpStatus.OK, "데모데이 테스트용 API 호출에 성공했습니다."), + RESTART_QNA_SUCCESS(HttpStatus.OK, "7일 이후 문답이 정상적으로 시작되었습니다."), /** diff --git a/umbba-domain/src/main/java/sopt/org/umbba/domain/domain/parentchild/Parentchild.java b/umbba-domain/src/main/java/sopt/org/umbba/domain/domain/parentchild/Parentchild.java index 96743556..31a19aa2 100644 --- a/umbba-domain/src/main/java/sopt/org/umbba/domain/domain/parentchild/Parentchild.java +++ b/umbba-domain/src/main/java/sopt/org/umbba/domain/domain/parentchild/Parentchild.java @@ -38,10 +38,8 @@ public class Parentchild extends AuditingTimeEntity { private int count; public void addCount() { - if (this.count < 7) { - this.count += 1; - log.info("Parentchild - addCount() 호출: {}", this.count); - } + this.count += 1; + log.info("Parentchild - addCount() 호출: {}", this.count); // 미답변 일수 필드 0으로 초기화 this.remindCnt = 0; } @@ -87,15 +85,19 @@ public void changeParentOnboardingAnswerList(List onboardingAn private boolean deleted = Boolean.FALSE; - public void initQnA() { + public void initQna() { qnaList = new ArrayList<>(); } - public void addQnA(QnA qnA) { + public void setQna(QnA qnA) { if (qnaList.size() >= 7) { throw new CustomException(ErrorType.ALREADY_QNA_LIST_FULL); } qnaList.add(qnA); } + public void addQna(QnA qnA) { + qnaList.add(qnA); + } + } diff --git a/umbba-domain/src/main/java/sopt/org/umbba/domain/domain/qna/repository/QuestionRepository.java b/umbba-domain/src/main/java/sopt/org/umbba/domain/domain/qna/repository/QuestionRepository.java index 1a9d7586..07afe0b8 100644 --- a/umbba-domain/src/main/java/sopt/org/umbba/domain/domain/qna/repository/QuestionRepository.java +++ b/umbba-domain/src/main/java/sopt/org/umbba/domain/domain/qna/repository/QuestionRepository.java @@ -17,6 +17,8 @@ public interface QuestionRepository extends Repository { List findByType(QuestionType type); + List findByTypeInAndIdNotIn(List types, List doneQuestionIds); + default List findBySectionAndTypeRandom(QuestionSection section, QuestionType type, int size) { List matchingQuestions = findBySectionAndType(section, type); List selectedQuestions = new ArrayList<>(); diff --git a/umbba-notification/src/main/java/sopt/org/umbba/notification/service/fcm/FCMController.java b/umbba-notification/src/main/java/sopt/org/umbba/notification/service/fcm/FCMController.java index 5d65b1a9..1fb4345a 100644 --- a/umbba-notification/src/main/java/sopt/org/umbba/notification/service/fcm/FCMController.java +++ b/umbba-notification/src/main/java/sopt/org/umbba/notification/service/fcm/FCMController.java @@ -6,7 +6,7 @@ import sopt.org.umbba.common.exception.SuccessType; import sopt.org.umbba.common.exception.dto.ApiResponse; import sopt.org.umbba.common.sqs.dto.FCMPushRequestDto; -import sopt.org.umbba.notification.service.fcm.FCMService; +import sopt.org.umbba.notification.config.ScheduleConfig; import sopt.org.umbba.notification.service.scheduler.FCMScheduler; import java.io.IOException; @@ -28,6 +28,7 @@ public class FCMController { @PostMapping("/qna") @ResponseStatus(HttpStatus.OK) public ApiResponse sendTopicScheduledTest() { + ScheduleConfig.resetScheduler(); return ApiResponse.success(SuccessType.PUSH_ALARM_PERIODIC_SUCCESS, fcmScheduler.pushTodayQna()); } diff --git a/umbba-notification/src/main/java/sopt/org/umbba/notification/service/fcm/FCMService.java b/umbba-notification/src/main/java/sopt/org/umbba/notification/service/fcm/FCMService.java index 52586d4b..f1a0966b 100644 --- a/umbba-notification/src/main/java/sopt/org/umbba/notification/service/fcm/FCMService.java +++ b/umbba-notification/src/main/java/sopt/org/umbba/notification/service/fcm/FCMService.java @@ -21,11 +21,15 @@ import org.springframework.transaction.support.DefaultTransactionDefinition; import sopt.org.umbba.common.exception.ErrorType; import sopt.org.umbba.common.exception.model.CustomException; -import sopt.org.umbba.common.sqs.dto.PushMessage; import sopt.org.umbba.domain.domain.parentchild.Parentchild; import sopt.org.umbba.domain.domain.parentchild.dao.ParentchildDao; import sopt.org.umbba.domain.domain.parentchild.repository.ParentchildRepository; import sopt.org.umbba.domain.domain.qna.QnA; +import sopt.org.umbba.domain.domain.qna.Question; +import sopt.org.umbba.domain.domain.qna.QuestionSection; +import sopt.org.umbba.domain.domain.qna.QuestionType; +import sopt.org.umbba.domain.domain.qna.repository.QnARepository; +import sopt.org.umbba.domain.domain.qna.repository.QuestionRepository; import sopt.org.umbba.domain.domain.user.SocialPlatform; import sopt.org.umbba.domain.domain.user.User; import sopt.org.umbba.domain.domain.user.repository.UserRepository; @@ -38,7 +42,13 @@ import java.io.IOException; import java.util.Arrays; import java.util.List; +import java.util.Random; import java.util.concurrent.ScheduledFuture; +import java.util.stream.Collectors; + +import static sopt.org.umbba.common.exception.ErrorType.NEED_MORE_QUESTION; +import static sopt.org.umbba.domain.domain.qna.QuestionType.MAIN; +import static sopt.org.umbba.domain.domain.qna.QuestionType.YET; /** * 서버에서 파이어베이스로 전송이 잘 이루어지는지 테스트하기 위한 컨트롤러 @@ -59,6 +69,8 @@ public class FCMService { private final UserRepository userRepository; private final ParentchildRepository parentchildRepository; + private final QnARepository qnARepository; + private final QuestionRepository questionRepository; private final ParentchildDao parentchildDao; private final ObjectMapper objectMapper; private final TaskScheduler taskScheduler; @@ -184,7 +196,6 @@ public void schedulePushAlarm(String cronExpression, Long parentchildId) { scheduledFuture = taskScheduler.schedule(() -> { - try { Thread.sleep(1000); } catch (InterruptedException e) { @@ -247,7 +258,12 @@ public void schedulePushAlarm(String cronExpression, Long parentchildId) { } // 부모와 자식 모두 답변한 경우 - else if (currentQnA.isParentAnswer() && currentQnA.isChildAnswer() && parentchild.getCount() < 7) { + else if (currentQnA.isParentAnswer() && currentQnA.isChildAnswer() && parentchild.getCount() != 7) { + + // 8일 이후 (7일 + 엔딩페이지 API 통신으로 추가된 1일) 에는 스케줄링을 돌며 QnA 직접 추가 + if (parentchild.getCount() >= 8) { + appendQna(parentchild); + } log.info("둘 다 답변함 다음 질문으로 ㄱ {}", parentchild.getCount()); parentchild.addCount(); // 오늘의 질문 UP & 리마인드 카운트 초기화 @@ -269,8 +285,6 @@ else if (currentQnA.isParentAnswer() && currentQnA.isChildAnswer() && parentchil todayQnA.getQuestion().getTopic()), parentchild.getId()); } } - - } transactionManager.commit(transactionStatus); } catch (PessimisticLockingFailureException | PessimisticLockException e) { @@ -294,7 +308,60 @@ public static void clearScheduledTasks() { log.info("ScheduledFuture: {}", scheduledFuture); } + private void appendQna(Parentchild parentchild) { + List qnaList = getQnAListByParentchild(parentchild); + + // 1. 메인 타입과 미사용 타입에 대해서 불러오기 + List types = Arrays.asList(MAIN, YET); + + // 2. 내가 이미 주고받은 질문 제외하기 + List doneQuestionIds = qnaList.stream() + .map(qna -> qna.getQuestion().getId()) + .collect(Collectors.toList()); + + // 5. 이 경우 아예 추가될 질문이 없으므로 예외 발생시킴 + List targetQuestions = questionRepository.findByTypeInAndIdNotIn(types, doneQuestionIds); + if (targetQuestions.isEmpty()) { + // 충실한 유저가 추가될 수 있는 질문을 모두 수행했을 경우, 기획 측에서 알 수 있도록 500 에러로 처리 + throw new CustomException(NEED_MORE_QUESTION); + } + QuestionSection section = qnaList.get(parentchild.getCount() - 1).getQuestion().getSection(); + List differentSectionQuestions = targetQuestions.stream() + .filter(question -> !question.getSection().equals(section)) + .collect(Collectors.toList()); + + Random random = new Random(); + Question randomQuestion; + if (!differentSectionQuestions.isEmpty()) { + // 3. 최근에 주고받은 질문의 section과 다른 질문들 중에서 랜덤하게 추출 + randomQuestion = differentSectionQuestions.get(random.nextInt(differentSectionQuestions.size())); + } else { + // 4. 없다면 동일한 section의 질문 중에서라도 랜덤하게 추출 + List equalSectionQuestions = targetQuestions.stream() + .filter(question -> !question.getSection().equals(section)) + .collect(Collectors.toList()); + randomQuestion = equalSectionQuestions.get(random.nextInt(equalSectionQuestions.size())); + } + + QnA newQnA = QnA.builder() + .question(randomQuestion) + .isParentAnswer(false) + .isChildAnswer(false) + .build(); + + qnARepository.save(newQnA); + parentchild.addQna(newQnA); + } + + private List getQnAListByParentchild(Parentchild parentchild) { + List qnaList = parentchild.getQnaList(); + if (qnaList == null || qnaList.isEmpty()) { + throw new CustomException(ErrorType.PARENTCHILD_HAVE_NO_QNALIST); + } + + return qnaList; + } /** * 사용 안하는 함수들