리액트 사진 게시판 - liaegteu sajin gesipan

Multer Installation

Multer는 multipart/form-data를 이용하여 파일을 업로드 할 수 있게 해주는 middleware이다. 

npm install multer

package.json에 아래와 같이 설치가 된다. 

"multer": "^1.4.2",

Multer Usage

uploadRouter.js

const express = require('express');
const router = express.Router();
const multer = require("multer");
const path = require("path");



//diskStorage 엔진으로 파일저장경로와 파일명을 세팅한다. 
let storage = multer.diskStorage({ //multer disk storage settings
    destination: function(req, file, callback) {
        callback(null, "uploads/")
    },
    filename: function(req, file, callback) {
        let extension = path.extname(file.originalname);
        let basename = path.basename(file.originalname, extension);
        callback(null, basename + "-" + Date.now() + extension);
    }
});


//특정 파일형식만 저장하기 위해서는 fileFilter함수를 사용한다. 
const upload = multer({ //multer settings
    storage: storage,
    fileFilter: function(req, file, callback) {
        var ext = path.extname(file.originalname);
        if (ext !== '.xlsx' && ext !== '.pdf' && ext !== '.png' && ext !== '.jpg' && ext !== '.gif' && ext !== '.jpeg') {
            return callback(new Error('Only .xlsx .pdf .png, .jpg .gif and .jpeg format allowed!'))
        }
        callback(null, true)
    },
}).any(); //.any()는 전달받는 모든 파일을 받는다. 파일배열은 req.files에 저장되어 있다. 

router.post('/files', (req, res, next) => {
    const reqFiles = [];
    try {
        upload(req, res, function(err) {
            if (err) {
                return res.status(400).send({ //에러발생하면, 에러 메시지와 빈 파일명 array를 return한다. 
                    message: err.message,
                    files: reqFiles
                });
            }

            for (var i = 0; i < req.files.length; i++) { //저장된 파일명을 차례로 push한다. 
                reqFiles.push(req.files[i].filename)
            }

            res.status(200).send({ //저장 성공 시, 저장성공 메시지와 저장된 파일명 array를 return한다. 
                message: "Uploaded the file successfully",
                files: reqFiles
            });
        })
    } catch (err) {
        console.log(err);
        res.status(500).send({
            message: `Could not upload the file: ${err}`,
            files: reqFiles
        });
    }
});

module.exports = router;

fileUploadService.js

import axios from 'axios';


const upload = (file) => {
    let formData = new FormData();

    for (const key of Object.keys(file)) {
        formData.append('file', file[key]);
    }
    //formData.append("file", file);

    return axios.post("/api/upload/files", formData, {
        headers: {
            "Content-Type": "multipart/form-data",
        }
    });
};

export default {
    upload
};

UploadFiles.js

부모 컴포넌트(NoticeComponentWrite.js)에서 자식컴포넌트(UploadFiles.js)의 메서드 호출할 수 있도록 하기 위해 

useImperativeHandle()을 사용하며, forwardRef()와 함께 사용한다. 

import React, {
    useState,
    useRef,
    useImperativeHandle,
    forwardRef
} from "react";
import FileUploadService from "../utils/fileUploadService";

const UploadFiles = forwardRef((props, ref) => {

    const [selectedFiles, setSelectedFiles] = useState(undefined);
    const [message, setMessage] = useState("");

    const selectFile = (event) => {
        setSelectedFiles(event.target.files);
    };
    
    //NoticeWriteComponent에서 저장버튼을 클릭 시, 파일 업로드를 실행하기 위해서 
    //useImperativeHandle이라는 hook을 사용한다. 
    useImperativeHandle(ref, () => ({
        upload: () => {
            return new Promise((resolve, reject) => {

                if (selectedFiles) {

                    let result = FileUploadService.upload(selectedFiles)
                        .then((response) => {
                            setMessage(response.data.message);
                            resolve(response.data.files);
                            //return UploadService.getFiles();
                        })
                        .catch((err) => {

                            fileInput.current.value = '';
                            setSelectedFiles(undefined);
                            if (err.response.data.message) {
                                setMessage(err.response.data.message);
                            } else {
                                setMessage("Could not upload the file!");
                            }
                            reject();
                        });
                } else {

                    resolve([]);
                }


            })
        }
    }));

    const fileInput = useRef();

    return (    
    <div className="form-group">

      <label className="btn btn-default">
      //여러개 파일을 한번에 업로드 하기 위해 multiple 속성을 추가한다. 
      //파일 정보를 배열로 받을 수 있다. 
      <input type="file" onChange={selectFile} ref={fileInput} multiple />
      </label>
	  
      //업로드 완료 시, 성공이나 실패 메시지를 붉은 색으로 표기해준다. 
      <div className="alert alert-light" role="alert" style={{color:'red'}}>
        {message}
      </div>

    </div>
    )

});

export default UploadFiles;

이제 UploadFiles 컴포넌트를 아무 곳에서나 import하여 사용이 가능하다. 

NoticeWriteComponent.js

import UploadFiles from '../UploadFiles';

const NoticeWriteComponent = () => {

    const uploadReferenece = React.createRef();

    async function onClickSearch() {
        await uploadReferenece.current.upload().then(function (result) {
            const files = result;
            alert('저장 완료');
        }).catch(function (err) {
        });
    }

    return (
       <div>
          <UploadFiles ref={uploadReferenece} />
          <div className="text-center pd12">
             <button className="lf-button primary" onClick={onClickSearch}>저장</button>
          </div>
      </div>
    )
};

export default NoticeWriteComponent;

파일을 2개 선택하면 아래와 같이 표시된다. 

리액트 사진 게시판 - liaegteu sajin gesipan

저장버튼을 클릭하면, 파일이 업로드 되고 형식에 맞지 않는 파일인 경우 아래와 같이 에러 메시지를 표시한다. 

리액트 사진 게시판 - liaegteu sajin gesipan