-
[Node.js] multer-s3를 이용한 AWS s3 파일 업로드카테고리 없음 2023. 11. 15. 11:54
AWS s3
파일 저장용 클라우드 서비스
사용자생성/ 엑세스 키 발급
엑세스 키 만들기
서버 만들 때 엑세스 키 사용함 => 엑세스 키 따로 잘 복사해서 보관해두기
ARN 도 복사해서 보관
s3 => 이미지나 파일을 저장할 수 있는 스토리지를 빌릴 수 있음
버킷 생성!!
=> 버킷만들기 클릭
버킷 접근 권한 설정
{ "Version": "2012-10-17", "Statement": [ { "Sid": "1", "Effect": "Allow", "Principal": "*", "Action": "s3:GetObject", "Resource": "arn:aws:s3:::버킷명/*" <= 설정 }, { "Sid": "2", "Effect": "Allow", "Principal": { "AWS": "ARN" <= 설정 }, "Action": [ "s3:PutObject", "s3:DeleteObject" ], "Resource": "arn:aws:s3:::버킷명/*" <= 설정 } ] }
정책이 총2개인데
1번은 모든 유저는 GET이 가능하고
2번은 관리자만 PUT, DELETE가 가능하다고 적어놓은 것임
[ { "AllowedHeaders": [ "*" ], "AllowedMethods": [ "PUT", "POST" ], "AllowedOrigins": [ "*" ], "ExposeHeaders": [ "ETag" ] } ]
모듈 설치
multer, multer-s3, aws-sdk 을 설치해주자
터미널에 입력
npm install multer multer-s3 aws-sdk
S3 객체 생성
엑세스 키 ID와 보안 엑세스 키가 유출되면 다른 사람이 여러분의 AWS 계정을 마음대로 사용할 수 있음.
.env 파일은 .gitignore에 추가해 깃허브 등에 올리지말고 서버에서 직접 생성해 내용을 작성하는 것이 좋음.env환경변수 설정해주기 위해서 dotenv도 설치하고 require해줌
터미널에 입력
npm install dotenv
routes/upload.js
require("dotenv").config()
.env
REACT_APP_S3_KEY = "엑세스키" REACT_APP_S3_SECRET = "보안 엑세스키"
.gitignore에 추가
routes/upload.js
require("dotenv").config() const multer = require('multer') const multerS3 = require('multer-s3') const { S3Client } = require('@aws-sdk/client-s3') const s3 = new S3Client({ region: 'ap-northeast-2', credentials: { accessKeyId: process.env.REACT_APP_S3_KEY, secretAccessKey: process.env.REACT_APP_S3_SECRET, } }) const upload = multer({ storage: multerS3({ s3: s3, bucket: '버킷명', key: function (요청, file, cb) { cb(null, Date.now().toString()) //업로드시 파일명 변경가능 } }), limits: { fileSize: 5 * 1024 * 1024 }, })
multer의 storage 옵션을 multerS3로 교체하고, multer의 옵션으로 s3 객체, 버킷명(bucket), 파일명(key)을 입력함
multerS3을 사용하면 req.file.location에 S3 버킷 이미지 주소가 담겨있다
const FileUpload = ({ setModal }) => { const [image, setImage] = useState({ preview: "", data: "" }); const submit = async (e) => { e.preventDefault(); let formData = new FormData(); let email = sessionStorage.getItem("email"); formData.append("file", image.data); formData.append("email", email); console.log(image.data, "선택한이미지! "); console.log(sessionStorage.getItem("email")) console.log(image.preview, "들어간파일"); console.log(formData.getAll('file')) try { await axios.post( "/upload/submit", formData, { headers: { "Content-Type": "multipart/form-data", }, }, ).then((res) => { console.log(res); sessionStorage.setItem('location', JSON.stringify( res.data ) ) } ); } catch (err) { console.error("Error during request:", err); } setModal(false) };
file을 담아 보낼 땐, FormData에 담아서 보낸다
혹시 back 서버에서 받을 때, req.body가 undefined로 나온다면 formData append 순서를 바꿔주자!
(순서에 따라 req.body가 완전히 채워지지 않아 값이 보이지 않을 수 있음)
출력은 아래와같이 해주었다.
My.jsx
let location = JSON.parse(sessionStorage.getItem("location")) const My = () => { return( <div className={styles.userImg} style={{ backgroundImage: `url(${location})`, backgroundSize: 'cover', }}> </div> ) }
전체코드
FileUpload.jsx
import React, { useState, useRef } from 'react' import axios from '../../axios' import styles from './FileUpload.module.css' const FileUpload = ({ setModal }) => { const [image, setImage] = useState({ preview: "", data: "" }); const submit = async (e) => { e.preventDefault(); let formData = new FormData(); let email = sessionStorage.getItem("email"); formData.append("file", image.data); formData.append("email", email); console.log(image.data, "선택한이미지! "); console.log(sessionStorage.getItem("email")) console.log(image.preview, "들어간파일"); console.log(formData.getAll('file')) try { await axios.post( "/upload/submit", formData, { headers: { "Content-Type": "multipart/form-data", }, }, ).then((res) => { console.log(res); sessionStorage.setItem('location', JSON.stringify( res.data ) ) } ); } catch (err) { console.error("Error during request:", err); } setModal(false) }; const handleFileChange = (e) => { const img = { preview: URL.createObjectURL(e.target.files[0]), data: e.target.files[0], }; setImage(img); }; return ( <div className={styles.modal} onSubmit={(e) => e.preventDefault}> <div className={styles.modalContent}> <div className={styles.close}> <button className={styles.closeBtn} onClick={() => { setModal() }}>X</button><br></br> </div> <label className={styles.imgLabel} htmlFor="profileImg">프로필 이미지 선택</label><br></br> <input type="file" onChange={handleFileChange} accept="Images/*" id="profileImg" name="file" // multiple //여러장업로드 할 때 /> <img src={image.preview} /> <button onClick={submit} className={styles.submitBtn}>확인</button> </div> </div> ) } export default FileUpload
upload.js
const express = require('express'); const app = express(); const router = express.Router() const path = require('path') const conn = require('../config/database'); router.use(express.static("Images")); const multer = require('multer') const multerS3 = require('multer-s3') const { S3Client } = require('@aws-sdk/client-s3') require("dotenv").config() // try { // fstat.readdirSync('uploads'); // } catch (err) { // console.err('uploads 폴더가 없어 uploads 폴더를 생성함') // fstat.mkdirSync('uploads'); // } const s3 = new S3Client({ region: 'ap-northeast-2', credentials: { accessKeyId: process.env.REACT_APP_S3_KEY, secretAccessKey: process.env.REACT_APP_S3_SECRET, } }) const upload = multer({ storage: multerS3({ s3: s3, bucket: 'contistoryprompt', key: function (요청, file, cb) { cb(null, Date.now().toString()) //업로드시 파일명 변경가능 } }), limits: { fileSize: 5 * 1024 * 1024 }, }) router.post("/submit", upload.single("file"), (req, res) => { console.log(req.file, "파일"); console.log(req.body.email) const user_profilpath = req.file.location; const user_email = req.body.email let sql = 'UPDATE t_user SET user_profilpath = ? WHERE user_email = ?' let sql2 = 'SELECT * FROM t_user WHERE user_email=?' conn.query(sql, [user_profilpath, user_email], (err, result) => { if (err) { console.log(err); res. status(500).send("Internal Server Error"); } else { conn.query(sql2, [user_email], (err, rows) => { if (rows) { console.log(rows) res.json(rows[0].user_profilpath) } else if (err) { console.log(err) res.status(500).send("err") } }) } }); }); module.exports = router;
=> DB에 저장까지 해주었다.