-
Notifications
You must be signed in to change notification settings - Fork 80
[그리디] 서현진 Spring Core 배포 7, 8, 9 단계 미션 제출합니다. #211
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: nonactress
Are you sure you want to change the base?
Changes from all commits
29a3411
76e20ba
6884888
b994a63
9234df4
103e6bb
ecd0c7f
334327e
eeb4f66
a75257b
f95bdc5
c1610e8
aa8870e
9f54ac5
a674fa5
f9f63d1
5ee3e74
cc28fde
b5cca3b
9deeb22
7742db7
0812bd7
8f084a7
4876179
0f3aadf
7a12ebf
b05b545
e83a263
916af1e
48a6984
659c17b
f4f58e9
4c51bea
db04811
1ca9fa1
494fffe
0f655d2
5ce1144
eecfe3a
bd4201c
d4fc462
fd5d1a5
7c953c7
ecf4071
c7303bc
1bfd9e3
3e72013
62a0229
6de128e
d488aff
8bd9234
c5bdf60
216d01e
4c169c7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -35,3 +35,5 @@ out/ | |
|
|
||
| ### VS Code ### | ||
| .vscode/ | ||
|
|
||
| **/application-key.properties | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| #!/bin/bash | ||
|
|
||
| echo "==========================================" | ||
| echo "배포 시작" | ||
| echo "==========================================" | ||
|
|
||
| echo ">>> Git Pull" | ||
| git pull origin main | ||
|
|
||
| if [ $? -ne 0 ]; then | ||
| echo "Git Pull 실패" | ||
| exit 1 | ||
| fi | ||
|
|
||
| echo ">>> 프로젝트 빌드 시작" | ||
| ./gradlew clean build | ||
|
|
||
| if [ $? -ne 0 ]; then | ||
| echo "빌드 실패" | ||
| exit 1 | ||
| fi | ||
|
|
||
| echo ">>> 실행 중인 애플리케이션 확인" | ||
| CURRENT_PID=$(pgrep -f roomescape) | ||
|
|
||
| if [ -z "$CURRENT_PID" ]; then | ||
| echo ">>> 실행 중인 애플리케이션이 없습니다." | ||
| else | ||
| echo ">>> 애플리케이션 종료 (PID: $CURRENT_PID)" | ||
| kill -15 $CURRENT_PID | ||
| sleep 5 | ||
| fi | ||
|
|
||
| echo ">>> 새 애플리케이션 실행" | ||
| nohup java -jar build/libs/roomescape-0.0.1-SNAPSHOT.jar > application.log 2>&1 & | ||
|
|
||
| sleep 3 | ||
|
|
||
| NEW_PID=$(pgrep -f roomescape) | ||
| echo ">>> 배포 완료 (PID: $NEW_PID)" | ||
|
|
||
| echo "==========================================" | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| package auth; | ||
|
|
||
| import io.jsonwebtoken.Claims; | ||
| import io.jsonwebtoken.JwtException; | ||
| import io.jsonwebtoken.Jwts; | ||
| import io.jsonwebtoken.SignatureAlgorithm; | ||
|
|
||
| import java.util.Date; | ||
|
|
||
|
|
||
| public class JwtTokenProvider { | ||
| private final String secretKey; | ||
| private final long validityInMilliseconds; | ||
|
|
||
| public JwtTokenProvider(String secretKey, long validityInMilliseconds) { | ||
| this.secretKey = secretKey; | ||
| this.validityInMilliseconds = validityInMilliseconds; | ||
| } | ||
|
|
||
| public String createToken(String payload) { | ||
| Claims claims = Jwts.claims().setSubject(payload); | ||
| Date now = new Date(); | ||
| Date validity = new Date(now.getTime() + validityInMilliseconds); | ||
|
|
||
| return Jwts.builder() | ||
| .setClaims(claims) | ||
| .setIssuedAt(now) | ||
| .setExpiration(validity) | ||
| .signWith(SignatureAlgorithm.HS256, secretKey) | ||
| .compact(); | ||
| } | ||
|
|
||
| public String getPayload(String token) { | ||
| return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody().getSubject(); | ||
| } | ||
|
|
||
| public boolean validateToken(String token) { | ||
| try { | ||
| Jwts.parser() | ||
| .setSigningKey(secretKey) | ||
| .parseClaimsJws(token); | ||
| return true; | ||
|
|
||
| } catch (JwtException | IllegalArgumentException e) { | ||
|
|
||
| return false; | ||
| } | ||
| } | ||
| } | ||
|
|
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,75 @@ | ||
| package roomescape.Loader; | ||
|
|
||
| import org.springframework.boot.CommandLineRunner; | ||
| import org.springframework.stereotype.Component; | ||
| import org.springframework.transaction.annotation.Transactional; | ||
| import roomescape.member.Member; | ||
| import roomescape.member.MemberRepository; | ||
| import roomescape.member.Role; | ||
| import roomescape.reservation.ReservationRepository; | ||
| import roomescape.theme.Theme; | ||
| import roomescape.theme.ThemeRepository; | ||
| import roomescape.time.Time; | ||
| import roomescape.time.TimeRepository; | ||
|
|
||
| @Component | ||
| public class ProductionDataLoader implements CommandLineRunner { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. .sql 파일을 활용하는 대신 CommandLineRunner를 활용했을 때 어떤 차이가 있었나요? 장단점은 있을까요?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. sql장점 - 퀴리 문으로 작동해서 매우 빠르다 CommandLineRunner장점 - 자바 로직으로 데이터를 만들 수 있다, 복잡한 엔티티 연관관계 보다 쉽게 만들 수 있다! 차이는 속도적인 차이가 존재하고 sql 엔티티 연관관계 구조를 매번 적용 시켜줘야 한다는 단점이 있는 거 같습니다! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 오호 그렇군요~ 차이를 공유해주셔서 감사합니다. 제가 경험했을 때는 sql문을 사용하다보면 테스트가 변경될 때마다 매번 복잡한 쿼리문을 작성하느라 까다로웠던 것 같아요. 어떤 상황에 테스트 더미 데이터를 적재하냐에 따라서 방법이 달라지는 것 같네요 👍 |
||
|
|
||
| private final MemberRepository memberRepository; | ||
| private final ThemeRepository themeRepository; | ||
| private final TimeRepository timeRepository; | ||
| private final ReservationRepository reservationRepository; | ||
|
|
||
| public ProductionDataLoader( | ||
| MemberRepository memberRepository, | ||
| ThemeRepository themeRepository, | ||
| TimeRepository timeRepository, | ||
| ReservationRepository reservationRepository | ||
| ) { | ||
| this.memberRepository = memberRepository; | ||
| this.themeRepository = themeRepository; | ||
| this.timeRepository = timeRepository; | ||
| this.reservationRepository = reservationRepository; | ||
| } | ||
|
|
||
| @Override | ||
| @Transactional | ||
| public void run(String... args) throws Exception { | ||
| // 1. 관리자 계정 생성 | ||
| if (memberRepository.findByEmail("admin").isEmpty()) { | ||
| Member admin = new Member("admin", "admin", "admin", Role.ADMIN); | ||
| memberRepository.save(admin); | ||
| System.out.println("관리자 계정이 생성되었습니다."); | ||
| } | ||
|
|
||
| // 2. 테마 데이터 생성 | ||
| if (themeRepository.count() == 0) { | ||
| 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); | ||
| System.out.println("테마 데이터가 생성되었습니다."); | ||
| } | ||
|
|
||
| // 3. 시간 데이터 생성 | ||
| if (timeRepository.count() == 0) { | ||
| 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); | ||
| System.out.println("시간 데이터가 생성되었습니다."); | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,108 @@ | ||
| package roomescape.Loader; | ||
|
|
||
| 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.Member; | ||
| import roomescape.member.MemberRepository; | ||
| import roomescape.member.Role; | ||
| import roomescape.reservation.Reservation; | ||
| import roomescape.reservation.ReservationRepository; | ||
| import roomescape.theme.Theme; | ||
| import roomescape.theme.ThemeRepository; | ||
| import roomescape.time.Time; | ||
| import roomescape.time.TimeRepository; | ||
|
|
||
| @Profile("test") | ||
| @Component | ||
| public class TestDataLoader implements CommandLineRunner { | ||
|
|
||
| private final MemberRepository memberRepository; | ||
| private final ThemeRepository themeRepository; | ||
| private final TimeRepository timeRepository; | ||
| private final ReservationRepository reservationRepository; | ||
|
|
||
| public TestDataLoader( | ||
| MemberRepository memberRepository, | ||
| ThemeRepository themeRepository, | ||
| TimeRepository timeRepository, | ||
| ReservationRepository reservationRepository | ||
| ) { | ||
| this.memberRepository = memberRepository; | ||
| this.themeRepository = themeRepository; | ||
| this.timeRepository = timeRepository; | ||
| this.reservationRepository = reservationRepository; | ||
| } | ||
|
|
||
| @Override | ||
| @Transactional | ||
| public void run(String... args) throws Exception { | ||
| // 1. 관리자 계정 생성 | ||
| if (memberRepository.findByEmail("admin").isEmpty()) { | ||
| Member admin = new Member("admin", "admin", "admin", Role.ADMIN); | ||
| memberRepository.save(admin); | ||
| System.out.println("관리자 계정이 생성되었습니다."); | ||
| } | ||
|
|
||
| // 2. 테마 데이터 생성 | ||
| if (themeRepository.count() == 0) { | ||
| 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); | ||
| System.out.println("테마 데이터가 생성되었습니다."); | ||
| } | ||
|
|
||
| // 3. 시간 데이터 생성 | ||
| if (timeRepository.count() == 0) { | ||
| 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); | ||
| System.out.println("시간 데이터가 생성되었습니다."); | ||
| } | ||
|
|
||
| if (reservationRepository.count() == 0) { | ||
| Member admin = memberRepository.findByEmail("admin") | ||
| .orElseThrow(() -> new RuntimeException("Admin not found")); | ||
|
|
||
| Time time1 = timeRepository.findById(1L).orElseThrow(); | ||
| Time time2 = timeRepository.findById(2L).orElseThrow(); | ||
| Time time3 = timeRepository.findById(3L).orElseThrow(); | ||
|
|
||
| Theme theme1 = themeRepository.findById(1L).orElseThrow(); | ||
| Theme theme2 = themeRepository.findById(2L).orElseThrow(); | ||
| Theme theme3 = themeRepository.findById(3L).orElseThrow(); | ||
|
|
||
| Reservation reservation1 = new Reservation("", "2024-03-01", time1, theme1, admin); | ||
| Reservation reservation2 = new Reservation("", "2024-03-01", time2, theme2, admin); | ||
| Reservation reservation3 = new Reservation("", "2024-03-01", time3, theme3, admin); | ||
|
|
||
| reservationRepository.save(reservation1); | ||
| reservationRepository.save(reservation2); | ||
| reservationRepository.save(reservation3); | ||
|
|
||
| Reservation reservation4 = new Reservation("브라운", "2024-03-01", time1, theme2); | ||
|
|
||
| reservationRepository.save(reservation4); | ||
| System.out.println("예약 데이터가 생성되었습니다."); | ||
| } | ||
|
|
||
| System.out.println("초기 데이터 로딩이 완료되었습니다."); | ||
| } | ||
|
|
||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| package roomescape.exception; | ||
|
|
||
| public class AuthenticationException extends RuntimeException { | ||
|
|
||
| public AuthenticationException(String message) { | ||
| super(message); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| package roomescape.exception; | ||
|
|
||
| public record ErrorResponse(String message) { | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| package roomescape.exception; | ||
|
|
||
| import io.jsonwebtoken.ExpiredJwtException; | ||
| import org.springframework.http.HttpStatus; | ||
| import org.springframework.http.ResponseEntity; | ||
| import org.springframework.web.bind.annotation.ControllerAdvice; | ||
| import org.springframework.web.bind.annotation.ExceptionHandler; | ||
|
|
||
| @ControllerAdvice | ||
| public class GlobalExceptionHandler { | ||
|
|
||
| @ExceptionHandler(IllegalArgumentException.class) | ||
| public ResponseEntity<ErrorResponse> handleIllegalArgumentException(IllegalArgumentException e) { | ||
| return ResponseEntity.badRequest() | ||
| .body(new ErrorResponse(e.getMessage())); | ||
| } | ||
|
|
||
| @ExceptionHandler(AuthenticationException.class) | ||
| public ResponseEntity<String> handleAuthenticationException(AuthenticationException e) { | ||
| return ResponseEntity.status(HttpStatus.UNAUTHORIZED) | ||
| .body(e.getMessage()); | ||
| } | ||
|
|
||
| @ExceptionHandler(Exception.class) | ||
| public ResponseEntity<ErrorResponse> handleException(Exception e) { | ||
| return ResponseEntity.internalServerError() | ||
| .body(new ErrorResponse("오류가 발생했습니다.")); | ||
| } | ||
|
|
||
| @ExceptionHandler(ExpiredJwtException.class) | ||
| public ResponseEntity<ErrorResponse> handleExpiredJwtException(ExpiredJwtException e) { | ||
| return ResponseEntity.status(HttpStatus.UNAUTHORIZED) | ||
| .body(new ErrorResponse("토큰 만료 시간 초과")); | ||
| } | ||
| } | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| package roomescape.infrastructure; | ||
|
|
||
| import java.lang.annotation.ElementType; | ||
| import java.lang.annotation.Retention; | ||
| import java.lang.annotation.RetentionPolicy; | ||
| import java.lang.annotation.Target; | ||
|
|
||
| @Target(ElementType.PARAMETER) | ||
| @Retention(RetentionPolicy.RUNTIME) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 위 어노테이션은 언제까지 살아남아 있을지를 결정하는 설정을 도와주는 어노테이션 입니다! 타입SOURCE: 소스 코드(.java)에만 존재하며, 컴파일 시점에 제거됩니다. (예: @OverRide) CLASS: 컴파일된 클래스 파일(.class)까지 유지되지만, 런타임에는 읽을 수 없습니다. (기본값) RUNTIME: 실행 중에도 유지되어 리플렉션을 통해 정보를 읽을 수 있습니다. 주로 RUNTIME을 사용하는 이유스프링이 실행 시점에 클래스를 스캔하여 빈(Bean)을 자동으로 등록하거나 의존성을 주입하려면, 런타임까지 어노테이션 정보가 살아있어야 하기 때문입니다. |
||
| public @interface AuthMember { | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
현진님이 작성해주신 배포스크립트를 활용하면 어떻게 배포할 수 있나요?
즉, 이 배포 스크립트 실행 전에 해야할 사전 작업이 있나요?
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
일단 EC2 기준으로 기본 사전 작업에 대해 생각해보면
2.
git pull origin main을 사용하므로 git 설치도 해야합니다!There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
그렇군요! 👍👍👍
몇 가지 더 사전작업이 필요할 것 같아요.
등이 더 필요할 것 같아보여요.
배포스크립트 목적에 따라 사전 작업 부분들까지 스크립트에 추가해도 좋을 것 같네요!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
아아 넵 위 두가지 사전작업도 기억해놓겠습니다!!