AWS

S3 presignedUrl 생성 , presignedUrl이용해서 S3,DB에 이미지저장

신입주니어개발자 2022. 3. 10. 10:17

서버에서 직접 multipart를 받아 S3 버킷에 업로드하면 서버쪽에서 multipart 파일을 쥐고 있어야 하는 둥 리소스 낭비가 크므로 S3에 접근할 수 있는 Presigned url을 생성하여 클라이언트가 직접 업로드하게 한다.

https://docs.aws.amazon.com/ko_kr/AmazonS3/latest/dev/PresignedUrlUploadObjectJavaSDK.html
presigned url 이란 AWS S3 버킷에 바로 파일을 업로드 할 수 있는 URL을 말한다.

생성 및 사용

const aws = require("aws-sdk")
const {AWS_ACCESS_KEY , AWS_SECRET_KEY}  = process.env


const s3 = new aws.S3({
    accessKeyId:AWS_ACCESS_KEY,
    secretAccessKey:AWS_SECRET_KEY  
})

const getSignedUrl = ({key})=>{
    return new Promise((resolve,reject)=>{
        s3.createPresignedPost({
            Bucket:"image-upload-tutorial",
            Fields:{
                key
            },
            Expires: 300, //만료기간에 업로드를 하지않으면 url날려버림
            Conditions:[
                ["content-length-range",0,0*50*1000*1000],
                ["starts-with","$Content-Type","image/"]//이미지파일만 받게 설정
            ]
        },(err,data)=>{
            if(err) throw reject(err)
            resolve(data)
        })
    })
}   

module.exports = {s3,getSignedUrl}

aws.js

 

const {s3,getSignedUrl} = require("../aws")
const {v4: uuid} = require('uuid')
const mime = require("mime-types");

 imageRouter.post("/presigned",async(req,res)=>{
     try{
        if(!req.user) throw new Error("권한이 없습니다")//로그인x
        const {contentTypes} = req.body
        if(!Array.isArray(contentTypes)) throw new Error("invalid contentTypes")
        //이미지 여러개 올릴경우 presignedUrl을 여러개만들어 한번에 보내주는게 좋음
        const preSignedData = await Promise.all(contentTypes.map(async(contentType) =>{
            const imageKey = `${uuid()}.${mime.extension(contentType)}`
            const key = `raw/${imageKey}`
            const presigned = await getSignedUrl({key})
            return {imageKey,presigned}
        }))
        return res.json(preSignedData)
     }catch(err){
        res.status(400).json({message:err.message})//400 :유저잘못입력 , 500:서버오류
        console.log(err)
     }
 })
 
 imageRouter.get("/:imageId" , async function(req,res){        
    try{
        const {imageId} = req.params;
        if(!mongoose.isValidObjectId(imageId)) throw new Error("올바르지않은 id입니다")
        const image = await Image.findOne({_id:imageId})
        if(!image) throw new Error("존재하지 않습니다")
        res.json(image);
    }catch(err){    
        res.status(400).json({message:err.message})
        console.log(err)
    }

 });
imageRouter.js
 
  const onSubmitV2 = async (e) => {
    e.preventDefault();
    try {
      setIsLoading(true);
      const presignedData = await axios.post("/images/presigned", {
        contentTypes: [...files].map((file) => file.type),
      });

      await Promise.all(
        [...files].map((file, index) => {
          const { presigned } = presignedData.data[index];
          const formData = new FormData();
          for (const key in presigned.fields) {
            formData.append(key, presigned.fields[key]);
          }
          formData.append("Content-Type", file.type);
          formData.append("file", file);
          return axios.post(presigned.url, formData);
        })
      );
uploadForm.js(프론트)