๐Ÿ’ก โ€œ๊ฐ™์€ ์ด๋ฏธ์ง€ ๋˜ ์˜ฌ๋ฆฌ์„ธ์š”?โ€

LinkDropperยท2025๋…„ 5์›” 13์ผ
16

Link Dropper

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

๐Ÿ’ฌ ๋ฐ˜๋ณต ์—…๋กœ๋“œ, ์‚ฌ์†Œํ•˜์ง€๋งŒ ๋ฌด์„œ์šด ๋น„์šฉ

์ด๋ฏธ์ง€๋ฅผ ์—…๋กœ๋“œํ•˜๋Š” ๊ธฐ๋Šฅ์€ ๋Œ€๋ถ€๋ถ„์˜ ์„œ๋น„์Šค์—์„œ ํ•„์ˆ˜์ž…๋‹ˆ๋‹ค.
์‚ฌ์šฉ์ž ํ”„๋กœํ•„, ๊ฒŒ์‹œ๊ธ€ ์ธ๋„ค์ผ, ์ฝ˜ํ…์ธ  ๋‚ด ์ฒจ๋ถ€ ์ด๋ฏธ์ง€๊นŒ์ง€.

์ฒ˜์Œ์—” ๋ฌธ์ œ์—†์–ด ๋ณด์ด์ง€๋งŒ,
โ€œ๊ฐ™์€ ์ด๋ฏธ์ง€๊ฐ€ ์—ฌ๋Ÿฌ ๋ฒˆ ์—…๋กœ๋“œ๋œ๋‹ค๋ฉด?โ€

  • ์ €์žฅ ์šฉ๋Ÿ‰ ๋‚ญ๋น„
  • S3 ํŠธ๋ž˜ํ”ฝ ์š”๊ธˆ ์ฆ๊ฐ€
  • ๊ด€๋ฆฌ ๋‚œ์ด๋„ ์ƒ์Šน
  • ๊ทธ๋ฆฌ๊ณ ... ๐Ÿ’ธ ์š”๊ธˆ ํญํƒ„

ํŠนํžˆ S3๋Š” ์ „์†ก๋Ÿ‰ + ์ €์žฅ๋Ÿ‰ ๋ชจ๋‘ ๊ณผ๊ธˆ๋˜๊ธฐ ๋•Œ๋ฌธ์—
๊ฐ™์€ ์ด๋ฏธ์ง€๋ฅผ ์ค‘๋ณต์œผ๋กœ ์˜ฌ๋ฆด์ˆ˜๋ก ์„œ๋น„์Šค ์ „์ฒด ๋น„์šฉ์ด ๊ธฐํ•˜๊ธ‰์ˆ˜์ ์œผ๋กœ ์˜ฌ๋ผ๊ฐ‘๋‹ˆ๋‹ค.


๐Ÿ“ฆ ์ค‘๋ณต ์ด๋ฏธ์ง€ ํŒ๋‹จ, ์–ด๋–ป๊ฒŒ ํ• ๊นŒ?

๋‹จ์ˆœํžˆ filename์œผ๋กœ ํŒ๋‹จํ•˜๊ธฐ์—” ์œ„ํ—˜ํ•ฉ๋‹ˆ๋‹ค.
์‚ฌ์šฉ์ž๋Š” ๊ฐ™์€ ์ด๋ฏธ์ง€๋ฅผ ์ด๋ฆ„๋งŒ ๋‹ค๋ฅด๊ฒŒ ์˜ฌ๋ฆด ์ˆ˜๋„ ์žˆ๊ณ ,
๊ฐ™์€ ์ด๋ฆ„์˜ ์™„์ „ํžˆ ๋‹ค๋ฅธ ์ด๋ฏธ์ง€์ผ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ทธ๋ž˜์„œ ํ•„์š”ํ•œ ๊ฑด?

โœ… ์ด๋ฏธ์ง€ ์ž์ฒด์˜ ๋‚ด์šฉ์„ ๊ธฐ์ค€์œผ๋กœ ํŒ๋‹จํ•˜๋Š” ๊ฒƒ

โ†’ ๊ฐ€์žฅ ์•ˆ์ •์ ์ด๊ณ  ํšจ์œจ์ ์ธ ๋ฐฉ์‹์ด ๋ฐ”๋กœ
โ†’ ์ด๋ฏธ์ง€๋ฅผ ํ•ด์‹œ๊ฐ’์œผ๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค.


๐Ÿ”‘ ํ•ด์‹œ ๊ธฐ๋ฐ˜ S3 ์—…๋กœ๋“œ ๋กœ์ง ์„ค๊ณ„

ํ๋ฆ„์€ ๋‹จ์ˆœํ•ฉ๋‹ˆ๋‹ค:

  1. ์ด๋ฏธ์ง€๋ฅผ ์ฝ์–ด์„œ ํ•ด์‹œ๊ฐ’(SHA-256 ๋“ฑ)์„ ์ƒ์„ฑ
  2. ํ•ด๋‹น ํ•ด์‹œ๊ฐ’์„ ๊ธฐ๋ฐ˜์œผ๋กœ S3 Key(Path)๋ฅผ ๊ตฌ์„ฑ
  3. S3์— ํ•ด๋‹น Key๊ฐ€ ์กด์žฌํ•˜๋Š”์ง€ HeadObject๋กœ ํ™•์ธ
  4. ์กด์žฌํ•œ๋‹ค๋ฉด? โžœ ์—…๋กœ๋“œ ์ƒ๋žต (URL๋งŒ ๋ฐ˜ํ™˜)
  5. ์—†๋‹ค๋ฉด? โžœ ์ƒˆ๋กœ ์—…๋กœ๋“œ ์ง„ํ–‰

์ด ๋ฐฉ์‹์˜ ๊ฐ€์žฅ ํฐ ์žฅ์ ์€:

  • ์ •ํ™•ํ•˜๋‹ค โ€“ ๋™์ผํ•œ ์ด๋ฏธ์ง€๋ผ๋ฉด ์–ธ์ œ๋‚˜ ๊ฐ™์€ ํ•ด์‹œ๊ฐ’
  • ๊ฐ„ํŽธํ•˜๋‹ค โ€“ ์ถ”๊ฐ€ DB ์—†์ด S3 ์ž์ฒด๋ฅผ ์ค‘๋ณต ํŒ๋‹จ ๊ธฐ์ค€์œผ๋กœ ํ™œ์šฉ
  • ๋น ๋ฅด๋‹ค โ€“ ์—…๋กœ๋“œ ์ƒ๋žต์œผ๋กœ I/O ์ค„์–ด๋“ฆ
  • ์ €๋ ดํ•˜๋‹ค โ€“ ์ค‘๋ณต ํŒŒ์ผ ์ตœ์†Œํ™”๋กœ ๋น„์šฉ ์ ˆ๊ฐ

๐Ÿงช ์‹ค์ „ ์˜ˆ์‹œ (Node.js + AWS SDK v3)

import crypto from 'crypto';
import { S3Client, HeadObjectCommand, PutObjectCommand } from '@aws-sdk/client-s3';

const s3 = new S3Client({
  region: 'ap-northeast-2',
  credentials: {
    accessKeyId: process.env.AWS_ACCESS_KEY!,
    secretAccessKey: process.env.AWS_SECRET_KEY!,
  },
});

const BUCKET_NAME = 'your-bucket-name';

// 1. ์ด๋ฏธ์ง€ ํ•ด์‹œ ์ƒ์„ฑ
const getImageHash = async (buffer: Buffer): Promise<string> => {
  return crypto.createHash('sha256').update(buffer).digest('hex');
};

// 2. S3์— ํ•ด๋‹น ์ด๋ฏธ์ง€ ์กด์žฌ ์—ฌ๋ถ€ ํ™•์ธ
const checkIfImageExists = async (hash: string): Promise<boolean> => {
  try {
    await s3.send(new HeadObjectCommand({
      Bucket: BUCKET_NAME,
      Key: `images/${hash}.jpg`,
    }));
    return true;
  } catch (e) {
    return false;
  }
};

// 3. ์ตœ์ข… ์—…๋กœ๋“œ ํ•จ์ˆ˜
const uploadImageIfNeeded = async (buffer: Buffer): Promise<string> => {
  const hash = await getImageHash(buffer);
  const key = `images/${hash}.jpg`;

  const exists = await checkIfImageExists(hash);
  if (exists) {
    console.log(`โœ… ์ค‘๋ณต ์ด๋ฏธ์ง€์ž…๋‹ˆ๋‹ค. ์—…๋กœ๋“œ ์ƒ๋žต โ†’ ${key}`);
    return key;
  }

  await s3.send(new PutObjectCommand({
    Bucket: BUCKET_NAME,
    Key: key,
    Body: buffer,
    ContentType: 'image/jpeg',
  }));

  console.log(`๐Ÿ“ค ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ ์™„๋ฃŒ โ†’ ${key}`);
  return key;
};

๐Ÿ’ก ํ•ด์‹œ ๊ธฐ๋ฐ˜ ์—…๋กœ๋“œ์˜ ์‹ค์ „ ํŒ

1. ํ•ด์‹œ ์ถฉ๋Œ์€ ๊ฑฑ์ •ํ•  ํ•„์š” ์—†๋‚˜์š”?

SHA-256์„ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด ์‚ฌ์‹ค์ƒ ์ถฉ๋Œ์€ ๊ฑฑ์ •ํ•˜์ง€ ์•Š์•„๋„ ๋ฉ๋‹ˆ๋‹ค.
์ „ ์„ธ๊ณ„ ์ธํ„ฐ๋„ท์—์„œ ์œ ํ†ต๋˜๋Š” ์ˆ˜์–ต ๊ฐœ ์ด๋ฏธ์ง€ ์ค‘,
์ถฉ๋Œ ์ผ€์ด์Šค๋Š” ๋ฌด์‹œํ•ด๋„ ๋  ์ˆ˜์ค€์ž…๋‹ˆ๋‹ค.


2. PNG, JPG์ฒ˜๋Ÿผ ํ™•์žฅ์ž๋Š” ๊ตฌ๋ถ„ํ•ด์•ผ ํ•˜๋‚˜์š”?

โœ”๏ธ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค.
์™œ๋ƒํ•˜๋ฉด ๊ฐ™์€ ๋‚ด์šฉ์ด๋”๋ผ๋„ ํฌ๋งท์ด ๋‹ค๋ฅด๋ฉด ๋ฐ”์ดํŠธ๋„ ๋‹ฌ๋ผ์ง€๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.
โ†’ images/{hash}.jpg, images/{hash}.png ๋“ฑ ํ™•์žฅ์ž ๊ธฐ๋ฐ˜์œผ๋กœ ๋ถ„๊ธฐํ•˜์„ธ์š”.


3. ์„œ๋ฒ„๋‹จ์—์„œ๋งŒ ์ฒ˜๋ฆฌํ•˜๋‚˜์š”?

ํด๋ผ์ด์–ธํŠธ์—์„œ ์ฒ˜๋ฆฌํ•ด๋„ ์ข‹์ง€๋งŒ,
๋ณด์•ˆ์„ ๊ณ ๋ คํ•˜๋ฉด ์„œ๋ฒ„๋‹จ์—์„œ ํ•ด์‹œ + ์กด์žฌ ์—ฌ๋ถ€ ํŒ๋‹จ์„ ์ˆ˜ํ–‰ํ•˜๋Š” ๊ฒŒ ๋” ์•ˆ์ „ํ•ฉ๋‹ˆ๋‹ค.


4. S3 Prefix ์ „๋žต์€ ์–ด๋–ป๊ฒŒ ์งœ๋Š” ๊ฒŒ ์ข‹์„๊นŒ์š”?

S3๋Š” prefix๊ฐ€ ๋น„์Šทํ•œ ํŒŒ์ผ์ด ๋งŽ์œผ๋ฉด ์„ฑ๋Šฅ ์ €ํ•˜๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  • ๊ถŒ์žฅ: images/{hash.slice(0,2)}/{hash}.jpg
    โ†’ ์˜ˆ: images/ab/ab123456....jpg
    โ†’ ์„ฑ๋Šฅ + ์ •๋ ฌ ๋ชจ๋‘ ์œ ๋ฆฌ

๐Ÿ“Œ ๋งˆ์น˜๋ฉฐ

์‚ฌ์†Œํ•ด ๋ณด์ผ ์ˆ˜ ์žˆ๋Š” ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ ํ•œ ์ค„์ด
์„œ๋น„์Šค ๋น„์šฉ๊ณผ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์— ์–ผ๋งˆ๋‚˜ ํฐ ์˜ํ–ฅ์„ ๋ฏธ์น˜๋Š”์ง€ ๋А๋ผ์…จ๋‚˜์š”?

์ด๋ฒˆ ๊ธ€์—์„œ ์†Œ๊ฐœํ•œ ๋ฐฉ์‹์€
๊ฐ„๋‹จํ•˜์ง€๋งŒ ๋งค์šฐ ์‹ค์šฉ์ ์ด๋ฉฐ,
๋ชจ๋“  ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ ์‹œ์Šคํ…œ์— ์‰ฝ๊ฒŒ ์ ์šฉ ๊ฐ€๋Šฅํ•œ ํŒจํ„ด์ž…๋‹ˆ๋‹ค.


๐Ÿงช ๋งํฌ ๋“œ๋ผํผ, ์ง€๊ธˆ ๋ฒ ํƒ€ ํ…Œ์ŠคํŠธ ์ค‘์ž…๋‹ˆ๋‹ค

๋งํฌ ๋“œ๋ผํผ๋Š” ๋‹จ์ˆœํ•œ ์ €์žฅ ๋„๊ตฌ๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค.
๋งํฌ๋ฅผ ์ •๋ฆฌํ•˜๊ณ , ๋‹ค์‹œ ๊บผ๋‚ด๋ณด๋Š” ์Šต๊ด€์„ ๋•๋Š” ์„œ๋น„์Šค์ž…๋‹ˆ๋‹ค.
โ€ข ๐Ÿ“Ž ๋น ๋ฅด๊ณ  ์ง๊ด€์ ์ธ ๋งํฌ ์ €์žฅ
โ€ข ๐Ÿ—‚ ํด๋”๋ณ„๋กœ ์ž์œ ๋กญ๊ฒŒ ์ •๋ฆฌ
โ€ข ๐Ÿ’ฌ ๋งํฌ ๊ณต์œ ๊นŒ์ง€ ํ•œ ๋ฒˆ์—
โ€ข โšก OG ์ด๋ฏธ์ง€/์ œ๋ชฉ ์ž๋™ ์ถ”์ถœ

๐Ÿ‘‰ ์ง€๊ธˆ ๋ฐ”๋กœ ๋ฒ ํƒ€ ๋ฒ„์ „์„ ์ฒดํ—˜ํ•ด๋ณด์„ธ์š”!
๐Ÿ”— ๋งํฌ ๋“œ๋ผํผ ๋ฒ ํƒ€ ์ฒดํ—˜ํ•˜๋Ÿฌ ๊ฐ€๊ธฐ

โธป

๐Ÿ“ข ๋” ๋งŽ์€ ์†Œ์‹, ๋” ๋น ๋ฅด๊ฒŒ ๋ฐ›๊ณ  ์‹ถ๋‹ค๋ฉด?

์นด์นด์˜คํ†ก ์ฑ„๋„์„ ์ถ”๊ฐ€ํ•˜์‹œ๋ฉด
์—…๋ฐ์ดํŠธ ์†Œ์‹, ๊ธฐ๋Šฅ ๊ฟ€ํŒ, ์ด๋ฒคํŠธ ๋“ฑ์„ ๊ฐ€์žฅ ๋น ๋ฅด๊ฒŒ ๋ฐ›์•„๋ณด์‹ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๐Ÿ‘‰ ๋งํฌ ๋“œ๋ผํผ ์นด์นด์˜คํ†ก ์ฑ„๋„ ์ถ”๊ฐ€ํ•˜๊ธฐ

profile
โ€œ๊ธฐ๋กํ•˜๋Š” ์Šต๊ด€์„ ๋„๊ตฌ๋กœ ๋งŒ๋“ค๋‹ค โ€” ๋‘ ๊ฐœ๋ฐœ์ž์˜ ๋งํฌ ๋“œ๋ผํผ ๊ตฌ์ถ•๊ธฐโ€

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

comment-user-thumbnail
2025๋…„ 5์›” 23์ผ

๊ธ€ ์žฌ๋ฏธ์žˆ๊ฒŒ ์ž˜ ์ฝ์—ˆ์Šต๋‹ˆ๋‹ค!
๋งŒ์•ฝ ๋‘ ๋ช…์˜ ์‚ฌ์šฉ์ž๊ฐ€ ๋™์ผํ•œ ์ด๋ฏธ์ง€๋ฅผ ์—…๋กœ๋“œํ–ˆ๊ณ , ๋‘˜ ์ค‘ ํ•œ ๋ช…์ด ์ด๋ฏธ์ง€๋ฅผ ์‚ญ์ œํ•˜๋Š” ์ผ์ด ๋ฐœ์ƒํ•œ๋‹ค๋ฉด ์–ด๋–ค ์‹์œผ๋กœ ์ฒ˜๋ฆฌํ•˜์‹œ๋‚˜์š”?

1๊ฐœ์˜ ๋‹ต๊ธ€