From 4d36d7194a69b2350e7d2a84fcdf45ce91486629 Mon Sep 17 00:00:00 2001 From: chxghee Date: Wed, 16 Jul 2025 21:14:08 +0900 Subject: [PATCH 1/7] =?UTF-8?q?refactor:=20JWT=20=EA=B4=80=EB=A0=A8=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EC=99=B8=EB=B6=80=20=EB=AA=A8=EB=93=88?= =?UTF-8?q?=EB=A1=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/jwt/JwtConfig.java | 15 +++++++++++++++ .../{roomescape/auth => jwt}/JwtProperties.java | 2 +- .../auth => jwt}/JwtTokenProvider.java | 5 ++--- .../java/roomescape/RoomescapeApplication.java | 5 +++-- .../roomescape/auth/AdminAuthInterceptor.java | 1 + .../auth/LoginMemberArgumentResolver.java | 1 + .../member/presentation/MemberController.java | 4 ++-- src/test/java/roomescape/MissionStepTest.java | 8 ++++++++ .../roomescape/auth/JwtTokenProviderTest.java | 2 ++ 9 files changed, 35 insertions(+), 8 deletions(-) create mode 100644 src/main/java/jwt/JwtConfig.java rename src/main/java/{roomescape/auth => jwt}/JwtProperties.java (95%) rename src/main/java/{roomescape/auth => jwt}/JwtTokenProvider.java (95%) diff --git a/src/main/java/jwt/JwtConfig.java b/src/main/java/jwt/JwtConfig.java new file mode 100644 index 000000000..180b7c217 --- /dev/null +++ b/src/main/java/jwt/JwtConfig.java @@ -0,0 +1,15 @@ +package jwt; + +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +@EnableConfigurationProperties(JwtProperties.class) +public class JwtConfig { + + @Bean + public JwtTokenProvider jwtTokenProvider(JwtProperties jwtProperties) { + return new JwtTokenProvider(jwtProperties); + } +} diff --git a/src/main/java/roomescape/auth/JwtProperties.java b/src/main/java/jwt/JwtProperties.java similarity index 95% rename from src/main/java/roomescape/auth/JwtProperties.java rename to src/main/java/jwt/JwtProperties.java index 891c545a1..c2ac86bac 100644 --- a/src/main/java/roomescape/auth/JwtProperties.java +++ b/src/main/java/jwt/JwtProperties.java @@ -1,4 +1,4 @@ -package roomescape.auth; +package jwt; import org.springframework.boot.context.properties.ConfigurationProperties; diff --git a/src/main/java/roomescape/auth/JwtTokenProvider.java b/src/main/java/jwt/JwtTokenProvider.java similarity index 95% rename from src/main/java/roomescape/auth/JwtTokenProvider.java rename to src/main/java/jwt/JwtTokenProvider.java index bbd64492d..fafffde1c 100644 --- a/src/main/java/roomescape/auth/JwtTokenProvider.java +++ b/src/main/java/jwt/JwtTokenProvider.java @@ -1,14 +1,13 @@ -package roomescape.auth; +package jwt; import io.jsonwebtoken.*; import io.jsonwebtoken.security.Keys; -import org.springframework.stereotype.Component; +import roomescape.auth.AuthException; import roomescape.exception.ApplicationException; import roomescape.member.domain.Member; import java.util.Date; -@Component public class JwtTokenProvider { private final String secretKey; diff --git a/src/main/java/roomescape/RoomescapeApplication.java b/src/main/java/roomescape/RoomescapeApplication.java index 3341d1549..dc5591c9c 100644 --- a/src/main/java/roomescape/RoomescapeApplication.java +++ b/src/main/java/roomescape/RoomescapeApplication.java @@ -1,11 +1,12 @@ package roomescape; +import jwt.JwtConfig; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.context.properties.ConfigurationPropertiesScan; +import org.springframework.context.annotation.Import; @SpringBootApplication -@ConfigurationPropertiesScan(basePackages = "roomescape") +@Import(JwtConfig.class) public class RoomescapeApplication { public static void main(String[] args) { SpringApplication.run(RoomescapeApplication.class, args); diff --git a/src/main/java/roomescape/auth/AdminAuthInterceptor.java b/src/main/java/roomescape/auth/AdminAuthInterceptor.java index c86e8580c..1e754761d 100644 --- a/src/main/java/roomescape/auth/AdminAuthInterceptor.java +++ b/src/main/java/roomescape/auth/AdminAuthInterceptor.java @@ -1,5 +1,6 @@ package roomescape.auth; +import jwt.JwtTokenProvider; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.stereotype.Component; diff --git a/src/main/java/roomescape/auth/LoginMemberArgumentResolver.java b/src/main/java/roomescape/auth/LoginMemberArgumentResolver.java index d68e5d806..eb0ec7188 100644 --- a/src/main/java/roomescape/auth/LoginMemberArgumentResolver.java +++ b/src/main/java/roomescape/auth/LoginMemberArgumentResolver.java @@ -1,5 +1,6 @@ package roomescape.auth; +import jwt.JwtTokenProvider; import jakarta.servlet.http.HttpServletRequest; import org.springframework.core.MethodParameter; import org.springframework.stereotype.Component; diff --git a/src/main/java/roomescape/member/presentation/MemberController.java b/src/main/java/roomescape/member/presentation/MemberController.java index 786774073..718a2df3b 100644 --- a/src/main/java/roomescape/member/presentation/MemberController.java +++ b/src/main/java/roomescape/member/presentation/MemberController.java @@ -8,8 +8,8 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; import roomescape.auth.AuthenticatedMember; -import roomescape.auth.JwtProperties; -import roomescape.auth.JwtTokenProvider; +import jwt.JwtProperties; +import jwt.JwtTokenProvider; import roomescape.auth.LoginMember; import roomescape.common.CookieUtil; import roomescape.member.application.MemberService; diff --git a/src/test/java/roomescape/MissionStepTest.java b/src/test/java/roomescape/MissionStepTest.java index 90bec4a2e..8b85142ee 100644 --- a/src/test/java/roomescape/MissionStepTest.java +++ b/src/test/java/roomescape/MissionStepTest.java @@ -1,11 +1,13 @@ package roomescape; +import jwt.JwtTokenProvider; import io.restassured.RestAssured; import io.restassured.http.ContentType; import io.restassured.response.ExtractableResponse; import io.restassured.response.Response; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.stereotype.Component; import org.springframework.test.annotation.DirtiesContext; import roomescape.reservation.presentation.response.MyReservationResponse; import roomescape.reservation.presentation.response.ReservationResponse; @@ -154,4 +156,10 @@ private static String createToken(String email, String password) { assertThat(status).isEqualTo("1번째 예약대기"); } + + @Test + void 칠단계() { + Component componentAnnotation = JwtTokenProvider.class.getAnnotation(Component.class); + assertThat(componentAnnotation).isNull(); + } } diff --git a/src/test/java/roomescape/auth/JwtTokenProviderTest.java b/src/test/java/roomescape/auth/JwtTokenProviderTest.java index 1f8566e5a..469d1826b 100644 --- a/src/test/java/roomescape/auth/JwtTokenProviderTest.java +++ b/src/test/java/roomescape/auth/JwtTokenProviderTest.java @@ -1,5 +1,7 @@ package roomescape.auth; +import jwt.JwtProperties; +import jwt.JwtTokenProvider; import org.junit.jupiter.api.Test; import roomescape.exception.ApplicationException; import roomescape.member.domain.Member; From 8a8ae72c123274235979b6b3380edf9c8dd1689c Mon Sep 17 00:00:00 2001 From: chxghee Date: Thu, 17 Jul 2025 01:04:47 +0900 Subject: [PATCH 2/7] =?UTF-8?q?refactor:=20DataLoader=EB=A1=9C=20=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=84=B0=20=EB=B2=A0=EC=9D=B4=EC=8A=A4=20=EC=B4=88?= =?UTF-8?q?=EA=B8=B0=ED=99=94=20=EB=B0=8F=20test=20prod=20=ED=99=98?= =?UTF-8?q?=EA=B2=BD=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/roomescape/DataLoader.java | 29 ++++++++ .../java/roomescape/common/CookieUtil.java | 1 + src/main/resources/application.properties | 14 ---- src/main/resources/application.yml | 27 +++++++ src/main/resources/data.sql | 24 ------- src/test/java/roomescape/JpaTest.java | 13 +++- src/test/java/roomescape/MissionStepTest.java | 12 ++++ src/test/java/roomescape/TestDataLoader.java | 71 +++++++++++++++++++ src/test/resources/application.yml | 23 ++++++ 9 files changed, 175 insertions(+), 39 deletions(-) create mode 100644 src/main/java/roomescape/DataLoader.java delete mode 100644 src/main/resources/application.properties create mode 100644 src/main/resources/application.yml delete mode 100644 src/main/resources/data.sql create mode 100644 src/test/java/roomescape/TestDataLoader.java create mode 100644 src/test/resources/application.yml diff --git a/src/main/java/roomescape/DataLoader.java b/src/main/java/roomescape/DataLoader.java new file mode 100644 index 000000000..33ed3acd6 --- /dev/null +++ b/src/main/java/roomescape/DataLoader.java @@ -0,0 +1,29 @@ +package roomescape; + +import org.springframework.boot.CommandLineRunner; +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; +import roomescape.member.domain.Member; +import roomescape.member.domain.MemberRepository; +import roomescape.member.domain.Role; + +@Profile("prod") +@Component +public class DataLoader implements CommandLineRunner { + + private final MemberRepository memberRepository; + + public DataLoader(MemberRepository memberRepository) { + this.memberRepository = memberRepository; + } + + @Override + @Transactional + public void run(String... args) throws Exception { + Member admin = new Member("어드민", "admin@email.com", "password", Role.ADMIN); + Member user = new Member("브라운", "brown@email.com", "password", Role.USER); + memberRepository.save(admin); + memberRepository.save(user); + } +} diff --git a/src/main/java/roomescape/common/CookieUtil.java b/src/main/java/roomescape/common/CookieUtil.java index abd3d08f8..8ef73636c 100644 --- a/src/main/java/roomescape/common/CookieUtil.java +++ b/src/main/java/roomescape/common/CookieUtil.java @@ -10,6 +10,7 @@ public class CookieUtil { public static void setToken(String accessToken, int cookieExpirationSeconds, HttpServletResponse response) { Cookie cookie = new Cookie("token", accessToken); cookie.setHttpOnly(true); + cookie.setSecure(true); cookie.setPath("/"); cookie.setMaxAge(cookieExpirationSeconds); response.addCookie(cookie); diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties deleted file mode 100644 index 67d7858cf..000000000 --- a/src/main/resources/application.properties +++ /dev/null @@ -1,14 +0,0 @@ -spring.sql.init.encoding=utf-8 -spring.h2.console.enabled=true -spring.h2.console.path=/h2-console -spring.datasource.url=jdbc:h2:mem:database - - -spring.sql.init.mode=always -spring.jpa.show-sql=true -spring.jpa.properties.hibernate.format_sql=true -spring.jpa.ddl-auto=create-drop -spring.jpa.defer-datasource-initialization=true - -roomescape.auth.jwt.secret= Yn2kjibddFAWtnPJ2AFlL8WXmohJMCvigQggaEypa5E= -roomescape.auth.jwt.expiration=1800000 diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 000000000..6283e4039 --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,27 @@ +server: + port: 8080 + +spring: + profiles: + active: prod + + h2: + console: + enabled: true + path: /h2-console + + datasource: + url: jdbc:h2:mem:database + + jpa: + show-sql: true + properties: + hibernate: + format_sql: true + ddl-auto: create + +roomescape: + auth: + jwt: + secret: Yn2kjibddFAWtnPJ2AFlL8WXmohJMCvigQggaEypa5E= + expiration: 1800000 diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql deleted file mode 100644 index 7107f8adc..000000000 --- a/src/main/resources/data.sql +++ /dev/null @@ -1,24 +0,0 @@ -INSERT INTO member (name, email, password, role) -VALUES ('어드민', 'admin@email.com', 'password', 'ADMIN'), - ('브라운', 'brown@email.com', 'password', 'USER'); - -INSERT INTO theme (name, description, deleted) -VALUES ('테마1', '테마1입니다.', false), - ('테마2', '테마2입니다.', false), - ('테마3', '테마3입니다.', false); - -INSERT INTO time (time_value, deleted) -VALUES ('10:00', false), - ('12:00', false), - ('14:00', false), - ('16:00', false), - ('18:00', false), - ('20:00', false); - -INSERT INTO reservation (member_id, name, date, time_id, theme_id) -VALUES (1, '', '2024-03-01', 1, 1), - (1, '', '2024-03-01', 2, 2), - (1, '', '2024-03-01', 3, 3); - -INSERT INTO reservation (name, date, time_id, theme_id) -VALUES ('브라운', '2024-03-01', 1, 2); diff --git a/src/test/java/roomescape/JpaTest.java b/src/test/java/roomescape/JpaTest.java index 99caee443..47cc3aecc 100644 --- a/src/test/java/roomescape/JpaTest.java +++ b/src/test/java/roomescape/JpaTest.java @@ -3,15 +3,21 @@ import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager; import roomescape.time.domain.Time; import roomescape.time.domain.TimeRepository; +import static org.assertj.core.api.Assertions.*; + @DataJpaTest public class JpaTest { + @Value("${roomescape.auth.jwt.secret}") + private String secretKey; + @Autowired private TestEntityManager entityManager; @@ -26,6 +32,11 @@ public class JpaTest { Time persistTime = timeRepository.findById(time.getId()).orElse(null); - Assertions.assertThat(persistTime.getValue()).isEqualTo(time.getValue()); + assertThat(persistTime.getValue()).isEqualTo(time.getValue()); + } + + @Test + void 팔단계() { + assertThat(secretKey).isNotBlank(); } } diff --git a/src/test/java/roomescape/MissionStepTest.java b/src/test/java/roomescape/MissionStepTest.java index 8b85142ee..2a71e3649 100644 --- a/src/test/java/roomescape/MissionStepTest.java +++ b/src/test/java/roomescape/MissionStepTest.java @@ -5,10 +5,13 @@ import io.restassured.http.ContentType; import io.restassured.response.ExtractableResponse; import io.restassured.response.Response; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.server.LocalServerPort; import org.springframework.stereotype.Component; import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ActiveProfiles; import roomescape.reservation.presentation.response.MyReservationResponse; import roomescape.reservation.presentation.response.ReservationResponse; import roomescape.waiting.presentation.response.WaitingResponse; @@ -22,8 +25,17 @@ @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) @DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) +@ActiveProfiles("test") public class MissionStepTest { + @LocalServerPort + private int port; + + @BeforeEach + void setUp() { + RestAssured.port = port; + } + @Test void 일단계() { String token = createToken("admin@email.com", "password"); diff --git a/src/test/java/roomescape/TestDataLoader.java b/src/test/java/roomescape/TestDataLoader.java new file mode 100644 index 000000000..f3a552051 --- /dev/null +++ b/src/test/java/roomescape/TestDataLoader.java @@ -0,0 +1,71 @@ +package roomescape; + +import org.springframework.boot.CommandLineRunner; +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; +import roomescape.member.domain.Member; +import roomescape.member.domain.MemberRepository; +import roomescape.member.domain.Role; +import roomescape.reservation.domain.Reservation; +import roomescape.reservation.domain.ReservationRepository; +import roomescape.theme.domain.Theme; +import roomescape.theme.domain.ThemeRepository; +import roomescape.time.domain.Time; +import roomescape.time.domain.TimeRepository; + +@Profile("test") +@Component +public class TestDataLoader implements CommandLineRunner { + + private final MemberRepository memberRepository; + private final TimeRepository timeRepository; + private final ThemeRepository themeRepository; + private final ReservationRepository reservationRepository; + + public TestDataLoader(MemberRepository memberRepository, TimeRepository timeRepository, + ThemeRepository themeRepository, ReservationRepository reservationRepository) { + this.memberRepository = memberRepository; + this.timeRepository = timeRepository; + this.themeRepository = themeRepository; + this.reservationRepository = reservationRepository; + } + + @Override + @Transactional + public void run(String... args) throws Exception { + Member admin = new Member("어드민", "admin@email.com", "password", Role.ADMIN); + Member user = new Member("브라운", "brown@email.com", "password", Role.USER); + memberRepository.save(admin); + memberRepository.save(user); + + Theme theme1 = new Theme("테마1", "테마1입니다."); + Theme theme2 = new Theme("테마2", "테마2입니다."); + Theme theme3 = new Theme("테마3", "테마3입니다."); + themeRepository.save(theme1); + themeRepository.save(theme2); + themeRepository.save(theme3); + + Time time1 = new Time("10:00"); + Time time2 = new Time("12:00"); + Time time3 = new Time("14:00"); + Time time4 = new Time("16:00"); + Time time5 = new Time("18:00"); + Time time6 = new Time("20:00"); + timeRepository.save(time1); + timeRepository.save(time2); + timeRepository.save(time3); + timeRepository.save(time4); + timeRepository.save(time5); + timeRepository.save(time6); + + Reservation reservation1 = new Reservation(admin, "2024-03-01", time1, theme1); + Reservation reservation2 = new Reservation(admin, "2024-03-01", time2, theme2); + Reservation reservation3 = new Reservation(admin, "2024-03-01", time3, theme3); + Reservation reservation4 = new Reservation("브라운", "2024-03-01", time1, theme2); + reservationRepository.save(reservation1); + reservationRepository.save(reservation2); + reservationRepository.save(reservation3); + reservationRepository.save(reservation4); + } +} diff --git a/src/test/resources/application.yml b/src/test/resources/application.yml new file mode 100644 index 000000000..875ece13e --- /dev/null +++ b/src/test/resources/application.yml @@ -0,0 +1,23 @@ +server: + port: 0 + +spring: + profiles: + active: test + + jpa: + show-sql: true + properties: + hibernate: + format_sql: true + ddl-auto: create-drop + + datasource: + url: jdbc:h2:mem:test_db + +roomescape: + auth: + jwt: + secret: Yn2kjibddFAWtnPJ2AFlL8WXmohJMCvigQggaEypa5E= + expiration: 1800000 + From 7d6bffecde6722858e24f358cd39447fc0d3b240 Mon Sep 17 00:00:00 2001 From: chxghee Date: Thu, 17 Jul 2025 01:31:58 +0900 Subject: [PATCH 3/7] =?UTF-8?q?docs:=20=EB=A6=AC=EB=93=9C=EB=AF=B8=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 71 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/README.md b/README.md index 40b4af945..55d034fda 100644 --- a/README.md +++ b/README.md @@ -128,6 +128,77 @@ +
+ +--- +## 🚀 7단계 - @Configuration + +### 📝 기능 요구사항 +✅ JWT 관련 로직을 roomescape와 같은 계층의 auth 패키지의 클래스로 분리하세요. +✅ 불필요한 DB 접근을 최소화 하세요. + + + +### 💻 구현 전략 + +1. jwt 모듈 분리 + ➡️ Jwt 토큰을 발급, 파싱했던 `JwtTokenProvider`와 + ➡️ Jwt관련 환경변수를 담은 `JwtProperties` 클래스를 외부 jwt 패키지로 분리하고 + ➡️ 해당 객체들을 `JwtConfig`를 통해 빈으로 등록 + +이렇게 분리된 설정을 Application 클래스에 `@Import` + + + + +
+ +--- +## 🚀 8단계 - Profile과 Resource + +### 📝 기능 요구사항 +✅ schema.sql 대신 데이터베이스를 초기화 해주기 위해 실행하는 클래스를 만드세요. +✅ token 생성에 필요한 비밀키값을 외부 파일로 분리하세요 + + + +### 💻 구현 전략 + +1. DB 초기화 + ➡️ Production용과 DataLoader + ➡️ Test용 TestDataLoader를 따로 만들어 DB 초기화 + +2. prod 환경과 test 환경 분리 + ➡️ test 에 관한 설정은 test 패키지 하위 application.yml 파일 생성 + + + + + +
+ +--- +## 🚀 9단계 - 배포 스크립트 + +### 📝 기능 요구사항 +✅ ec2나 서버에서 배포를 할 수 있게 배포 스크립트를 작성하세요. + + + +### 💻 구현 전략 + +1. EC2 인스턴스 생성 + + + +2. 인스턴스에 ssh 접속 후 메모리 스왑 설정 + ➡️ 부족한 인스턴스의 메모리를 디스크를 활용해 메모리의 내용을 스왑함으로써, 사용가능한 메모리 상한 올림 + + +3. github 에 코드를 pull + +4. 프로젝트 빌드 +5. 빌드된 jar 파일 백 그라운드로 실행 From 06266fe2897871117dd11802177bbb74941f639b Mon Sep 17 00:00:00 2001 From: chxghee Date: Fri, 18 Jul 2025 12:10:16 +0900 Subject: [PATCH 4/7] =?UTF-8?q?feat:=20=EB=B0=B0=ED=8F=AC=20=EC=8A=A4?= =?UTF-8?q?=ED=81=AC=EB=A6=BD=ED=8A=B8=20=EC=9E=91=EC=84=B1=20deploy.sh?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- deploy.sh | 64 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 deploy.sh diff --git a/deploy.sh b/deploy.sh new file mode 100644 index 000000000..70b51027b --- /dev/null +++ b/deploy.sh @@ -0,0 +1,64 @@ +#!/bin/bash + +# 명령 에러 발생 시 종료 +set -e + +PROJECT_ROOT="/home/ubuntu/app" +APP_NAME="spring-basic-roomescape-playground" + +APP_LOG="$PROJECT_ROOT/application.log" +APP_ERROR_LOG="$PROJECT_ROOT/error.log" +DEPLOY_LOG="$PROJECT_ROOT/deploy.log" + +# 배포 중 에러 발생으로 중단 시 로그 기록 함수 +on_error() { + echo "********** [배포 중 에러 발생] : $(date +%Y-%m-%d\ %H:%M:%S) **********" >> $DEPLOY_LOG + echo " -> 실패한 명령어: '$BASH_COMMAND'" >> $DEPLOY_LOG + echo " -> 위치: ${BASH_SOURCE[1]}:${LINENO[1]}" >> $DEPLOY_LOG + exit 1 +} +trap on_error ERR + +echo "=========== [배포 시작] : $(date +%Y-%m-%d\ %H:%M:%S) ===========" >> $DEPLOY_LOG + +cd $PROJECT_ROOT + +# 1. 최신 코드 pull +echo "> Git pull" >> $DEPLOY_LOG +git pull >> $DEPLOY_LOG 2>&1 + +# 2. 프로젝트 빌드 +echo "> 프로젝트 빌드 시작" >> $DEPLOY_LOG +chmod +x ./gradlew +./gradlew build >> $DEPLOY_LOG 2>&1 + +# 3. jar 파일 시간 순 정렬 후 가장 상단에 있는(가장 최신) plain 이 아닌 jar 파일을 선택 +JAR_FILE=$(ls -t $PROJECT_ROOT/build/libs/*.jar | grep -v "\-plain.jar$" | head -n 1) + +# 4. 실행 중인 애플리케이션의 PID +CURRENT_PID=$(pgrep -f "$APP_NAME") + +# 5. 실행 중인 애플리케이션이 있으면 종료 +echo "> 실행 중인 애플리케이션이 있다면 종료" >> $DEPLOY_LOG +if [ -z "$CURRENT_PID" ]; then + echo " -> 현재 실행 중인 애플리케이션이 없습니다." >> $DEPLOY_LOG +else + echo " -> 실행 중인 애플리케이션 종료 (PID: $CURRENT_PID)" >> $DEPLOY_LOG + kill -15 $CURRENT_PID + sleep 5 +fi + +# 6. 새 애플리케이션 백그라운드 실행 +echo "> 새 애플리케이션 실행" >> $DEPLOY_LOG +nohup java -jar $JAR_FILE > $APP_LOG 2> $APP_ERROR_LOG & # 정상 출력 로그-> > APP_LOG / 비정상 출력 로그 -> 2> ERROR_LOG + +# 7. 애플리케이션 실행 여부 체크 +NEW_PID=$(pgrep -f "$APP_NAME") +if [ -n "$NEW_PID" ]; then + echo " -> 애플리케이션 실행 성공 (PID: $NEW_PID)" >> $DEPLOY_LOG +else + echo " -> 애플리케이션 실행 실패" >> $DEPLOY_LOG + exit 1 +fi + +echo "=========== [배포 완료] : $(date +%Y-%m-%d\ %H:%M:%S) ===========" >> $DEPLOY_LOG From bcd4db20e092a728b0cebfae993e30f4f58afc3d Mon Sep 17 00:00:00 2001 From: chxghee Date: Mon, 21 Jul 2025 22:58:17 +0900 Subject: [PATCH 5/7] =?UTF-8?q?refactor:=20prod,=20test=20=ED=99=98?= =?UTF-8?q?=EA=B2=BD=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application-prod.yml | 6 +++++ src/main/resources/application.yml | 8 ------- src/test/java/roomescape/JpaTest.java | 3 ++- .../member/application/MemberServiceTest.java | 2 ++ .../ReservationServiceConcurrencyTest.java | 4 ++-- .../application/ReservationServiceTest.java | 2 ++ .../WaitingServiceConcurrencyTest.java | 7 ++---- .../application/WaitingServiceTest.java | 2 ++ src/test/resources/application-test.yml | 6 +++++ src/test/resources/application.yml | 23 ------------------- 10 files changed, 24 insertions(+), 39 deletions(-) create mode 100644 src/main/resources/application-prod.yml create mode 100644 src/test/resources/application-test.yml delete mode 100644 src/test/resources/application.yml diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml new file mode 100644 index 000000000..fe11db077 --- /dev/null +++ b/src/main/resources/application-prod.yml @@ -0,0 +1,6 @@ +server: + port: 8080 + +spring: + datasource: + url: jdbc:h2:mem:database diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 6283e4039..a287b2811 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,18 +1,10 @@ -server: - port: 8080 - spring: - profiles: - active: prod h2: console: enabled: true path: /h2-console - datasource: - url: jdbc:h2:mem:database - jpa: show-sql: true properties: diff --git a/src/test/java/roomescape/JpaTest.java b/src/test/java/roomescape/JpaTest.java index 47cc3aecc..f7a71b587 100644 --- a/src/test/java/roomescape/JpaTest.java +++ b/src/test/java/roomescape/JpaTest.java @@ -1,11 +1,11 @@ package roomescape; -import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager; +import org.springframework.test.context.ActiveProfiles; import roomescape.time.domain.Time; import roomescape.time.domain.TimeRepository; @@ -13,6 +13,7 @@ @DataJpaTest +@ActiveProfiles("test") public class JpaTest { @Value("${roomescape.auth.jwt.secret}") diff --git a/src/test/java/roomescape/member/application/MemberServiceTest.java b/src/test/java/roomescape/member/application/MemberServiceTest.java index 50aad2d66..5e5fe1b44 100644 --- a/src/test/java/roomescape/member/application/MemberServiceTest.java +++ b/src/test/java/roomescape/member/application/MemberServiceTest.java @@ -4,6 +4,7 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; import org.springframework.transaction.annotation.Transactional; import roomescape.exception.ApplicationException; import roomescape.member.domain.Member; @@ -17,6 +18,7 @@ @SpringBootTest @Transactional +@ActiveProfiles("test") class MemberServiceTest { @Autowired diff --git a/src/test/java/roomescape/reservation/application/ReservationServiceConcurrencyTest.java b/src/test/java/roomescape/reservation/application/ReservationServiceConcurrencyTest.java index 6974c2d97..dce23d6db 100644 --- a/src/test/java/roomescape/reservation/application/ReservationServiceConcurrencyTest.java +++ b/src/test/java/roomescape/reservation/application/ReservationServiceConcurrencyTest.java @@ -1,11 +1,11 @@ package roomescape.reservation.application; -import org.assertj.core.api.SoftAssertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ActiveProfiles; import roomescape.member.domain.Member; import roomescape.member.domain.MemberRepository; import roomescape.member.domain.Role; @@ -15,7 +15,6 @@ import roomescape.theme.domain.ThemeRepository; import roomescape.time.domain.Time; import roomescape.time.domain.TimeRepository; -import roomescape.waiting.domain.repository.WaitingRepository; import java.util.ArrayList; import java.util.List; @@ -27,6 +26,7 @@ import static org.assertj.core.api.SoftAssertions.assertSoftly; @SpringBootTest +@ActiveProfiles("test") @DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) public class ReservationServiceConcurrencyTest { diff --git a/src/test/java/roomescape/reservation/application/ReservationServiceTest.java b/src/test/java/roomescape/reservation/application/ReservationServiceTest.java index c1976da7b..dc24254dc 100644 --- a/src/test/java/roomescape/reservation/application/ReservationServiceTest.java +++ b/src/test/java/roomescape/reservation/application/ReservationServiceTest.java @@ -4,6 +4,7 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; import org.springframework.transaction.annotation.Transactional; import roomescape.auth.LoginMember; import roomescape.exception.ApplicationException; @@ -29,6 +30,7 @@ @SpringBootTest @Transactional +@ActiveProfiles("test") class ReservationServiceTest { @Autowired diff --git a/src/test/java/roomescape/waiting/application/WaitingServiceConcurrencyTest.java b/src/test/java/roomescape/waiting/application/WaitingServiceConcurrencyTest.java index 351761e30..82a216f27 100644 --- a/src/test/java/roomescape/waiting/application/WaitingServiceConcurrencyTest.java +++ b/src/test/java/roomescape/waiting/application/WaitingServiceConcurrencyTest.java @@ -5,6 +5,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ActiveProfiles; import roomescape.member.domain.Member; import roomescape.member.domain.MemberRepository; import roomescape.member.domain.Role; @@ -13,13 +14,10 @@ import roomescape.time.domain.Time; import roomescape.time.domain.TimeRepository; import roomescape.waiting.application.command.WaitingCommand; -import roomescape.waiting.domain.WaitingOrderCounter; -import roomescape.waiting.domain.repository.WaitingOrderCounterRepository; import roomescape.waiting.domain.repository.WaitingRepository; import java.util.ArrayList; import java.util.List; -import java.util.Optional; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -28,14 +26,13 @@ import static org.assertj.core.api.Assertions.assertThat; @SpringBootTest +@ActiveProfiles("test") @DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) class WaitingServiceConcurrencyTest { @Autowired private WaitingRepository waitingRepository; @Autowired - private WaitingOrderCounterRepository orderCounterRepository; - @Autowired private MemberRepository memberRepository; @Autowired private TimeRepository timeRepository; diff --git a/src/test/java/roomescape/waiting/application/WaitingServiceTest.java b/src/test/java/roomescape/waiting/application/WaitingServiceTest.java index bf589ccbd..03536adfa 100644 --- a/src/test/java/roomescape/waiting/application/WaitingServiceTest.java +++ b/src/test/java/roomescape/waiting/application/WaitingServiceTest.java @@ -5,6 +5,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ActiveProfiles; import org.springframework.transaction.annotation.Transactional; import roomescape.auth.AuthException; import roomescape.auth.LoginMember; @@ -32,6 +33,7 @@ @SpringBootTest @Transactional +@ActiveProfiles("test") class WaitingServiceTest { @Autowired diff --git a/src/test/resources/application-test.yml b/src/test/resources/application-test.yml new file mode 100644 index 000000000..2574fb4ef --- /dev/null +++ b/src/test/resources/application-test.yml @@ -0,0 +1,6 @@ +server: + port: 0 + +spring: + datasource: + url: jdbc:h2:mem:test_db diff --git a/src/test/resources/application.yml b/src/test/resources/application.yml deleted file mode 100644 index 875ece13e..000000000 --- a/src/test/resources/application.yml +++ /dev/null @@ -1,23 +0,0 @@ -server: - port: 0 - -spring: - profiles: - active: test - - jpa: - show-sql: true - properties: - hibernate: - format_sql: true - ddl-auto: create-drop - - datasource: - url: jdbc:h2:mem:test_db - -roomescape: - auth: - jwt: - secret: Yn2kjibddFAWtnPJ2AFlL8WXmohJMCvigQggaEypa5E= - expiration: 1800000 - From 7381a062ce4df2feb033bcb7a1950f2447eb1ab8 Mon Sep 17 00:00:00 2001 From: chxghee Date: Mon, 21 Jul 2025 23:56:21 +0900 Subject: [PATCH 6/7] =?UTF-8?q?refactor:=20deploy.sh=20=ED=94=84=EB=A1=9C?= =?UTF-8?q?=EC=84=B8=EC=8A=A4=20=EC=A2=85=EB=A3=8C=20=EB=A1=9C=EC=A7=81=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 --- deploy.sh | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/deploy.sh b/deploy.sh index 70b51027b..a2c4db827 100644 --- a/deploy.sh +++ b/deploy.sh @@ -3,7 +3,7 @@ # 명령 에러 발생 시 종료 set -e -PROJECT_ROOT="/home/ubuntu/app" +PROJECT_ROOT="/home/ubuntu/spring-basic-roomescape-playground" APP_NAME="spring-basic-roomescape-playground" APP_LOG="$PROJECT_ROOT/application.log" @@ -36,7 +36,7 @@ chmod +x ./gradlew JAR_FILE=$(ls -t $PROJECT_ROOT/build/libs/*.jar | grep -v "\-plain.jar$" | head -n 1) # 4. 실행 중인 애플리케이션의 PID -CURRENT_PID=$(pgrep -f "$APP_NAME") +CURRENT_PID=$(pgrep -f "$APP_NAME" || true) # 5. 실행 중인 애플리케이션이 있으면 종료 echo "> 실행 중인 애플리케이션이 있다면 종료" >> $DEPLOY_LOG @@ -45,7 +45,21 @@ if [ -z "$CURRENT_PID" ]; then else echo " -> 실행 중인 애플리케이션 종료 (PID: $CURRENT_PID)" >> $DEPLOY_LOG kill -15 $CURRENT_PID - sleep 5 + + for _ in {1..10} + do + if ! ps -p $CURRENT_PID > /dev/null 2>&1; then + echo " → 종료 완료" >> $DEPLOY_LOG + break + fi + sleep 1 + done + + if ps -p $CURRENT_PID > /dev/null 2>&1; then + echo " → 정상 종료 실패, 강제 종료 시도." >> $DEPLOY_LOG + kill -9 $CURRENT_PID + sleep 2 + fi fi # 6. 새 애플리케이션 백그라운드 실행 From 0d13b971211783233e9310f40dfe05e6c8e6abe4 Mon Sep 17 00:00:00 2001 From: chxghee Date: Tue, 22 Jul 2025 00:11:22 +0900 Subject: [PATCH 7/7] =?UTF-8?q?refactor:=20=EC=8B=A4=ED=96=89=20=EC=8B=9C?= =?UTF-8?q?=20prod=20=ED=99=98=EA=B2=BD=20=EC=84=A4=EC=A0=95=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EC=8B=A4=ED=96=89=20=EB=90=98=EB=8F=84=EB=A1=9D=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 --- deploy.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy.sh b/deploy.sh index a2c4db827..a8b37249a 100644 --- a/deploy.sh +++ b/deploy.sh @@ -64,7 +64,7 @@ fi # 6. 새 애플리케이션 백그라운드 실행 echo "> 새 애플리케이션 실행" >> $DEPLOY_LOG -nohup java -jar $JAR_FILE > $APP_LOG 2> $APP_ERROR_LOG & # 정상 출력 로그-> > APP_LOG / 비정상 출력 로그 -> 2> ERROR_LOG +nohup java -Dspring.profiles.active=prod -jar $JAR_FILE > $APP_LOG 2> $APP_ERROR_LOG & # 정상 출력 로그-> > APP_LOG / 비정상 출력 로그 -> 2> ERROR_LOG # 7. 애플리케이션 실행 여부 체크 NEW_PID=$(pgrep -f "$APP_NAME")