From bce9f8610868e1deacb15e779b155786a1cb9a8f Mon Sep 17 00:00:00 2001 From: jimin <010709min@ewhain.net> Date: Sat, 16 Nov 2024 13:14:08 +0900 Subject: [PATCH 1/6] [feat] docker --- Dockerfile | 4 +++ build.gradle | 2 ++ docker-compose.yml | 30 +++++++++++++++++++ .../instagram/auth/service/AuthService.java | 4 ++- .../instagram/config/SecurityConfig.java | 2 +- .../com/ceos20/instagram/jwt/JwtUtil.java | 7 +++++ .../jwt/filter/LoginSuccessHandler.java | 4 +-- src/main/resources/application.yml | 26 ++++++++++++---- 8 files changed, 68 insertions(+), 11 deletions(-) create mode 100644 Dockerfile create mode 100644 docker-compose.yml diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..21c07082 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,4 @@ +FROM openjdk:17 +ARG JAR_FILE=/build/libs/*.jar +COPY ${JAR_FILE} app.jar +ENTRYPOINT ["java","-jar", "/app.jar"] \ No newline at end of file diff --git a/build.gradle b/build.gradle index d4a7a96d..73c3c820 100644 --- a/build.gradle +++ b/build.gradle @@ -25,6 +25,8 @@ dependencies { compileOnly 'org.projectlombok:lombok:1.18.24' annotationProcessor 'org.projectlombok:lombok:1.18.24' implementation 'org.springframework.boot:spring-boot-starter-validation' + implementation 'mysql:mysql-connector-java:8.0.34' + runtimeOnly 'com.mysql:mysql-connector-j' implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.2' implementation 'org.springframework.boot:spring-boot-starter-security' diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..3db75069 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,30 @@ +version: "3" + +services: + db: + image: mysql:5.7 #windows + environment: + MYSQL_ROOT_PASSWORD: root1234 + MYSQL_DATABASE: instagram + volumes: + - dbdata:/var/lib/mysql + ports: + - 3306:3306 + restart: always + + web: + container_name: web + build: . + ports: + - "8080:8080" + depends_on: + - db + environment: + mysql_host: db + restart: always + volumes: + - app:/app + +volumes: + dbdata: + app: diff --git a/src/main/java/com/ceos20/instagram/auth/service/AuthService.java b/src/main/java/com/ceos20/instagram/auth/service/AuthService.java index 00d55da6..06837ed7 100644 --- a/src/main/java/com/ceos20/instagram/auth/service/AuthService.java +++ b/src/main/java/com/ceos20/instagram/auth/service/AuthService.java @@ -10,6 +10,7 @@ import com.ceos20.instagram.user.repository.UserRepository; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; @@ -17,6 +18,7 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.util.Collection; import java.util.Optional; @Service @RequiredArgsConstructor @@ -66,7 +68,7 @@ public String reissue(String refreshToken) { User user = userRepository.findByNickname(nickname) .orElseThrow(() -> new NotFoundException(ExceptionCode.NOT_FOUND_USER)); - return jwtUtil.generateAccessToken(nickname, user.getRole()); + return jwtUtil.generateAccessToken(nickname, user.getRole().toString()); } else { throw new RuntimeException("존재하지 않는 토큰입니다."); } diff --git a/src/main/java/com/ceos20/instagram/config/SecurityConfig.java b/src/main/java/com/ceos20/instagram/config/SecurityConfig.java index e7932737..20408977 100644 --- a/src/main/java/com/ceos20/instagram/config/SecurityConfig.java +++ b/src/main/java/com/ceos20/instagram/config/SecurityConfig.java @@ -63,7 +63,7 @@ public JwtAuthenticationFilter loginAuthenticationFilter() throws Exception { // 시큐리티 필터 설정 @Bean public SecurityFilterChain filterChain(HttpSecurity http, JwtUtil jwtUtil) throws Exception { - final String[] ALL_URL = new String[] {"/accounts/login", "/accounts/user/signup"}; + final String[] ALL_URL = new String[] {"/accounts/login", "/accounts/user/signup", "/swagger-ui.html", "/swagger-ui/**", "/v3/api-docs/**"}; http .cors((cors -> cors.configurationSource(new CorsConfigurationSource() { diff --git a/src/main/java/com/ceos20/instagram/jwt/JwtUtil.java b/src/main/java/com/ceos20/instagram/jwt/JwtUtil.java index 8bc23852..ca7338ab 100644 --- a/src/main/java/com/ceos20/instagram/jwt/JwtUtil.java +++ b/src/main/java/com/ceos20/instagram/jwt/JwtUtil.java @@ -76,6 +76,12 @@ public String getUserNickname(String token) { return Jwts.parser().setSigningKey(secretKey).build().parseClaimsJws(token).getBody().getSubject(); } + // 토큰에서 유효시가 추출 + public long getExpiration(String token) { + return Jwts.parser().setSigningKey(secretKey).build().parseClaimsJws(token).getBody().getExpiration().getTime(); + } + + // Request의 Header에서 token 값 public String resolveToken(HttpServletRequest request) { return request.getHeader("X-AUTH-TOKEN"); @@ -92,5 +98,6 @@ public boolean validateToken(String token) { } + } diff --git a/src/main/java/com/ceos20/instagram/jwt/filter/LoginSuccessHandler.java b/src/main/java/com/ceos20/instagram/jwt/filter/LoginSuccessHandler.java index 31965bb0..8bedf4b2 100644 --- a/src/main/java/com/ceos20/instagram/jwt/filter/LoginSuccessHandler.java +++ b/src/main/java/com/ceos20/instagram/jwt/filter/LoginSuccessHandler.java @@ -46,7 +46,6 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo redisService.save(nickname, refreshToken, Duration.ofMillis(jwtUtil.getExpiration(refreshToken))); -// Cookie accessTokenCookie = createCookie(accessToken, "accessToken"); Cookie refreshTokenCookie = createCookie(refreshToken, "refreshToken"); response.addHeader("Authorization", "Bearer " + accessToken); @@ -57,13 +56,12 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo response.setStatus(HttpServletResponse.SC_OK); response.getWriter().write(jsonResponse); -// response.addCookie(accessTokenCookie); response.addCookie(refreshTokenCookie); } private Cookie createCookie(String accessToken, String cookieName) { Cookie accessTokenCookie = new Cookie(accessToken, cookieName); - long expiration = jwtUtil.v(accessToken); + long expiration = jwtUtil.getExpiration(accessToken); int maxAge = (int)((expiration - new Date(System.currentTimeMillis()).getTime()) / 1000); accessTokenCookie.setMaxAge(maxAge); accessTokenCookie.setPath("/"); diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index e70e38ac..2ff58830 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,17 +1,31 @@ spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://localhost:3306/instagram + url: jdbc:mysql://localhost:3306/instagram?useSSL=false&allowPublicKeyRetrieval=true username: root password: root1234 jpa: database: mysql - database-platform: org.hibernate.dialect.MySQL8Dialect + database-platform: org.hibernate.dialect.MySQL5Dialect hibernate: - ddl-auto: create + ddl-auto: update properties: hibernate: - dialect: org.hibernate.dialect.MySQL8Dialect - database-platform: org.hibernate.dialect.MySQL8Dialect + dialect: org.hibernate.dialect.MySQL5Dialect + database-platform: org.hibernate.dialect.MySQL5Dialect show_sql: true - format_sql: true \ No newline at end of file + format_sql: true + + security: + cors: + allow-origins: "http://localhost:3000" + allowed-methods: GET, POST, PUT, DELETE, OPTIONS + + jwt: + secret: "Vwgff4uvzQ4pes0Zt7sDNtL6pxGIkg2k95ZIrVhvlGmxcDRq9O1fnLN2lEzItsNE4w_lQ3f7xd09ukYxzIYS6InrYfg4ed2BSP0wmZ2RJEswzDsCLNqwRRXW780o1TYbuQpiXuUN0TnwXzb2l4YnNcXLHyBBJoIU17y1H56Aq1-ABW6MREvcFvlW-oUcMw92R0piQK4hO_Xo8AFIDnbeAqQUQ2Q91iQZRTtiNrV9Gv_pF_f1LF9OLDnvmTTy7Av7yFRstie90G9ABYsFTrxywHLzA-QMDYOeUOk8wq6TfxKbULK8PqWR__s1ebFlA3bFO1shhUdffA" + + data: + redis: + host: localhost + port: 6379 + password: "1111" \ No newline at end of file From f8a8831e889bba3c5e8d05c7ba25ec66c7c6d076 Mon Sep 17 00:00:00 2001 From: jiminnimij <124450012+jiminnimij@users.noreply.github.com> Date: Sat, 16 Nov 2024 15:52:33 +0900 Subject: [PATCH 2/6] [doc] README.md --- README.md | 220 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 220 insertions(+) diff --git a/README.md b/README.md index 524f6525..b102929f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,226 @@ # spring-instagram-20th CEOS 20th BE study - instagram clone coding +## 6주차 +Image + +: container라는 독립 환경에서 서비스가 실행 가능하도록 필요한 요소 (서버환경, 실행 파일, 라이브러리 구저 등) 하나의 패키지 형태로 묶는 형태 + +Container + +: 소프트웨어 서비스를 실행하ㅡ데 필요하 요소를 포함하ㅡ데 필요하 요소를 포함하ㅡ 경량 패키ㅣ + +ㅡㄱ, imageㅡ ㅡㄱ ㅓㅇ 화경과 파일, 라이브러리 등을 실행할 수 있는 Container를 생성하기 위한 파일! + +image를 통해 container를 계속 생성할 수 있고 하나의 image를 통해 여러 container를 생성 가ㅡㅇ + +### image layer +imageㅡ container 실행을 위한 요소를 layer 구ㅗ를 ㅗㅇ해 과리 + +=> 모드 것을 하ㅏ의 파일로 과리하기 비효율ㅓㄱ + +layer로 이뤄ㅣ 구ㅗ에서의 도악 + +CentOS 버전 7 환경헤서 jdk 1.8을 사용하는 java로 개발된 프로그램을 실행하는 image +![image](https://github.com/user-attachments/assets/05604979-3c20-49ac-a6cd-3aadc5ac9339) + +만약 my_app.jar에서 문제가 발생해서 이걸 new_my_app.jar로 바꾼 image를 만든다면 + +![image](https://github.com/user-attachments/assets/83487c9b-63a0-4b7f-b72b-82e51feda447) + +image 구성하는 파일 전체를 수정하는 것이 아닌 마지막으로 수정된 layer만 변경 + +그렇다면 중간에 위치한 layer인 jdk 1.8을 jdk 1.7로 변경하면? + +중간 레이어만 수정하는 것은 아님!! + +![image](https://github.com/user-attachments/assets/7e5d3a8e-69b8-4a06-8526-c88c2c01b9b1) + +layer는 기존 이미지에서 변경된 사항을 저장하는 구조 + +?? 이게 무슨 말이지?? + +즉, 여기서 이미지는 new_my_app.jar, jek 1.8, CentOS:7인 이미지만 있는게 아니라 사실은 CenOS:7이 이쓴 image CentOS:7과 jdk 1.8이 저장된 이미지까지 총 3개의 image 파일이 존재! +![image](https://github.com/user-attachments/assets/5e684ae5-e2f0-40ae-b650-487ae3fa1582) + +그래서 만약 수정을 하게 된다면 jdk 1.8이 변경되는 image를 전부 수정해주어야 함 + +Docker에서 docker inspect 명령어를 사용하면 맨아래 Layers 항목을 통해 해당 image에 layer를 확인할 수 있 + +이런 image의 layer의 구조가 저장 방식의 효율을 끌어올림 + +### Docker info +뭐가 이미ㅣ를 pull하고 run하ㅡ 과ㅓㅇ에서 오류가 발생해서 실행파일의 위치 등을 파악할 필요가 있다며 + +docker info 명령어를 사용하며 도움이 될 수 있음 + +```hell +Client: + Version: 27.1.1 + Context: desktop-linux + Debug Mode: false + Plugins: + buildx: Docker Buildx (Docker Inc.) + Version: v0.16.1-desktop.1 + Path: C:\Program Files\Docker\cli-plugins\docker-buildx.exe + compose: Docker Compose (Docker Inc.) + Version: v2.29.1-desktop.1 + Path: C:\Program Files\Docker\cli-plugins\docker-compose.exe + debug: Get a shell into any image or container (Docker Inc.) + Version: 0.0.34 + Path: C:\Program Files\Docker\cli-plugins\docker-debug.exe + desktop: Docker Desktop commands (Alpha) (Docker Inc.) + Version: v0.0.14 + Path: C:\Program Files\Docker\cli-plugins\docker-desktop.exe + dev: Docker Dev Environments (Docker Inc.) + Version: v0.1.2 + Path: C:\Program Files\Docker\cli-plugins\docker-dev.exe + extension: Manages Docker extensions (Docker Inc.) + Version: v0.2.25 + Path: C:\Program Files\Docker\cli-plugins\docker-extension.exe + feedback: Provide feedback, right in your terminal! (Docker Inc.) + Version: v1.0.5 + Path: C:\Program Files\Docker\cli-plugins\docker-feedback.exe + init: Creates Docker-related starter files for your project (Docker Inc.) + Version: v1.3.0 + Path: C:\Program Files\Docker\cli-plugins\docker-init.exe + sbom: View the packaged-based Software Bill Of Materials (SBOM) for an image (Anchore Inc.) + Version: 0.6.0 + Path: C:\Program Files\Docker\cli-plugins\docker-sbom.exe + scout: Docker Scout (Docker Inc.) + Version: v1.15.1 + Path: C:\Users\01070\.docker\cli-plugins\docker-scout.exe + +Server: + Containers: 7 + Running: 2 + Paused: 0 + Stopped: 5 + Images: 3 + Server Version: 27.1.1 + Storage Driver: overlayfs + driver-type: io.containerd.snapshotter.v1 + Logging Driver: json-file + Cgroup Driver: cgroupfs + Cgroup Version: 1 + Plugins: + Volume: local + Network: bridge host ipvlan macvlan null overlay + Log: awslogs fluentd gcplogs gelf journald json-file local splunk syslog + Swarm: inactive + Runtimes: io.containerd.runc.v2 runc + Default Runtime: runc + Init Binary: docker-init + containerd version: 2bf793ef6dc9a18e00cb12efb64355c2c9d5eb41 + runc version: v1.1.13-0-g58aa920 + init version: de40ad0 + Security Options: + seccomp + Profile: unconfined + Kernel Version: 5.15.153.1-microsoft-standard-WSL2 + Operating System: Docker Desktop + OSType: linux + Architecture: x86_64 + CPUs: 8 + Total Memory: 3.751GiB + Name: docker-desktop + ID: fdaccdc0-6074-497e-bcce-a561ac5a26d1 + Docker Root Dir: /var/lib/docker + Debug Mode: false + HTTP Proxy: http.docker.internal:3128 + HTTPS Proxy: http.docker.internal:3128 + No Proxy: hubproxy.docker.internal + Labels: + com.docker.desktop.address=npipe://\\.\pipe\docker_cli + Experimental: false + Insecure Registries: + hubproxy.docker.internal:5555 + 127.0.0.0/8 + Live Restore Enabled: false + +WARNING: No blkio throttle.read_bps_device support +WARNING: No blkio throttle.write_bps_device support +WARNING: No blkio throttle.read_iops_device support +WARNING: No blkio throttle.write_iops_device support +WARNING: daemon is not using the default seccomp profile +``` + +이러 식으로 뜨ㅡ데 여기서 erver 부부을 확이하며며 +| Containers | 컨테이너 수 | +| --- | --- | +| Images | 이미지의 수 | +| Server Version | Docker 버전 | +| Storage Driver | 스토리지 드라이버 종류 | +| Kernel Version | 커널 버전 | +| Operation System | OS 버전 | +| OSType | OS 종류 | +| Architecture | 아키텍처 | +| CPUs | 사용 CPU | +| Total Memory | 사용 메모리 | +| Docker Root Dir | Docker 파일이 저장되는 root 디렉토리 위 | + +이러 ㅓㅇ보를 확이할 수 있고 + +-f 옵셔으로 사요아 ㅓㅇ의 ㅔㅁ플릿을 사용하여 출력 형식도 ㅣㅓㅇ 가ㅡㅇ! +### Dockerfile +: application을 container화 하기 위한 과정을 기록(layer)하는 것! -> 이것을 통해 image를 생성 + +**[Dockerfilet 문법]** + +- FROM (필수 설정) + + 생성할 image의 베이스가 되는 image 서렁 + + 멀티 스테이지 빌드 시 여러개 사용 가능 +- LABEL + + 생성할 image에 key=value 형식으로 metadata 추가 (다수의 meatadata 추가 가ㅡㅇ) +- ENV + + 생성할 image의 추가할 화경벼수 서렁 + + image를 ㅗㅇ해 생성되 container ㅐ부에서도 사용 가ㅡㅇ +- ARG + + Dockerfile ㅐ부에서 사용할 벼수를 key=value 또ㅡ key 혀애로 서렁 + + |입력|결과| + |----|----| + |key마 입력|key 값의 arguement가 입력될 것이라ㅡ 명시 ㅏㅏㅐㅁ| + |key=value|argument가 입력되ㅣ 앟아도 value로 ㅓㄱ용| + |key=value 해쓰데 argument 입력|오버라이드| +- RUN + + image를 마드ㅡ 과ㅓㅇ에서 FROM으로 서ㅓㅇ되 베이스 image에 추가로 실행할 명령어 입력 +- USER + + image를 마드ㅡ 과ㅓㅇ에서 사용할 사요아 계ㅓㅇ 서렁 + + UER를 사용하기 위해서ㅡ 해당 UER가 기ㅗ layer에 생성되어 있어야 함 + + 서렁아하며 슈퍼 유ㅓ로 ㅣ행됨 +- WORKDIR + + image 마드ㅡ 과ㅓㅇ에서 기보으로 ㅏㄱ업할 디레고리 서렁렁 +- COPY +- ADD +- EXPOSE +- CMD +- ENTRYPOINT +- ONBUILD +- STOPSIGNAL +- HEALTHCHECK +- HELL + +머리 스테이지 +: Dockerfile을 통해 image를 생성하는 과정에서 필요한 라이브러리와 패키지들을 모두 포함하기 때문에 최종 결과물로 생성된 image의 크기가 매우 커지는(GB) 경우도 발생하는데 + +이때 image 만드는 과정과 image를 통해 container로 실행하기 위해 필요한 역역을 구분 지어 최종 결과물인 image의 경량화를 할 수 있는데 + +Dockerfile을 이걸 위해서 멀티 스테이지 기능을 지원 + + + ## 5주차 ### Spring Security 주요 객체 - SecurityContextHolder, SecurityContext, Authentication From fe0400caadcb937d3964f1e3a747ffdf03855e07 Mon Sep 17 00:00:00 2001 From: jiminnimij <124450012+jiminnimij@users.noreply.github.com> Date: Sat, 16 Nov 2024 16:51:36 +0900 Subject: [PATCH 3/6] [doc] README.md --- README.md | 121 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 118 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index b102929f..73ad810d 100644 --- a/README.md +++ b/README.md @@ -201,25 +201,140 @@ WARNING: daemon is not using the default seccomp profile 서렁아하며 슈퍼 유ㅓ로 ㅣ행됨 - WORKDIR - image 마드ㅡ 과ㅓㅇ에서 기보으로 ㅏㄱ업할 디레고리 서렁렁 + image 만드는 과정(layer)에서 기본으로 작업할 디렉토리를 설정 - COPY + + image를 만든느 과정(layer)에서 추가할 파일과 추가될 경로를 입력 (복사) + + 추가할 파일은 Dockerfile이 있는 경로를 기반으로! + 추가할 파일의 경우 context folder의 상위 디렉토리에 접근할 수 없기 때문에 접근이 필요할 경우 context folder 변경 필요 - ADD + + image를 만드는 과정에서 추가할 파일과 추가될 경로 입력 + + COPY와 유사하지만 추가 기능 제공 + + URL을 통해 파일을 다운로드 가능, 압축된 파일을 자동으로 추출 + + 명료성과 예측 가능성을 위해서 COPY를 사용하는 것을 권장! - EXPOSE + + imageㄹ르 통해 생성되는 container에서 노출할 port 명시 + + 실제로 bind하기 위해서는 container 생성 시 옵션을 추가해야 함! - CMD + + image를 통해 생성되는 container에서 실행할 command를 입력 + + Dockerfile 내부에서 한번만! 사용 가능 (생략 가능) + + ENTRYPOINT 설정 시 CMD 내용이 ENTRYPOINT의 파라미터로 변경 - ENTRYPOINT + + image를 통해 생성되는 container에서 실행할 command를 입력 + + Dockerfile 내부에서 한 번만! 사용 가능(생략 가능) + + CMD가 설정될 경우 ENTRYPOINT의 파라미터로 사용 - ONBUILD + + Dockerfile을 통해 생성된 image가 다른 Dockerfile에서 FROM을 통해 베이스 image로 사용되어 build될 때 실행할 명령어 입력 + + ``` + FROM mirror.gcr.io/library/alpine:3.16 + LABEL imagename="hackerpark" + LABEL version="1.0" + RUN echo "First Image Build" + ONBUILD RUN echo "First Base Image" >> /first.txt + ENTRYPOINT ["/bin/sh", "-c", "ls /"] + ``` - STOPSIGNAL + + image를 통해 생성되는 container 종료시 사용될 SIGNAL 설정 + + 설정하지 않는 경우 기 SIGTERM + + SIGTERM은 컨테이너가 프로세스를 정상적으로 종료할 수 있을 때까지 기다리고, 지정된 시간 (default-10sec)동안 종료되지 않으면 SIGKILL 전송 - HEALTHCHECK -- HELL -머리 스테이지 + image를 통해 생성되는 container에서 실행되는 프로세스의 상태를 확인하기 위해 설정 + + image에 curl이 포함되어야 함 + + - --interval 옵션을 통해 healthcheck의 interval을 설정 + - --timeout 옵션을 통해 healthcheck CMD의 timeout을 설정 + - --retries 옵션을 통해 healthcheck CMD의 tiemout 제한 개수 설정정 +- SHELL + + Dockerfile에서 사용할 기본 shell을 설정 + +멀티 스테이지 : Dockerfile을 통해 image를 생성하는 과정에서 필요한 라이브러리와 패키지들을 모두 포함하기 때문에 최종 결과물로 생성된 image의 크기가 매우 커지는(GB) 경우도 발생하는데 이때 image 만드는 과정과 image를 통해 container로 실행하기 위해 필요한 역역을 구분 지어 최종 결과물인 image의 경량화를 할 수 있는데 Dockerfile을 이걸 위해서 멀티 스테이지 기능을 지원 +멀티 스테이지를 사용하기 위해서는 image를 만드는 과정에서 필요한 내용과 최종적으로 실행할 환경을 분리하는 작업 필요! + +이를 FROM을 사용하여 구분 + +Dockerfile에 FROM을 여러번 사용해 각 stage를 분리하여 image의 크리르 줄일 수 있음 + +멀티 스테이지에서 FROM과 ARG 위치에 따라 ARG 사용 방법이 다른데 + +FROM 보다 ARG가 위에 정의된 경우 ARG 아래 쪽 FROM에서 모두 ARG 사용이 가능하지만 + +아닌 경우 ARG를 포함하는 FROM에서만 사용이 가능하고 다른 FROM에서는 ARG를 다시 정의해 사용해야 함 + +### Cache +layer는 이전 과정에서의 변경되는 것을 기록하기 때문에 Dockerfile을 사용하여 image를 여러 번 build 하게 될 경우 layer의 변경점이 없다면 Cache 되어 이미 존재하는 layer를 재사용 + +이 점을 활용하여 변하지 않는 layer(명령어)를 Dockerfile의 상단에 배치하여 cache 된 layer를 재사용하는 것이 유리 + +### Docker-compose +여러 개의 컨테이너를 하나로 묶어주는 역할 + +yaml 포맷을 활용 + +[도커 컴포즈 문법] +- version + + 도커 컴포즈 파일의 버전 +- service + + 컨테이너를 실행하기 위한 단위 + + 하위에 서비스 이름 -> 서비스 옵션 순으로 작성 +- build + + build할 dockerfile의 경로 지정 +- ports + + 포트포워딩 지정 옵션 + + <호스트 포트>:<컨테이너 포트> +- volume + + 바인드 마운트, 볼륨 지정 +- environment + + 컨테이너에서 사용할 환경변수 설정 +- depends_on + + 실행 순서를 보장받고 싶을 때, 사용 + +### 오류 +자꾸 mysql과 연결이 안되는 문제가 발생했다. + +(communications link failure) +#### 원인이 될 수 있는 요소 +1. password 입력 실수 +2. user의 권한 +3. Docker 컨테이너 내부에서 localhost로 접근할 때 호스트 머신의 localhost가 아닌 컨테이너 내부의 localhost로 해석됨 +4. mysql 서버 미실행 상태 +5. ## 5주차 ### Spring Security 주요 객체 From a278989dfcf04da720a849a3fdcb582cf19d796d Mon Sep 17 00:00:00 2001 From: jiminnimij <124450012+jiminnimij@users.noreply.github.com> Date: Sat, 16 Nov 2024 22:07:45 +0900 Subject: [PATCH 4/6] Update README.md --- README.md | 36 +++++++++++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 73ad810d..f14b5820 100644 --- a/README.md +++ b/README.md @@ -330,11 +330,41 @@ yaml 포맷을 활용 (communications link failure) #### 원인이 될 수 있는 요소 -1. password 입력 실수 +1. application.yml 파일의 정보 오류 + 패스워드, Hibernate 설정 등 확인 -> 문제 없었음! + + 2. user의 권한 -3. Docker 컨테이너 내부에서 localhost로 접근할 때 호스트 머신의 localhost가 아닌 컨테이너 내부의 localhost로 해석됨 + application에 설정한 user가 db에 접근할 권한이 있도록 설정 + ```mysql + CREATE USER 'simple'@'localhost' IDENTIFIED BY 'simple'; + CREATE USER 'simple'@'%' IDENTIFIED BY 'simple'; + + GRANT ALL PRIVILEGES ON *.* TO 'simple'@'localhost'; + GRANT ALL PRIVILEGES ON *.* TO 'simple'@'%'; + ``` + 4. mysql 서버 미실행 상태 -5. + + 윈도우라면 서비스로 확인 가능 + ![image](https://github.com/user-attachments/assets/c9977625-ac4d-44f2-b139-1b4afa8fcbdc) + +5. MySQL 8 버전에서 인증 방식의 변경으로 인한 오류 + MySQL 5.7 버전으로 재설치 혹은 + + caching_sha2_password 방식엔서 mysql_native_password로 변경 + +6. Docker 컨테이너 내부에서 localhost로 접근할 때 호스트 머신의 localhost가 아닌 컨테이너 내부의 localhost로 해석됨 + + 5-1. Docker Compose를 활용한 통합 설정 + docker-compse.yml 파일을 통해 서비스 간 네트워크를 공유하도록 설정 + + 5-2. Spring 서버 설정 수정 + application.yaml의 datasource: url을 localhost가 아닌 mysql로 설정 + + 5-3. 도커 compose를 통해 spring 서버와 mysql을 함께 띄움 + 함께 실행하는 것을 통해 컨테이너 간의 네트워크 설정 + ## 5주차 ### Spring Security 주요 객체 From f1ac3e944535c4c094a8076eaebb85cfd2879f75 Mon Sep 17 00:00:00 2001 From: jimin <010709min@ewhain.net> Date: Mon, 18 Nov 2024 15:25:15 +0900 Subject: [PATCH 5/6] [fix] docker --- README.md | 39 ------- build.gradle | 5 +- docker-compose.yml | 56 ++++++++-- src/main/resources/application.yml | 16 ++- .../instagram/InstagramApplicationTests.java | 8 +- .../post/service/PostServiceTest.java | 104 ------------------ .../user/repository/PostRepositoryTest.java | 51 --------- .../user/repository/UserRepositoryTest.java | 80 -------------- 8 files changed, 67 insertions(+), 292 deletions(-) delete mode 100644 src/test/java/com/ceos20/instagram/post/service/PostServiceTest.java delete mode 100644 src/test/java/com/ceos20/instagram/user/repository/PostRepositoryTest.java delete mode 100644 src/test/java/com/ceos20/instagram/user/repository/UserRepositoryTest.java diff --git a/README.md b/README.md index f14b5820..7c75245b 100644 --- a/README.md +++ b/README.md @@ -324,46 +324,7 @@ yaml 포맷을 활용 실행 순서를 보장받고 싶을 때, 사용 -### 오류 -자꾸 mysql과 연결이 안되는 문제가 발생했다. -(communications link failure) - -#### 원인이 될 수 있는 요소 -1. application.yml 파일의 정보 오류 - 패스워드, Hibernate 설정 등 확인 -> 문제 없었음! - - -2. user의 권한 - application에 설정한 user가 db에 접근할 권한이 있도록 설정 - ```mysql - CREATE USER 'simple'@'localhost' IDENTIFIED BY 'simple'; - CREATE USER 'simple'@'%' IDENTIFIED BY 'simple'; - - GRANT ALL PRIVILEGES ON *.* TO 'simple'@'localhost'; - GRANT ALL PRIVILEGES ON *.* TO 'simple'@'%'; - ``` - -4. mysql 서버 미실행 상태 - - 윈도우라면 서비스로 확인 가능 - ![image](https://github.com/user-attachments/assets/c9977625-ac4d-44f2-b139-1b4afa8fcbdc) - -5. MySQL 8 버전에서 인증 방식의 변경으로 인한 오류 - MySQL 5.7 버전으로 재설치 혹은 - - caching_sha2_password 방식엔서 mysql_native_password로 변경 - -6. Docker 컨테이너 내부에서 localhost로 접근할 때 호스트 머신의 localhost가 아닌 컨테이너 내부의 localhost로 해석됨 - - 5-1. Docker Compose를 활용한 통합 설정 - docker-compse.yml 파일을 통해 서비스 간 네트워크를 공유하도록 설정 - - 5-2. Spring 서버 설정 수정 - application.yaml의 datasource: url을 localhost가 아닌 mysql로 설정 - - 5-3. 도커 compose를 통해 spring 서버와 mysql을 함께 띄움 - 함께 실행하는 것을 통해 컨테이너 간의 네트워크 설정 ## 5주차 diff --git a/build.gradle b/build.gradle index 73c3c820..f878999d 100644 --- a/build.gradle +++ b/build.gradle @@ -25,7 +25,7 @@ dependencies { compileOnly 'org.projectlombok:lombok:1.18.24' annotationProcessor 'org.projectlombok:lombok:1.18.24' implementation 'org.springframework.boot:spring-boot-starter-validation' - implementation 'mysql:mysql-connector-java:8.0.34' + implementation 'mysql:mysql-connector-java:8.0.32' runtimeOnly 'com.mysql:mysql-connector-j' implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.2' @@ -35,6 +35,9 @@ dependencies { implementation 'io.jsonwebtoken:jjwt-impl:0.12.3' implementation 'io.jsonwebtoken:jjwt-jackson:0.12.3' implementation 'org.springframework.boot:spring-boot-starter-data-redis' + + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.1' + testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.7.1' } tasks.named('test') { diff --git a/docker-compose.yml b/docker-compose.yml index 3db75069..3e264a00 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,30 +1,70 @@ -version: "3" +version: '3.8' services: - db: - image: mysql:5.7 #windows + database: + container_name: mysql + image: mysql:5.7 environment: MYSQL_ROOT_PASSWORD: root1234 MYSQL_DATABASE: instagram + TZ: 'Asia/Seoul' volumes: - dbdata:/var/lib/mysql ports: - - 3306:3306 + - 3307:3306 restart: always + networks: + - network + healthcheck: + test: [ "CMD-SHELL", "mysqladmin ping -h 127.0.0.1 -proot1234 --silent" ] + interval: 30s + retries: 5 + + redis: + container_name: redis + image: redis:latest + command: ["redis-server", "--requirepass", "1111"] + ports: + - 6379:6379 + volumes: + - redisdata:/data + restart: always + networks: + - network + healthcheck: + test: ["CMD", "redis-cli", "-a", "1111", "ping"] + interval: 10s + retries: 3 web: - container_name: web - build: . + container_name: instagram + build: + dockerfile: Dockerfile ports: - "8080:8080" depends_on: - - db + database: + condition: service_healthy + redis: + condition: service_healthy environment: - mysql_host: db + SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/instagram?useSSL=false&serverTimezone=Asia/Seoul + SPRING_DATASOURCE_USERNAME: root + SPRING_DATASOURCE_PASSWORD: root1234 + SPRING_REDIS_HOST: redis + SPRING_REDIS_PORT: 6379 + SPRING_REDIS_PASSWORD: "1111" restart: always volumes: - app:/app + networks: + - network volumes: dbdata: + redisdata: app: + +networks: + network: + driver: bridge diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 2ff58830..ec0cd623 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,18 +1,18 @@ spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://localhost:3306/instagram?useSSL=false&allowPublicKeyRetrieval=true + url: jdbc:mysql://mysql:3306/instagram?useSSL=false&oracle.jdbc.timezoneAsRegion=false username: root password: root1234 jpa: database: mysql - database-platform: org.hibernate.dialect.MySQL5Dialect + database-platform: org.hibernate.dialect.MySQLDialect hibernate: ddl-auto: update properties: hibernate: - dialect: org.hibernate.dialect.MySQL5Dialect - database-platform: org.hibernate.dialect.MySQL5Dialect + dialect: org.hibernate.dialect.MySQLDialect + database-platform: org.hibernate.dialect.MySQLDialect show_sql: true format_sql: true @@ -28,4 +28,10 @@ spring: redis: host: localhost port: 6379 - password: "1111" \ No newline at end of file + password: "1111" + +logging: + level: + org.hibernate.SQL: debug # Hibernate SQL 쿼리 로깅 (디버그 레벨) + org.hibernate.orm.jdbc.bind: trace # Hibernate 바인딩된 변수 로깅 (트레이스 레벨) + com.ceos19.everytime: debug # 프로젝트 도메인 패키지에 대한 디버그 레벨 diff --git a/src/test/java/com/ceos20/instagram/InstagramApplicationTests.java b/src/test/java/com/ceos20/instagram/InstagramApplicationTests.java index 35ac0012..7161f1e5 100644 --- a/src/test/java/com/ceos20/instagram/InstagramApplicationTests.java +++ b/src/test/java/com/ceos20/instagram/InstagramApplicationTests.java @@ -5,9 +5,9 @@ @SpringBootTest class InstagramApplicationTests { - - @Test - void contextLoads() { - } +// +// @Test +// void contextLoads() { +// } } diff --git a/src/test/java/com/ceos20/instagram/post/service/PostServiceTest.java b/src/test/java/com/ceos20/instagram/post/service/PostServiceTest.java deleted file mode 100644 index 479cf155..00000000 --- a/src/test/java/com/ceos20/instagram/post/service/PostServiceTest.java +++ /dev/null @@ -1,104 +0,0 @@ -package com.ceos20.instagram.post.service; - -import com.ceos20.instagram.post.domain.Post; -import com.ceos20.instagram.post.domain.PostImage; -import com.ceos20.instagram.post.dto.PostRequestDto; -import com.ceos20.instagram.post.repository.PostImageRepository; -import com.ceos20.instagram.post.repository.PostRepository; -import com.ceos20.instagram.user.domain.User; -import com.ceos20.instagram.user.repository.UserRepository; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.http.HttpStatus; -import org.springframework.mock.web.MockMultipartFile; -import org.springframework.web.server.ResponseStatusException; - -import java.util.List; -import java.util.Optional; - -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.*; - -@SpringBootTest -class PostServiceTest { - @Mock - private UserRepository userRepository; - - @Mock - private PostRepository postRepository; - - @Mock - private PostImageRepository postImageRepository; - - @Mock - private PostImageService postImageService; - - @InjectMocks - private PostService postService; - - private User user; - private Post post; - private PostRequestDto postRequestDto; - private PostImage postImage; - - @BeforeEach - void setUp() { - user = User.builder() - .nickname("testUser") - .build(); - - post = Post.builder() - .content("Test content") - .writer(user) - .build(); - - MockMultipartFile image1 = new MockMultipartFile("image1", "image1.png", "image/png", "image content".getBytes()); - MockMultipartFile image2 = new MockMultipartFile("image2", "image2.png", "image/png", "image content".getBytes()); - - postRequestDto = PostRequestDto.builder() - .content("Test content") - .images(List.of(image1, image2)) // MultipartFile 리스트로 추가 - .build(); - - postImage = PostImage.builder() - .path("image1.png") - .post(post) - .build(); - } - - @Test - void create() { - when(userRepository.findByNickname("testUser")).thenReturn(Optional.empty()); - - ResponseStatusException exception = assertThrows( - ResponseStatusException.class, - () -> postService.create(postRequestDto, "testUser") - ); - - assertEquals(HttpStatus.NOT_FOUND, exception.getStatusCode()); - assertEquals("Post not found", exception.getReason()); - } - - @Test - void getPost() { - when(postRepository.findById(1L)).thenReturn(Optional.empty()); - - ResponseStatusException exception = assertThrows( - ResponseStatusException.class, - () -> postService.getPost(1L) - ); - - assertEquals(HttpStatus.NOT_FOUND, exception.getStatusCode()); - assertEquals("Post not found", exception.getReason()); - } - -// @Test -// void delete() { -// postService.delete(1L, ); -// -// verify(postRepository, times(1)).deleteById(1L); -// } -} \ No newline at end of file diff --git a/src/test/java/com/ceos20/instagram/user/repository/PostRepositoryTest.java b/src/test/java/com/ceos20/instagram/user/repository/PostRepositoryTest.java deleted file mode 100644 index 2fd7fc13..00000000 --- a/src/test/java/com/ceos20/instagram/user/repository/PostRepositoryTest.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.ceos20.instagram.user.repository; - -import com.ceos20.instagram.post.domain.Post; -import com.ceos20.instagram.post.repository.PostRepository; -import com.ceos20.instagram.user.repository.UserRepository; -import com.ceos20.instagram.user.domain.User; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; - -import java.util.List; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -@SpringBootTest -public class PostRepositoryTest { - @Autowired - UserRepository userRepository; - - @Autowired - PostRepository postRepository; - - @Test - @DisplayName("user가 생성한 포스트 전부 조회") - public void findPostsByUser() { - // When - User user1 = User.builder() - .nickname("user1") - .password("1234") - .build(); - userRepository.save(user1); - - Post post1 = Post.builder() - .writer(user1) - .content("1234") - .build(); - - Post post2 = Post.builder() - .writer(user1) - .content("1234") - .build(); - - postRepository.save(post1); - postRepository.save(post2); - - List posts = postRepository.findPostByWriter_Id(user1.getId()); - - assertEquals(2, posts.size()); - } -} diff --git a/src/test/java/com/ceos20/instagram/user/repository/UserRepositoryTest.java b/src/test/java/com/ceos20/instagram/user/repository/UserRepositoryTest.java deleted file mode 100644 index 47aea3ed..00000000 --- a/src/test/java/com/ceos20/instagram/user/repository/UserRepositoryTest.java +++ /dev/null @@ -1,80 +0,0 @@ -package com.ceos20.instagram.user.repository; - -import com.ceos20.instagram.user.domain.User; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.transaction.annotation.Transactional; - -import java.time.LocalDateTime; -import java.util.Optional; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -@SpringBootTest -@Transactional -public class UserRepositoryTest { - @Autowired - UserRepository userRepository; - - @Test - @DisplayName("nickname으로 유저 조회") - void findByNicknameTest() { - // given - User user = User.builder() - .nickname("010709min") - .name("유지민") - .password("1234") - .registedAt(LocalDateTime.now()) - .build(); - - // when - userRepository.save(user); - Optional findUser = userRepository.findByNickname("010709min"); - - //then - assertEquals(user.getNickname(), findUser.get().getNickname()); - } - - @Test - @DisplayName("id로 유저 조회") - void findByIdTest() { - // given - User user = User.builder() - .nickname("010709min") - .name("유지민") - .password("1234") - .registedAt(LocalDateTime.now()) - .build(); - - // when - userRepository.save(user); - Optional findUser = userRepository.findById(user.getId()); - - // then - assertEquals(user.getId(), findUser); - } - - @Test - @DisplayName("삭제") - void deleteTest() { - // given - User user = User.builder() - .nickname("010709min") - .name("유지민") - .password("1234") - .registedAt(LocalDateTime.now()) - .build(); - - // when - userRepository.save(user); - - } - - - - - - -} From 84291b7733e68db5b1b256d88bd14d915ead6108 Mon Sep 17 00:00:00 2001 From: jimin <010709min@ewhain.net> Date: Mon, 18 Nov 2024 16:35:00 +0900 Subject: [PATCH 6/6] =?UTF-8?q?[fix]=20build.gradle=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/build.gradle b/build.gradle index f878999d..76487134 100644 --- a/build.gradle +++ b/build.gradle @@ -18,26 +18,33 @@ repositories { } dependencies { + // Spring Boot 기본 의존성 implementation 'org.springframework.boot:spring-boot-starter-web' - testImplementation 'org.springframework.boot:spring-boot-starter-test' - testRuntimeOnly 'org.junit.platform:junit-platform-launcher' implementation 'org.springframework.boot:spring-boot-starter-data-jpa' - compileOnly 'org.projectlombok:lombok:1.18.24' - annotationProcessor 'org.projectlombok:lombok:1.18.24' implementation 'org.springframework.boot:spring-boot-starter-validation' - implementation 'mysql:mysql-connector-java:8.0.32' - - runtimeOnly 'com.mysql:mysql-connector-j' - implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.2' implementation 'org.springframework.boot:spring-boot-starter-security' + implementation 'org.springframework.boot:spring-boot-starter-data-redis' + + // MySQL 드라이버 + runtimeOnly 'mysql:mysql-connector-java:8.0.32' + // Lombok + compileOnly 'org.projectlombok:lombok:1.18.24' + annotationProcessor 'org.projectlombok:lombok:1.18.24' + + // JWT implementation 'io.jsonwebtoken:jjwt-api:0.12.3' implementation 'io.jsonwebtoken:jjwt-impl:0.12.3' implementation 'io.jsonwebtoken:jjwt-jackson:0.12.3' - implementation 'org.springframework.boot:spring-boot-starter-data-redis' + // OpenAPI (Swagger) + implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.2' + + // JUnit + testImplementation 'org.springframework.boot:spring-boot-starter-test' + testRuntimeOnly 'org.junit.platform:junit-platform-launcher' testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.1' - testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.7.1' + testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.7.1' } tasks.named('test') {