-
๊ธ ์ฌ์ด ์ด๋ฏธ์ง ๋ฃ๊ณ ๊ด๋ฆฌํ๊ธฐ๐ FE 2023. 9. 3. 22:32
๐ ๋ฐฐ๊ฒฝ
์ฌ์ด๋ ํ๋ก์ ํธ์์ ๊ธ ์ฌ์ด์ ์ด๋ฏธ์ง๋ฅผ ๋ฃ๊ณ ๊ด๋ฆฌํ ์ ์๋ ๊ฒ์ํ CRUD ๊ธฐ๋ฅ์ ๋ง๋ค์ด์ผ ํ๋ค.
์ด๋ฏธ์ง, ๊ฒ์๊ธ ์์ฑ ๊ด๋ฆฌ ๋ฐฉ์์ ๋๊ท๋ชจ ์์ ์ด ์ฌ๋ฌ ๋ฒ ์์๊ณ … ์ต์ข ์ ํํ ๋ฐฉ์๊ณผ ํจ๊ป ์ ๊ทผํ๋ ๋ฐฉ์์ ์ ๋ฆฌํด๋ณด๊ณ ์ถ์ด์ ํฌ์คํ ์ ์์ฑํ๋ค..!
๐ ์๊ฐํ๋ ๋ฐฉ๋ฒ๋ค…
- ์ด๋ฏธ์ง๋ฅผ ์ ํํ ๋๋ง๋ค s3 ์คํ ๋ฆฌ์ง์ ์ด๋ฏธ์ง๋ฅผ ์ ์ฅํ๊ณ → ์๋ต์ผ๋ก ๋๊ฒจ์ค ์ฃผ์๋ฅผ img ํ๊ทธ๋ก ์ถ๊ฐํ์ฌ ํ๋ก ํธ์ ๋ณด์ฌ์ฃผ๋ ๋ฐฉ๋ฒ
- ๊ธ ์์ฑ์ ์๋ฃํ๊ธฐ๋ ์ ์ ์ด๋ฏธ์ง ์ ๋ก๋ → ์๋ฃ ์ ์ทจ์ํ๋ค๋ฉด ๋ญ๋น๊ฐ…
- ์ด๋ฏธ์ง ๋ฐ ๊ธ ์ ์ฒด ๋ค ์ ์ก
- ์ผ๋ฐ์ ์ผ๋ก ์น ์๋ฒ์์ request body ์ฌ์ด์ฆ ์ ํ์ ๊ฑธ์ด์ ์ด์ฉํ๊ธฐ์ ๋์ฉ๋ ๋๋ ์ฌ๋ฌ ํ์ผ ์ ๋ก๋ ์ ๋ฌธ์ ๋ฅผ ์ฝ๊ฒ ๋ง๋๋ค…
- ์ผ๋ฐ์ ์ผ๋ก ์คํ ๋ฆฌ์ง๋ ๊ณต์ฉ ์ฌ์ฉ์ ์ํด ์๋ฒ์ ๋ณ๊ฐ๋ก ๊ตฌ์ถํ๋ค๊ณ ํจ…
๐ ๊ทธ๋์ ์ฑํํ ๋ฐฉ๋ฒ์…
- ๊ธ ์์ฑ ์ ๊น์ง ์์ฑํ ํ ์คํธ์ ์ ํํ ์ด๋ฏธ์ง๋ฅผ base64๋ก ์ธ์ฝ๋ฉํ์ฌ ํ๋ก ํธ์์ ๋ณด์ฌ์ฃผ๊ณ / ํธ์งํ ์ ์๋๋ก ํจ
- ๊ธ ์์ฑ ์์ ์ ์ด๋ฏธ์ง ์ ๋ฌด๋ฅผ ๋ถ๋ฆฌํ์ฌ
- ์ด๋ฏธ์ง๊ฐ ์๋ ๊ฒฝ์ฐ ๊ธ ์์ฑ api ํธ์ถ
- ์ด๋ฏธ์ง๊ฐ ์๋ ๊ฒฝ์ฐ ๊ธ ์์ฑ api ํธ์ถ - ๋ณด์ฌ์ฃผ๋ ์ด๋ฏธ์ง๋ฅผ form data๋ก ๋ณํ ํ s3 ์๋ฒ์ ์
๋ก๋ & base 64 url์ s3 url๋ก ๋ณ๊ฒฝ ํ ๊ธ ์
๋ฐ์ดํธ api ํธ์ถ
(*์? ๊ฒ์๊ธ์ด ์์ฑ๋ ๋ ๊ฒ์๊ธ id๊ฐ ์์ฑ๋๊ณ , ์ด๋ฏธ์ง๋ ๊ฒ์๊ธ id๋ฅผ ์ฐธ์กฐํ์ฌ ์กฐํ/์ญ์ ์ ์ฌ์ฉ๋จ)
์ ๋ก๋ํ๋ ๋์ ์ปค๋ฅ์ ์ ๊ณ์ ๋ฌผ๊ณ ์์ด์ผ ํ๊ณ ๊ตฌํ์ด ๋ณต์กํ๋ค๋ ๋จ์ ์ ์์์ง๋ง…
๋ถํ์ํ ์์ ๋ญ๋น๋ฅผ ์ต์ํํ ์ ์๋ ๋ฐฉ๋ฒ์ด๋ผ๊ณ ์๊ฐํ๋ค...
๐ ์ด๋ป๊ฒ ๊ตฌํํ๋์…
1. content editable ์์ฑ ์ฌ์ฉ
๊ธ ์ฌ์ด์ ์ด๋ฏธ์ง๋ฅผ ๋ฃ๋ ๋ฐฉ๋ฒ์ ๋ชจ์ํ๋ ์ค ์ฐพ์ ์์ฑ์ด๋ค. ์ฌ์ฉ์๊ฐ ๋์ ์ผ๋ก ํ ์คํธ๋ ๋ด์ฉ์ ํธ์งํ ์ ์๊ณ , ํ ์คํธ, ์ด๋ฏธ์ง, ๋งํฌ ๋ฑ์ ์์ ํ๊ฑฐ๋ ์ถ๊ฐํ ์ ์๊ฒ ํด ์ค๋ค. div contenteditable์๋ ์ ๋ ฅํ๋ ์์๋ค์ด html๋ก ๋ณํ๋์ด ๋ค์ด๊ฐ๋ค.
<Box contentEditable={true}></Box>
React๋ ์ฌ์ฉ์ ์ ๋ ฅ์ ๋ ๋๋ง ํ ๋ ์์ฒด์ ์ผ๋ก ์ด์ค์ผ์ดํ ์ฒ๋ฆฌ๋ฅผ ํ์ฌ ํด๋น ์ ๋ ฅ์ด HTML ํ๊ทธ๋ก ํด์๋์ง ์๊ฒ ํ์ฌ ์๋์ผ๋ก XSS ๊ณต๊ฒฉ์ ๋ฐฉ์งํ๋ค๊ณ ํ๋ค… ํ์ง๋ง contentEditable ์์ฑ์ ํตํด ์ฌ์ฉ์๊ฐ ์ง์ ๋ด์ฉ์ ํธ์งํ๊ณ ์์ ํ๋ ๊ณผ์ ์์ ์ ๋ ฅ / ์กฐํ ์์๋ค์ด html๋ก ๋ณํ๋์ด ๋ค์ด๊ฐ๋ ๊ฒ์ ํ์ฉํ๊ณ , ๋๋ฌธ์ ์ ์์ ์ธ ์ฌ์ฉ์๊ฐ ์คํฌ๋ฆฝํธ๋ฅผ ์ฝ์ ํ๊ฑฐ๋ (XSS ๊ณต๊ฒฉ) ๋ค๋ฅธ ๊ณต๊ฒฉ์ ์๋ํ ์ ์๋ค๊ณ ํ๋ค… ์ด๋ฐ ์ด์ ๋๋ฌธ์ ๊ธ ์ฌ์ด ์ด๋ฏธ์ง ๋ฃ๋ ์๋น์ค๋ฅผ ๋ง์ด ๋ชป ๋ณธ ๊ฑด๊ฐ ์ถ๊ธฐ๋ ํ๊ณ …
2. ์ด๋ฏธ์ง๋ฅผ base64 url๋ก ๋ณ๊ฒฝ
type์ด file์ธ input ํ๊ทธ์ ๋ด๊ธด ํ์ผ์ input.current.files๋ก ์ถ์ถ ํ FileReader๋ก ํ์ผ ๋ด์ฉ์ base 64 ๋ฐ์ดํฐ url๋ก ์ฝ์ด์จ๋ค. img ํ๊ทธ๋ฅผ ์์ฑํ๊ณ content editable ์์ ๋ด ๋์ ์ผ๋ก ์ถ๊ฐํด ์ค๋ค.
function KeyboardFixedElement(){ const fileInputRef = useRef<HTMLInputElement | null>(null); function handleFileChange() { if ( fileInputRef.current && fileInputRef.current.files && fileInputRef.current.files.length > 0 // ์ ํํ ํ์ผ์ด ์๋ ๊ฒฝ์ฐ ) { const files = fileInputRef.current.files; for (let i = 0; i < files.length; i++) { const file = files[i]; const reader = new FileReader(); reader.onloadend = () => { // ํ์ผ์ base 64 url๋ก ๋ณํ if (reader.result) { const newNode = document.createElement("img"); newNode.src = reader.result as string; newNode.alt = "ํฌ์คํ ์ด๋ฏธ์ง"; newNode.style.maxWidth = "100%"; const targetElement = document.getElementById("contentEditable"); targetElement && targetElement.appendChild(newNode); // ๋ณํํ url์ src๋ก ๊ฐ๋ ์ด๋ฏธ์ง ํ๊ทธ๋ฅผ contenteditable ๋ณธ๋ฌธ์ ์ฝ์ } }; reader.readAsDataURL(file); } } } return ( ... <input type="file" accept="image/*" ref={fileInputRef} style={{ display: "none" }} onChange={handleFileChange} multiple /> ... ) }
์ถ๊ฐ๋ก ๋ชจ๋ ํ๋๊ฐ ์์ฑ ์๋ฃ๋ ๊ฒฝ์ฐ์๋ง ์ ์ถ ๋ฒํผ์ ํ์ฑํ์ํจ๋ค๊ณ ํ์ ๋, ๋ณธ๋ฌธ ๋ด์ฉ(innerHTML) ๊ฐ๋ณ๊ฐ์ ์ํ๊ฐ์ผ๋ก ๊ด๋ฆฌํด์ฃผ๋ ค ํ์ง๋ง div์ ๋ค๋ฅธ input, textarea ํ๊ทธ๋ค์ฒ๋ผ onchange ์ด๋ฒคํธ๊ฐ ๋ฐ์ํ์ง ์๊ณ placeholder๋ฅผ ์ง์ ํ ์ ์๋ ์ด์๊ฐ ์์ด DOM ์์์ ๋ณ๊ฒฝ์ ๊ฐ์งํ ์ ์๋๋ก mutation observer๋ฅผ ๋ฑ๋กํ๋ค...
function GeneralPostForm(){ const editableDivRef = useRef<HTMLDivElement | null>(null); ... useEffect(() => { const observerCallback: MutationCallback = (mutationsList, observer) => { for (const mutation of mutationsList) { if ( mutation.type === "childList" || mutation.type === "characterData" ) { handleContentChange(editableDivRef.current?.innerHTML); } } }; const observer = new MutationObserver(observerCallback); if (editableDivRef.current) { const observerConfig: MutationObserverInit = { childList: true, subtree: true, characterData: true, }; observer.observe(editableDivRef.current, observerConfig); } return () => { observer.disconnect(); }; }, []); ... return ( <Box contentEditable id="contentEditable" onBlur={handlePlaceholderChange} ref={editableDivRef} onFocus={handlePlaceholderChange} ></Box> ) }
3. img base url ์ถ์ถ
๋ณธ๋ฌธ ๋ด์ฉ์ ์ฝ์ ํ ์ด๋ฏธ์ง Data URL ๋ด base64๋ก ์ธ์ฝ๋ฉํ ์ด๋ฏธ์ง๋ฅผ ์ถ์ถํ๊ณ …
// img str ์ถ์ถ ํจ์ function extractImgBaseStr() { const innerHTML = document.querySelector("#contentEditable")!.innerHTML; const imgSrcPattern = /data:[^"]+/g; // Data URL ์ ๊ทํํ์ const encodedImgLst = innerHTML.match(imgSrcPattern) || []; return encodedImgLst; }
4. img url form data ๋ณํ ์ ๋ก๋
Data URL์ ์ฌ์ฉํ์ฌ ๋ค์ ์ด๋ฏธ์ง ์ ๋ก๋ ํ์ผ ๊ฐ์ฒด๋ฅผ ์์ฑํ์ฌ ์ ๋ฌํด์ผ ํ ํํ์ form data๋ก ๋ณํํ๋ค.
function addBase64ImagesToFormData(base64Images: any, formData: any) { base64Images.forEach((base64ImageData: any, index: number) => { const fileName = `img${index + 1}.jpeg`; / const file = dataURLtoFile(base64ImageData, fileName); formData.append("multipartFiles", file); }); } function dataURLtoFile(dataurl: string, fileName: string) { const arr = dataurl.split(","); const mime = arr[0].match(/:(.*?);/)?.[1]; const bstr = atob(arr[1]); let n = bstr.length; const u8arr = new Uint8Array(n); while (n--) { u8arr[n] = bstr.charCodeAt(n); } return new File([u8arr], fileName, { type: mime }); }
s3 ์๋ฒ์ ์ฌ๋ฆฐ ํ s3 url์ ๋ฐ์์จ ํ…
5. ๋ณธ๋ฌธ์์ ์์ ์ถ์ถํ img base url์ blob url์ s3 url๋ก ๋ณํํ๊ณ
์ต์ข s3 url๋ก ๋์ฒด๋ img ํ๊ทธ๊ฐ ํฌํจ๋ ๋ณธ๋ฌธ์ ์ ์ถํ๋ค~ ๋์
// img blob string์ s3 ์ด๋ฏธ์ง ๋งํฌ๋ก replace ํ๋ ํจ์ function replaceImgStrToS3(imgUrls: string[]) { let newInnerHTML = document.querySelector("#contentEditable")!.innerHTML; const encodedImgLst = extractImgBaseStr(); for (let i = 0; i < imgUrls.length; i++) { const replaceFrom = encodedImgLst[i]; const replaceTo = imgUrls[i]; newInnerHTML = newInnerHTML.replace(`${replaceFrom}`, replaceTo); } return newInnerHTML; }
๐ ํ ์ค ์์ฝ
๊ฐ๋ฅํ ๋ฐฉ๋ฒ์ ์ฐพ๋ ๊ณผ์ ์์ ๋ง์ด ํค๋งค์์ง ๋๋ถ๋ถ์ ์ฝ๋๋ ์งํผํฐ๊ฐ ๋์์คฌ๋ค,,,
๊ด๋ จ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ์๋ค๋ ๊ฒ๋ ์ ๊ธฐํ๊ณ … ๋ ๋์ ๋ฐฉ๋ฒ์ด ์๋๋ฐ ๋ณต์กํ ๋ฐฉ๋ฒ์ผ๋ก ์ฐํํ ๊ฑด๊ฐ ์ถ๊ธฐ๋ ํ๊ณ …
๊ทธ๋๋ ๋น์ฅ ๊ธํ์ ๋ถ๋ค์ ์์ด๋์ด๋ฅผ ์ป์ ์ ์์ง ์์๊น ํด์ ๊ณต์ ํด ๋ด ๋๋ค…
'๐ FE' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
์ฌ๋ด ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๊ฐ๋ฐ ๊ธฐ๋ก (feat. monorepo, submodule) (0) 2024.04.09 - ์ด๋ฏธ์ง๋ฅผ ์ ํํ ๋๋ง๋ค s3 ์คํ ๋ฆฌ์ง์ ์ด๋ฏธ์ง๋ฅผ ์ ์ฅํ๊ณ → ์๋ต์ผ๋ก ๋๊ฒจ์ค ์ฃผ์๋ฅผ img ํ๊ทธ๋ก ์ถ๊ฐํ์ฌ ํ๋ก ํธ์ ๋ณด์ฌ์ฃผ๋ ๋ฐฉ๋ฒ