본문 바로가기

Node.js

[Node.js 12강] 파일 업로드 하기 (multer 모듈 사용, 한번에 파일 여러개 업로드하기, 삽질한 결과 공유)

이번엔 nodejs에서 파일을 업로드하는 방법을 알아보도록 하겠습니다. 

한번에 여러 이미지를 업로드하는 케이스를 개발했는데 상당히 삽질을 했습니다. 1개만 업로드 할 때는 쉽게 처리하는건 굉장히 쉬운데 말이죠. 

 

1. multer 모듈 설치

npm install --save multer

 

 

2. HTML 구조

form 안에 input type="FILE"이 여러개 있을 수 있는 형태입니다.

DB의 PK는 TEST_SN + Q_SN  형태로 저장됩니다. (갑자기 PK 구조를 언급한 이유는 뒤에 나옵니다.)

form의 method는 POST, enctype은 'multipart/form-data'로 설정해야 합니다. 

<form name="questionForm" method="post" enctype="multipart/form-data" action="/test/save">
  <input type="hidden" name="TEST_SN" value="1">
  
  <ul id="questionFormList">
    <li>
      <input type="hidden" name="Q_SN" value="2">
      <input type="file" name="IMG_FILE">
    </li>
    <li>
      ...
    </li>
    ...
  </ul>
  <input type="submit" value="전송">
</form>
    

 

3. 라우트 파일

 

3.1 파일명이나 파일경로를 변경없이 사용하고자 할 경우
var multer = require('multer');  //multer 모듈 import
var upload = multer({dest: 'public/images/yesno/'}); //업로드 경로 설정
//미리 폴더를 만들어놔야 하며, 경로 맨 앞에 '/'는 붙이지 않습니다. 

 

3.2 파일 업로드 경로 및 파일명을 동적으로 변경하고자 할 경우

전 파일명을 동적으로 DB의 PK 구조와 동일하게 만들어서 저장해야 했습니다. (TEST_SN_Q_SN.확장자 형태)

var multer = require('multer');	

//multer 의 diskStorage를 정의
var storage = multer.diskStorage({
  //경로 설정
  destination : function(req, file, cb){    

    cb(null, 'publics/images/');
  },

  //실제 저장되는 파일명 설정
  filename : function(req, file, cb){
	//파일명 설정을 돕기 위해 요청정보(req)와 파일(file)에 대한 정보를 전달함
    var testSn = req.body.TEST_SN;
    var qSn = req.body.Q_SN;

    //Multer는 어떠한 파일 확장자도 추가하지 않습니다. 
    //사용자 함수는 파일 확장자를 온전히 포함한 파일명을 반환해야 합니다.        
    var mimeType;

    switch (file.mimetype) {
      case "image/jpeg":
        mimeType = "jpg";
      break;
      case "image/png":
        mimeType = "png";
      break;
      case "image/gif":
        mimeType = "gif";
      break;
      case "image/bmp":
        mimeType = "bmp";
      break;
      default:
        mimeType = "jpg";
      break;
    }

    cb(null, testSn + "_" + qSn + "." + mimeType);
  }
});

var upload = multer({storage: storage});

 

3.3 파일업로드 요청(HTTP) 처리 메소드 정의
//파일이 여러개이므로 두번째 인자에 upload.array(name) 을 이용
//혹시 파일이 한개인 경우는 upload.single(name)을 이용
router.post('/test/save', upload.array('IMG_FILE'), function (req, res) {
  
  var testSn = req.body.TEST_SN;
  var qSnArr = req.body.Q_SN;
  var imgFileArr = req.files; //파일 객체를 배열 형태로 리턴함.
  //var imgFile = req.file; //파일이 1개인 경우(upload.single()을 이용한 경우)
  
  ....
  
  //DB insert 처리
  ...
});

 

4. 추가설명

여기까지는 그다시 복잡한 건 없어 보입니다. 하지만 한번에 파일을 여러개 보내서 핸들링 해야 하다보니 몇가지 심화학습이 필요했습니다. 

 

4.1 업로드된 파일 정보 활용

multer의 파일은 아래의 정보를 제공합니다. 

 

실제 콘솔에 찍어본 File 객체 정보 로그

{ fieldname: 'IMG_FILE',
  originalname: '20181228_075440.jpg',
  encoding: '7bit',
  mimetype: 'image/jpeg',
  destination: 'public/images/yesno/',
  filename: '22_1.jpg',
  path: 'public\\images\\yesno\\22_1.jpg',
  size: 469791 }

 

multer.diskStorage에서 제공하는 정보는 조금 다릅니다. 

{ fieldname: 'IMG_FILE',
  originalname: '20181228_075440.jpg',
  encoding: '7bit',
  mimetype: 'image/jpeg',  
  size: 469791 }

 

multer는 파일 확장자를 별도로 지정하지 않기 때문에 나중에 파일을 읽어서 활용하기위해선느 mimeType을 이용해서 확장자를 따로 DB에 저장하거나, 파일을 저장할 때 multer.diskStorage를 이용해서 확장자까지 지정해서 파일을 저장해야 합니다. 

 

 

4.2 실행순서
  1. HTML에서 Form Submit
  2. multer의 diskStorage() 실행 (별도로 선언한 경우만)
  3. 실제 파일 업로드
  4. HTTP요청 처리 라우트 메서드에서 파일 정보 활용
    diskStorage에서 결정한 파일저장 경로와 실제 저장된 파일명을 라우트 메서드로 전달해줍니다.

 

4.3 파일을 여러개 전송한 경우 multer.diskStroage()에 전달되는 req, file의 정보의 형태는?

처음엔 파일을 여러개 전송해도 multer.diskStroage() 는 한번 수행될거라 생각했는데 아니었습니다. 

파일 개수만큼 수행이 되며, 인자값도, 형태도 달랐습니다. 

특히, html구조에서 File 태그보다 아래에 text(Q_SN) 태그가 위치한 경우 값이 req에 넘어오지 않습니다.

 

아래와 같이 2개의 값이 넘어온 경우
<input type="hidden" name="Q_SN" value="11">
<input type="file" name="IMG_FILE">

<input type="hidden" name="Q_SN" value="22">
<input type="file" name="IMG_FILE">



var storage = multer.diskStorage({
  ...
  filename : function(req, file, cb){
    var qSn = req.body.Q_SN;

    //첫번째의 경우 qSn 값은 11 이고, typeof는 string 입니다. 

    //두번째의 경우 qSn 값은 배열(11, 22) 이고, typeof는 object 입니다. 

    ..
  }
});    

 

그래서 혹시 파일명에 input text 값을 사용하기 위해서는 아래와 같이 꼼수를 부려야 했습니다. 

var sn;
if(typeof qSn === 'string'){
  sn = qSn;
}else{
  sn = qSn[qSn.length - 1];
}

...

cb(null, testSn + "_" + sn + "." + mimeType);

 

 

4.4 동일한 이름의 input type=File 이 여러 개인데 비어있는 값이 있는 경우?

동일한 입력폼이 여러개인데 첨부이미지가 선택사항인 경우 이런 사항이 발생할 수 있습니다. 

이런 경우 라우트 메서드에서 접근할 수 있는 파일 객체(req.files)는 실제 업로드한 갯수만큼만 리턴하게 됩니다.

단순히 파일업로드만 하고 끝나면 모르겠지만 for문을 돌면서 DB에 파일에 대한 정보를 저장해야 한다면 상당히 곤란한 상황이 발생합니다. 

 

아래와 같이 2개의 값이 넘어온 경우(파일은 한개만 선택함)
<input type="hidden" name="Q_SN" value="11">
<input type="file" name="IMG_FILE" value="파일 선택함">

<input type="hidden" name="Q_SN" value="22">
<input type="file" name="IMG_FILE">


라우트 메서드에서..
router.post('/test/save', upload.array('IMG_FILE'), function (req, res) {

  var qSnArr = req.body.Q_SN;  // length 2개
  var imgFileArr = req.files;	//length 1개

  var insertValArr = [];
  for (var i = 0; i < qSnArr.length; i++) {
      insertValArr.push([qSnArr[i], imgFileArr[i].filename]); //에러 발생함!!
  }    

  var sql = 'INSERT INTO TEST (Q_SN, IMG_FILE) VALUES ? ';
  dbconn.query(sql, [insertValArr], function(err, result){
    ...
  });
		
});

 

위의 경우가 이해가 되시나요? 실제로 저장된 파일 이름을 DB에 저장하려면 req.files 객체에 접근해서 filename 을 통해서 실제 저장된 파일명을 가져와야 합니다. 그런데 이게 몇번째인지 알 수가 없습니다. DB에 저장할 데이터가 총 10row 인데 req.files는 5개인 경우 매핑할 방법이 없는거죠. 

그래서 어쩔 수 없이 꼼수를 부려야 했습니다. 

 

 

맨 처음에 생뚱맞게 DB의 PK 구조를 이야기했던 이유가 여기에 있습니다. 

실제 저장되는 파일명을 PK구조(유일한 식별자) 형태로 만들어서 중첩 for문을 통해 값을 비교해서 빼내와야 했습니다. 

var testSn = req.params.testSn;
var qSnArr = req.body.Q_SN;

var insertValArr = [];
for (var i = 0; i < qSnArr.length; i++) {

  var filename = "";

  //전체 배열 중 이미지를 업로드 하지 않은 파일이 있을 수 있음. 
  for (var z = 0; z < imgFileArr.length; z++) {
    var fileNm = imgFileArr[z].filename;
    //확장자까지는 알 수 없으므로 indexOf로 비교
    if(fileNm.indexOf(testSn + "_" + qSnArr[i]) > -1){
      filename = fileNm;
    }
  }
  
  ...insert 구문
}  

 

예전에 node.js에서 multer를 이용해서 파일업로드를 쉽게 구현했었는데 한번에 파일을 여러개 업로드하려고 하니 이것저것 삽질을 엄청했네요. 이번 포스팅을 작성하는데만도 두시간 넘게 걸리고 있습니다. ㅎㅎ

 

아무튼 누군가에게는 도움이 되길 바라며 이번 포스팅을 마칩니다. 

 

참고. multer github

https://github.com/expressjs/multer/blob/master/doc/README-ko.md

 

expressjs/multer

Node.js middleware for handling `multipart/form-data`. - expressjs/multer

github.com