[서버 week_7-1] 파일 업로드

SH·2022년 6월 24일
0

서버 세미나

목록 보기
12/14

파일 업로드

http body에 json 사용해서 메시지 보내는 경우
Content-Type : application/json

파일 전송하는 경우
Content-Type : multipart/form-data


파일 업로드 하기 전에

  1. multer, multer-s3, aws-sdk 추가

  1. AWS Console -> 우 상단 계정 아이디 클릭 -> 보안 자격 증명
    으로 엑세스 키(위에서 세번째) 받아옴

  1. S3_ACCESS_KEY, S3_SECRET_KEY, BUCKET_NAME을 .env파일에 지정해주고 config/index.ts 파일에서도 지정

BUCKET_NAME은 검색 -> S3에서 확인 가능


src/config/s3Config.ts

const s3: AWS.S3= new AWS.S3({
    accessKeyId: config.s3AccessKey,
    secretAccessKey: config.s3SecretKey,
    region: 'ap-northeast-2'
})

export default s3;

aws-sdk 모듈로 AWS.S3을 만들고 export해줌


config/multer.ts

const upload = multer({
    storage: multerS3({
        s3: s3,
        bucket: config.bucketName,
        contentType: multerS3.AUTO_CONTENT_TYPE,
        acl: 'public-read',
        key: function(req: Express.Request, file: Express.MulterS3.File, cb ) {
            cb(null, `${Date.now()}_${file.originalname}`);
        }
    })
})

export default upload

미들웨어로 사용할 multer 만든 뒤에 export
이때 만든 upload는 router에서 미들웨어로 사용됨

router.post('/upload', upload.single('file'), FileController.uploadFileToS3);

router.post('/upload', upload.array('file'), FileController.uploadFilesToS3);

이렇게!

single - 파일 하나만
array - 파일 여러개

multipart/form-data 에 들어온 데이터 중 ‘file’ 필드로 받아옴. 즉, 필드명에 upload.XXX()의 파라미터로 들어오는 것과 동일해야함


const FileSchema = new mongoose.Schema({
    link: {
        type: String,
        required: true
    },
    fileName: {
        type: String,
        required: true
    },   
}, {
    timestamps: true
})

export default mongoose.model<FileInfo & mongoose.Document>("File", FileSchema);

export interface FileInfo {
	link: string;
    fileName: string;
}

File schema 및 interface 생성.
프로퍼티는 파일 url과 file 이름이 필요하다.


+) s3 권한에서 ACL 활성화 해주어야함! 자세한 내용은 밑에


파일 하나 업로드 시

FileController.ts

const uploadFileToS3 = async (req: Request, res: Response) => {
    if (!req.file) return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, message.BAD_REQUEST))

    const image: Express.MulterS3.File = req.file as Express.MulterS3.File;
    const { originalname, location } = image

    try {
        const data = await FileService.createFile(location, originalname)
        res.status(statusCode.CREATED).send(util.success(statusCode.CREATED, message.CREATE_FILE_SUCCESS, data))
    
    } catch (error) {
        console.log(error)
        res.status(statusCode.INTERNAL_SERVER_ERROR).send(util.fail(statusCode.INTERNAL_SERVER_ERROR, message.INTERNAL_SERVER_ERROR))
    }
}

req.file에 image라는 애가 오는데 걔한테 originalname(파일이름), location(파일 link) 프로퍼티가 있나봄

또한 req.file은 기본으로 Express.Multer.File로 추론되어서 MulterS3로 타입단언해야됨


FileResponseDto.ts

export interface FileResponseDto {
    _id: mongoose.Schema.Types.ObjectId;
    link: string
}

FileService.ts

const createFile = async (link: string, fileName:string): Promise<FileResponseDto> => {
    try {
        const file = new File({
            link, 
            fileName
        });

        await file.save();

        const data = {
            _id: file._id,
            link
        };

        return data

    } catch (error) {
        console.log(error)
        throw error;
    }
}

FileRouter.ts

router.post('upload', upload.single('file'), FileController.uploadFileToS3);

파일 여러개 업로드 시

FileController.ts

const uploadFilesToS3 = async (req: Request, res: Response) => {
    if (!req.files) return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, message.NULL_VALUE));

    const images: Express.MulterS3.File[] = req.files as Express.MulterS3.File[];

    try {
    
    	// 여기 부가설명 아래에 있음
        const imageList: {
            location: string;
            originalname: string;
        }[] = await Promise.all(images.map((image: Express.MulterS3.File) => {
            return {
                location: image.location,
                originalname: image.originalname
            }
        }));

        const data = await FileService.createFiles(imageList);

        res.status(statusCode.CREATED).send(util.success(statusCode.CREATED, message.CREATE_FILE_SUCCESS, data));
    } catch (error) {
        console.log(error)
        res.status(statusCode.INTERNAL_SERVER_ERROR).send(util.fail(statusCode.INTERNAL_SERVER_ERROR, message.INTERNAL_SERVER_ERROR))
        
    }
}

imageList - location과 originalname이라는 키를 가진 객체의 배열
Promise.all() 메소드를 사용해서 각각의 객체가 전부 준비되면 반환되도록 함
메소드 안에서는 map을 통해 images 배열을 돌면서 location과 originalname이라는 키를 가진 객체를 반환


FileService.ts

const createFiles = async (imageList: { location: string, originalname: string }[]): Promise<FileResponseDto[]> => {
    
    try {
        const data = await Promise.all(imageList.map(async image => {
            const file = new File({
                link: image.location,
                fileName: image.originalname
            });

            await file.save();

            return {
                _id: file._id,
                link: file.link
            };
        }));

        return data;

    } catch (error) {
        console.log(error)
        throw error;
    }
}

FileRouter.ts

router.post('/upload', upload.array('file'), FileController.uploadFilesToS3);

잠깐! s3 권한 -> ACL 활성화 해주어야 함

s3 검색 -> 해당 bucket 선택 -> 권한 탭 -> 밑으로 쭉 내려서 ACL(액세스 제어 목록) 찾아라 -> ACL 활성화하고 변경사항 저장


세미나대로 했는데 안될 수 있음

버전오류일 수 있다 package.json에서 뭐 버전 수정해보셈 자세한 내용은 슬랙참고


테스트 할 때

postman에서 body -> form data -> key에 필드명을 file로 하고 왼쪽에 text말고 file로 설정한 뒤에, value에서 파일 선택


profile
블로그 정리안하는 J개발자

0개의 댓글