From ac4ea3da1b621353dcd684ba0ae41e2badd3403f Mon Sep 17 00:00:00 2001 From: shawn9272 Date: Tue, 31 Mar 2026 21:21:39 +0900 Subject: [PATCH 01/12] =?UTF-8?q?chore=20-=20CORS=20=ED=94=84=EB=A1=A0?= =?UTF-8?q?=ED=8A=B8=20=EB=A1=9C=EC=BB=AC=20=EA=B2=BD=EB=A1=9C=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../security/config/SecurityConfig.java | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/main/java/Comprehensive_Design_Project/CUK_Compasser/global/security/config/SecurityConfig.java b/src/main/java/Comprehensive_Design_Project/CUK_Compasser/global/security/config/SecurityConfig.java index 2f51b13..7990a3a 100644 --- a/src/main/java/Comprehensive_Design_Project/CUK_Compasser/global/security/config/SecurityConfig.java +++ b/src/main/java/Comprehensive_Design_Project/CUK_Compasser/global/security/config/SecurityConfig.java @@ -17,6 +17,10 @@ import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.CorsConfigurationSource; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; +import java.util.List; @Configuration @RequiredArgsConstructor @@ -31,6 +35,8 @@ public JWTAuthenticationFilter jwtAuthenticationFilter(JWTProvider jwtProvider, SecurityFilterChain securityFilterChain(HttpSecurity http, JWTAuthenticationFilter jwtAuthenticationFilter) throws Exception { http + .cors(cors -> cors.configurationSource(corsConfigurationSource())) + .csrf(AbstractHttpConfigurer::disable) .formLogin(AbstractHttpConfigurer::disable) .httpBasic(AbstractHttpConfigurer::disable) @@ -59,6 +65,27 @@ SecurityFilterChain securityFilterChain(HttpSecurity http, JWTAuthenticationFilt return http.build(); } + @Bean + public CorsConfigurationSource corsConfigurationSource() { + CorsConfiguration configuration = new CorsConfiguration(); + + // 프론트엔드 로컬 개발 서버 주소 허용 (3000, 3001) + configuration.setAllowedOrigins(List.of( + "http://localhost:3000", + "http://localhost:3001" + )); + + configuration.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS")); + + // 모든 헤더 허용 + configuration.setAllowedHeaders(List.of("*")); + + configuration.setAllowCredentials(true); + + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/**", configuration); + return source; + } @Bean public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception { From e2b29b7d125a49d40936abfe6596dbc919353c7b Mon Sep 17 00:00:00 2001 From: shawn9272 Date: Wed, 1 Apr 2026 23:23:04 +0900 Subject: [PATCH 02/12] =?UTF-8?q?fix=20-=20data.sql=20=EA=B7=9C=EC=B9=99?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application-prod.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml index 1c6cf1f..035ed56 100644 --- a/src/main/resources/application-prod.yml +++ b/src/main/resources/application-prod.yml @@ -35,9 +35,14 @@ spring: idle-timeout: 600000 max-lifetime: 1800000 pool-name: HikariPool-1 + # 다음 배포 때는 never로 바꿔서 중복 삽입 에러 방지 + sql: + init: + mode: always jpa: database-platform: org.hibernate.dialect.MySQLDialect + defer-datasource-initialization: true hibernate: ddl-auto: update show-sql: false From 80927fbbceb1e51a713df5e87f8f7f0e28190ea7 Mon Sep 17 00:00:00 2001 From: shawn9272 Date: Wed, 1 Apr 2026 23:37:02 +0900 Subject: [PATCH 03/12] =?UTF-8?q?fix=20-=20data.sql=20=EA=B2=BD=EB=A1=9C?= =?UTF-8?q?=20=EC=84=A4=EC=A0=95=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application-prod.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml index 035ed56..2e143d0 100644 --- a/src/main/resources/application-prod.yml +++ b/src/main/resources/application-prod.yml @@ -39,6 +39,10 @@ spring: sql: init: mode: always + data-locations: + - classpath:sql/UserData.sql + - classpath:sql/StoreDataInsertSQL.sql + - classpath:sql/RandomBoxDataInsertSQL.sql jpa: database-platform: org.hibernate.dialect.MySQLDialect From 8b4da12fb03341181cc8296bfb2b9c92e313be51 Mon Sep 17 00:00:00 2001 From: shawn9272 Date: Wed, 1 Apr 2026 23:49:05 +0900 Subject: [PATCH 04/12] =?UTF-8?q?fix=20-=20Randombox=20=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=84=B0=20=EC=82=BD=EC=9E=85=20=EB=AC=B8=EC=A0=9C=20=ED=95=B4?= =?UTF-8?q?=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/sql/RandomBoxDataInsertSQL.sql | 2 +- src/main/resources/sql/StoreDataInsertSQL.sql | 2 +- src/main/resources/sql/UserData.sql | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/resources/sql/RandomBoxDataInsertSQL.sql b/src/main/resources/sql/RandomBoxDataInsertSQL.sql index e25bed5..075596f 100644 --- a/src/main/resources/sql/RandomBoxDataInsertSQL.sql +++ b/src/main/resources/sql/RandomBoxDataInsertSQL.sql @@ -1,4 +1,4 @@ -INSERT INTO random_boxes ( +INSERT IGNORE INTO random_boxes ( created_at, updated_at, store_id, box_name, content, stock, price, buy_limit, sale_status ) VALUES diff --git a/src/main/resources/sql/StoreDataInsertSQL.sql b/src/main/resources/sql/StoreDataInsertSQL.sql index dba75e5..3799b2c 100644 --- a/src/main/resources/sql/StoreDataInsertSQL.sql +++ b/src/main/resources/sql/StoreDataInsertSQL.sql @@ -3,7 +3,7 @@ -- 일반 데이터 기준: 37.5445, 127.0560 -- 가톨릭대 정문: 37.4855, 126.8025 -- --------------------------------------------------------- -INSERT INTO stores (latitude, longitude, created_at, updated_at, +INSERT INTO IGNORE stores (latitude, longitude, created_at, updated_at, store_manager_id, store_name, store_details, business_hours, input_address, jibun_address, road_address, store_email, tag diff --git a/src/main/resources/sql/UserData.sql b/src/main/resources/sql/UserData.sql index 73bd33d..dcbec8e 100644 --- a/src/main/resources/sql/UserData.sql +++ b/src/main/resources/sql/UserData.sql @@ -1,7 +1,7 @@ -- --------------------------------------------------------- -- 1. members 데이터 삽입 (ID 1~40) -- --------------------------------------------------------- -INSERT INTO members (created_at, updated_at, email, member_name, password, phone, provider, provider_id, role, status) +INSERT IGNORE INTO members (created_at, updated_at, email, member_name, password, phone, provider, provider_id, role, status) VALUES (NOW(), NOW(), 'manager1@test.com', '성수매니저', 'password', '010-1111-1111', 'NORMAL', NULL, 'STORE_MANAGER', 'ACTIVE'), (NOW(), NOW(), 'manager2@test.com', '강남매니저', 'password', '010-2222-2222', 'NORMAL', NULL, 'STORE_MANAGER','ACTIVE'), (NOW(), NOW(), 'manager3@test.com', '연남매니저', 'password', '010-3333-3333', 'NORMAL', NULL, 'STORE_MANAGER','ACTIVE'), From bf6e5ed14396a5d5e93ad10d4d15ff754c5ff55e Mon Sep 17 00:00:00 2001 From: shawn9272 Date: Wed, 1 Apr 2026 23:59:35 +0900 Subject: [PATCH 05/12] =?UTF-8?q?fix=20-=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20?= =?UTF-8?q?=EC=A4=91=EB=B3=B5=20=EC=82=BD=EC=9E=85=20=EC=97=90=EB=9F=AC=20?= =?UTF-8?q?=ED=95=B4=EA=B2=B0=20"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/sql/UserData.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/sql/UserData.sql b/src/main/resources/sql/UserData.sql index dcbec8e..520cf59 100644 --- a/src/main/resources/sql/UserData.sql +++ b/src/main/resources/sql/UserData.sql @@ -46,7 +46,7 @@ VALUES (NOW(), NOW(), 'manager1@test.com', '성수매니저', 'password', '010-1 -- --------------------------------------------------------- -- 2. store_managers 데이터 삽입 (member_id와 1:1 매칭) -- --------------------------------------------------------- -INSERT INTO store_managers (created_at, member_id, updated_at, verified_at, business_license_number) +INSERT IGNORE INTO store_managers (created_at, member_id, updated_at, verified_at, business_license_number) VALUES (NOW(), 1, NOW(), NOW(), '111-81-12345'), (NOW(), 2, NOW(), NOW(), '222-82-23456'), (NOW(), 3, NOW(), NOW(), '333-83-34567'), From 0d9cfa3c649c4076fc84ef31054f62a476d62afe Mon Sep 17 00:00:00 2001 From: shawn9272 Date: Thu, 2 Apr 2026 00:05:57 +0900 Subject: [PATCH 06/12] =?UTF-8?q?refac=20-=20=EC=98=A4=ED=83=80=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/sql/StoreDataInsertSQL.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/sql/StoreDataInsertSQL.sql b/src/main/resources/sql/StoreDataInsertSQL.sql index 3799b2c..f9c82d7 100644 --- a/src/main/resources/sql/StoreDataInsertSQL.sql +++ b/src/main/resources/sql/StoreDataInsertSQL.sql @@ -3,7 +3,7 @@ -- 일반 데이터 기준: 37.5445, 127.0560 -- 가톨릭대 정문: 37.4855, 126.8025 -- --------------------------------------------------------- -INSERT INTO IGNORE stores (latitude, longitude, created_at, updated_at, +INSERT IGNORE INTO stores (latitude, longitude, created_at, updated_at, store_manager_id, store_name, store_details, business_hours, input_address, jibun_address, road_address, store_email, tag From 9d9d786e2807d03e5e0c0f02a774581104e89e24 Mon Sep 17 00:00:00 2001 From: shawn9272 Date: Sun, 5 Apr 2026 03:12:34 +0900 Subject: [PATCH 07/12] =?UTF-8?q?fix:=20JWT=20Refresh=20Token=20=EB=A7=8C?= =?UTF-8?q?=EB=A3=8C=20=EC=8B=9C=EA=B0=84=20=EC=84=A4=EC=A0=95=20=EB=B2=84?= =?UTF-8?q?=EA=B7=B8=20=EC=88=98=EC=A0=95=20-=20JWTProvider=EC=97=90?= =?UTF-8?q?=EC=84=9C=20Refresh=20Token=EC=9D=98=20=EB=A7=8C=EB=A3=8C=20?= =?UTF-8?q?=EC=8B=9C=EA=B0=84=EC=9D=B4=20Access=20Token=EA=B3=BC=20?= =?UTF-8?q?=EB=8F=99=EC=9D=BC=ED=95=98=EA=B2=8C(1=EC=8B=9C=EA=B0=84)=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=EB=90=98=EC=96=B4=20=EC=9E=88=EB=8D=98=20?= =?UTF-8?q?=EB=AC=B8=EC=A0=9C=20=EC=88=98=EC=A0=95=20(7=EC=9D=BC=EB=A1=9C?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CUK_Compasser/global/security/jwt/JWTProvider.java | 6 +++--- src/main/resources/application-local.yml | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/java/Comprehensive_Design_Project/CUK_Compasser/global/security/jwt/JWTProvider.java b/src/main/java/Comprehensive_Design_Project/CUK_Compasser/global/security/jwt/JWTProvider.java index 23bca37..33030f4 100644 --- a/src/main/java/Comprehensive_Design_Project/CUK_Compasser/global/security/jwt/JWTProvider.java +++ b/src/main/java/Comprehensive_Design_Project/CUK_Compasser/global/security/jwt/JWTProvider.java @@ -22,9 +22,9 @@ @Component public class JWTProvider { - // 일단 1시간으로 세팅 - private static final Long ACCESS_TOKEN_EXPIRE_TIME = (long) 1000 * 60 * 60; - private static final Long REFRESH_TOKEN_EXPIRE_TIME = (long) 1000 * 60 * 60; + // 일단 1시간으로 세팅 -> access token만 30분 설정, refresh token은 1주일로 설정 + private static final Long ACCESS_TOKEN_EXPIRE_TIME = (long) 1000 * 60 * 30; + private static final Long REFRESH_TOKEN_EXPIRE_TIME = (long) 1000 * 60 * 60 * 24 * 7; private final Key key; private final CustomUserDetailsService customUserDetailsService; diff --git a/src/main/resources/application-local.yml b/src/main/resources/application-local.yml index 9d6bb4f..0972221 100644 --- a/src/main/resources/application-local.yml +++ b/src/main/resources/application-local.yml @@ -27,10 +27,10 @@ spring: - account_email datasource: - driver-class-name: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://localhost:3306/${MYSQL_DB_NAME} - username: ${MYSQL_USERNAME} + url: jdbc:mysql://localhost:3306/${DB_NAME:cuk_compasser}?useSSL=false&serverTimezone=Asia/Seoul&allowPublicKeyRetrieval=true&characterEncoding=UTF-8 + username: root password: ${MYSQL_PASSWORD} + driver-class-name: com.mysql.cj.jdbc.Driver hikari: maximum-pool-size: 5 From a1d68364bd89a4ab9706f7efb473e9e200d56a00 Mon Sep 17 00:00:00 2001 From: shawn9272 Date: Sun, 5 Apr 2026 03:29:17 +0900 Subject: [PATCH 08/12] =?UTF-8?q?fix=20-=20=EB=B3=B8=20=EC=84=9C=EB=B2=84?= =?UTF-8?q?=20Swagger=20403=20CORS=20=EC=97=90=EB=9F=AC=20=ED=95=B4?= =?UTF-8?q?=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/security/config/SecurityConfig.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/main/java/Comprehensive_Design_Project/CUK_Compasser/global/security/config/SecurityConfig.java b/src/main/java/Comprehensive_Design_Project/CUK_Compasser/global/security/config/SecurityConfig.java index 7990a3a..a7f0b86 100644 --- a/src/main/java/Comprehensive_Design_Project/CUK_Compasser/global/security/config/SecurityConfig.java +++ b/src/main/java/Comprehensive_Design_Project/CUK_Compasser/global/security/config/SecurityConfig.java @@ -69,10 +69,14 @@ SecurityFilterChain securityFilterChain(HttpSecurity http, JWTAuthenticationFilt public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration configuration = new CorsConfiguration(); - // 프론트엔드 로컬 개발 서버 주소 허용 (3000, 3001) - configuration.setAllowedOrigins(List.of( + // 로컬 프론트, 로컬 스웨거, 본 서버 도메인 모두 추가 + configuration.setAllowedOriginPatterns(List.of( "http://localhost:3000", - "http://localhost:3001" + "http://localhost:3001", + "http://localhost:8080", // 로컬 테스트용 스웨거 (포트 8080일 경우) + "http://localhost:8081", // 로컬 테스트용 스웨거 (포트 8081일 경우) + "https://compasser.site", // 본 서버 스웨거 및 배포된 프론트엔드 도메인 + "https://www.compasser.site" // www가 붙은 도메인 )); configuration.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS")); @@ -80,6 +84,7 @@ public CorsConfigurationSource corsConfigurationSource() { // 모든 헤더 허용 configuration.setAllowedHeaders(List.of("*")); + // 쿠키(인증 정보) 전송 허용 configuration.setAllowCredentials(true); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); From ecb468c4a72ec2f7f54c87327766a7e726b150e1 Mon Sep 17 00:00:00 2001 From: shawn9272 Date: Thu, 16 Apr 2026 23:00:19 +0900 Subject: [PATCH 09/12] =?UTF-8?q?chore=20-=20Nginx=20docker-compose?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EA=B4=80=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker-compose-prod.yml | 21 ++++++++++++++---- nginx/default.conf | 48 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 4 deletions(-) create mode 100644 nginx/default.conf diff --git a/docker-compose-prod.yml b/docker-compose-prod.yml index 8821331..41c6a9a 100644 --- a/docker-compose-prod.yml +++ b/docker-compose-prod.yml @@ -3,8 +3,6 @@ services: image: shawn9272/cuk-compasser:${IMAGE_TAG} container_name: cuk-compasser-service restart: always - ports: - - "8080:8080" env_file: .env environment: - DB_HOST=mysql-db @@ -48,6 +46,23 @@ services: limits: memory: 400M + nginx: + image: nginx:latest + container_name: nginx-proxy + restart: always + ports: + - "80:80" + - "443:443" # HTTPS 포트 개방 + volumes: + - ./nginx/default.conf:/etc/nginx/conf.d/default.conf + # ✨ 핵심: EC2 호스트의 인증서 폴더를 Nginx 컨테이너 내부로 연결 (읽기 전용: ro) + - /etc/letsencrypt:/etc/letsencrypt:ro + depends_on: + - app + - grafana + networks: + - cuk-compasser-net + redis: image: redis:latest container_name: redis @@ -103,8 +118,6 @@ services: image: grafana/grafana:latest container_name: grafana restart: always - ports: - - "3000:3000" environment: - GF_SECURITY_ADMIN_PASSWORD=${GF_SECURITY_ADMIN_PASSWORD} - GF_SERVER_DOMAIN=compasser.site diff --git a/nginx/default.conf b/nginx/default.conf new file mode 100644 index 0000000..e0bf861 --- /dev/null +++ b/nginx/default.conf @@ -0,0 +1,48 @@ +server { + listen 80; + server_name compasser.site; + + # HTTP로 들어오는 모든 요청을 HTTPS로 강제 리다이렉트 + return 301 https://$host$request_uri; +} + +server { + listen 443 ssl; + server_name compasser.site; + + # Certbot이 관리하는 SSL 인증서 설정 (경로 유지) + ssl_certificate /etc/letsencrypt/live/compasser.site/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/compasser.site/privkey.pem; + include /etc/letsencrypt/options-ssl-nginx.conf; + ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; + + # 1. 그라파나 접속 설정 + location /grafana/ { + # 127.0.0.1 대신 도커 서비스 이름 사용 + proxy_pass http://grafana:3000; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # 웹소켓 지원 + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + } + + # 2. 기본 서비스 (스프링 부트) 접속 설정 + location / { + # 127.0.0.1 대신 도커 서비스 이름 사용 + proxy_pass http://app:8080; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # 웹소켓 지원 + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + } +} \ No newline at end of file From 88d55e7713d34cfda2a40823b6cb9535609593b3 Mon Sep 17 00:00:00 2001 From: shawn9272 Date: Thu, 16 Apr 2026 23:33:51 +0900 Subject: [PATCH 10/12] =?UTF-8?q?chore=20-=20=EC=B9=B4=EC=B9=B4=EC=98=A4?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=20=EB=B0=8F=20=EA=B5=AD=EC=84=B8=EC=B2=AD=20?= =?UTF-8?q?=ED=99=98=EA=B2=BD=20=EB=B3=80=EC=88=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy.yml | 8 ++++++-- docker-compose-prod.yml | 3 +++ src/main/resources/application-prod.yml | 21 ++++++++++++++++++++- 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index b0a82a4..4e77c71 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -79,6 +79,10 @@ jobs: echo "KAKAO_LOGIN_REDIRECT_URI=${{ secrets.KAKAO_LOGIN_REDIRECT_URI }}" >> .env echo "KAKAO_REST_API_KEY=${{ secrets.KAKAO_REST_API_KEY }}" >> .env + echo "KAKAO_JS_KEY=${{ secrets.KAKAO_JS_KEY }}" >> .env + echo "KAKAOPAY_SECRET_KEY_DEV=${{ secrets.KAKAOPAY_SECRET_KEY_DEV }}" >> .env + echo "NTS_SERVICE_KEY=${{ secrets.NTS_SERVICE_KEY }}" >> .env + # 3. 네트워크 생성 확인 sudo docker network inspect cuk-compasser-net >/dev/null 2>&1 || sudo docker network create cuk-compasser-net @@ -89,7 +93,7 @@ jobs: # 5. 미사용 이미지 정리 sudo docker image prune -f - # 6. 디스코드 알림 (성공 시) - SSH 액션 밖으로 분리 + # 6. 디스코드 알림 (성공 시) - name: Discord Notification - Success if: success() uses: sarisia/actions-status-discord@v1 @@ -103,7 +107,7 @@ jobs: 성공적으로 EC2에 배포되었습니다! color: 0x00ff00 - # 7. 디스코드 알림 (실패 시) - SSH 액션 밖으로 분리 + # 7. 디스코드 알림 (실패 시) - name: Discord Notification - Failure if: failure() uses: sarisia/actions-status-discord@v1 diff --git a/docker-compose-prod.yml b/docker-compose-prod.yml index 41c6a9a..4c6a853 100644 --- a/docker-compose-prod.yml +++ b/docker-compose-prod.yml @@ -23,6 +23,9 @@ services: - KAKAO_LOCAL_BASE_URL=https://dapi.kakao.com - KAKAO_REST_API_KEY=${KAKAO_REST_API_KEY} - JAVA_TOOL_OPTIONS=-Xms256m -Xmx256m + - KAKAO_JS_KEY=${KAKAO_JS_KEY} + - KAKAOPAY_SECRET_KEY_DEV=${KAKAOPAY_SECRET_KEY_DEV} + - NTS_SERVICE_KEY=${NTS_SERVICE_KEY} depends_on: mysql-db: condition: service_healthy diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml index 2e143d0..21374d1 100644 --- a/src/main/resources/application-prod.yml +++ b/src/main/resources/application-prod.yml @@ -105,4 +105,23 @@ logging: max-file-size: 10MB level: root: INFO - org.hibernate.SQL: ERROR \ No newline at end of file + org.hibernate.SQL: ERROR + +kakaopay: + cid: TC0ONETIME + secret-key: ${KAKAOPAY_SECRET_KEY_DEV} + base-url: https://open-api.kakaopay.com + approval-url: https://compasser.co.kr/payments/kakaopay/success + cancel-url: https://compasser.co.kr/payments/kakaopay/cancel + fail-url: https://compasser.co.kr/payments/kakaopay/fail + +app: + frontend-base-url: https://www.compasser.co.kr + +springdoc: + packages-to-scan: Comprehensive_Design_Project.CUK_Compasser + +nts: + business: + base-url: https://api.odcloud.kr/api/nts-businessman/v1 + service-key: ${NTS_SERVICE_KEY} \ No newline at end of file From e74ba05c644ebcd619e603945e7d5f4e711cb2b5 Mon Sep 17 00:00:00 2001 From: shawn9272 Date: Mon, 18 May 2026 22:44:50 +0900 Subject: [PATCH 11/12] =?UTF-8?q?chore=20-=20owner=20cors=20url=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 --- .../CUK_Compasser/global/security/config/SecurityConfig.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/Comprehensive_Design_Project/CUK_Compasser/global/security/config/SecurityConfig.java b/src/main/java/Comprehensive_Design_Project/CUK_Compasser/global/security/config/SecurityConfig.java index a7f0b86..e42ea81 100644 --- a/src/main/java/Comprehensive_Design_Project/CUK_Compasser/global/security/config/SecurityConfig.java +++ b/src/main/java/Comprehensive_Design_Project/CUK_Compasser/global/security/config/SecurityConfig.java @@ -76,7 +76,8 @@ public CorsConfigurationSource corsConfigurationSource() { "http://localhost:8080", // 로컬 테스트용 스웨거 (포트 8080일 경우) "http://localhost:8081", // 로컬 테스트용 스웨거 (포트 8081일 경우) "https://compasser.site", // 본 서버 스웨거 및 배포된 프론트엔드 도메인 - "https://www.compasser.site" // www가 붙은 도메인 + "https://www.compasser.site", // www가 붙은 도메인 + "https://owner.compasser.co.kr/" )); configuration.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS")); From 2d3c442f4d770257f51bb3fad45ebad8e8dc0433 Mon Sep 17 00:00:00 2001 From: shawn9272 Date: Mon, 25 May 2026 22:42:24 +0900 Subject: [PATCH 12/12] =?UTF-8?q?feat:=20S3=20=EC=9D=B4=EB=AF=B8=EC=A7=80?= =?UTF-8?q?=20=EC=97=85=EB=A1=9C=EB=93=9C=20=EC=97=B0=EB=8F=99=20=EB=B0=8F?= =?UTF-8?q?=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20role=20=EB=B0=98=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../oAuth2/controller/OAuth2Controller.java | 5 +- .../domain/oAuth2/dto/TokenRespDTO.java | 4 +- .../domain/oAuth2/service/OAuth2Service.java | 4 ++ .../store/service/StoreImageService.java | 22 +++++++- .../CUK_Compasser/global/aws/S3Service.java | 56 +++++++++++++++++++ src/main/resources/application-local.yml | 4 +- 6 files changed, 88 insertions(+), 7 deletions(-) create mode 100644 src/main/java/Comprehensive_Design_Project/CUK_Compasser/global/aws/S3Service.java diff --git a/src/main/java/Comprehensive_Design_Project/CUK_Compasser/domain/oAuth2/controller/OAuth2Controller.java b/src/main/java/Comprehensive_Design_Project/CUK_Compasser/domain/oAuth2/controller/OAuth2Controller.java index 9c6133e..f1325d0 100644 --- a/src/main/java/Comprehensive_Design_Project/CUK_Compasser/domain/oAuth2/controller/OAuth2Controller.java +++ b/src/main/java/Comprehensive_Design_Project/CUK_Compasser/domain/oAuth2/controller/OAuth2Controller.java @@ -1,6 +1,7 @@ package Comprehensive_Design_Project.CUK_Compasser.domain.oAuth2.controller; import Comprehensive_Design_Project.CUK_Compasser.domain.member.dto.MemberRespDTO; +import Comprehensive_Design_Project.CUK_Compasser.domain.member.entity.MemberRole; import Comprehensive_Design_Project.CUK_Compasser.domain.member.service.MemberService; import Comprehensive_Design_Project.CUK_Compasser.domain.oAuth2.dto.LoginReqDTO; import Comprehensive_Design_Project.CUK_Compasser.domain.oAuth2.dto.SignUpReqDTO; @@ -42,7 +43,7 @@ public class OAuth2Controller { private final OAuth2Service oAuth2Service; - public record AccessTokenDTO(String accessToken) {} + public record AccessTokenDTO(String accessToken, MemberRole role) {} @PostMapping("/sign-up") @Operation(summary = "일반 회원가입 API", description = "이름, 닉네임, 이메일 등을 입력받아 회원가입을 진행합니다.") @@ -84,7 +85,7 @@ public ApiResponse login( response.addHeader(HttpHeaders.SET_COOKIE, cookie.toString()); - return ApiResponse.onSuccess(SuccessStatus.OK, new AccessTokenDTO(tokenResponse.accessToken())); + return ApiResponse.onSuccess(SuccessStatus.OK, new AccessTokenDTO(tokenResponse.accessToken(), tokenResponse.role())); } @PostMapping("/login-kakao") diff --git a/src/main/java/Comprehensive_Design_Project/CUK_Compasser/domain/oAuth2/dto/TokenRespDTO.java b/src/main/java/Comprehensive_Design_Project/CUK_Compasser/domain/oAuth2/dto/TokenRespDTO.java index 3ffe808..27f64ef 100644 --- a/src/main/java/Comprehensive_Design_Project/CUK_Compasser/domain/oAuth2/dto/TokenRespDTO.java +++ b/src/main/java/Comprehensive_Design_Project/CUK_Compasser/domain/oAuth2/dto/TokenRespDTO.java @@ -1,9 +1,11 @@ package Comprehensive_Design_Project.CUK_Compasser.domain.oAuth2.dto; +import Comprehensive_Design_Project.CUK_Compasser.domain.member.entity.MemberRole; import lombok.Builder; @Builder public record TokenRespDTO ( String accessToken, - String refreshToken + String refreshToken, + MemberRole role ) { } diff --git a/src/main/java/Comprehensive_Design_Project/CUK_Compasser/domain/oAuth2/service/OAuth2Service.java b/src/main/java/Comprehensive_Design_Project/CUK_Compasser/domain/oAuth2/service/OAuth2Service.java index 3c5a88b..394644a 100644 --- a/src/main/java/Comprehensive_Design_Project/CUK_Compasser/domain/oAuth2/service/OAuth2Service.java +++ b/src/main/java/Comprehensive_Design_Project/CUK_Compasser/domain/oAuth2/service/OAuth2Service.java @@ -113,6 +113,9 @@ public TokenRespDTO login(LoginReqDTO request) { Authentication authentication = authenticationManager.authenticate(authenticationToken); + Member member = memberRepository.findByEmail(authentication.getName()) + .orElseThrow(() -> new GeneralException(ErrorStatus.USER_NOT_FOUND)); + JWT jwt = jwtProvider.generateToken(authentication); redisTemplate.opsForValue().set( @@ -125,6 +128,7 @@ public TokenRespDTO login(LoginReqDTO request) { return TokenRespDTO.builder() .accessToken(jwt.getAccessToken()) .refreshToken(jwt.getRefreshToken()) + .role(member.getRole()) .build(); } diff --git a/src/main/java/Comprehensive_Design_Project/CUK_Compasser/domain/store/service/StoreImageService.java b/src/main/java/Comprehensive_Design_Project/CUK_Compasser/domain/store/service/StoreImageService.java index cafa7c3..1a95e8f 100644 --- a/src/main/java/Comprehensive_Design_Project/CUK_Compasser/domain/store/service/StoreImageService.java +++ b/src/main/java/Comprehensive_Design_Project/CUK_Compasser/domain/store/service/StoreImageService.java @@ -7,6 +7,7 @@ import Comprehensive_Design_Project.CUK_Compasser.domain.store.repository.StoreImageRepository; import Comprehensive_Design_Project.CUK_Compasser.domain.store.repository.StoreRepository; import Comprehensive_Design_Project.CUK_Compasser.domain.storeManager.repository.StoreManagerRepository; +import Comprehensive_Design_Project.CUK_Compasser.global.aws.S3Service; import Comprehensive_Design_Project.CUK_Compasser.global.common.apiPayload.code.status.ErrorStatus; import Comprehensive_Design_Project.CUK_Compasser.global.common.apiPayload.exception.GeneralException; import lombok.RequiredArgsConstructor; @@ -14,6 +15,8 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; +import java.util.UUID; + @Service @RequiredArgsConstructor public class StoreImageService { @@ -24,6 +27,7 @@ public class StoreImageService { private final StoreImageRepository storeImageRepository; private final StoreManagerRepository storeManagerRepository; private final StoreImageConverter storeImageConverter; + private final S3Service s3Service; @Transactional(readOnly = true) public StoreImageRespDTO getRepresentativeImage(Long memberId) { @@ -42,7 +46,8 @@ public StoreImageRespDTO uploadRepresentativeImage(Long memberId, MultipartFile throw new GeneralException(ErrorStatus.STORE_IMAGE_NOT_FOUND); } - String url = "uploaded://" + storeImage.getOriginalFilename(); + String imageKey = createStoreImageKey(store.getId(), storeImage); + String url = s3Service.uploadImage(storeImage, imageKey); storeImageRepository.deleteAllByStore_Id(store.getId()); @@ -70,4 +75,17 @@ private Store getMyStoreEntity(Long memberId) { return storeRepository.findByStoreManager_MemberId(memberId) .orElseThrow(() -> new GeneralException(ErrorStatus.STORE_NOT_FOUND)); } -} \ No newline at end of file + + private String createStoreImageKey(Long storeId, MultipartFile file) { + return "stores/" + storeId + "/representative/" + UUID.randomUUID() + getExtension(file); + } + + private String getExtension(MultipartFile file) { + String originalFilename = file.getOriginalFilename(); + if (originalFilename == null || !originalFilename.contains(".")) { + return ""; + } + + return originalFilename.substring(originalFilename.lastIndexOf(".")); + } +} diff --git a/src/main/java/Comprehensive_Design_Project/CUK_Compasser/global/aws/S3Service.java b/src/main/java/Comprehensive_Design_Project/CUK_Compasser/global/aws/S3Service.java new file mode 100644 index 0000000..6f367d3 --- /dev/null +++ b/src/main/java/Comprehensive_Design_Project/CUK_Compasser/global/aws/S3Service.java @@ -0,0 +1,56 @@ +package Comprehensive_Design_Project.CUK_Compasser.global.aws; + +import Comprehensive_Design_Project.CUK_Compasser.global.common.apiPayload.code.status.ErrorStatus; +import Comprehensive_Design_Project.CUK_Compasser.global.common.apiPayload.exception.GeneralException; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; +import software.amazon.awssdk.core.sync.RequestBody; +import software.amazon.awssdk.services.s3.S3Client; +import software.amazon.awssdk.services.s3.model.PutObjectRequest; + +import java.io.IOException; + +@Service +@RequiredArgsConstructor +public class S3Service { + + @Value("${spring.cloud.aws.s3.bucket}") + private String bucket; + + @Value("${spring.cloud.aws.region.static}") + private String region; + + private final S3Client s3Client; + + public String uploadImage(MultipartFile file, String key) { + validateImage(file); + + try { + PutObjectRequest putObjectRequest = PutObjectRequest.builder() + .bucket(bucket) + .key(key) + .contentType(file.getContentType()) + .contentLength(file.getSize()) + .build(); + + s3Client.putObject(putObjectRequest, RequestBody.fromInputStream(file.getInputStream(), file.getSize())); + + return "https://" + bucket + ".s3." + region + ".amazonaws.com/" + key; + } catch (IOException e) { + throw new GeneralException(ErrorStatus.FILE_UPLOAD_FAILED); + } + } + + private void validateImage(MultipartFile file) { + if (file == null || file.isEmpty()) { + throw new GeneralException(ErrorStatus.FILE_REQUIRED); + } + + String contentType = file.getContentType(); + if (contentType == null || !contentType.startsWith("image/")) { + throw new GeneralException(ErrorStatus.UNSUPPORTED_IMAGE_TYPE); + } + } +} diff --git a/src/main/resources/application-local.yml b/src/main/resources/application-local.yml index 6342136..7a5e55c 100644 --- a/src/main/resources/application-local.yml +++ b/src/main/resources/application-local.yml @@ -108,7 +108,7 @@ kakao: kakaopay: cid: TC0ONETIME - secret-key: ${KAKAOPAY_SECRET_KEY_DEV} + secret-key: ${KAKAOPAY_SECRET_KEY_DEV:local-dummy} base-url: https://open-api.kakaopay.com approval-url: https://compasser.co.kr/payments/kakaopay/success cancel-url: https://compasser.co.kr/payments/kakaopay/cancel @@ -123,4 +123,4 @@ springdoc: nts: business: base-url: https://api.odcloud.kr/api/nts-businessman/v1 - service-key: ${NTS_SERVICE_KEY} \ No newline at end of file + service-key: ${NTS_SERVICE_KEY:local-dummy}