1. S3 만들기
1. S3로 이동한다.
2. [버킷 만들기]로 이동한다.
3. 원하는 이름을 입력한다.
4. '모든 퍼블릭 액세스 차단'을 해제하고 퍼블릭 상태가 될 수 있음을 알고 있다는 박스에 체크 후 생성한다.
2. 직접 파일(이미지) 업로드 해보기
1. 생성된 버킷으로 이동 > [업로드] 선택한다.
2. [파일 추가]를 통해 추가하고 [업로드]를 한다.
3. 이미지 정보에 들어오면 객체 URL을 확인할 수 있다.
하지만 링크 이동하면 아래처럼 에러가 뜬다.
따라서 퍼블릭하게 접근할 수 있도록 추가 설정을 해야 한다.
4. 내 버킷 > 속성으로 간다.
5. 정적 웹 사이트 호스팅 > 편집으로 이동한다.
6. 아래처럼 설정을 변경한다.
7. 권한으로 이동한다.
8. 아래처럼 버킷 정책을 수정한다.
{
"Version": "2012-10-17",
"Id": "Policy1689489055171",
"Statement": [
{
"Sid": "Stmt1689489029299",
"Effect": "Allow",
"Principal": "*",
"Action": [
"s3:GetObject",
"s3:PutObject"
],
"Resource": "[여러분들의 S3 버킷 arn]/*"
}
]
}
9. 아까 객체 URL을 실행하면 잘 되는 것을 확인할 수 있다.
3 Node.js에서 S3에 사진 업로드 세팅
1. 모듈을 다운받는다.
npm install aws-sdk
2. IAM으로 이동 > [사용자 생성]으로 이동한다.
3. 아래와 같이 권한을 설정한다.
4. 보안 자격 증명 > 액세스 키 > 액세스 키 만들기로 이동한다.
5. AWS 외부에서 실행되는 애플리케이션을 선택한다.
6. .env 파일에 다음과 같이 저장한다.
- 만약 elastic beanstalk을 통해 배포한다면 환경 > 구성 > 환경 속성 편집을 통해 추가해줘야 한다.
AWS_S3_REAGION=ap-northeast-2
AWS_S3_BUCKET_NAME=my-first-project-s3
AWS_S3_ACCESS_KEY=내 access key
AWS_S3_SECRET_KEY=내 secret key
7. uuid 사용을 위한 모듈을 설치한다.
npm install uuid
8. 버킷 내 파일 디렉토리 생성한다.
- S3 대시보드에 [폴더 만들기] > 원하는 폴더 이름 입력 후 생성한다.
9. ./src/middleware/image.uploader.js에 다음과 같이 코드를 넣는다.
import AWS from "aws-sdk";
import multer from "multer";
import multerS3 from "multer-s3";
import { createUUID } from "./uuid.js";
import path from "path";
import dotenv from "dotenv";
import { BaseError } from "../../config/error.js";
import { status } from "../../config/response.status.js";
dotenv.config(); // .env 파일 사용
export const s3 = new AWS.S3({
region: process.env.AWS_S3_REAGION,
accessKeyId: process.env.AWS_S3_ACCESS_KEY,
secretAccessKey: process.env.AWS_S3_SECRET_KEY,
});
// 확장자 검사 목록
const allowedExtensions = [".png", ".jpg", ".jpeg", ".bmp", ".gif"];
export const imageUploader = multer({
storage: multerS3({
s3: s3, // S3 객체
bucket: process.env.AWS_S3_BUCKET_NAME, // Bucket 이름
contentType: multerS3.AUTO_CONTENT_TYPE, // Content-type, 자동으로 찾도록 설정
key: (req, file, callback) => {
// 파일명
const uploadDirectory = req.query.directory ?? ""; // 디렉토리 path 설정을 위해서
const extension = path.extname(file.originalname); // 파일 이름 얻어오기
const uuid = createUUID(); // UUID 생성
// extension 확인을 위한 코드 (확장자 검사용)
if (!allowedExtensions.includes(extension)) {
return callback(new BaseError(status.WRONG_EXTENSION));
}
callback(null, `${uploadDirectory}/${uuid}_${file.originalname}`);
},
acl: "public-read-write", // 파일 액세스 권한
}),
// 이미지 용량 제한 (5MB)
limits: { fileSize: 5 * 1024 * 1024 },
});
10. ./src/middleware/uuid.js 에 다음과 같이 코드를 넣는다.
import { v4 } from "uuid";
// uuid 생성
export const createUUID = () => {
const tokens = v4().split("-");
console.log("token", tokens);
return tokens[2] + tokens[1] + tokens[0] + tokens[3] + tokens[4];
};
11. route에 아래 코드 추가
imageUploader.single('image') // 단일 파일
imageUploader.array("image", 20) // 여러 파일 (최대 20개)
4 Node.js에서 S3에 사진 업로드 코드 (feat. 리뷰 생성 API)
0. 직접 리뷰 생성 API로 예시를 들어보겠다.
1. ./src/models/review.sql.js
// 리뷰 생성
export const insertReviewSql =
`
INSERT INTO review (score, body, created_at)
VALUES (?, ?, NOW());
`
// 리뷰 사진 생성
export const insertReviewImageSql =
`
INSERT INTO review_image (image_url, review_id) VALUES ?
`
2. ./src/models/review.dao.js
import { pool } from "../../config/db.connect.js";
import { status } from "../../config/response.status.js";
import { BaseError } from "../../config/error.js";
import { insertReviewSql, insertReviewImageSql } from "../models/review.sql.js";
// 리뷰 생성
export const addReviewDao = async (reviewData) => {
try {
const conn = await pool.getConnection();
const [review] = await pool.query(insertReviewSql, [
reviewData.score,
reviewData.body,
]);
conn.release();
return review.insertId;
} catch (err) {
throw new BaseError(status.PARAMETER_IS_WRONG);
}
};
// 리뷰 생성 - 이미지
export const addReviewImagesDao = async (reviewId, imageList) => {
try {
const conn = await pool.getConnection();
const reviewImages = imageList.map((location) => {
return [location, reviewId];
});
await pool.query(insertReviewImageSql, [reviewImages]);
conn.release();
} catch (err) {
throw new BaseError(status.PARAMETER_IS_WRONG);
}
};
3. ./src/providers/review.provider.js
import { addReviewDao, addReviewImagesDao } from "../models/review.dao.js"
// 리뷰 생성
export const addReviewProvider = async (body, imageList) => {
try{
const reviewData = await addReviewDao({
score: body.score,
body: body.body,
}, imageList);
if(imageList && imageList.length > 0){
await addReviewImagesDao(reviewData, imageList);
}
return "생성되었습니다.";
} catch(err){
throw err;
}
}
4. ./src/controllers/review.controller.js
import { response } from "../../config/response.js";
import { status } from "../../config/response.status.js";
import { addReviewProvider } from "../providers/review.provider.js";
// 이벤트 생성
export const addReviewController = async (req, res, next) => {
console.log("리뷰 생성을 요청하였습니다!");
console.log("body", req.body);
const reviewImage = req.files;
const imageList = [];
if (reviewImage) {
reviewImage.forEach((v) => {
imageList.push(v.location);
});
}
res.send(
response(
status.SUCCESS,
await addReviewProvider(req.body, imageList)
)
);
};
5. ./src/routes/review.route.js
import express from "express";
import asyncHandler from 'express-async-handler';
import { addReviewController } from "../controllers/review.controller.js";
import { imageUploader } from "../middleware/image.uploader.js";
export const reviewRouter = express.Router();
reviewRouter.post('/', imageUploader.array("image", 20), asyncHandler(addReviewController)); // 생성
6. index.js
( ... )
import { reviewRouter } from './src/routes/review.route.js';
( ... )
app.use('/reviews', reviewRouter);
( ... )
7. ./swagger/review/swagger.yaml
paths:
/reviews:
post:
tags:
- Review
summary: 리뷰 생성
consumes:
- multipart/form-data
parameters:
- in: query
name: directory
schema:
type: string
example: reviews
- in: formData
name: image
type: array
items:
type: file
format: binary
description: 파일 업로드 (최소 0개, 최대 20개)
- in: formData
name: score
type: integer
schema:
type: integer
description: 점수
required: true
- in: formData
name: body
type: string
schema:
type: string
description: 내용
required: true
responses:
'200':
description: 리뷰 생성 성공
schema:
type: object
properties:
isSuccess:
type: boolean
example: true
code:
type: integer
example: 2000
message:
type: string
example: "SUCCESS!"
result:
type: array
example: "생성되었습니다."
** 참고용 내 코드
https://github.com/dokyung-kang/UMC_node.js/tree/feat%233
'Server > node.js' 카테고리의 다른 글
[node.js] Swagger 세팅 (0) | 2024.08.31 |
---|---|
[node.js] DB 연동 (0) | 2024.08.31 |
[CICD] Node.js + Github Action + Elastic Beanstalk 무중단 배포 (3-完) (0) | 2024.08.28 |
[CICD] Node.js + Github Action + Elastic Beanstalk 무중단 배포 (2) (0) | 2024.08.28 |
[CICD] Node.js + Github Action + Elastic Beanstalk 무중단 배포 (1) (0) | 2024.08.28 |