๐Ÿ”Ž์„œ๋ฒ„๋ฆฌ์Šค ๋…ธ๋“œ ๊ฐœ๋ฐœ

์„œ๊ฐ€ํฌยท2021๋…„ 12์›” 19์ผ
1

Node.js

๋ชฉ๋ก ๋ณด๊ธฐ
15/15
post-thumbnail

์„œ๋ฒ„๋ฆฌ์Šค ์ดํ•ดํ•˜๊ธฐ

1. ์„œ๋ฒ„๋ฆฌ์Šค ์ปดํ“จํŒ… ์ดํ•ดํ•˜๊ธฐ

์„œ๋ฒ„๋ฆฌ์Šค(serverless, server+less)

  • ์„œ๋ฒ„๊ฐ€ ์—†๋‹ค๋Š” ๋œป์ด์ง€๋งŒ ์„œ๋ฒ„๊ฐ€ ์—†๋Š” ๊ฒƒ์€ ์•„๋‹ˆ๊ณ , ์„œ๋ฒ„๋ฅผ ์ง์ ‘ ์šด์˜ํ•˜์ง€ ์•Š์•„๋„ ๋œ๋‹ค๋Š” ๋œป
  • ๊ฐœ๋ฐœ์ž๋Š” ์ž์‹ ์˜ ์„œ๋น„์Šค ๋กœ์ง ์ž‘์„ฑ์—๋งŒ ์ง‘์ค‘ํ•  ์ˆ˜ ์žˆ์Œ
  • ๋‹จ์ˆœํžˆ ์ฝ”๋“œ๋ฅผ ์—…๋กœ๋“œํ•œ ๋’ค, ์‚ฌ์šฉ๋Ÿ‰์— ๋”ฐ๋ผ ์š”๊ธˆ์„ ์ง€๋ถˆํ•˜๋ฉด ๋จ(ํ•จ์ˆ˜์ฒ˜๋Ÿผ ํ˜ธ์ถœํ•  ๋•Œ๋งŒ ์‹คํ–‰๋จ, FaaS(Function as a Service))
  • 24์‹œ๊ฐ„ ์ž‘๋™ํ•  ํ•„์š”๊ฐ€ ์—†๋Š” ์„œ๋ฒ„์ธ ๊ฒฝ์šฐ, ์„œ๋ฒ„๋ฆฌ์Šค ์ปดํ“จํŒ…์„ ์‚ฌ์šฉํ•˜๋ฉด ์š”๊ธˆ ์ ˆ์•ฝ
  • AWS๋Š” Lambda, GCP์—์„œ๋Š” Cloud Functions๋‚˜ Firebase๊ฐ€ ์œ ๋ช…ํ•จ
  • ์ด๋ฅผ ํ™œ์šฉํ•ด NodeBird์—์„œ ์—…๋กœ๋“œํ•˜๋Š” ์ด๋ฏธ์ง€๋ฅผ ๋ฆฌ์‚ฌ์ด์ง• ๋ฐ ์ €์žฅํ•  ๊ฒƒ์ž„

AWS S3 ์‚ฌ์šฉํ•˜๊ธฐ

1. AWS S3 ์‚ฌ์šฉํ•ด๋ณด๊ธฐ

์Šคํ† ๋ฆฌ์ง€ ์„น์…˜์˜ S3๋ฅผ ์„ ํƒ

2. ๋ฒ„ํ‚ท ๋งŒ๋“ค๊ธฐ

๋ฒ„ํ‚ท ๋งŒ๋“ค๊ธฐ๋‚˜ ์‹œ์ž‘ํ•˜๊ธฐ ๋ฒ„ํŠผ ํด๋ฆญ

3. ๋ฒ„ํ‚ท ๋ฆฌ์ „ ์„ค์ •ํ•˜๊ธฐ

๋ฒ„ํ‚ท ์ด๋ฆ„์€ ๊ณ ์œ ํ•ด์•ผ ํ•˜๋ฏ€๋กœ ๊ณ ์œ ํ•œ ์ด๋ฆ„์„ ์‚ฌ์šฉํ•  ๊ฒƒ

  • ์ด๋ฆ„๋งŒ ์ •ํ•˜๊ณ  ๊ณ„์† ๋‹ค์Œ ๋ฒ„ํŠผ์„ ๋ˆŒ๋Ÿฌ ๋„˜์–ด๊ฐ
  • ๊ถŒํ•œ์—์„œ ๋ชจ๋“  ํผ๋ธ”๋ฆญ ์•ก์„ธ์Šค ์ฐจ๋‹จ ์ฒดํฌ๋ฐ•์Šค ํ•ด์ œ

4. ๋ฒ„ํ‚ท ์ƒ์„ฑ ํ™•์ธํ•˜๊ธฐ

ํ™”๋ฉด์ด ๋œจ๋ฉด nodebird ๋ฒ„ํ‚ท ํด๋ฆญ

5. ๋ฒ„ํ‚ท ์ •์ฑ… ์ˆ˜์ •ํ•˜๊ธฐ

๊ถŒํ•œ โ€“ ๋ฒ„ํ‚ท ์ •์ฑ… ๋ฉ”๋‰ด ์„ ํƒ

  • ๋‹ค์Œ ์ฝ”๋“œ๋ฅผ ์ž…๋ ฅ ํ›„ ์ €์žฅ
  • Resource์˜ nodebird๋Š” ์ž์‹ ์˜ ๋ฒ„ํ‚ท๋ช…์œผ๋กœ ๋ฐ”๊ฟ€ ๊ฒƒ

6. ๋‚ด ๋ณด์•ˆ ์ž๊ฒฉ ์ฆ๋ช…ํ•˜๊ธฐ

์ƒ๋‹จ ๋ฉ”๋‰ด์—์„œ ๊ณ„์ • ์ด๋ฆ„ ํด๋ฆญ ํ›„ ๋‚ด ๋ณด์•ˆ ์ž๊ฒฉ ์ฆ๋ช… ๋ฉ”๋‰ด ์„ ํƒ

  • ๋ณด์•ˆ ์ž๊ฒฉ ์ฆ๋ช…์œผ๋กœ ๊ณ„์† ๋ฒ„ํŠผ ํด๋ฆญ
  • ์‹ค ์„œ๋น„์Šค์—์„œ๋Š” IAM ์‚ฌ์šฉ์ž ์‹œ์ž‘ํ•˜๊ธฐ ๋ฒ„ํŠผ ๋ˆ„๋ฅผ๊ฒƒ

7. ์•ก์„ธ์Šค ํ‚ค ๋ฐœ๊ธ‰๋ฐ›๊ธฐ

์ƒˆ ์•ก์„ธ์Šค ํ‚ค ๋งŒ๋“ค๊ธฐ ๋ฒ„ํŠผ ํด๋ฆญ
๋ณด์•ˆ ์•ก์„ธ์Šค ํ‚ค๋Š” ๋‹ค์‹œ ๋ณผ ์ˆ˜ ์—†์œผ๋ฏ€๋กœ ์•„๋ž˜ ๊ทธ๋ฆผ์˜ ํ‚ค ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ ๋ฒ„ํŠผ ๋ˆŒ๋Ÿฌ ์ €์žฅ

9. aws-sdk๋กœ S3 ๋„์ž…ํ•˜๊ธฐ

multer-s3์™€ aws-sdk ํŒจํ‚ค์ง€๋ฅผ ์„ค์น˜ํ•œ ํ›„ .env์— ๋ฐœ๊ธ‰๋ฐ›์€ ๋ณด์•ˆ ํ‚ค ๊ธฐ์ž…

npm i multer-s3 aws-sdk

๐Ÿ”ป.env

COOKIE_SECRET=nodebirdsecret
KAKAO_ID=5d4daf57becfd72fd9c919882552c4a6
SEQUELIZE_PASSWORD=nodejsbook
REDIS_HOST=redis-18954.c92.us-east-1-3.ec2.cloud.redislabs.com
REDIS_PORT=18954
REDIS_PASSWORD=JwTwGgKM4P0OFGStgQDgy2AcXvZjX4dc
S3_ACCESS_KEY_ID=AKIAID6RLNYHFCZEEODA
S3_SECRET_ACCESS_KEY=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

10. aws-sdk๋กœ S3 ๋„์ž…ํ•˜๊ธฐ

AWS.config.update๋กœ AWS์— ๊ด€ํ•œ ์„ค์ •์„ ํ•จ(ap-northeast-2๋Š” ์„œ์šธ ๋ฆฌ์ „)
multer๋ฅผ multerS3๋กœ ๊ต์ฒดํ•จ(๋ฒ„ํ‚ท์€ ์—ฌ๋Ÿฌ๋ถ„์˜ ๋ฒ„ํ‚ท๋ช…์„ ์‚ฌ์šฉํ•  ๊ฒƒ)
req.file.location์— S3 ๋ฒ„ํ‚ท ์ด๋ฏธ์ง€ ์ฃผ์†Œ๊ฐ€ ๋‹ด๊ฒจ ์žˆ์Œ

๐Ÿ”ปnodebird/routes/post.js

const express = require('express');
const multer = require('multer');
const path = require('path');
const fs = require('fs');
const AWS = require('aws-sdk');
const multerS3 = require('multer-s3');

const { Post, Hashtag } = require('../models');
const { isLoggedIn } = require('./middlewares');

const router = express.Router();

try {
  fs.readdirSync('uploads');
} catch (error) {
  console.error('uploads ํด๋”๊ฐ€ ์—†์–ด uploads ํด๋”๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.');
  fs.mkdirSync('uploads');
}

AWS.config.update({
  accessKeyId: process.env.S3_ACCESS_KEY_ID,
  secretAccessKey: process.env.S3_SECRET_ACCESS_KEY,
  region: 'ap-northeast-2',
});
const upload = multer({
  storage: multerS3({
    s3: new AWS.S3(),
    bucket: 'nodebird',
    key(req, file, cb) {
      cb(null, `original/${Date.now()}${path.basename(file.originalname)}`);
    },
  }),
  limits: { fileSize: 5 * 1024 * 1024 },
});
router.post('/img', isLoggedIn, upload.single('img'), (req, res) => {
  console.log(req.file);
  res.json({ url: req.file.location });
});

const upload2 = multer();
router.post('/', isLoggedIn, upload2.none(), async (req, res, next) => {
  try {
    console.log(req.user);
    const post = await Post.create({
      content: req.body.content,
      img: req.body.url,
      UserId: req.user.id,
    });
    const hashtags = req.body.content.match(/#[^\s#]*/g);
    if (hashtags) {
      const result = await Promise.all(
        hashtags.map(tag => {
          return Hashtag.findOrCreate({
            where: { title: tag.slice(1).toLowerCase() },
          })
        }),
      );
      await post.addHashtags(result.map(r => r[0]));
    }
    res.redirect('/');
  } catch (error) {
    console.error(error);
    next(error);
  }
});

module.exports = router;

11. ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ ์‹œ๋„ํ•˜๊ธฐ

http://localhost:8001์— ์ ‘์†ํ•˜์—ฌ ๋กœ๊ทธ์ธ ํ›„ ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ

  • S3 ๋ฒ„ํ‚ท์— ์ด๋ฏธ์ง€๊ฐ€ ์—…๋กœ๋“œ๋œ ๊ฒƒ ํ™•์ธ

AWS Lambda ์‚ฌ์šฉํ•˜๊ธฐ

1. ์ด๋ฏธ์ง€ ๋ฆฌ์‚ฌ์ด์ง•์„ ์œ„ํ•ด ๋žŒ๋‹ค ์‚ฌ์šฉ

์ด๋ฏธ์ง€ ๋ฆฌ์‚ฌ์ด์ง•์€ CPU๋ฅผ ๋งŽ์ด ์‚ฌ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๊ธฐ์กด ์„œ๋ฒ„๋กœ ์ž‘์—…ํ•˜๋ฉด ๋ฌด๋ฆฌ๊ฐ€ ๊ฐ

  • Lambda๋ผ๋Š” ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•ด ํ•„์š”ํ•  ๋•Œ๋งŒ ์„œ๋ฒ„๋ฅผ ์‹คํ–‰ํ•ด์„œ ๋ฆฌ์‚ฌ์ด์ง•

2. ๋žŒ๋‹ค์šฉ package.json ์ž‘์„ฑํ•˜๊ธฐ

aws-upload ํด๋” ๋งŒ๋“  ํ›„ package.json ์ž‘์„ฑ

  • Lambda๋ผ๋Š” ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•ด ํ•„์š”ํ•  ๋•Œ๋งŒ ์„œ๋ฒ„๋ฅผ ์‹คํ–‰ํ•ด์„œ ๋ฆฌ์‚ฌ์ด์ง•

๐Ÿ”ปaws-upload/package.json

{
  "name": "aws-upload",
  "version": "1.0.0",
  "description": "Lambda ์ด๋ฏธ์ง€ ๋ฆฌ์‚ฌ์ด์ง•",
  "main": "index.js",
  "author": "ZeroCho",
  "license": "ISC",
  "dependencies": {
    "aws-sdk": "^2.634.0",
    "sharp": "^0.25.1"
  }
}

๐Ÿ”ปaws-upload/.gitignore

node_modules

3. sharp๋กœ ๋ฆฌ์‚ฌ์ด์ง•ํ•˜๊ธฐ

Sharp๋Š” ์ด๋ฏธ์ง€ ๋ฆฌ์‚ฌ์ด์ง•์„ ์œ„ํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ

  • exports.handler๊ฐ€ ๋žŒ๋‹ค ์‹คํ–‰ ๋ถ€๋ถ„
  • event์— ๋ฒ„ํ‚ท๊ณผ ๋ฐ์ดํ„ฐ ์ •๋ณด๊ฐ€ ๋“ค์–ด ์žˆ์Œ
  • s3.getObject๋กœ ์ด๋ฏธ์ง€๋ฅผ ๋ฒ„ํ‚ท์—์„œ ๊ฐ€์ ธ์˜ด
  • sharp๋กœ ๋ฆฌ์‚ฌ์ด์ง•
  • resize(๊ฐ€๋กœ, ์„ธ๋กœ, ๋ชจ๋“œ), ๋ชจ๋“œ๋Š” inside(๋น„์œจ ์œ ์ง€ํ•˜๋ฉด์„œ ๊ฝ‰ ์ฐจ๊ฒŒ)
  • toFormat์œผ๋กœ ํ™•์žฅ์ž ์ง€์ •, toBuffer๋กœ ๋ฒ„ํผ๋กœ ๋ณ€ํ™˜
  • S3.putObject๋กœ ๋ฒ„ํ‚ท์— ์ด๋ฏธ์ง€ ๋ฐ์ดํ„ฐ ์ €์žฅ
  • callback์œผ๋กœ ๋žŒ๋‹ค ์ข…๋ฃŒ ๋ฐ ์‘๋‹ต ๋ฐ์ดํ„ฐ ์ „๋‹ฌ

๐Ÿ”ปaws-upload/index.js

const AWS = require('aws-sdk');
const sharp = require('sharp');

const s3 = new AWS.S3();

exports.handler = async (event, context, callback) => {
  const Bucket = event.Records[0].s3.bucket.name;
  const Key = event.Records[0].s3.object.key;
  const filename = Key.split('/')[Key.split('/').length - 1];
  const ext = Key.split('.')[Key.split('.').length - 1];
  const requiredFormat = ext === 'jpg' ? 'jpeg' : ext; // sharp์—์„œ๋Š” jpg ๋Œ€์‹  jpeg ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
  console.log('name', filename, 'ext', ext);

  try {
    const s3Object = await s3.getObject({ Bucket, Key }).promise(); // ๋ฒ„ํผ๋กœ ๊ฐ€์ ธ์˜ค๊ธฐ
    console.log('original', s3Object.Body.length);
    const resizedImage = await sharp(s3Object.Body) // ๋ฆฌ์‚ฌ์ด์ง•
      .resize(200, 200, { fit: 'inside' })
      .toFormat(requiredFormat)
      .toBuffer();
    await s3.putObject({ // thumb ํด๋”์— ์ €์žฅ
      Bucket,
      Key: `thumb/${filename}`,
      Body: resizedImage,
    }).promise();
    console.log('put', resizedImage.length);
    return callback(null, `thumb/${filename}`);
  } catch (error) {
    console.error(error);
    return callback(error);
  }
};

4. ์ฝ”๋“œ ๊นƒํ—ˆ๋ธŒ๋กœ ์ „์†กํ•˜๊ธฐ

๋จผ์ € GitHub์— aws-upload ๋ฆฌํŒŒ์ง€ํ† ๋ฆฌ๋กœ ์˜ฌ๋ฆฐ ํ›„ Lightsail ์ธ์Šคํ„ด์Šค์—์„œ ํด๋ก 

์••์ถ• ํ›„ S3๋กœ ์—…๋กœ๋“œ

5. ์ฝ”๋“œ ์••์ถ•ํ•ด์„œ S3๋กœ ๋ณด๋‚ด๊ธฐ

์••์ถ• ํ›„ S3๋กœ ์—…๋กœ๋“œ

6. ๋žŒ๋‹ค ์„œ๋น„์Šค ์„ค์ •ํ•˜๊ธฐ

์ปดํ“จํŒ…-Lambda

7. ์ƒˆ ํ•จ์ˆ˜ ๋งŒ๋“ค๊ธฐ

ํ•จ์ˆ˜ ์ƒ์„ฑ ๋ฒ„ํŠผ ํด๋ฆญ

8. ์ƒˆ ํ•จ์ˆ˜ ๋งŒ๋“ค๊ธฐ

ํ•จ์ˆ˜๋ช…์€ node-deploy๋กœ, ๋Ÿฐํƒ€์ž„์€ Node.js 12.x์œผ๋กœ

  • ์—ญํ• ์€ ํ…œํ”Œ๋ฆฟ์—์„œ ์ƒˆ ์—ญํ•  ์ƒ์„ฑ ์„ ํƒ, S3 ๊ฐ์ฒด ์ฝ๊ธฐ ์ „์šฉ ๊ถŒํ•œ ๋ถ€์—ฌ

9. zip ํŒŒ์ผ ์—…๋กœ๋“œํ•˜๊ธฐ

ํ•จ์ˆ˜ ์ฝ”๋“œ ์„น์…˜์—์„œ S3์— ์˜ฌ๋ฆฐ ํŒŒ์ผ์„ ์„ ํƒ

10. ๋žŒ๋‹ค ๊ธฐ๋ณธ ์„ค์ •ํ•˜๊ธฐ

๊ธฐ๋ณธ ์„ค์ • ์„น์…˜์—์„œ ํŽธ์ง‘ ๋ฒ„ํŠผ ํด๋ฆญ

  • ํ•ธ๋“ค๋Ÿฌ๋Š” โ€˜ํŒŒ์ผ๋ช….ํ•จ์ˆ˜๋ช…โ€™์œผ๋กœ (index.handler)
  • ๋ฉ”๋ชจ๋ฆฌ๋‚˜ ์ œํ•œ ์‹œ๊ฐ„์€ ๋ถ€์กฑํ•œ ๊ฒฝ์šฐ์— ๋Š˜๋ ค์ฃผ๊ธฐ
  • ์‹คํ–‰ ์—ญํ• ์€ S3 ๊ฐ์ฒด ์ฝ๊ธฐ ์ „์šฉ ๊ถŒํ•œ ๋ถ€์—ฌ

11. S3๋ฅผ ํŠธ๋ฆฌ๊ฑฐ๋กœ ์„ค์ •ํ•˜๊ธฐ

์ขŒ์ธก S3๋ฅผ ํŠธ๋ฆฌ๊ฑฐ๋กœ ์„ ํƒ

12. ํŠธ๋ฆฌ๊ฑฐ ์ƒ์„ธ ์„ค์ •ํ•˜๊ธฐ

๋ชจ๋“  ๊ฐ์ฒด ์ƒ์„ฑ ์ด๋ฒคํŠธ๋ฅผ ์„ ํƒํ•˜๊ณ  ์ ‘๋ฏธ์‚ฌ์— original/ ๋ˆ„๋ฅธ ํ›„ ์ €์žฅ

13. NodeBird์— ๋žŒ๋‹ค ์—ฐ๊ฒฐํ•˜๊ธฐ

๊ธฐ์กด original ํด๋” ๋ถ€๋ถ„์„ thumb(๋ฆฌ์‚ฌ์ด์ง• ๋จ) ํด๋”๋กœ ๊ต์ฒด

14. ์ด๋ฏธ์ง€ ์—…๋กœ๋“œํ•˜๊ธฐ

๋žŒ๋‹ค๋ฅผ ํ†ตํ•ด ์ด๋ฏธ์ง€ ๋ฆฌ์‚ฌ์ด์ง• ๋œ ๊ฒƒ ํ™•์ธํ•˜๊ธฐ

Google Cloud Storage ์‚ฌ์šฉํ•˜๊ธฐ

1. Cloud Storage ์ด์šฉํ•˜๊ธฐ

์ขŒ์ธก ๋ฉ”๋‰ด์—์„œ Storage๋ฅผ ์„ ํƒํ•œ ํ›„ ๋ฒ„ํ‚ท ๋งŒ๋“ค๊ธฐ ๋ฒ„ํŠผ ํด๋ฆญ
)

2. Cloud Storage ๋ฒ„ํ‚ท ๋งŒ๋“ค๊ธฐ

๋ฒ„ํ‚ท ์ด๋ฆ„์€ ๊ณ ์œ ํ•ด์•ผ ํ•˜๋ฏ€๋กœ ๊ณ ์œ ํ•œ ๋ฒ„ํ‚ท ์ด๋ฆ„ ์ •ํ•œ ํ›„ ๋งŒ๋“ค๊ธฐ ๋ฒ„ํŠผ ํด๋ฆญ

3. ๋ฒ„ํ‚ท ๊ถŒํ•œ ์ˆ˜์ •ํ•˜๊ธฐ

์šฐ์ธก ๋ฒ„ํ‚ท ๋ฉ”๋‰ด์—์„œ ๋ฒ„ํ‚ท ๊ถŒํ•œ ์ˆ˜์ • ์„ ํƒ

  • ๊ตฌ์„ฑ์› ์ถ”๊ฐ€์— allUsers๋ฅผ ์ž…๋ ฅํ•˜๊ณ  ์ €์žฅ์†Œ ๊ฐœ์ฒด ๋ทฐ์–ด ์„ ํƒ ํ›„ ์ถ”๊ฐ€

4. ํด๋ผ์šฐ๋“œ ์Šคํ† ๋ฆฌ์ง€ ํ‚ค ๋ฐœ๊ธ‰๋ฐ›๊ธฐ

https://console.cloud.google.com/apis/credentials ์— ์ ‘์†ํ•˜๊ธฐ

  • ์‚ฌ์šฉ์ž ์ธ์ฆ ์ •๋ณด ํ™”๋ฉด์—์„œ ์„œ๋น„์Šค ๊ณ„์ • ํ‚ค ์„ ํƒ

5. ํด๋ผ์šฐ๋“œ ์Šคํ† ๋ฆฌ์ง€ ํ‚ค ๋ฐœ๊ธ‰๋ฐ›๊ธฐ

์ƒ์„ฑ ๋ฒ„ํŠผ์œผ๋กœ json ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ ํ›„ NodeBird ํด๋”๋กœ ๋ณต์‚ฌ

6. ํด๋ผ์šฐ๋“œ ์Šคํ† ๋ฆฌ์ง€ ์—ฐ๊ฒฐํ•˜๊ธฐ

multer-google-storage์™€ axios ์„ค์น˜

  • routes/post.js์— multer-google-storage๋ฅผ multer ๋Œ€์‹  ์—ฐ๊ฒฐ
  • ์•„๊นŒ ๋‹ค์šด๋ฐ›์€ json ํŒŒ์ผ ์ด๋ฆ„์„ keyFilename์— ์ž…๋ ฅ, projectId๋Š” ํ”„๋กœ์ ํŠธ์˜ ์•„์ด๋”” ์ž…๋ ฅ(ํ™ˆ ๋ฉ”๋‰ด์˜ ํ”„๋กœ์ ํŠธ ์ •๋ณด ์„น์…˜์— ์žˆ์Œ)

๐Ÿ”ปnodebird/routes/post.js

const express = require('express');
const multer = require('multer');
const fs = require('fs');
const multerGoogleStorage = require('multer-google-storage');

const { Post, Hashtag } = require('../models');
const { isLoggedIn } = require('./middlewares');

const router = express.Router();

try {
  fs.readdirSync('uploads');
} catch (error) {
  console.error('uploads ํด๋”๊ฐ€ ์—†์–ด uploads ํด๋”๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.');
  fs.mkdirSync('uploads');
}

const upload = multer({
  storage: multerGoogleStorage.storageEngine({
    bucket: 'nodebird',
    projectId: 'node-deploy-270114',
    keyFilename: 'node-deploy-270114-b024dbed754a.json',
  }),
  limits: { fileSize: 5 * 1024 * 1024 },
});

router.post('/img', isLoggedIn, upload.single('img'), (req, res) => {
  console.log(req.file);
  res.json({ url: req.file.path });
});

const upload2 = multer();
router.post('/', isLoggedIn, upload2.none(), async (req, res, next) => {
  try {
    console.log(req.user);
    const post = await Post.create({
      content: req.body.content,
      img: req.body.url,
      UserId: req.user.id,
    });
    const hashtags = req.body.content.match(/#[^\s#]*/g);
    if (hashtags) {
      const result = await Promise.all(
        hashtags.map(tag => {
          return Hashtag.findOrCreate({
            where: { title: tag.slice(1).toLowerCase() },
          })
        }),
      );
      await post.addHashtags(result.map(r => r[0]));
    }
    res.redirect('/');
  } catch (error) {
    console.error(error);
    next(error);
  }
});

module.exports = router;

11. ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ ์‹œ๋„ํ•˜๊ธฐ

http://localhost:8001์— ์ ‘์†ํ•˜์—ฌ ๋กœ๊ทธ์ธ ํ›„ ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ
ํด๋ผ์šฐ๋“œ ์Šคํ† ๋ฆฌ์ง€ ๋ฒ„ํ‚ท์— ์ด๋ฏธ์ง€๊ฐ€ ์—…๋กœ๋“œ๋œ ๊ฒƒ ํ™•์ธ

Google Cloud Functions ์‚ฌ์šฉํ•˜๊ธฐ

1. ์ด๋ฏธ์ง€ ๋ฆฌ์‚ฌ์ด์ง•์„ ์œ„ํ•ด ํŽ‘์…˜ ์‚ฌ์šฉ

์ด๋ฏธ์ง€ ๋ฆฌ์‚ฌ์ด์ง•์€ CPU๋ฅผ ๋งŽ์ด ์‚ฌ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๊ธฐ์กด ์„œ๋ฒ„๋กœ ์ž‘์—…ํ•˜๋ฉด ๋ฌด๋ฆฌ๊ฐ€ ๊ฐ

  • Cloud Functions๋ผ๋Š” ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•ด ํ•„์š”ํ•  ๋•Œ๋งŒ ์„œ๋ฒ„๋ฅผ ์‹คํ–‰ํ•ด์„œ ๋ฆฌ์‚ฌ์ด์ง•

2. ํŽ‘์…˜์šฉ package.json ์ž‘์„ฑํ•˜๊ธฐ

gcp-upload ํด๋” ์•ˆ์— package.json ์ž‘์„ฑํ•˜๊ธฐ

๐Ÿ”ปgcp-upload/package.json

{
  "name": "gcp-upload",
  "version": "1.0.0",
  "description": "Cloud Functions ์ด๋ฏธ์ง€ ๋ฆฌ์‚ฌ์ด์ง•",
  "main": "index.js",
  "author": "ZeroCho",
  "license": "ISC",
  "dependencies": {
    "@google-cloud/storage": "^1.6.0",
    "gm": "^1.23.1",
    "sharp": "^0.25.1"
  }
}

๐Ÿ”ปgcp-upload/.gitingnore

node_modules

3. ํŽ‘์…˜์œผ๋กœ ์ด๋ฏธ์ง€ ๋ฆฌ์‚ฌ์ด์ง•ํ•˜๊ธฐ

resizeAndUpload ๋ฉ”์„œ๋“œ์— ์ฝ”๋“œ ์ž‘์„ฑ

  • storage.bucket(๋ฒ„ํ‚ท๋ช…).file(ํŒŒ์ผ๋ช…)
  • readStream์œผ๋กœ ํŒŒ์ผ ์ฝ์–ด๋“ค์ž„
  • sharp๋กœ ์ด๋ฏธ์ง€ ๋ฆฌ์‚ฌ์ด์ง•
  • writeStream์œผ๋กœ ํŒŒ์ผ ์ถœ๋ ฅ
  • resolve๋กœ ์‘๋‹ต ๋งˆ๋ฌด๋ฆฌ

๐Ÿ”ปgcp-upload/index.js

const storage = require('@google-cloud/storage')();
const sharp = require('sharp');

exports.resizeAndUpload = (data, context) => {
  const { bucket, name } = data;
  const ext = name.split('.')[name.split('.').length - 1];
  const requiredFormat = ext === 'jpg' ? 'jpeg' : ext; // sharp์—์„œ๋Š” jpg ๋Œ€์‹  jpeg์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค
  console.log('name', name, 'ext', ext);

  const file = storage.bucket(bucket).file(name);
  const readStream = file.createReadStream();

  const newFile = storage.bucket(bucket).file(`thumb/${name}`);
  const writeStream = newFile.createWriteStream();

  sharp(readStream)
    .resize(200, 200, { fit: 'inside' })
    .toFormat(requiredFormat)
    .pipe(writeStream);
  return new Promise((resolve, reject) => {
    writeStream.on('finish', () => {
      resolve(`thumb/${name}`);
    });
    writeStream.on('error', reject);
  });
};

4. ์ฝ”๋“œ ๊นƒํ—ˆ๋ธŒ๋กœ ์ „์†กํ•˜๊ธฐ

๋จผ์ € GitHub์— gcp-upload ๋ฆฌํฌ์ง€ํ„ฐ๋ฆฌ๋กœ ์˜ฌ๋ฆฐ ํ›„ ์ปดํ“จํŠธ์—”์ง„ ์ธ์Šคํ„ด์Šค์—์„œ ํด๋ก 


์••์ถ• ํ›„ ํด๋ผ์šฐ๋“œ ์Šคํ† ๋ฆฌ์ง€๋กœ ์—…๋กœ๋“œ

5. ์ฝ”๋“œ ์••์ถ•ํ•ด์„œ ํด๋ผ์šฐ๋“œ ์Šคํ† ๋ฆฌ์ง€๋กœ ๋ณด๋‚ด๊ธฐ

์••์ถ• ํ›„ S3๋กœ ์—…๋กœ๋“œ

6. ํŽ‘์…˜ ์ด์šฉํ•˜๊ธฐ

GCP ์›น์‚ฌ์ดํŠธ ์ขŒ์ธก ๋ฉ”๋‰ด์—์„œ Cloud Functions ์„ ํƒํ•œ ํ›„ ํ•จ์ˆ˜ ๋งŒ๋“ค๊ธฐ ๋ฒ„ํŠผ ํด๋ฆญ

7. ์ƒˆ ํ•จ์ˆ˜ ๋งŒ๋“ค๊ธฐ

ํ•จ์ˆ˜ ๋งŒ๋“ค๊ธฐ ๋ฒ„ํŠผ ํด๋ฆญ

8. ํ•จ์ˆ˜ ์„ค์ •ํ•˜๊ธฐ

ํ•จ์ˆ˜ ์ด๋ฆ„์€ gcp-upload

  • Cloud Storage ํŠธ๋ฆฌ๊ฑฐ ์‚ฌ์šฉ
  • ์•„๊นŒ ๋งŒ๋“  ๋ฒ„ํ‚ท ์„ ํƒ
  • ์•„๊นŒ ์—…๋กœ๋“œํ•œ zip ํŒŒ์ผ ์„ ํƒ
  • ์‹คํ–‰ํ•  ํ•จ์ˆ˜ ์ด๋ฆ„์€ resizeAndUpload๋กœ
  • ๋งŒ๋“ค๊ธฐ ๋ฒ„ํŠผ์œผ๋กœ ์„ค์ • ์™„๋ฃŒ

9. NodeBird์— ํŽ‘์…˜ ์—ฐ๊ฒฐํ•˜๊ธฐ

๊ธฐ์กด original ํด๋” ๋ถ€๋ถ„์„ thumb(๋ฆฌ์‚ฌ์ด์ง• ๋จ) ํด๋”๋กœ ๊ต์ฒด

10. ์ด๋ฏธ์ง€ ์—…๋กœ๋“œํ•˜๊ธฐ

ํŽ‘์…˜์„ ํ†ตํ•ด ์ด๋ฏธ์ง€ ๋ฆฌ์‚ฌ์ด์ง• ๋œ ๊ฒƒ ํ™•์ธํ•˜๊ธฐ

๐Ÿ˜ƒ์ถœ์ฒ˜๐Ÿ˜ƒ
Node.js ๊ต๊ณผ์„œ - ๊ธฐ๋ณธ๋ถ€ํ„ฐ ํ”„๋กœ์ ํŠธ ์‹ค์Šต๊นŒ์ง€
https://www.inflearn.com/course/%EB%85%B8%EB%93%9C-%EA%B5%90%EA%B3%BC%EC%84%9C/dashboard

0๊ฐœ์˜ ๋Œ“๊ธ€