From 7398fcb103e582c0f741b9de10ee4da710fe264f Mon Sep 17 00:00:00 2001 From: Kim-Dongdong Date: Sat, 26 Sep 2026 01:25:19 +0900 Subject: [PATCH 1/3] =?UTF-8?q?fix:=20SMS=20=EC=9D=B8=EC=A6=9D=EB=B2=88?= =?UTF-8?q?=ED=98=B8=20=EB=A1=9C=EA=B7=B8=20=EC=A0=9C=EA=B1=B0=20=EB=B0=8F?= =?UTF-8?q?=20SecureRandom=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/grabpt/config/sms/SmsCertificationUtil.java | 11 +++++++++-- .../com/grabpt/service/SmsService/SmsServiceImpl.java | 7 +++++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/grabpt/config/sms/SmsCertificationUtil.java b/src/main/java/com/grabpt/config/sms/SmsCertificationUtil.java index 796e382e..0eb85892 100644 --- a/src/main/java/com/grabpt/config/sms/SmsCertificationUtil.java +++ b/src/main/java/com/grabpt/config/sms/SmsCertificationUtil.java @@ -34,8 +34,7 @@ public void init() { // 단일 메시지 발송 public void sendSMS(String to, String certificationCode) { - log.info("발신번호 = " + fromNumber); - log.info("수신번호 = " + to); + log.info("인증번호 발송 요청 - 수신번호 = {}", maskPhoneNumber(to)); Message message = new Message(); // 새 메시지 객체 생성 message.setFrom("01063506423"); // 발신자 번호 설정 message.setTo(to); // 수신자 번호 설정 @@ -43,4 +42,12 @@ public void sendSMS(String to, String certificationCode) { this.messageService.sendOne(new SingleMessageSendingRequest(message)); // 메시지 발송 요청 } + + // 로그용 전화번호 마스킹 (01012345678 -> 010****5678) + private String maskPhoneNumber(String phoneNumber) { + if (phoneNumber == null || phoneNumber.length() < 8) { + return "****"; + } + return phoneNumber.substring(0, 3) + "****" + phoneNumber.substring(phoneNumber.length() - 4); + } } diff --git a/src/main/java/com/grabpt/service/SmsService/SmsServiceImpl.java b/src/main/java/com/grabpt/service/SmsService/SmsServiceImpl.java index 8196e3b4..369b32e1 100644 --- a/src/main/java/com/grabpt/service/SmsService/SmsServiceImpl.java +++ b/src/main/java/com/grabpt/service/SmsService/SmsServiceImpl.java @@ -1,5 +1,7 @@ package com.grabpt.service.SmsService; +import java.security.SecureRandom; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -13,6 +15,8 @@ @Service @Slf4j public class SmsServiceImpl implements SmsService { + private static final SecureRandom SECURE_RANDOM = new SecureRandom(); + private final SmsCertificationUtil smsCertificationUtil; private final SmsCertificationStorage smsCertificationStorage; private final UserQueryService userQueryService; @@ -30,8 +34,7 @@ public void SendSms(SmsRequestDto smsRequestDto) { String phoneNum = smsRequestDto.getPhoneNumber(); // SmsrequestDto에서 전화번호를 가져온다. String certificationCode = Integer.toString( - (int)(Math.random() * (999999 - 100000 + 1)) + 100000); // 6자리 인증 코드를 랜덤으로 생성 - log.info("CertificationCode = " + certificationCode); + SECURE_RANDOM.nextInt(900000) + 100000); // 6자리 인증 코드를 랜덤으로 생성 // 문자 발송 smsCertificationUtil.sendSMS(phoneNum, certificationCode); From 4dcc96d4d4bd543e2c0f2c2f951146041b7ab934 Mon Sep 17 00:00:00 2001 From: Kim-Dongdong Date: Sat, 26 Sep 2026 01:26:54 +0900 Subject: [PATCH 2/3] =?UTF-8?q?fix:=20actuator=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?=EB=B0=8F=20=EB=B0=B0=ED=8F=AC=20=ED=97=AC=EC=8A=A4=EC=B2=B4?= =?UTF-8?q?=ED=81=AC=20=EC=8B=A4=ED=8C=A8=20=EC=8B=9C=20=EB=B0=B0=ED=8F=AC?= =?UTF-8?q?=20=EC=A4=91=EB=8B=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 1 + scripts/start.sh | 19 +++++++++++++------ .../com/grabpt/config/SecurityConfig.java | 1 + src/main/resources/application.yml | 9 +++++++++ 4 files changed, 24 insertions(+), 6 deletions(-) diff --git a/build.gradle b/build.gradle index 6730a902..8a646b2b 100644 --- a/build.gradle +++ b/build.gradle @@ -44,6 +44,7 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework.boot:spring-boot-starter-actuator' // Swagger implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0' diff --git a/scripts/start.sh b/scripts/start.sh index 29a24df2..36c8e6ff 100755 --- a/scripts/start.sh +++ b/scripts/start.sh @@ -22,12 +22,19 @@ docker pull "${ECR_REGISTRY}/${ECR_REPO}:${IMAGE_TAG}" # ====== Compose 업데이트(무중단) ====== docker compose --env-file "$APP_DIR/.env" up -d -# ====== 앱 헬스 확인(필요 시 포트/엔드포인트 조정) ====== -for i in {1..30}; do - if curl -fsS "http://127.0.0.1:8080/actuator/health" | grep -q '"status":"UP"'; then - echo "App is UP" - break +# ====== 앱 헬스 확인 (실패 시 배포 실패 처리) ====== +# CodeDeploy ApplicationStart timeout(300초) 안에 끝나도록 최대 240초 대기 +HEALTH_TIMEOUT=240 +SECONDS=0 +while [ "$SECONDS" -lt "$HEALTH_TIMEOUT" ]; do + if curl -fsS --max-time 5 "http://127.0.0.1:8080/actuator/health" 2>/dev/null | grep -q '"status":"UP"'; then + echo "App is UP (${SECONDS}s)" + exit 0 fi - echo "Waiting app health... ($i/30)"; sleep 2 + echo "Waiting app health... (${SECONDS}s/${HEALTH_TIMEOUT}s)"; sleep 3 done +echo "App health check failed" +docker compose --env-file "$APP_DIR/.env" logs --tail=100 app || true +exit 1 + diff --git a/src/main/java/com/grabpt/config/SecurityConfig.java b/src/main/java/com/grabpt/config/SecurityConfig.java index 76c388d4..51a4753a 100644 --- a/src/main/java/com/grabpt/config/SecurityConfig.java +++ b/src/main/java/com/grabpt/config/SecurityConfig.java @@ -106,6 +106,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // 온보딩/인증 관련 .requestMatchers( "/favicon.ico", + "/actuator/health", "/api/auth/reissue", "/api/auth/logout", "/api/auth/user-signup", diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index c2715424..6f1856fe 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -25,6 +25,15 @@ springdoc: swagger-ui: path: /swagger-ui.html +management: + endpoints: + web: + exposure: + include: health # 배포 헬스체크용 health만 노출 + endpoint: + health: + show-details: never + spring: config: import: optional:file:.env.properties From dbf15a74a35da56414dccde1fdcd13ba38358e0c Mon Sep 17 00:00:00 2001 From: Kim-Dongdong Date: Sat, 26 Sep 2026 01:28:06 +0900 Subject: [PATCH 3/3] =?UTF-8?q?feat:=20MySQL=20=EC=9D=BC=EC=9D=BC=20?= =?UTF-8?q?=EB=B0=B1=EC=97=85=20=EC=8A=A4=ED=81=AC=EB=A6=BD=ED=8A=B8=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/backup-db.sh | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 scripts/backup-db.sh diff --git a/scripts/backup-db.sh b/scripts/backup-db.sh new file mode 100644 index 00000000..2df4bd23 --- /dev/null +++ b/scripts/backup-db.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash +set -euo pipefail + +# ====== MySQL 백업 (mysqldump -> gzip -> S3) ====== +# EC2 crontab 등록 예시 (매일 새벽 4시): +# 0 4 * * * /home/ubuntu/apps/grabpt/scripts/backup-db.sh >> /home/ubuntu/backup/backup.log 2>&1 + +APP_DIR="/home/ubuntu/apps/grabpt" +BACKUP_DIR="/home/ubuntu/backup" +RETENTION_DAYS=7 +cd "$APP_DIR" + +# ====== 배포 환경 변수(.env) 읽기 (DB_ROOT_PASSWORD, DB_NAME) ====== +if [ -f "$APP_DIR/.env" ]; then + set -a + source "$APP_DIR/.env" + set +a +fi + +# 백업 버킷은 EC2의 /home/ubuntu/.backup.env 에 BACKUP_S3_BUCKET=버킷명 으로 지정 +if [ -f "/home/ubuntu/.backup.env" ]; then + set -a + source "/home/ubuntu/.backup.env" + set +a +fi +: "${BACKUP_S3_BUCKET:?BACKUP_S3_BUCKET is not set}" + +mkdir -p "$BACKUP_DIR" +TIMESTAMP=$(date +%Y%m%d-%H%M%S) +FILE="$BACKUP_DIR/${DB_NAME}-${TIMESTAMP}.sql.gz" +trap 'rm -f "$FILE"' ERR # 실패 시 불완전한 백업 파일 삭제 + +# ====== 덤프 (--single-transaction: 서비스 중단 없이 일관된 스냅샷) ====== +docker compose --env-file "$APP_DIR/.env" exec -T -e MYSQL_PWD="$DB_ROOT_PASSWORD" mysql \ + mysqldump -uroot --single-transaction --routines --triggers "$DB_NAME" \ + | gzip > "$FILE" +echo "[$(date '+%F %T')] Dump created: $FILE ($(du -h "$FILE" | cut -f1))" + +# ====== S3 업로드 ====== +aws s3 cp "$FILE" "s3://${BACKUP_S3_BUCKET}/db-backup/$(basename "$FILE")" --only-show-errors +echo "[$(date '+%F %T')] Uploaded to s3://${BACKUP_S3_BUCKET}/db-backup/" + +# ====== 로컬 보관 기간 지난 백업 삭제 ====== +find "$BACKUP_DIR" -name "*.sql.gz" -mtime +"$RETENTION_DAYS" -delete