[AWS] Spring Boot ๋ฐฐํฌ
๐ข ํด๋น ํ๋ก์ ํธ๋ AWS์ EC2, RDS, LoadBalancer, Route53, S3์ Docker, Github action์ ์ฌ์ฉํด ๋ฐฐํฌํ ๊ฒฝํ์ ๊ณต์ ํฉ๋๋ค.
Nginx(์น์๋ฒ)๊ฐ ํ์ํ ์ด์ ๋?
์น์๋ฒ๊ฐ ์๋ค๋ฉด DB์์ ๋ฐ์ดํฐ๋ฅผ ์ฒ๋ฆฌํด์ผํ๋ ๋์ ์์ฒญ์ ๋ฌผ๋ก ์ ์ ์์ฒญ๊น์ง WAS ์๋ฒ์์ ์ฒ๋ฆฌํด์ผ ํ๋ค.
NGINX๋ ์ ์ ์์ฒญ์ ์ฒ๋ฆฌํจ์ผ๋ก์จ WAS ์๋ฒ์ ๋ถ๋ด์ ์ค์ฌ์ค๋ค. ์ถ๊ฐ์ ์ผ๋ก ์์ฒญ์ ์ด๋ ์๋ฒ๋ก ๋ณด๋ผ์ง ๊ฒฐ์ ํ๋ ๋ก๋๋ฐธ๋ฐ์ ์ ์ญํ ๊ณผ ์บ์์๋ฒ์ ์ญํ , ๋ฏผ๊ฐ ์ ๋ณด๋ฅผ ์จ๊ฒจ์ฃผ๋ ์ญํ , SSL ์ธ์ฆ ์ฒ๋ฆฌ๊น์ง ํ ์ ์๋ค.
Load Balancer๊ฐ ํ์ํ ์ด์ ๋?
๋ก๋๋ฐธ๋ฐ์๋ ์์ฒญ์ ์ฌ๋ฌ ์๋ฒ๋ก ๋ถ์ฐ์์ผ ์๋ฒ์ ๊ณผ๋ถํ๋ฅผ ๋ง๋๋ค.
์ถ๊ฐ์ ์ผ๋ก ALB์ ๊ฒฝ์ฐ Auto scaling๊ณผ SSL ์ธ์ฆ ์ฒ๋ฆฌ๋ฅผ ํ ์ ์๋ค.
Nginx์ Load Balancer๊ฐ ๋๋ค ํ์ํ ๊น?
ALB(Application Load Balancer)์ Nginx๋ WAS ์๋ฒ ์ ์ ์์ฒญ์ ์ฒ๋ฆฌํด์ฃผ ์ , ๋ก๋๋ฐธ๋ฐ์ฑ๊ณผ SSL ์ธ์ฆ ์ฒ๋ฆฌ๋ฅผ ํ ์ ์๋ ์ ์์ ๋น์ทํ๋ค.
๋๋ ์ด๋ฒ ํ๋ก์ ํธ์์ ์ฌ์ฉํ ์๋ฒ๊ฐ 1๋์ด๊ธฐ ๋๋ฌธ์ ๋ก๋๋ฐธ๋ฐ์ฑ์ด ํ์ํ์ง ์์๊ณ , ๋๋ฉ์ธ์ ์ฐ๊ฒฐํ๊ณ SSL ์ธ์ฆ์ ์ฒ๋ฆฌํ๋ ๊ณผ์ ์์ Nginx์ ALB ์ค ์ด๋ค ๊ฒ์ ์ฌ์ฉํด์ผํ๋์ง, ๊ผญ ๋๋ค ์ฌ์ฉํ ํ์๊ฐ ์๋์ง ๊ณ ๋ฏผํ๋ค.
๊ฒฐ๋ก ๋ง ๋งํ์๋ฉด, Nginx๋ก SSL ์ธ์ฆ์ ์ฒ๋ฆฌํ๋ ค๋ฉด EC2์์ SSL ์ธ์ฆ์๋ฅผ ์์ฑํ๊ณ ์ด๋ฅผ ์ฃผ๊ธฐ์ ์ผ๋ก ๊ฐฑ์ ํ๋ ์์ ์ด ํ์ํ๋ค. ๋ฐ๋ฉด์, AWS Certificate Manager์์ SSL ์ธ์ฆ์๋ฅผ ๋ฐ๊ธ๋ฐ๊ณ ์ด๋ฅผ ALB์ ๋ถ์ฐฉํ๋ฉด ๊ฐฑ์ ํ๋ ์์ ์ด ํ์ ์์๋ค. ๋ฐ๋ผ์, ๋๋ ํ์์ ๋ฐฉ๋ฒ์ ์ ํํ๋ค.
ALB์์ 80 ํฌํธ๋ฅผ ํตํด ๋ค์ด์ค๋ HTTP ์์ฒญ๊ณผ 443 ํฌํธ๋ฅผ ํตํด ๋ค์ด์ค๋ HTTPS ์์ฒญ์ ๋ฐ์๋ค์ด๋๋ก ๋ฆฌ์ค๋๋ฅผ ์ค์ ํ๋ค. HTTPS ์์ฒญ์ด ๋ค์ด์ค๋ฉด, ALB๋ฅผ ํตํด ๋์๊ทธ๋ฃน(EC2 ์๋ฒ์ ๋ฌถ์)์ผ๋ก ์ ์กํ๋ค. (HTTPS๋ฅผ ๋ฆฌ์ค๋๋ก ๋ฑ๋กํ๊ธฐ ์ํด์๋ SSL ์ธ์ฆ์๊ฐ ํ์->๋ถ์ฐฉํด๋์์) HTTP ์์ฒญ์ 443 ํฌํธ๋ก ๋ฆฌ๋ค์ด๋ ํธ์์ผ HTTPS ์์ฒญ์ผ๋ก ์๋ ๋ณ๊ฒฝํ๋ค.
NGINX์ ๊ฒฝ์ฐ SSL ์ธ์ฆ์ ์ฒ๋ฆฌํ์ง ์๊ธฐ์ 80 ํฌํธ๋ฅผ ํตํด์๋ง ์์ฒญ์ด ๋ค์ด์จ๋ค.
๋ค์ด์จ ์์ฒญ์ ํฐ์บฃ ์๋ฒ์ ํฌํธ์ธ 8080์ผ๋ก ์ฐ๊ฒฐํ๋ค.
์ฌ๊ธฐ์ HTTPS ํต์ ์ด ์ด๋ค์ง๋ ์ด๋ก์ ๋ถ๋ถ ์ธ์๋ SSL ์ธ์ฆ์ด ์ฒ๋ฆฌ๋์ง ์๊ธฐ์ ๋ณด์์์ ๋ฌธ์ ๊ฐ ๋ฐ์ํ ์ ์๋ค.
๋ฐ๋ผ์, ALB์ ์๋ฒ๊ฐ ๋์ผํ ๋ฐ์ดํฐ์ผํฐ ๋ด์ ์๊ฒ ํ๊ฑฐ๋ private VPC๋ก ์ฐ๊ฒฐํ๋ฉด, ์์ ํ๊ฒ ํต์ ํ ์ ์๋ค.
S3 presigned URL์ ์ฌ์ฉํ ์ด์ ๋?
์ผ๋ฐ์ ์ผ๋ก S3๋ฅผ ์ฌ์ฉํ๋ ์๋น์ค์ ๊ฒฝ์ฐ ์์ ๊ฐ์ ๋ฐฉ์์ผ๋ก ํ์ผ์ ์ ๋ก๋/๋ค์ด๋ก๋ํด์๋ค.
ํ์ง๋ง, ํฐํ์ผ์ ์๋ฒ์ ๋ณด๋ด๊ฒ ๋๋ค๋ฉด 1) ์๋ฒ์ ๋ฆฌ์์ค ์ฌ์ฉ๋ ์ฆ๊ฐ 2) HTTP์์ ํฐํ์ผ์ ์ฌ๋ฌ๋ฒ์ ์์ ์์ฒญ์ผ๋ก ๋ถํ ํ์ฌ ์ ์กํจ์ผ๋ก์จ ์ ์ก ์๋ ๊ฐ์ ๋ฑ ์ฑ๋ฅ์ ์ ์ํฅ์ ๋ฏธ์น๋ค.
์ด๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด S3์์ ์ ๊ณตํ๋ Presigned Url์ ์ฌ์ฉํจ์ผ๋ก์จ ์๋์ ๊ฐ์ด ํ์ผ์ ์ง์ ์ ์ผ๋ก ์๋ฒ์ ์ฌ๋ฆฌ๋ ๊ฒ์ ๋ง์๋ค.
Presigned Url์ด๋ S3 ๋ฒํท์ ์์ ์๊ฐ ๋ฏธ๋ฆฌ ํ์ผ ์ ๋ก๋/๋ค์ด๋ก๋ ๊ถํ์ ๋ํด ์๋ช ์ ํด์ค๋ค ์ฌ์ฉ์์๊ฒ ํด๋น Url์ ์ ๊ณตํ๋ ๊ธฐ๋ฅ์ด๋ค. ์ด๋ก์จ ํด๋ผ์ด์ธํธ๊ฐ ์๋ฒ๋ฅผ ๊ฑฐ์น์ง ์๊ณ ํ์ผ์ ์ ์ฅ์์ ์ง์ ์ ๋ก๋/๋ค์ด๋ก๋ ํ ์ ์์ด ์๋ฒ์ ์์์ ์ ์ฝํ ์ ์๋ค.
cf. ์ด๋ฒ ๊ธ์๋ ์นด์นด์ค ํ ํฐ ID ๊ธฐ๋ฐ์ผ๋ก ์ ์ ๋ง๋ค ํด๋๋ฅผ ์์ฑํ ํ, ๊ทธ ํด๋์ ์ด๋ฏธ์ง๋ฅผ ์ ๋ก๋ํ๋ ๊ณผ์ ์ด ์ถ๊ฐ๋์ด ์๋ค.
์ค์ ๊ตฌ์ถ ๊ฒฝํ์ ํตํด ์ดํด๋ณด์.
RDS ์ค์
ํผ๋ธ๋ฆญ์ก์ธ์ค๋ฅผ ํ์ฉํ RDS ์์ฑ
mysql workbanch์์ Hostname์ RDS endpoint๋ฅผ, port์ mysql ํฌํธ(3306)๋ฅผ ์ฝ์
(ํ์ฌ IPv4 ํ๋๋ง์ ๋ฌด๋ฃ๋ก ์ฌ์ฉ๊ฐ๋ฅํ๋ฏ๋ก, RDS๋ ๋ก๋๋ฐธ๋ฐ์ ์ฌ์ฉ์ ์ถ๊ฐ IPv4๋น์ฉ์ด ๋ฐ์, EC2๋ฅผ ์ด์ฉํด RDS ์ธ๋ถ์ฐ๊ฒฐ(RDS ์ฐ๊ฒฐ์ EC2 ์ปดํจํ ๋ฆฌ์์ค์ ์ฐ๊ฒฐ ์ ํ ํ ํผ๋ธ๋ฆญ ์์ธ์ค ๋นํ์ฉ)์ ์ฌ์ฉํ๋ฉด ๋น์ฉ ๊ฐ์ ๊ฐ๋ฅ)
deploy.yml ( github action์ด ๋๋ฆฌ๋ ํ์ผ ) ์ค์
Github action = ์๋ฒ์ ์ ์ํด docker๋ฅผ ์คํ + main์ ํธ์ฌ๋ ์ปค๋ฐ์ ๋ณต์ฌ = CI/CD
deploy.yml ํ์ผ์ด ์คํ๋๋ฉด์, ec2์ docker ์ด๋ฏธ์ง๋ฅผ pullํ์ฌ ์๋ ๋ฐฐํฌ
name: Java CI with Gradle
on:
push:
branches: [ "main" ]
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest # ec2 ubuntu๋ก ๋ง๋ฆ
steps:
- name: checkout
uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version: '17'
## create application.yml
- name: make application.yml
run: |
cd ./src/main/resources
touch ./application.yml
echo "${{ secrets.APPLICATION }}" >> ./application.yml # application.yml ์ ์ฒด -> ๊ณต๋ฐฑ์ฒ๋ฆฌ ํด๋๊ฒ, ๊น์ด๊ทธ๋
ธ์ด์ ์ฌ๋ฆฌ์ง ๋ง๊ฒ
shell: bash
## gradle build
- name: Build with Gradle
run: |
chmod +x ./gradlew
./gradlew clean build -x test
## ์ด๋ฏธ์ง ๋น๋ ๋ฐ ๋์ปคํ๋ธ์ push
- name: web docker build and push
run: |
docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }} # ๋์ปค ํ๋ธ ์์ด๋, ๋น๋ฐ๋ฒํธ
docker build -t ${{ secrets.DOCKER_REPO }}/docker-web . # ๋์ปค ํ๋ธ ๋ ํฌ ์ด๋ฆ
docker push ${{ secrets.DOCKER_REPO }}/docker-web
docker build -f ./config/nginx/Dockerfile -t ${{ secrets.DOCKER_REPO }}/docker-nginx .
docker push ${{ secrets.DOCKER_REPO }}/docker-nginx
- name: Create Remote Directory
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.HOST }}
username: ubuntu
key: ${{ secrets.KEY }}
script: mkdir -p ~/srv/ubuntu # ec2์์ ๊ฒฝ๋ก ์ค์
- name: copy source via ssh key
uses: burnett01/rsync-deployments@4.1
with:
switches: -avzr --delete
remote_path: ~/srv/ubuntu
remote_host: ${{ secrets.HOST }} # ec2 DNS ์ฃผ์
remote_user: ubuntu
remote_key: ${{ secrets.KEY }} # pem ํค
## ์ด๋ฏธ์ง pull ๋ฐ docker compose up
- name: executing remote ssh commands using password
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.HOST }}
username: ubuntu
key: ${{ secrets.KEY }}
script: |
sh ~/srv/ubuntu/config/scripts/deploy.sh
cd /home/ubuntu/srv/ubuntu/
sudo chmod 666 /var/run/docker.sock
sudo docker rm -f $(sudo docker ps -qa)
sudo docker pull ${{ secrets.DOCKER_REPO }}/docker-web
sudo docker pull ${{ secrets.DOCKER_REPO }}/docker-nginx
docker-compose up -d # docker-compose.yml ์คํ
docker image prune -f
APPLICATION ํ์ผ์๋ application.ymlํ์ผ์ ๋ด์ฉ์, HOST ํ์ผ์๋ EC2 ip ์ฃผ์๋ฅผ, KEY ํ์ผ์๋ .pemํค๋ฅผ ์ฝ์
Jetbrain ํ๊ฒฝ๋ณ์ ์ค์
${} ์์ ๋ด์ฉ์ ์ฑ์ ๋ฃ์ ๊ฒ
๋ก์ปฌ์์ ์์ ํ application.yml์์ ์ค ์๋ ๋ด์ฉ์ ๊ทธ๋๋ก ๊นํ๋ธ์ ๋ ธ์ถ์ํค๋ฉด ํดํน์ํ์ด ์์ผ๋ฏ๋ก,
jetbrains ๊ธฐ์ค run > edit configuraiton > enviroment variable์ ํตํด ${}์ฒ๋ฆฌํ๋ ๊ฑธ ๊ถ์ฅ
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://${RDS_ENDPOINT}:3306/${RDS_DATABASE}?serverTimezone=Asia/Seoul
username: admin
password: ${RDS_PASSWORD}
security:
oauth2:
client:
registration:
kakao:
client-id: ${KAKAO_CLIENT_ID}
client-name: Kakao
redirect-uri: http://localhost:8080/login/oauth2/code/kakao
scope: profile_nickname, profile_image, account_email
authorization-grant-type: authorization_code
client-authentication-method: POST
provider:
kakao:
authorization_uri: https://kauth.kakao.com/oauth/authorize
token_uri: https://kauth.kakao.com/oauth/token
user-info-uri: https://kapi.kakao.com/v2/user/me
user_name_attribute: id
jpa:
hibernate:
ddl-auto: update
properties:
generate-ddl: true
format_sql: true
use_sql_comments: true
session:
store-type: jdbc
jdbc.initialize-schema: always
jwt:
secretKey: ${SECRET_KEY}
blacklist:
access-token: BlackList_AccessToken_
mvc:
pathmatch:
matching-strategy: ant_path_matcher
redis:
host: 127.0.0.1
port: 6379
springdoc:
version: 0.0.1
default-consumes-media-type: application/json # media-type ๋ํดํธ๊ฐ ์ค์
default-produces-media-type: application/json
swagger-ui:
path: /swagger-ui.html
app:
deployment:
url: ${request_URL}
processor:
url: ${redirect_URL}
cloud:
aws:
credentials:
access-key: ${S3_ACCESS_KEY}
secret-key: ${S3_SECRET_KEY}
s3:
bucket: ${S3_BUCKET}
region:
static: ap-northeast-2
stack:
auto: false
NGINX ํ์ผ ์ค์
nginx > nginx.conf
nginx์์ 80๋ฒ ํฌํธ๋ก ๋ค์ด์จ ์์ฒญ์ Spring ์๋ฒ์ 8080๋ฒ ํฌํธ๋ก ์ฐ๊ฒฐ
server {
listen 80;
client_max_body_size 0;
location / {
proxy_pass http://web:8080;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_redirect off;
}
}
nginx > scripts
#!/bin/bash
# Installing docker engine if not exists
if ! type docker > /dev/null
then
echo "docker does not exist"
echo "Start installing docker"
sudo apt-get update
sudo apt install -y apt-transport-https ca-certificates curl software-properties-common
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu bionic stable"
sudo apt update
apt-cache policy docker-ce
sudo apt install -y docker-ce
fi
# Installing docker-compose if not exists
if ! type docker-compose > /dev/null
then
echo "docker-compose does not exist"
echo "Start installing docker-compose"
sudo curl -L "https://github.com/docker/compose/releases/download/1.27.3/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
fi
Docker ํ์ผ ์ค์
nginx > Dockerfile
FROM nginx
COPY ./config/nginx/nginx.conf /etc/nginx/conf.d/default.conf
docker-compose.yml
web ์ด๋ฏธ์ง์ nginx ์ด๋ฏธ์ง ์์ฑ ํ ์ด๋ป๊ฒ ๊ณต์ ํ ์ง ์ค์
nginx์์ 80๋ฒ ํฌํธ๋ก ๋ค์ด์จ ์์ฒญ์ Spring(web) ์๋ฒ์ 8080๋ฒ ํฌํธ๋ก ์ฐ๊ฒฐ
version: '3'
services:
web:
container_name: web
image: ${{ secrets.DOCKER_REPO }}/docker-web
expose:
- "8080"
ports:
- "8080:8080"
environment:
- TZ=Asia/Seoul
nginx:
container_name: nginx
image: ${{ secrets.DOCKER_REPO }}/docker-nginx
ports:
- "80:80"
depends_on:
- web
environment:
- TZ=Asia/Seoul
Dockerfile
FROM openjdk:17
EXPOSE 80
ARG JAR_FILE=/build/libs/docker-0.0.1-SNAPSHOT.jar # ์๋ ์ฌ์ง์์ bootjar ์คํํ์ฌ ๊ฒฝ๋ก ๊ฐ์ ธ์ค๊ธฐ
COPY ${JAR_FILE} app.jar # app.jar๋ก ๋ณต์ฌ
ENTRYPOINT ["java","-jar","/app.jar"]
์ด์ ํ๋ก์ ํธ๋ฅผ main ๋ธ๋์น์ push ํ๋ฉด, deploy.yml ํ์ผ์ด ๋์๊ฐ๋ฉฐ ์๋ ๋ฐฐํฌ (CI/CD)
Docker hub์ ์ด๋ฏธ์ง Push
๋์ปคํ๋ธ์ ์๋์ฒ๋ผ ์ด๋ฏธ์ง๊ฐ push๋ ์ํ๋ฅผ ํ์ธํ๋ค๋ฉด,
AWS EC2 ์ฐ๊ฒฐ์์ ์คํ ์ค์ธ ๋์ปค ๋ฐ ๊ฒฝ๋ก ํ์ธ
docker pull ${{ secrets.DOCKER_REPO }}/docker-web
docker pull ${{ secrets.DOCKER_REPO }}/docker-nginx
docker ps
web์ด ์๋จ๊ฑฐ๋ ๋ฐ๋ก ์ข ๋ฃ๋๋ค๋ฉด,
๋ฅผ ํตํด
docker logs {container ID}
๋ก error ํ์ธ -> error๋ฅผ ๊ณ ์น ํ ์ฌ๋ฐฐํฌ
๋๋ฉ์ธ ์ฐ๊ฒฐ
1. ๊ฐ๋น์์์ ๋๋ฉ์ธ ๊ตฌ์ (morak.shop)
2. AWS certification manager์์ SSL ์ธ์ฆ์ ๋ฐ๊ธฐ
3. Route53 ํธ์คํ ์์ญ ์์ฑ
4. Route53 CNAME ๋ ์ฝ๋ ์์ฑ
5. ๊ฐ๋น์์ ๋ค์์๋ฒ AWS๋ก ๋ฐ๊พธ๊ธฐ
6. ๋ก๋๋ฐธ๋ฐ์ ๋์๊ทธ๋ฃน ๋ง๋ค๊ธฐ
7. ๋ก๋๋ฐธ๋ฐ์ ์์ฑํ์ฌ ์๊น ๋ง๋ ๋์ ๊ทธ๋ฃน๊ณผ ์ธ์ฆ์ ์ถ๊ฐ
8. HTTP๋ฅผ HTTPS๋ก ๋ฆฌ๋ค์ด๋ ํธ ์ค์
9. ' '(๋น์นธ) ์์ฑ ํ ๋ก๋๋ฐธ๋ฐ์๋ฅผ Route53์ ๋ผ์ฐํ ๋์์ผ๋ก ๋ฑ๋ก
Swagger๋ก ์ธํฐ๋ท์์ ๋ช ์ธ์ ํ์ธ
https://morak.shop/swagger-ui/ ์์ ๋ช ์ธ์ ํ์ธ ๊ฐ๋ฅ
ํค๋์ JWTํ ํฐ ๋ฃ์ ํ, API ํ ์คํธ ์งํ
+ ์ถ๊ฐ
S3์์ ์ฐ๋
1. ๋ฒํท ์์ฑ: ์์ธ์ค ์ฐจ๋จ ์ค์ ์ ํด์
2. ์ฌ์ฉ์ ์์ฑ: AWS console > IAM > ์์ธ์ค ๊ด๋ฆฌ > ์ฌ์ฉ์ > ์ฌ์ฉ์ ์ถ๊ฐ ํด๋ฆญ > ์ง์ ์ ์ฑ
์ฐ๊ฒฐ > AmozonS3FullAccess ๋ฅผ ์ ํ > ์ฌ์ฉ์ ์์ฑ์ ํด๋ฆญ
(S3์ ์ ๊ทผํ๊ธฐ ์ํด์๋ IAM ์ฌ์ฉ์์๊ฒ S3 ์ ๊ทผ ๊ถํ์ ์ฃผ๊ณ , ์์ธ์ค ํค๋ฅผ ๋ง๋ค์ด ์ก์ธ์ค ํค, ๋น๋ฐ ์์ธ์ค ํค๋ก ์ ๊ทผ)
3. ์์ธ์ค ํค ์์ฑ: AWS Console > IAM > ์์ธ์ค ๊ด๋ฆฌ์ > ์ฌ์ฉ์ > ์์ฑํ ์ฌ์ฉ์ ์ด๋ฆ ํด๋ฆญ > ๋ณด์ ์๊ฒฉ ์ฆ๋ช > ์์ธ์ค ํค ๋ง๋ค๊ธฐ ํด๋ฆญ -> ์์ธ์ค ํค ์์ฑ ์๋ฃ ํ๋ฉด์์ ์์ฑ๋ ๊ณต๊ฐํค์ ๋น๋ฐํค๋ฅผ ํ์ธ
4. ์คํ๋ง ์ฐ๋ํ๊ธฐ
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.services.s3.AmazonS3Client;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
@Configuration
public class S3Config {
@Value("${cloud.aws.credentials.access-key}")
private String accessKey;
@Value("${cloud.aws.credentials.secret-key}")
private String secretKey;
@Value("${cloud.aws.region.static}")
private String region;
@Bean
public AmazonS3Client amazonS3Client() {
BasicAWSCredentials awsCredentials= new BasicAWSCredentials(accessKey, secretKey);
return (AmazonS3Client)AmazonS3ClientBuilder.standard()
.withRegion(region)
.withCredentials(new AWSStaticCredentialsProvider(awsCredentials))
.build();
}
}
applicaiton.yml ํ์ผ์ ์๋ ์ค์ ์ถ๊ฐ
cloud:
aws:
credentials:
access-key:
secret-key:
s3:
bucket: moraktest
region:
static: ap-northeast-2
stack:
auto: false
ํ์ผ์ ๋ก๋ API ์์ฑ
ํ๋ก ํธ์์ ์ด๋ฏธ์ง๋ฅผ ํค๋์ ํจ๊ป ์ ๋ก๋ํ๋ฉด ํค๋์์ ์นด์นด์คID ์ถ์ถ
S3์์ ํด๋น ์นด์นด์คID ๋ช ์ผ๋ก ํด๋๋ฅผ ๋ง๋ค๊ณ ์ด๋ฏธ์ง๋ฅผ ์ ์ฅ
์ด๋ฏธ์ง PresignedURL์ ๋ฐ๊ธ๋ฐ์ RDS์ ์ ์ฅํ ํ, ํ๋ก ํธ๋ก PresignedURL ์ ๋ฌ
@Operation(summary = "์์
๋ฌผ ์ด๋ฏธ์ง ๋ฑ๋ก")
@PostMapping("/upload")
public ResponseEntity<List<String>> uploadFiles(HttpServletRequest httpRequest,
@RequestParam("files") List<MultipartFile> files) {
try {
List<String> preSignedUrls = new ArrayList<>();
User user = jwtTokenProvider.getUserInfoByToken(httpRequest);
Portfolio portfolio = portfolioRepository.findPortfolioByUser(user);
for (MultipartFile file : files) {
// ํ์ผ์ด ์์ผ๋ฉด ์คํต
if (file == null) {
continue;
}
String fileName = file.getOriginalFilename();
// S3 Presigned URL ๋ฐ objectKey ์์ฑ
Map<String, Serializable> presignedUrlInfo = s3service.getPreSignedUrl(httpRequest, fileName);
String preSignedUrl = removeQueryString(presignedUrlInfo.get("preSignedUrl").toString());
String objectKey = presignedUrlInfo.get("objectKey").toString();
ObjectMetadata metadata = new ObjectMetadata();
metadata.setContentType(file.getContentType());
metadata.setContentLength(file.getSize());
// S3์ ํ์ผ ์
๋ก๋
s3service.uploadFileToS3(objectKey, file, metadata);
preSignedUrls.add(preSignedUrl);
}
// ์
๋ก๋๋ ํ์ผ์ ๊ฐ์๋งํผ Presigned URL์ ์ฌ์ฉํ์ฌ ํฌํธํด๋ฆฌ์ค์ ์ด๋ฏธ์ง URL์ ์ ์ฅ
switch (preSignedUrls.size()) {
case 4:
portfolio.uploadImageUrls(preSignedUrls.get(0), preSignedUrls.get(1), preSignedUrls.get(2), preSignedUrls.get(3));
break;
case 3:
portfolio.uploadImageUrls(preSignedUrls.get(0), preSignedUrls.get(1), preSignedUrls.get(2), null);
break;
case 2:
portfolio.uploadImageUrls(preSignedUrls.get(0), preSignedUrls.get(1), null, null);
break;
case 1:
portfolio.uploadImageUrls(preSignedUrls.get(0), null, null, null);
break;
default:
break;
}
portfolioRepository.save(portfolio);
return ResponseEntity.ok(preSignedUrls);
} catch (IOException e) {
e.printStackTrace();
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
์นด์นด์ค ID ํด๋์ ์ด๋ฏธ์ง๊ฐ ์ ์ฅ๋๋ ๊ฒ์ ํ์ธ