From 1c6a7294bb75caa8b88a5f1f6b9d4cd58dfb8e30 Mon Sep 17 00:00:00 2001 From: Starlight258 Date: Sun, 28 Jan 2024 01:57:18 +0900 Subject: [PATCH 1/5] [#1] feat: Add H2 database dependency and enable iframe for H2 console --- .DS_Store | Bin 0 -> 6148 bytes build.gradle | 66 ++++++++++-------- src/.DS_Store | Bin 0 -> 6148 bytes src/main/.DS_Store | Bin 0 -> 6148 bytes .../dnd/modutimer/config/SecurityConfig.java | 54 ++++++++------ .../org/dnd/modutimer/timer/domain/Timer.java | 10 ++- .../org/dnd/modutimer/user/domain/User.java | 9 ++- src/main/resources/application.yml | 2 +- 8 files changed, 82 insertions(+), 59 deletions(-) create mode 100644 .DS_Store create mode 100644 src/.DS_Store create mode 100644 src/main/.DS_Store diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5baf4066e4729f5b30b1ec804352c7d8426c8168 GIT binary patch literal 6148 zcmeHK&2G~`82mO#>zEXg1C@F~vc$CtDHH+X;-<-=5^$&y8~_Eowu*)8tzxG>M3H=k zci7FehlXHYe2|o!E9;|UqhjG9-@b@_&?%fRvXihODRK0)2BjEsL@Lh&Xed{Na26Rd@ z@+pKas@(U`v2P9C=H*=a+~8VOE*15eTc-_nug*qR0dmUgD!GnNm33J3gpTNtCiILv znl#uVqbj>i9R4)sWVafb*mR8f4rokIfde{gx+xXad2dig5#{*Alwuv(Z$16EmQ@D- zPwKfd!K(gZZOjT*P-N%&x%?$KPvR^e4!^QiyR)%rS+>=)-gxhL;pKil&qn_2mAHD& zc@nJJe()lWifRAmAy0EZPNSI=#9;)=>z8pF@?ym2X_!f^rv_}hYft+-i^cwf;jXhc zyuaLa7LV?ty?^iFa%p$BZ{K-*Jbj;>rThajM=Jq~=4j2di69>fe!9fw|YAhAXM+Yi-1ORr>Edy=w;vh7RMpt905F-et zq(CKQ`iQ}l9Q}^wxf)A_N={54KA8SyrcWqLemmxOB%PS6(A35O<3Qbkwz}+#{eN=# z`M)kQSH=P3z(3^x>x{ke2usp?>)PVPUhAN|MG+x*r9vq|rMF|*5L@vMiVTc7q5*U@ UmI~2>FfRhq22&Xa{-^^#03`{_B>(^b literal 0 HcmV?d00001 diff --git a/build.gradle b/build.gradle index 544ea1e..4b9a275 100644 --- a/build.gradle +++ b/build.gradle @@ -1,55 +1,59 @@ plugins { - id 'java' - id 'org.springframework.boot' version '3.2.1' - id 'io.spring.dependency-management' version '1.1.4' - id "org.sonarqube" version "4.0.0.2929" + id 'java' + id 'org.springframework.boot' version '3.2.1' + id 'io.spring.dependency-management' version '1.1.4' + id "org.sonarqube" version "4.0.0.2929" } group = 'org.dnd' version = '0.0.1-SNAPSHOT' java { - sourceCompatibility = '17' + sourceCompatibility = '17' } configurations { - compileOnly { - extendsFrom annotationProcessor - } + compileOnly { + extendsFrom annotationProcessor + } } repositories { - mavenCentral() + mavenCentral() } dependencies { - implementation 'org.springframework.boot:spring-boot-starter-data-jpa' - implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' - implementation 'org.springframework.boot:spring-boot-starter-web' - implementation 'org.springframework.boot:spring-boot-starter-aop' - implementation 'org.springframework.boot:spring-boot-starter-security' - implementation 'org.springframework.boot:spring-boot-starter-validation' - implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0' - - compileOnly 'org.projectlombok:lombok' - runtimeOnly 'com.mysql:mysql-connector-j' - annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor' - annotationProcessor 'org.projectlombok:lombok' - testImplementation 'org.springframework.boot:spring-boot-starter-test' - testImplementation 'org.springframework.security:spring-security-test' - testRuntimeOnly 'com.h2database:h2' - - // third-party - implementation group: 'com.auth0', name: 'java-jwt', version: '4.3.0' + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' + implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework.boot:spring-boot-starter-aop' + implementation 'org.springframework.boot:spring-boot-starter-security' + implementation 'org.springframework.boot:spring-boot-starter-validation' + implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0' + implementation("org.springframework.boot:spring-boot-devtools") + + + compileOnly 'org.projectlombok:lombok' + runtimeOnly 'com.mysql:mysql-connector-j' + runtimeOnly("com.h2database:h2") + + annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor' + annotationProcessor 'org.projectlombok:lombok' + testImplementation 'org.springframework.boot:spring-boot-starter-test' + testImplementation 'org.springframework.security:spring-security-test' + testRuntimeOnly 'com.h2database:h2' + + // third-party + implementation group: 'com.auth0', name: 'java-jwt', version: '4.3.0' } tasks.named('test') { - useJUnitPlatform() + useJUnitPlatform() } sonar { - properties { - property "sonar.projectKey", "dnd-10th-2-backend" - } + properties { + property "sonar.projectKey", "dnd-10th-2-backend" + } } \ No newline at end of file diff --git a/src/.DS_Store b/src/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..03537a3610b273ce71468742a74b1778ecced4e3 GIT binary patch literal 6148 zcmeHK!EVz)5S>j8iB(m~0i+x)ajl9AtpIT`AwBe3SLM*6V3Vk|aJ^OR;6oJ2XZRSd z{StnseY3lb+J+vB2%3>*-|Xy+?R{(Q?hujcPe+F+xs0WyMI> zeLXEkrOA!$fbVzwVYfG%?Ys#$gY9s4z8TEky@I{-`t5w~cb>m^`Tk(|Ek7^x4@Icv9rLIE9bnJ*sF-B~|zM_=U0Ce4r~L3|44q!HNW>JJRq0gN0r7U6*?n+mk4%9R+(RSwFg zV?1*4jYXSI%DfrRac@?xLQ$^pP=N_26d1|h~=ZW2Q>nJzy>gK*jR)IA{PNegEYdx HUuEDLAeUf< literal 0 HcmV?d00001 diff --git a/src/main/.DS_Store b/src/main/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..3454a4f02ac67b6b236d3b78a215357575336044 GIT binary patch literal 6148 zcmeHKO;6iE5S|SpW(-F z<(KsD&^Nmq)HGB*QMJ{KH2Y?E=k3OC<6SQisopTzBr;5HM`0CJ z!?G6*K5*?s>pY&uQT#4TNBz#qp)RT@E7E}>WJ!vYkMFZ0(W9Oo7D;Kiq3!Uzw%6~h zj>kKD-3@=UyF1zN#|K-8ceeK@6R-Vz{pG8-{V(~~LSM5ecnR csrf.disable()); - // iframe 거부 + // iframe 허용 : h2-console 사용 목적 http.headers(headers -> headers - .contentSecurityPolicy(csp -> csp.policyDirectives("frame-ancestors 'none'"))); + .frameOptions(frameOptions -> frameOptions.disable()) + ); + // cors 재설정 http.cors(cors -> cors.configurationSource(configurationSource())); // jSessionId 사용 거부 (토큰 인증 방식 사용) http.sessionManagement(session -> session - .sessionCreationPolicy(SessionCreationPolicy.STATELESS)); + .sessionCreationPolicy(SessionCreationPolicy.STATELESS)); // form 로그인 해제 (UsernamePasswordAuthenticationFilter 비활성화) http.formLogin(form -> form.disable()); @@ -89,23 +95,25 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http, Authentication http.httpBasic(httpBasic -> httpBasic.disable()); // JwtAuthenticationFilter 추가 - JwtAuthenticationFilter jwtAuthenticationFilter = new JwtAuthenticationFilter(authenticationManager, userUtilityService); + JwtAuthenticationFilter jwtAuthenticationFilter = new JwtAuthenticationFilter(authenticationManager, + userUtilityService); http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); // 인증 실패, 권한 실패 처리 http.exceptionHandling(exception -> exception - .authenticationEntryPoint(authenticationEntryPoint()) // 인증 실패 처리 - .accessDeniedHandler(((request, response, accessDeniedException) -> { - // 접근 거부 처리 - FilterResponseUtils.forbidden(response, new ForbiddenError( - ForbiddenError.ErrorCode.ROLE_BASED_ACCESS_ERROR, - Collections.singletonMap("access", "Access is denied") - )); - }))); + .authenticationEntryPoint(authenticationEntryPoint()) // 인증 실패 처리 + .accessDeniedHandler(((request, response, accessDeniedException) -> { + // 접근 거부 처리 + FilterResponseUtils.forbidden(response, new ForbiddenError( + ForbiddenError.ErrorCode.ROLE_BASED_ACCESS_ERROR, + Collections.singletonMap("access", "Access is denied") + )); + }))); // 인증, 권한 필터 설정 http.authorizeHttpRequests(auth -> auth .requestMatchers(PUBLIC_URLS).permitAll() // 인증 없이 접근 허용 + // .anyRequest().authenticated() .anyRequest().authenticated() ); diff --git a/src/main/java/org/dnd/modutimer/timer/domain/Timer.java b/src/main/java/org/dnd/modutimer/timer/domain/Timer.java index b2a36e7..93afce1 100644 --- a/src/main/java/org/dnd/modutimer/timer/domain/Timer.java +++ b/src/main/java/org/dnd/modutimer/timer/domain/Timer.java @@ -1,7 +1,13 @@ package org.dnd.modutimer.timer.domain; -import jakarta.persistence.*; +import jakarta.persistence.AttributeOverride; +import jakarta.persistence.Column; +import jakarta.persistence.Embedded; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.Table; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; @@ -14,7 +20,7 @@ @Getter @Table(name = "timer") @AttributeOverride(name = "id", column = @Column(name = "timer_id")) -@Where(clause = "is_deleted=0") // 삭제가 되지 않는 것만 조회 +@Where(clause = "is_deleted=false") // 삭제가 되지 않는 것만 조회 public class Timer extends AbstractJpaEntity { @Enumerated(EnumType.STRING) diff --git a/src/main/java/org/dnd/modutimer/user/domain/User.java b/src/main/java/org/dnd/modutimer/user/domain/User.java index 1d108f0..5ffdfdd 100644 --- a/src/main/java/org/dnd/modutimer/user/domain/User.java +++ b/src/main/java/org/dnd/modutimer/user/domain/User.java @@ -1,6 +1,11 @@ package org.dnd.modutimer.user.domain; -import jakarta.persistence.*; +import jakarta.persistence.AttributeOverride; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.Table; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; @@ -12,7 +17,7 @@ @Getter @Entity @Table(name = "member") -@Where(clause = "is_deleted=0") +@Where(clause = "is_deleted=false") @AttributeOverride(name = "id", column = @Column(name = "user_id")) public class User extends AbstractJpaEntity { diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 6b0116f..d963877 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,4 +1,4 @@ spring: profiles: active: - - test + - local From 506675866503b50347d4e933e9e583a08afafa60 Mon Sep 17 00:00:00 2001 From: Starlight258 Date: Tue, 30 Jan 2024 02:41:28 +0900 Subject: [PATCH 2/5] [#5] feat: Implement Docker support and switch H2 DB to file-based storage - Created Dockerfile and docker-compose.yml for containerized deployment. - Changed H2 DB configuration from in-memory to file-based for persistent data storate in Docker environment. --- .gitignore | 7 +++++-- Dockerfile | 13 +++++++++++++ docker-compose.yml | 18 ++++++++++++++++++ 3 files changed, 36 insertions(+), 2 deletions(-) create mode 100644 Dockerfile create mode 100644 docker-compose.yml diff --git a/.gitignore b/.gitignore index 401c4ee..396bc0f 100644 --- a/.gitignore +++ b/.gitignore @@ -4,11 +4,12 @@ build/ !gradle/wrapper/gradle-wrapper.jar !**/src/main/**/build/ !**/src/test/**/build/ - +# yml **/application-local.yml **/application-prod.yml **/application-test.yml - +# db data +data/ ### IntelliJ IDEA ### .idea *.iws @@ -19,3 +20,5 @@ out/ !**/src/test/**/out/ +/data/ +/data/ diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..4f887ca --- /dev/null +++ b/Dockerfile @@ -0,0 +1,13 @@ +# JDK 17을 기반으로 하는 이미지 사용 +FROM openjdk:17-alpine + +WORKDIR /usr/src/app + +ARG JAR_PATH=./build/libs + +# 빌드한 파일 옮기기 +COPY ${JAR_PATH}/modutimer-0.0.1-SNAPSHOT.jar ${JAR_PATH}/modutimer-0.0.1-SNAPSHOT.jar + +# 실행하기 +CMD ["java","-jar","./build/libs/modutimer-0.0.1-SNAPSHOT.jar"] + diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..532c217 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,18 @@ +version: '3.7' +services: + app: + build: + context: . + dockerfile: Dockerfile + environment: # 환경 변수 설정 + - SPRING_DATASOURCE_URL=jdbc:h2:file:/usr/src/app/data/mydb;DB_CLOSE_ON_EXIT=FALSE + platform: linux/amd64 + ports: + - "8080:8080" + # h2data 볼륨을 컨테이너 내부의 /usr/src/app/data에 매핑 + volumes: + - h2data:/usr/src/app/data + +# 볼륨 정의 (기본 설정 사용) +volumes: + h2data: \ No newline at end of file From 5aae3fe5039edad7a63e12da1383ca7ef9036098 Mon Sep 17 00:00:00 2001 From: Starlight258 Date: Tue, 30 Jan 2024 02:46:55 +0900 Subject: [PATCH 3/5] [#6] chore: Add git commit message template --- .github/.gitmessage.txt | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 .github/.gitmessage.txt diff --git a/.github/.gitmessage.txt b/.github/.gitmessage.txt new file mode 100644 index 0000000..49d2377 --- /dev/null +++ b/.github/.gitmessage.txt @@ -0,0 +1,41 @@ +[#issue] type: description + +# Commit Message Convention +# +# - 커밋 메시지는 반드시 영어로 작성합니다. +# - GitHub 이슈 번호를 참조합니다. +# - Description의 첫 글자는 대문자로 시작합니다. +# +# 기본 형식 +# - `[#{issue-number}] : ` +# +# 예시 +# - `[#123] feat: Add login functionality` +# +# Commit Types +# +# 🌟 새로운 기능 및 주요 변경 +# - `feat`: 새로운 기능 추가 +# - `!BREAKING CHANGE`: 큰 API 변경 +# +# 🛠️ 버그 수정 및 긴급 조치 +# - `fix`: 버그 수정 +# - `!HOTFIX`: 긴급한 버그 수정 +# +# 🔧 코드 개선 +# - `refactor`: 코드 리팩토링 +# - `style`: 코드 포맷 변경, 세미콜론 누락 등 코드 수정 없는 경우 +# +# 📚 문서 및 주석 +# - `docs`: 문서 수정 +# - `comment`: 주석 추가 및 변경 +# +# 🧪 테스트 +# - `test`: 테스트 코드 추가, 프로덕션 코드 변경 없음 +# +# 🏗️ 구조적 변경 +# - `rename`: 파일/폴더명 수정, 이동 +# - `remove`: 파일 삭제 +# +# 🎨 기타 +# - `chore`: 빌드 업무 수정, 패키지 매니저 변경 등 프로덕션 코드 변경 없는 경우 \ No newline at end of file From 618dfe80189b42db4777623f6fcb00f84d8bb3b3 Mon Sep 17 00:00:00 2001 From: Starlight258 Date: Tue, 30 Jan 2024 14:36:00 +0900 Subject: [PATCH 4/5] [#8] chore: Exclude .DS_Store from Git tracking --- .DS_Store | Bin 6148 -> 0 bytes .gitignore | 95 ++++++++++++++++++++++++++++++++++++++++++++- src/.DS_Store | Bin 6148 -> 0 bytes src/main/.DS_Store | Bin 6148 -> 0 bytes 4 files changed, 93 insertions(+), 2 deletions(-) delete mode 100644 .DS_Store delete mode 100644 src/.DS_Store delete mode 100644 src/main/.DS_Store diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 5baf4066e4729f5b30b1ec804352c7d8426c8168..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHK&2G~`82mO#>zEXg1C@F~vc$CtDHH+X;-<-=5^$&y8~_Eowu*)8tzxG>M3H=k zci7FehlXHYe2|o!E9;|UqhjG9-@b@_&?%fRvXihODRK0)2BjEsL@Lh&Xed{Na26Rd@ z@+pKas@(U`v2P9C=H*=a+~8VOE*15eTc-_nug*qR0dmUgD!GnNm33J3gpTNtCiILv znl#uVqbj>i9R4)sWVafb*mR8f4rokIfde{gx+xXad2dig5#{*Alwuv(Z$16EmQ@D- zPwKfd!K(gZZOjT*P-N%&x%?$KPvR^e4!^QiyR)%rS+>=)-gxhL;pKil&qn_2mAHD& zc@nJJe()lWifRAmAy0EZPNSI=#9;)=>z8pF@?ym2X_!f^rv_}hYft+-i^cwf;jXhc zyuaLa7LV?ty?^iFa%p$BZ{K-*Jbj;>rThajM=Jq~=4j2di69>fe!9fw|YAhAXM+Yi-1ORr>Edy=w;vh7RMpt905F-et zq(CKQ`iQ}l9Q}^wxf)A_N={54KA8SyrcWqLemmxOB%PS6(A35O<3Qbkwz}+#{eN=# z`M)kQSH=P3z(3^x>x{ke2usp?>)PVPUhAN|MG+x*r9vq|rMF|*5L@vMiVTc7q5*U@ UmI~2>FfRhq22&Xa{-^^#03`{_B>(^b diff --git a/.gitignore b/.gitignore index 396bc0f..74f33d8 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,97 @@ out/ !**/src/main/**/out/ !**/src/test/**/out/ +### Database ### +*.accdb +*.db +*.dbf +*.mdb +*.pdb +*.sqlite3 +*.db-shm +*.db-wal -/data/ -/data/ +### Java ### +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* +replay_pid* + +### macOS ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### macOS Patch ### +# iCloud generated files +*.icloud + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk \ No newline at end of file diff --git a/src/.DS_Store b/src/.DS_Store deleted file mode 100644 index 03537a3610b273ce71468742a74b1778ecced4e3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHK!EVz)5S>j8iB(m~0i+x)ajl9AtpIT`AwBe3SLM*6V3Vk|aJ^OR;6oJ2XZRSd z{StnseY3lb+J+vB2%3>*-|Xy+?R{(Q?hujcPe+F+xs0WyMI> zeLXEkrOA!$fbVzwVYfG%?Ys#$gY9s4z8TEky@I{-`t5w~cb>m^`Tk(|Ek7^x4@Icv9rLIE9bnJ*sF-B~|zM_=U0Ce4r~L3|44q!HNW>JJRq0gN0r7U6*?n+mk4%9R+(RSwFg zV?1*4jYXSI%DfrRac@?xLQ$^pP=N_26d1|h~=ZW2Q>nJzy>gK*jR)IA{PNegEYdx HUuEDLAeUf< diff --git a/src/main/.DS_Store b/src/main/.DS_Store deleted file mode 100644 index 3454a4f02ac67b6b236d3b78a215357575336044..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHKO;6iE5S|SpW(-F z<(KsD&^Nmq)HGB*QMJ{KH2Y?E=k3OC<6SQisopTzBr;5HM`0CJ z!?G6*K5*?s>pY&uQT#4TNBz#qp)RT@E7E}>WJ!vYkMFZ0(W9Oo7D;Kiq3!Uzw%6~h zj>kKD-3@=UyF1zN#|K-8ceeK@6R-Vz{pG8-{V(~~LSM5ecnR Date: Tue, 30 Jan 2024 15:20:02 +0900 Subject: [PATCH 5/5] [#8] refactor: Use wildcard for JAR file in Dockerfile - Modified Dockerfile to use wildcard(*) for JAR file selection - Set JAR_PATH as an environment variable for use in CMD instruction --- Dockerfile | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 4f887ca..5ff665d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,13 +1,17 @@ # JDK 17을 기반으로 하는 이미지 사용 FROM openjdk:17-alpine +# 작업 디렉토리 WORKDIR /usr/src/app +# 변수 정의 (빌드 시 사용) ARG JAR_PATH=./build/libs # 빌드한 파일 옮기기 -COPY ${JAR_PATH}/modutimer-0.0.1-SNAPSHOT.jar ${JAR_PATH}/modutimer-0.0.1-SNAPSHOT.jar +COPY ${JAR_PATH}/*.jar ${JAR_PATH}/app.jar -# 실행하기 -CMD ["java","-jar","./build/libs/modutimer-0.0.1-SNAPSHOT.jar"] +# 환경변수 설정 (실행중인 컨테이너에 액세스 가능) +ENV JAR_PATH ${JAR_PATH} +# ENV 이용해서 실행 +CMD java -jar ${JAR_PATH}/app.jar