Image Processing Optimization ๐จ
Question Content์ ํฌํจ๋๋, ์ด๋ฏธ์ง๋ฅผ ์ฒ๋ฆฌํ๋ ๋ฐฉ๋ฒ์ ๋ํด ์๊ฐํ๊ณ ์ ํฉ๋๋ค.
Amazon S3๋, ์ด๋ฏธ์ง / ๋์์ / ๋ฌธ์ / ๋ฐฑ์
๋ฐ์ดํฐ ๋ฑ ๋ค์ํ ํ์ผ์ ์ ์ฅํ๊ณ ๊ด๋ฆฌํ ์ ์๋, AWS์์ ์ ๊ณตํ๋ ํด๋ผ์ฐ๋ ๊ฐ์ฒด ์คํ ๋ฆฌ์ง ์๋น์ค์
๋๋ค.
Base64๋ก ์ฝ์
๋ ์ด๋ฏธ์ง๋ฅผ WebP ํ์ผ๋ก ๋ณํํด AWS S3์ ์
๋ก๋ํ๊ณ , HTML ์ฝํ
์ธ ์ <img> ํ๊ทธ src๋ฅผ S3 ์ด๋ฏธ์ง URL๋ก ๊ต์ฒดํ๋ ์ด๋ฏธ์ง ์ฒ๋ฆฌ ์ต์ ํ์ ๋ํด ์ค๋ช
ํ๊ณ ์ ํฉ๋๋ค.
async processContentImages(content: string): Promise<string> {
const parser = new DOMParser();
const doc = parser.parseFromString(content, "text/html");
const images = doc.querySelectorAll("img");
for (const img of images) {
if (img.src.startsWith("data:image")) {
try {
const file = await this.convertBase64ToWebPFileWithFallback(img.src);
const uploadURL = await this.uploadFileToS3(file);
img.src = uploadURL;
} catch (error) {
console.error("Error processing image:", error);
}
}
}
return doc.getElementsByTagName("body")[0].innerHTML;
}
์ง๋ฌธ ์ ์ถ ๋ฒํผ์ ํด๋ฆญํ๋ ์๊ฐ ๊ฐ์ฅ ๋จผ์ ๋์ํ๊ฒ ๋๋ ํจ์์ ๋๋ค.
์ด ํจ์๋ HTML ๋ฌธ์์ด(content)์ ๋ฐ์, ๊ทธ ์์ ์ด๋ฏธ์ง ํ๊ทธ ์ค base64๋ก ์ธ์ฝ๋ฉ๋ ์ด๋ฏธ์ง(data:image/...)๋ฅผ ์ฐพ์ WebP ํ์์ผ๋ก ๋ณํํ ๋ค AWS S3์ ์ ๋ก๋ํ๊ณ , ํด๋น ์ด๋ฏธ์ง ํ๊ทธ์ src๋ฅผ ์ ๋ก๋๋ S3 URL๋ก ๊ต์ฒดํ ํ, ์ต์ข ์ ์ผ๋ก ์์ ๋ HTML ๋ฌธ์์ด์ ๋ฐํํฉ๋๋ค.
๋จผ์ base64๋ก ์ธ์ฝ๋ฉ๋์๋ค๋ ๊ฒ์ด ์ด๋ค ์๋ฏธ์ธ์ง ์ดํด๋ณด๊ฒ ์ต๋๋ค.
์ด๋ฏธ์ง๋ ํฝ์
์ ์งํฉ์
๋๋ค. RGB ์์ ๋ชจ๋ธ์ ๊ด์ ์์ ๋ณด๋ฉด, ๊ฐ ํฝ์
์ 0๋ถํฐ 255 ์ฌ์ด์ ์ซ์ ๊ฐ์ผ๋ก ํํ๋ฉ๋๋ค. 0์ด ์์์ด ์ ํ ์๋ ์ํ๋ผ๋ฉด, 255๋ ๊ฐ์ฅ ๊ฐํ ์์์ ๋ปํฉ๋๋ค. ์ฆ, ์ด๋ฏธ์ง๋ ๋ฐ์ด๋๋ฆฌ ๋ฐ์ดํฐ์
๋๋ค.
Base64๋ ์ปดํจํฐ๊ฐ ์ฒ๋ฆฌํ๋ ๋ฐ์ด๋๋ฆฌ(0๊ณผ 1๋ก ์ด๋ฃจ์ด์ง ์ด์ง ๋ฐ์ดํฐ)๋ฅผ ํ
์คํธ ๋ฌธ์(์ํ๋ฒณ, ์ซ์, ํน์๊ธฐํธ)๋ก ์์ ํ๊ฒ ๋ณํํ๋ ์ธ์ฝ๋ฉ ๋ฐฉ์์
๋๋ค. ์ด๋ฏธ์ง๋ฅผ ๋น๋กฏํ ๋ชจ๋ ์ด์ง ํ์ผ์ ์๋ RGB ๊ฐ ๋ฑ์ผ๋ก ๊ตฌ์ฑ๋ ๋ฐ์ด๋๋ฆฌ ๋ฐ์ดํฐ์ง๋ง, HTML์ด๋ JSON ๊ฐ์ ํ
์คํธ ๊ธฐ๋ฐ ํ๊ฒฝ์์๋ ๊ทธ๋๋ก ์ธ ์ ์๊ธฐ ๋๋ฌธ์, base64๋ก ์ธ์ฝ๋ฉํด ํ
์คํธ ํํ๋ก ๋ฐ๊ฟ ์ ์ฅํ๊ฑฐ๋ ์ ์กํฉ๋๋ค. ์ด๋ฅผ ํตํด ์ด๋ฏธ์ง ๋ฐ์ดํฐ๋ฅผ ๋ฌธ์์ด๋ก ํํํ๊ณ ์น ๋ฌธ์๋ API ๋ฑ์ ์ฝ์
ํ ์ ์๊ฒ ๋ฉ๋๋ค.
๋ค์์ผ๋ก parser์ ๋ํด ์์๋ด์ผ ํ๊ฒ ์ต๋๋ค.
parser๋, ๋ฌธ์์ด ํํ์ HTML, XML ๊ฐ์ ๋ฌธ์๋ฅผ ๋ธ๋ผ์ฐ์ ๊ฐ ์ดํดํ๋ DOM(Document Object Model) ๊ฐ์ฒด๋ก ๋ณํํด ์ฃผ๋ ๋๊ตฌ์
๋๋ค. ํ
์คํธ๋ฅผ DOM ๊ฐ์ฒด๋ก ๋ณํํด์ผ ํ์ฌ์ document์์ ์ด๋ฏธ์ง 'ํ๊ทธ'๋ฅผ ์ถ์ถํ ์ ์๊ฒ ์ฃ .
parser์์ ์ ๊ณตํ๋ parseFromString() ๋ฉ์๋๋ฅผ ํตํด, ๋ฌธ์์ด ํํ์ธ content๋ฅผ HTML ๋ฌธ์ ๊ตฌ์กฐ(DOM)๋ก ๋ณํํฉ๋๋ค.
content์ ์ด๋ฏธ์ง ํ ์ฅ์ ๋ด์์ ์ ๋ฌํ๋ฉด doc ๋ณ์์๋ ๋ค์๊ณผ ๊ฐ์ DOM ์ ๋ณด๊ฐ ์ ์ฅ๋ฉ๋๋ค.
#document (http://localhost:5173/edit)
<html>
<head></head>
<body>
<p>
<img src="https://react-nest-bucket.s3.ap-northeast-2.amazonaws.com/images/image-1747463476560.webp">
</p>
</body>
</html>
doc(DOM ๊ฐ์ฒด)์ querySelectorAll() ๋ฉ์๋๋ฅผ ์ ์ฉํ์ฌ, ์ด๋ฏธ์ง ํ๊ทธ๋ง ์ถ์ถํฉ๋๋ค.
์ด์ ์ด๋ฏธ์ง ํ๊ทธ์ ํฌํจ๋ src๋ฅผ ํตํด convertBase64ToWebPFileWithFallback() ํจ์์ uploadFileToS3() ํจ์๋ฅผ ์คํํ ๋ค, ์ด๋ฏธ์ง URL์ S3์ URL๋ก ๊ต์ฒดํ๊ณ , ๋ณํ๋ doc์ boby์ ์ฒซ ๋ฒ์งธ ์์๋ฅผ ๋ฐํํ๊ฒ ๋ฉ๋๋ค.
<p>
<img src="https://react-nest-bucket.s3.ap-northeast-2.amazonaws.com/images/image-1747463476560.webp">
</p>
processContentImages() ํจ์์์ ์ด๋ฏธ์ง ํ๊ทธ์ src๋ฅผ ์ถ์ถํ ๋ค ๊ฐ์ฅ ๋จผ์ ์คํ๋๋ ํจ์๊ฐ ๋ฐ๋ก convertBase64ToWebPFileWithFallback() ํจ์์ ๋๋ค.
async convertBase64ToWebPFileWithFallback(base64: string): Promise<File> {
return new Promise((resolve) => {
const img = new Image();
img.src = base64;
img.onload = () => {
const canvas = document.createElement("canvas");
canvas.width = img.width;
canvas.height = img.height;
const ctx = canvas.getContext("2d");
if (!ctx) {
resolve(this.convertBase64ToOriginal(base64));
return;
}
ctx.drawImage(img, 0, 0);
canvas.toBlob(
(blob) => {
if (blob) {
const webpFile = new File([blob], `image-${Date.now()}.webp`, {
type: "image/webp",
});
resolve(webpFile);
} else {
resolve(this.convertBase64ToOriginal(base64));
}
},
"image/webp",
0.8
);
};
img.onerror = () => resolve(this.convertBase64ToOriginal(base64));
});
}
convertBase64ToWebPFileWithFallback() ํจ์์ ๋ค์ด์ค๋ base64๋ "data:image"๋ก ์์ํ๋ ์ด๋ฏธ์ง URL์ ๋๋ค.
const img = new Image();
img.src = base64;
์ ๊ณผ์ ์ด ์ ํ๋์ด์ผ ํฉ๋๋ค.
base64 ๋ฌธ์์ด์ ๋จ์ ํ
์คํธ ๋ฐ์ดํฐ์ด๊ธฐ ๋๋ฌธ์ ๋ฐ๋ก ์ด๋ฏธ์ง ํ์ผ๋ก ๋ค๋ฃฐ ์ ์์ต๋๋ค. ํ
์คํธ ๋ฐ์ดํฐ๋ฅผ ์ค์ ์ด๋ฏธ์ง์ฒ๋ผ ๋ธ๋ผ์ฐ์ ๊ฐ ์ธ์ํ๊ณ ์ฒ๋ฆฌํ๊ธฐ ์ํด์๋ Image ๊ฐ์ฒด์ src๋ฅผ ์ง์ ํ์ฌ ์ด๋ฏธ์ง๋ฅผ ๋ก๋ํ๊ณ ํฌ๊ธฐ๋ ํฝ์
๋ฐ์ดํฐ ๋ฑ์ ์ป๋ ๊ณผ์ ์ด ํ์ํฉ๋๋ค.
const canvas = document.createElement("canvas");
canvas.width = img.width;
canvas.height = img.height;
const ctx = canvas.getContext("2d");
if (!ctx) {
resolve(this.convertBase64ToOriginal(base64));
return;
}
ctx.drawImage(img, 0, 0);
img.onload๋, ์ด๋ฏธ์ง๊ฐ img.src์ ์ง์ ๋ base64 ๋ฐ์ดํฐ๋ฅผ ๋ค ์ฝ์ด์ ์์ ํ ๋ก๋๋๋ฉด ์คํ๋๋ ์ฝ๋ฐฑ ํจ์์ ๋๋ค.
์น ํ์ด์ง์ ์ค์ ๋ก ํ์๋์ง ์๋ ๊ฐ์์ <canvas> ์์๋ฅผ ์๋ก ๋ง๋ญ๋๋ค. ์ด๋ฏธ์ง ๋ณํ์ ์ํด ์์๋ก ์ด๋ฏธ์ง๋ฅผ ๊ทธ๋ฆฌ๊ฒ ๋ ๊ณต๊ฐ์ ํด๋นํฉ๋๋ค. ์ดํ, ์บ๋ฒ์ค์ ํฌ๊ธฐ๋ฅผ ์๋ณธ ์ด๋ฏธ์ง์ ํฌ๊ธฐ ์ ๋์ผํ๊ฒ ๋ง์ถฐ์, ์ด๋ฏธ์ง๊ฐ ์๊ณก ์์ด ๊ทธ๋๋ก ๊ทธ๋ ค์ง ์ ์๋๋ก ํฉ๋๋ค.
const ctx = canvas.getContext("2d");
canvas์ getContext("2d") ๋ฉ์๋๋ฅผ ์ ์ฉํ ๋ชจ์ต์ ํ์ธํ ์ ์์ต๋๋ค. canvas๊ฐ ๋ํ์ง๋ผ๋ฉด, ctx๋ ๋ํ์ง์ ๊ทธ๋ฆผ์ ๊ทธ๋ฆด ์ ์๋ ๋ถ์ ๋ฐ๋ ๊ณผ์ ์ ๋๋ค. ์ ํํ๋ 2d ํํ์ ๊ทธ๋ฆผ์ ๊ทธ๋ฆฌ๊ธฐ ์ํ ๋ถ์ ๋ฐ์ ๊ฒ์ ๋๋ค. ์ดํ drawImage() ๋ฉ์๋๋ฅผ ํตํด ์บ๋ฒ์ค ์ข์๋จ(0,0)๋ถํฐ ๊ทธ๋ฆผ์ ๊ทธ๋ฆฌ๊ธฐ ์์ํฉ๋๋ค.
canvas.toBlob(
(blob) => {
if (blob) {
const webpFile = new File([blob], `image-${Date.now()}.webp`, {
type: "image/webp",
});
resolve(webpFile);
} else {
resolve(this.convertBase64ToOriginal(base64));
}
},
"image/webp",
0.8
);
์ ์ฝ๋๊ฐ convertBase64ToWebPFileWithFallback() ํจ์์ ํต์ฌ์ ๋๋ค.
Blob์ Binary Large Object์ ์ฝ์๋ก, 0๊ณผ 1๋ก ์ด๋ฃจ์ด์ง ์ด์ง ๋ฐ์ดํฐ์ ๋๋ค. ์ ์ฝ๋๋, ์บ๋ฒ์ค์ ๊ทธ๋ฆฐ ๊ทธ๋ฆผ์ ๋ฐ์ด๋๋ฆฌ ๋ฐ์ดํฐ๋ก ๋ณํํ ๋ค webp ํ์ผ ํ์์ผ๋ก ๋ค์ ๋ฐํํ๋ ์ฝ๋๋ผ๊ณ ์์ฝํ ์ ์์ต๋๋ค.
์บ๋ฒ์ค์ ์๊ฐ์ ์ ๋ณด๋ ๋ฉ๋ชจ๋ฆฌ์ ํฝ์ ๋จ์๋ก ์ ์ฅ๋๋๋ฐ, ์ด ์ ๋ณด๋ฅผ ๋ฐ๋ก webp ํ์ผ ํํ๋ก ๋ณํํ๊ธฐ ์ด๋ ต๊ธฐ ๋๋ฌธ์, ์ค๊ฐ ๋จ๊ณ๋ก Blob์ด๋ผ๋ ๋ฒ์ฉ ์ด์ง ๋ฐ์ดํฐ ํ์์ผ๋ก ๋จผ์ ๋ณํํด ์ฃผ๋ ๊ฒ์ ๋๋ค.
convertBase64ToWebPFileWithFallback() ํจ์์์ webp ํ์ผ๋ก ๋ณํ์ ์คํจํ๋ ๋ชจ๋ ์ํฉ์์๋, convertBase64ToOriginal() ํจ์๋ฅผ ์คํํ๊ฒ ๋ฉ๋๋ค.
์ด๋ฏธ์ง๋ฅผ ๋ ํจ์จ์ ์ธ WebP ํฌ๋งท์ผ๋ก ๋ณํํ๋, ๋ธ๋ผ์ฐ์ ๊ฐ WebP๋ฅผ ์ง์ํ์ง ์๊ฑฐ๋ ๋ณํ์ ์คํจํ๋ ๊ฒฝ์ฐ๋ฅผ ๋๋นํด ์๋ณธ JPEG๋ก ๋์๊ฐ๋ ์์ ์ฅ์น(fallback)๋ฅผ ๊ตฌํํ ํจ์๋ผ๊ณ ์ ๋ฆฌํ ์ ์๊ฒ ์ต๋๋ค.
async convertBase64ToOriginal(src: string): Promise<File> {
const base = atob(src.split(",")[1]);
const blob = Uint8Array.from(base, (char) => char.charCodeAt(0));
return new File([blob], `image-${Date.now()}.jpeg`, { type: "image/jpeg" });
}
convertBase64ToWebPFileWithFallback() ํจ์์ ์์ธ๋ฅผ ์ฒ๋ฆฌํ๋ ํจ์์ ๋๋ค. WebP ๋ณํ์ ์คํจํ์ ๊ฒฝ์ฐ ์๋ณธ JPEG ํ์ผ์ ๋ฐํํ๋ ํจ์์ ๋๋ค.
data:image/jpeg;base64,/9j/4AAQSkZJRg...๊ณผ ๊ฐ์ ํ์์์ ์ค์ base64 ๋ฐ์ดํฐ ๋ถ๋ถ๋ง ์ถ์ถํฉ๋๋ค. ์ค์ ๋ฐ์ดํฐ๋ฅผ atob() ๋ฉ์๋๋ฅผ ํตํด ์ด์ง ๋ฌธ์์ด๋ก ๋ณํํ๊ฒ ๋ฉ๋๋ค. ์ดํ '์ด์ง ๋ฌธ์์ด'์ ์ค์ ๋ฐ์ด๋๋ฆฌ ๋ฐ์ดํฐ๋ก ๋ณํํ ๊ฐ์ด blob์ด ๋ฉ๋๋ค. ํด๋น ๋ฐ์ด๋๋ฆฌ ๋ฐ์ดํฐ๋ก jpeg ํ์ผ์ ์์ฑํ๊ฒ ๋์ฃ .
์ค์ ๋ฐ์ดํฐ๊ฐ ์ด์ง ๋ฌธ์์ด์ด ๋๊ณ , ์ด์ง ๋ฌธ์์ด์ด ์ค์ ๋ฐ์ด๋๋ฆฌ ๋ฐ์ดํฐ๋ก ๋ณํ๋๋ ๊ณผ์ ์ ์ดํดํด์ผ ํฉ๋๋ค.
๋ง์ฝ , ์ดํ์ ์ค์ ๋ฐ์ดํฐ๊ฐ "SGk="๋ผ๋ฉด ์ด์ง ๋ฌธ์์ด์ "Hi"์ด๊ณ ๋ฐ์ดํธ ๋ฐฐ์ด์ [72, 105]๊ฐ ๋ฉ๋๋ค.
์ญ์ผ๋ก ์๊ฐํด ๋ณด์ฃ .
H์ ASCII ๊ฐ์ 72(01001000), i์ ASCII ๊ฐ์ 105(01101001)์ ๋๋ค. ์ด๋ ์ด 16๋นํธ์ด์ฃ . ํ์ง๋ง Base64๋ ํญ์ 3๋ฐ์ดํธ(24๋นํธ) ๋จ์๋ก ๋ฐ์ดํฐ๋ฅผ ์ธ์ฝ๋ฉํ๊ธฐ ๋๋ฌธ์, ๋ถ์กฑํ 1๋ฐ์ดํธ(8๋นํธ)๋ 0์ผ๋ก ํจ๋ฉํ์ฌ ์๋์ ๊ฐ์ด 24๋นํธ๋ก ๋ง๋ญ๋๋ค.
01001000 01101001 00000000
์ด์ ์ด 24๋นํธ๋ฅผ 6๋นํธ ๋จ์๋ก ์ชผ๊ฐ๋ฉด ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
010010 / 000110 / 100100 / 000000
๊ฐ 6๋นํธ ๋ธ๋ก์ 10์ง์๋ก ๋ณํํ๋ฉด 18, 6, 36, 0์ด ๋๊ณ , Base64 ์ธ์ฝ๋ฉ ํ
์ด๋ธ์์ ๊ฐ๊ฐ์ ๋์ํ๋ ๋ฌธ์๋ "S", "G", "k", "A"์
๋๋ค. ํ์ง๋ง ์ค์ ๋ฐ์ดํฐ๋ 2๋ฐ์ดํธ(Hi)์๊ณ , ๋ง์ง๋ง ๋ฐ์ดํธ๋ ํจ๋ฉ์ผ๋ก ์ถ๊ฐ๋ ๊ฒ์ด๋ฏ๋ก "A"๋ ์ ๊ฑฐ๋๊ณ , ๊ทธ ์๋ฆฌ๋ =๋ก ๋์ฒด๋ฉ๋๋ค. ๋ฐ๋ผ์ ์ต์ข
๊ฒฐ๊ณผ๋ "SGk="์
๋๋ค.
Base64๋ ๋ฐ์ด๋๋ฆฌ ๋ฐ์ดํฐ๋ฅผ ํ ์คํธ ๊ธฐ๋ฐ ํ๊ฒฝ(HTML, JSON ๋ฑ)์ ์์ ํ๊ฒ ์ ๋ฌํ๊ธฐ ์ํ ์ธ์ฝ๋ฉ ๋ฐฉ์์ ๋๋ค. ์ด ๋ฐฉ์์ ๋ค์๊ณผ ๊ฐ์ ์๋ฆฌ๋ก ์ค๊ณ๋์์ต๋๋ค.
์ปดํจํฐ์ ๋ฐ์ดํฐ๋ ์ผ๋ฐ์ ์ผ๋ก 8๋นํธ(1๋ฐ์ดํธ) ๋จ์๋ก ๊ตฌ์ฑ๋ฉ๋๋ค.
Base64๋ 6๋นํธ ๋จ์๋ก ์ชผ๊ฐญ๋๋ค. ์๋ํ๋ฉด 6๋นํธ๋ 2^6 = 64๊ฐ์ ๊ฐ์ ํํํ ์ ์์ผ๋ฉฐ, ์ด๋ ํ
์คํธ๋ก ์์ ํ๊ฒ ํํํ ์ ์๋ 64๊ฐ์ ๋ฌธ์(A-Z, a-z, 0-9, +, /)์ ์ ํํ ์ผ์นํ๊ธฐ ๋๋ฌธ์
๋๋ค.
๊ทธ๋ฐ๋ฐ 8๋นํธ์ 6๋นํธ๋ ์๋ก ๋ฑ ๋ง์๋จ์ด์ง์ง ์๊ธฐ ๋๋ฌธ์, 6๊ณผ 8์ ์ต์๊ณต๋ฐฐ์์ธ 24๋นํธ ๋จ์๋ก ๋ฌถ์ด ์ฒ๋ฆฌํ๋ ๊ฒ์ด ๊ฐ์ฅ ํจ์จ์ ์
๋๋ค.
๋ค์๊ณผ ๊ฐ์ด ์ธ์ฝ๋ฉ ๋ฉ๋๋ค.
3๊ฐ์ 8๋นํธ(3๋ฐ์ดํธ) = 24๋นํธ โ 6๋นํธ์ฉ 4์กฐ๊ฐ์ผ๋ก ๋๋์ด 4๊ธ์๋ก ์ธ์ฝ๋ฉ
๋ฐ๋๋ก ๋์ฝ๋ฉ ํ ๋๋ ๋ค์๊ณผ ๊ฐ์ด ์๋๋๋ก ๋ณต์๋ฉ๋๋ค.
4๊ฐ์ Base64 ๋ฌธ์ โ ๊ฐ๊ฐ 6๋นํธ = 24๋นํธ โ 3๋ฐ์ดํธ(8๋นํธ์ฉ)๋ก ๋ณต์
"SGk="๋ ์ค์ ์ด๋ฏธ์ง ๋ฐ์ดํฐ ๋ถ๋ถ์ ๋ํ ์์์์ต๋๋ค.
์ด ๊ฐ์ base64 ์ธ์ฝ๋ฉ์ ๊ฑฐ์น ๊ฐ์ธ๋ฐ "Hi"๋ฅผ ์ธ์ฝ๋ฉํ๋ฉด "SGk="๊ฐ ๋๋ค๋ ๊ฒ์ ์๋ฏธํฉ๋๋ค. ๋ฐ๋๋ก "SGk="๋ฅผ ๋์ฝ๋ฉ ํ๋ฉด, "Hi"๊ฐ ๋๊ฒ ์ฃ . ์ด๋ "Hi"์ ๋์๋๋ ๋ฐ์ด๋๋ฆฌ๊ฐ [72, 105]๊ฐ ๋๋ค๊ณ ์ดํดํ ์ ์์ต๋๋ค.
constructor() {
AWS.config.update({
accessKeyId: import.meta.env.VITE_AWS_ACCESS_KEY_ID,
secretAccessKey: import.meta.env.VITE_AWS_SECRET_ACCESS_KEY,
region: import.meta.env.VITE_AWS_REGION,
});
this.s3 = new AWS.S3();
}
์ฝ๋ ์๋จ์์ S3 Service๋ฅผ ์ฌ์ฉํ๊ธฐ ์ํ ๊ธฐ๋ณธ์ ์ธ Configuration ์ค์ ์ ์งํํ์ต๋๋ค.
async uploadFileToS3(file: File): Promise<string> {
const params = {
Bucket: import.meta.env.VITE_AWS_BUCKET_NAME,
Key: `images/${file.name}`,
Body: file,
ContentType: file.type,
};
return new Promise<string>((resolve, reject) => {
this.s3.upload(
params,
(
err: Error | null,
data: AWS.S3.ManagedUpload.SendData | undefined
) => {
if (err) {
reject(err);
} else {
resolve(data?.Location || "");
}
}
);
});
}
๋๋ฆ๋๋ก์ ๋ณํ ๊ณผ์ ์ ๊ฑฐ์น ์ด๋ฏธ์ง ํ์ผ์, ์ต์ข ์ ์ผ๋ก S3 Bucket์ ์ ๋ก๋ํ๋ ๋ก์ง์ ๋๋ค. ๋จ์ํ ์ฌ์ฉ๋ฒ์ ๋๋ค.
HTML ์ฝํ ์ธ ์ ํฌํจ๋, Base64๋ก ์ธ์ฝ๋ฉ๋ ์ด๋ฏธ์ง๋ฅผ WebP ํ์์ผ๋ก ๋ณํํ์ฌ AWS S3์ ์ ๋ก๋ํ๋ ๊ณผ์ ์ ์ค๋ช ํ์ต๋๋ค.
processContentImages() ํจ์์์ ์์ํ์ฌ, HTML ๋ฌธ์์์ Base64 ์ด๋ฏธ์ง๋ฅผ ์ฐพ์ convertBase64ToWebPFileWithFallback() ํจ์๋ฅผ ํตํด ๋ ํจ์จ์ ์ธ WebP ํฌ๋งท์ผ๋ก ๋ณํํ์ต๋๋ค. ๋ณํ์ ์คํจํ ๊ฒฝ์ฐ์๋ convertBase64ToOriginal() ํจ์๋ก ์๋ณธ JPEG ํ์์ ์ ์งํ์ฃ .
์ต์ข ์ ์ผ๋ก uploadFileToS3() ํจ์๋ฅผ ํตํด ๋ณํ๋ ์ด๋ฏธ์ง๋ฅผ S3์ ์ ๋ก๋ํ๊ณ HTML์ ์ด๋ฏธ์ง ํ๊ทธ src ์์ฑ์ S3 URL๋ก ๊ต์ฒดํจ์ผ๋ก์จ ์น ํ์ด์ง์ ๋ก๋ฉ ์ฑ๋ฅ์ ํฅ์์ํค๋ ์ด๋ฏธ์ง ์ต์ ํ ์๋ฃจ์ ์ ๊ฐ๋ฐํ์ต๋๋ค.