Storage 설정

1. [개발] 에서 Storage 를 선택한다.

2. [시작하기]

 

3. [다음]

저장소 보안 규칙

 

4. [완료]

Blaze 요금제를 사용하는 고객은 추가 버킷을 사용할 수 있다. 는 설명이 왼쪽 아래에 써있다.

 

5. Storage 가 만들어졌다.

 


Storage 사용법

Cloud Storage

 

Cloud Storage  |  Firebase

Cloud Storage는 사진, 동영상 등의 사용자 제작 콘텐츠를 저장하고 제공해야 하는 앱 개발자를 위해 만들어졌습니다.

firebase.google.com

 

Storage 에 이미지를 업로드하고 다운로드 하는 방법을 알아보고 오픈 소스 Rich Text Editor Quill 을 이용하여 이미지를 포함한 게시글을 Cloud Storage 에 저장 후 다시 불러오는 법을 알아보겠다.

 

Storage 이미지 업로드, 다운로드

 

1. Node.js 미들웨어 인 multer를 설치하고 세팅한다.

$ npm install multer --save

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

 

2. stream 을 require 해준다.

const stream = require('stream');

 

storage.js

var express = require('express');
var router = express.Router();

const admin = require('firebase-admin');
const dateFormat = require('dateformat');
const multer = require('multer');
const stream = require('stream');

// firebase Admin 초기화
const firebaseAdmin = admin.initializeApp({
  credential: admin.credential.cert({
      
    }),

}, "storage");

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


///
/// Storage 관련
///

router.get('/upload', function(req, res, next) {
  res.render('storage/upload');
  return;
});

router.post('/photo', upload.single('photo'), function(req, res, next) {
  var image = req.file;
  var bufferStream = new stream.PassThrough();
  bufferStream.end(new Buffer.from(image.buffer, 'ascii'));

  var fileName = image.originalname;
  let file = firebaseAdmin.storage().bucket().file(fileName);

  bufferStream.pipe(file.createWriteStream({ 
    metadata:{
      contentType: image.mimetype
    }
  })).on('error', (eer) => {
    console.log(err);
  }).on('finish', () => {
    console.log(fileName + " finish");

    res.redirect('download?imgName=' + image.originalname);
    return;
  });
});

router.get('/download', function(req, res, next) {
  var imgName = req.query.imgName;

  var file = firebaseAdmin.storage().bucket().file(imgName);
  const config = {
    action: "read",
    expires: '03-17-2030'
  };

  file.getSignedUrl(config, (err, url) => {
    if (err) {
      console.log(err);
    }

    console.log(url);

    res.render('storage/download', {image: url});
    return;
  });
});

module.exports = router;

 

upload.ejs

<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <title>Test & MySite</title>
    <link rel='stylesheet' href='/stylesheets/style.css' />
</head>
<body>
  <form action="photo" method="POST" enctype="multipart/form-data">
    <input type="file" name="photo">
    <input type="submit">
  </form>
</body>
</html>

 

download.ejs

<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <title>Test & MySite</title>
    <link rel='stylesheet' href='/stylesheets/style.css' />
</head>
<body>
  <img src="<%= image %>">
</body>
</html>

 

구동

1. http://localhost:3000/storage/upload 에 접속한 후 이미지를 업로드한 후 [제출] 를 눌러 이미지를 Firebase Storage 에 업로드한다.

 

2. Firebase Storage 에 업로드된 이미지의 이름을 이용하여 다운로드 링크를 생성하여 이미지를 보여준다.

 

 

3. Firebase Storage 에 업로드가 된 것을 확인 할 수 있다.

 

업로드 처리 부분

router.post('/photo', upload.single('photo'), function(req, res, next) {
  var image = req.file;
  
  var bufferStream = new stream.PassThrough();
  bufferStream.end(new Buffer.from(image.buffer, 'ascii'));

  var fileName = image.originalname;
  let file = firebaseAdmin.storage().bucket().file(fileName);

  bufferStream.pipe(file.createWriteStream({ 
    metadata:{
      contentType: image.mimetype
    }
  })).on('error', (eer) => {
    console.log(err);
  }).on('finish', () => {
    console.log(fileName + " finish");

    res.redirect('download?imgName=' + image.originalname);
    return;
  });
});

 

 

let file = firebaseAdmin.storage().bucket().file('filename');

 

File 에 대한 자세한 정보는 여기서 보자.

File 은 이미지가 업로드될 위치와 파일 이름을 설정한다.

만약 file('testfolder/test.png'); 로 설정하여 이미지를 업로드할 경우

위와 같이 testfolder 폴더 안에 test.png 라는 이름으로 이미지가 업로드가 된다.

 

 

bufferStream.pipe(file.createWriteStream({ 
    metadata:{
      contentType: image.mimetype
    }
  })).on('error', (eer) => {
    console.log(err);
  }).on('finish', () => {
    console.log(fileName + " finish");

    res.redirect('download?imgName=' + image.originalname);
    return;
  });

 

file.createWriteStream 함수를 이용하여 이미지를 업로드하며 metadata 를 이용하여 이미지에 대한 정보를 선언한다.

 

 

var file = firebaseAdmin.storage().bucket().file(imgName);

 

Firebase Storage 에 업로드한 이미지 이름을 이용하여 이미지에 대한 정보를 받아온다.

 

 

const config = {
    action: "read",
    expires: '03-17-2030'
};
  
file.getSignedUrl(config, (err, url) => {
    if (err) {
      console.log(err);
    }

    console.log(url);

    res.render('storage/download', {image: url});
    return;
});

 

read 형식의 2030년 3월 17일 까지 유지되는 다운로드 링크를 만든다.

 

 

 

게시글 형태 데이터 이미지 업로드, 다운로드

 

전에 만들었던 Write.ejs에 Quill 를추가해준다.

 

Write.ejs

<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <title>Test & MySite</title>
    <link rel='stylesheet' href='/stylesheets/style.css' />
</head>

<body>
  <form name="form1" action="Save" method="POST">
    <table border="1" style="width:600px">
      <caption>Firebase Cloud Firesotre Database Sample</caption>
      <colgroup>
        <col width='15%'/>
        <col width='*%'/>
      </colgroup>
      <tbdoy>
        <tr>
          <td>Writer</td>
          <td><input type="text" name="postswriter" size="20" maxlength="20" value="<%=row.postswriter%>"></td>
        </tr>
        <tr>
          <td>Title</td>
          <td><input type="text" name="poststitle" size="70" maxlength="250" value="<%=row.poststitle%>"></td>
        </tr>
        <tr>
          <td>Memo</td>
          <td>

              <link href="https://cdn.quilljs.com/1.3.6/quill.snow.css" rel="stylesheet">

              <div class="row form-group">
                  <input id="postsmemo" type="hidden" name="postsmemo" value="<%=row.postsmemo%>">
                  <!-- Create the editor container -->
                  <div id="editor">
                  </div>
              </div>

              <!-- Include the Quill library -->
              <script src="https://cdn.quilljs.com/1.3.6/quill.js"></script>

              <!-- Initialize Quill editor -->
            <script>
              // 이미지 갯수 (사진 id)
              var imgCount = 0;
              // 이미지 저장용 배열
              var imgDatas = [];
              
              var toolbarOptions = [
                  [{header: [1, 2, false] }],
                  ['bold', 'italic', 'underline'],
                  ['image', 'cpde-block']
              ];
              
              var quill = new Quill('#editor', 
              {
                  modules: {
                      toolbar: {
                          container: toolbarOptions,
                          handlers: {
                              //image: this.imageHandler
                          }
                      }
                  },
                  placeholder: '내용을 입력해주세요.',
                  theme: 'snow'
              });

              quill.setText("<%=row.postsmemo%>");

              quill.on('text-change', function(delta, oldDelta, source) {
              //console.clear();
                document.getElementById('postsmemo').value=quill.root.innerHTML;                                        
                let currrentContents = quill.getContents();

                // data:~~~ 삭제
                delta.ops.forEach(element => {
                  if (element.insert != null && element.insert != "")
                  {
                    try
                    {
                      if (element.insert.image.length > 0)
                      {
                        var str = element.insert.image;

                        // base64 자른부분
                        var base64cut = str.substring(str.indexOf("d"), str.indexOf(",") + 1);
                        // 이미지 base64 부분
                        var end = str.replace(base64cut, "");
                         // 이미지 타입 (png, jpeg...)
                        var imgType = base64cut.substring(base64cut.indexOf("/") + 1, base64cut.indexOf(";"));

                        var imgData = {
                          Name: imgCount,
                          base64Cut: base64cut,
                          img: end,
                          imgType: imgType
                        }

                          imgDatas.push(imgData);

                          imgCount++;
                        }
                    }
                      catch
                      {
                        //console.log(err);
                      }
                    }
                  });
                });
            </script>
          </td>
        </tr>
      </tbdoy>
    </table>
    <a href="#" onclick="goData()">저장</a>
    <input type="hidden" name="postsno" value="<%=row.postsno%>">
    <input type="hidden" name="postsdate" value="<%=row.postsdate%>">
    
    <input type="hidden" id="imgArray" name = "imgArray">

    <script>
        function goData(){
            try
            {
                var form = document.form1;
                console.log(imgDatas);
                console.log(imgDatas.values);

                form.imgArray.value = JSON.stringify(imgDatas);

                form.submit();
            }
            catch (err)
            {
                console.log(err);
            }
        }                               
    </script>
  </form>
</body>
</html>
// 이미지 갯수 (사진 id)
var imgCount = 0;
// 이미지 저장용 배열
var imgDatas = [];

// data:~~~ 삭제
delta.ops.forEach(element => {
  if (element.insert != null && element.insert != "")
  {
    try
    {
      if (element.insert.image.length > 0)
      {
        var str = element.insert.image;

        // base64 자른부분
        var base64cut = str.substring(str.indexOf("d"), str.indexOf(",") + 1);
        // 이미지 base64 부분
        var end = str.replace(base64cut, "");
         // 이미지 타입 (png, jpeg...)
        var imgType = base64cut.substring(base64cut.indexOf("/") + 1, base64cut.indexOf(";"));

        var imgData = {
          Name: imgCount,
          base64Cut: base64cut,
          img: end,
          imgType: imgType
        }

          imgDatas.push(imgData);

          imgCount++;
        }
    }
      catch
      {
        //console.log(err);
      }
    }
});

function goData(){
    try
    {
        var form = document.form1;
        console.log(imgDatas);
        console.log(imgDatas.values);

        form.imgArray.value = JSON.stringify(imgDatas);

        form.submit();
    }
    catch (err)
    {
        console.log(err);
    }
}              

Firebase Storage에 base64로 변환된 이미지를 업로드하기 위해서는 (Quill에 이미지를 업로드할 경우 자동으로 base64로 변환된다.)  "data:image/png;base64,iVBORw0KGgoAAAANSU...." 의 "data:image/png;base64," 부분을 삭제해줘야한다.

차후 이 데이터를 다시 이용하기 위해 imgData 라는 객체로 가공한 후 imgDatas 배열에 저장한다.

 

storage.js 에 새로운 3개의 함수를 선언해주고 read, save 부분도 수정해준다.

 

storage.js

// gsLink를 이용하여 다운로드 이미지 링크를 구하기
function refFromURL(gsLink)
{
    var fileEntryTemp = gsLink.replace("gs://", "");
    var bucketName = fileEntryTemp.substring(0, fileEntryTemp.indexOf("/"));
    var filename = gsLink.match("gs://" + bucketName + "/" + "(.*)")[1];
    var file = firebaseAdmin.storage().bucket().file(filename);
    return file;
}

// gsLink 링크 제작
function creFromURL(bucketName, fileName)
{
    var gsLink = "gs://" + bucketName + fileName;
    return gsLink;
}

// imgSrc 링크만 추출
function getSrc(str)
{
    var strReg = new RegExp("gs://*[^>]*\\.(jpg|gif|png|jpeg)","gim");
    var xArr = str.match(strReg);
    return xArr;
}

 

router.get('/Read', function(req, res, next) {
  firebaseAdmin.firestore().collection('posts').doc(req.query.postsno).get()
  .then((snapshot) => {
    var childData = snapshot.data();

    // 글에 포함된 이미지 링크들 추출
    var gsLinks = getSrc(childData.postsmemo);

    const config = {
      action: 'read',
      expires: '03-17-2030'
    };

    function callback() {
      childData.postsdate = dateFormat (childData.postsdate, "yyyy-mm-dd TT hh:mm:ss");	
      res.render('storage/Read', {row: childData});
    }

    var imgCount = 0;

    if (gsLinks != null)
    {
      gsLinks.forEach(element => {
        var file = refFromURL(element);
        //console.log(file);
    
        file.getSignedUrl(
        config, (error, url) => {
          if (error) {
            console.log(error);
          }
          childData.postsmemo = childData.postsmemo.replace(element, url);
          imgCount++;
    
          if (imgCount === gsLinks.length)
            return callback();
        });
      });
    }
    else return callback();
  }).catch((err) => {
    console.log(err);
  });
});

 

// 공지 저장 위치
const postsSavePath = "posts/";

router.post('/Save', function(req, res, next) {
  var imgData = JSON.parse(req.body.imgArray);

  var postData = req.body;

  try
  {
    var bucket = firebaseAdmin.storage().bucket();

    // 글에 들어있는 이미지들 firebase storage에 업로드
    imgData.forEach(element => {
      var bufferStream = new stream.PassThrough();
      bufferStream.end(new Buffer.from(element.img, 'base64'));

      var fileName = postsSavePath + postData.poststitle + '/' + element.Name + '.' + element.imgType;
      let file = bucket.file(fileName);

      bufferStream.pipe(file.createWriteStream({
        metadata:{
          contentType: 'image/' + element.imgType
        }
      })).on('error', (err) => {console.log(err);})
      .on('finish', () => {console.log(fileName + ' upload Complate!');});

      // gsLink 제작
      var gsLink = creFromURL('testproject-bc8ea.appspot.com', "/" + fileName);

      // 글에 삽입되어 있는 이미지 링크들 변경
      postData.postsmemo = postData.postsmemo.replace(element.base64Cut + element.img, gsLink);
    });

    //console.log(postData.postsmemo);
  } catch(err) {
    console.log(err);
  }

  if (!postData.postsno) 
  {
    postData.postsdate = Date.now();
    
		var doc = firebaseAdmin.firestore().collection('posts').doc();
    postData.postsno = doc.id;
    doc.set(postData);
    
    doc.update({
      imgArray: admin.firestore.FieldValue.delete()
    });
  } 
  else {
		doc = firebaseAdmin.firestore().collection('posts').doc(postData.postsno);
    doc.update(postData);
    
    doc.update({
      imgArray: admin.firestore.FieldValue.delete()
    });
  }

  res.redirect('List');
  return;
});

 

"data:image/png;base64,iVBORw0KGgoAAAANSU...." 의 base64 이미지를 firebase storage 저장소 위치 링크 (gs://~~) 로 변경하여 DB에 저장한다. 

DB에 굳이 저장할 필요가 없는 데이터인 imgArray를 데이터 삭제 한다.

 

1. 이미지를 글에 첨부하고 저장한다.

 

2. firebase db 에 새로 추가된 데이터의 postsmemo 의 이미지 링크가 "gs://~~" 로 변하여 들어간 것을 확인할 수 있다.

 

3. 글 목록에서 새롭게 추가된 글을 눌러 내용을 확인한다. Read 부분에서 "gs://~~" 링크가 firebase storage 의 다운로드 링크로 변하여 들어가진다.

 

4. 다운로드 링크는 지정된 기간까지 유효한 링크이다.

 


 

만약 Storage 사용 도중 아래와 같은 Google Cloud Error 가 발생 시 아래와 같은 방법으로 해결이 가능하다.

Error getting documents Error: Could not load the default credentials. Browse to https://cloud.google.com/docs/authentication/getting-started for more information.
>      at GoogleAuth.getApplicationDefaultAsync (C:~~~~\firebaseHosting\functions\node_modules\google-auth-library\build\src\auth\googleauth.js:161:19)
>      at process._tickCallback (internal/process/next_tick.js:68:7)

 

1. Windows 에서 https://cloud.google.com/sdk/docs/downloads-interactive 를 설치한다.

 

2. 검색창에서 "Google Cloud SDK Shell"를 검색한 후 "Google Cloud SDK Shell" 을 실행한다.

 

3. gcloud init --skip-diagnostics 실행시키고 Firebase Storage가 활성화 되어있는 구글 아이디로 로그인한다.

 

4. gcloud auth application-default login 을 실행시키고 구글 아이디 로그인을 해준다.

 

참고 : https://stackoverflow.com/questions/42043611/could-not-load-the-default-credentials-node-js-google-compute-engine-tutorial

  • 네이버 블러그 공유하기
  • 네이버 밴드에 공유하기
  • 페이스북 공유하기
  • 카카오스토리 공유하기