Storage 설정
1. [개발] 에서 Storage 를 선택한다.
2. [시작하기]
3. [다음]
4. [완료]
Blaze 요금제를 사용하는 고객은 추가 버킷을 사용할 수 있다. 는 설명이 왼쪽 아래에 써있다.
5. Storage 가 만들어졌다.
Storage 사용법
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 을 실행시키고 구글 아이디 로그인을 해준다.
'Programming > Firebase' 카테고리의 다른 글
[Firebase] Unity3d에서 시작하기 (0) | 2019.11.26 |
---|---|
[Firebase] Hosting 사용하기 (0) | 2019.11.26 |
[Firebase] Web에서 Database 사용하기 (0) | 2019.11.18 |
[Firebase] Web에서 Email, Google 로그인 사용하기 (0) | 2019.11.18 |
[Firebase] Node.js 개발 환경 구축 (0) | 2019.11.18 |
최근댓글