๋ชฉ์ฐจ
๋ฆฌ์กํธ api axios ์์ ๋ค์ด๋ก๋ ๋ฒํผ ๊ตฌํ react.JS filesaver.saveAs javascript blob response.headers ๊ฐ์์ filename์ถ์ถํ๊ธฐ
๋ฆฌ์กํธ๋ก ํ๋ก์ ํธ๋ฅผ ์งํํ๋ฉด์ ์๋ฒ๋ก๋ถํฐ ์์ ํ์ผ์ ๋ค์ด๋ก๋ ๋ฐ๋ ๊ธฐ๋ฅ์ ๊ตฌํํด์ผ ํ ๊ฒฝ์ฐ๊ฐ ์ข ์ข ์์ต๋๋ค. ํนํ API ํธ์ถ ์ Axios๋ฅผ ์ฌ์ฉํ์ฌ ๋ฐ์ดํฐ๋ฅผ ๋ฐ์์ค๊ณ , ๊ทธ ๋ฐ์ดํฐ๋ฅผ ํ์ผ๋ก ์ ์ฅํ๋ ๊ณผ์ ์์ ์ฌ๋ฌ ๊ฐ์ง ์ ์ฝ๊ณผ ์ด์๋ฅผ ๋ง์ฃผํ๊ฒ ๋ฉ๋๋ค. ์ด๋ฒ ํฌ์คํ ์์๋ ๋ฆฌ์กํธ ํ๊ฒฝ์์ Axios๋ฅผ ํ์ฉํ์ฌ ์์ ํ์ผ ๋ค์ด๋ก๋ ๋ฒํผ์ ๊ตฌํํ๋ ๋ฐฉ๋ฒ๊ณผ ํจ๊ป, response.headers์ ํฌํจ๋ ํ์ผ๋ช ์ ์ถ์ถํ๋ ๊ผผ์์ ๋ํด ์์ธํ ๋ค๋ฃจ๊ณ ์ ํฉ๋๋ค.
๋ฆฌ์กํธ ํ๋ก์ ํธ ๊ฐ์ ๋ฐ ๊ตฌํ ๋ฐฐ๊ฒฝ
๋ฆฌ์กํธ๋ ๋จ์ํ ์ปดํฌ๋ํธ ๊ธฐ๋ฐ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ก์, ๊ธฐ์กด์ PHP๋ Angular์ ๋ฌ๋ฆฌ ํ์ผ ๋ค์ด๋ก๋์ ๊ฐ์ ๊ธฐ๋ฅ์ ์ง์ ๊ตฌํํ๋ ๋ฐ ์์ด ์ ์ฝ์ด ๋ฐ๋ฅด๋ ๊ฒฝ์ฐ๊ฐ ๋ง์ต๋๋ค. ๊ทธ ์ค์์๋ ์์ ํ์ผ ๋ค์ด๋ก๋๋ ์๋ฒ์์ ์์ฑ๋ ํ์ผ์ ํด๋ผ์ด์ธํธ๋ก ์ ์ก๋ฐ์ ์ ์ฅํด์ผ ํ๋ฏ๋ก, ์ธ์ฆ ํ ํฐ๊ณผ ๊ฐ์ ๋ณด์ ์์ ๋ฐ ํ์ผ ํ์ ์ฒ๋ฆฌ ๋ฑ์ด ์ถ๊ฐ์ ์ผ๋ก ํ์ํฉ๋๋ค.
์ค์ ๊ตฌํ ์, Axios๋ฅผ ์ด์ฉํ์ฌ ๋ฐฑ์๋ API๋ฅผ ํธ์ถํ๊ณ , ๊ทธ ๊ฒฐ๊ณผ๋ก ๋ฐ์์จ blob ๋ฐ์ดํฐ๋ฅผ FileSaver ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ์ฌ ์ฌ์ฉ์๊ฐ ๋ฐ๋ก ๋ค์ด๋ก๋ ๋ฐ์ ์ ์๋๋ก ์ฒ๋ฆฌํ๊ฒ ๋ฉ๋๋ค. ๋ค๋ง, ์ด ๊ณผ์ ์์ ๋ฐฑ์๋์์ ์ ์กํ๋ HTTP ์๋ต ํค๋์ ํฌํจ๋ ํ์ผ๋ช ์ ๋ฐ๋ก ์ฌ์ฉํ์ง ๋ชปํ๋ ๋ฌธ์ ๊ฐ ๋ฐ์ํ๋๋ฐ, ์ด๋ ๋ฆฌ์กํธ์ response.headers ๊ฐ์ฒด๊ฐ Angular์ ๋ค๋ฅด๊ฒ ๋์ํ๋ ์ ์์ ๊ธฐ์ธํฉ๋๋ค. ํนํ, HTTP ํค๋์ key ๊ฐ์ ํ์ดํ(-)์ด ํฌํจ๋์ด ์์ ๊ฒฝ์ฐ, ์ผ๋ฐ์ ์ธ ์ ๊ทผ ๋ฐฉ์์ผ๋ก๋ ๊ฐ์ ์ถ์ถํ ์ ์๋ ๊ฒฝ์ฐ๊ฐ ๋ฐ์ํฉ๋๋ค.
Axios๋ฅผ ์ด์ฉํ API ํธ์ถ๊ณผ ํ์ผ ๋ค์ด๋ก๋ ์ฒ๋ฆฌ
์ฐ์ , ํ์ผ ๋ค์ด๋ก๋ ๊ธฐ๋ฅ์ ๊ตฌํํ๊ธฐ ์ํด Axios ์ธ์คํด์ค๋ฅผ ์์ฑํ๊ณ , ๋ฐฑ์๋ API๋ก๋ถํฐ ์์ ํ์ผ์ blob ํ์์ผ๋ก ๋ฐ์์ค๋ ๊ธฐ๋ณธ์ ์ธ ๊ตฌ์กฐ๋ฅผ ์ดํด๋ณด๊ฒ ์ต๋๋ค. ๋ฆฌ์กํธ ํ๋ก์ ํธ์์ Axios๋ฅผ ์ฌ์ฉํ ๋, ํ ํฐ๊ณผ ๊ฐ์ ์ธ์ฆ ์ ๋ณด๊ฐ ํ์ํ๋ค๋ฉด Axios ์ธ์คํด์ค ์์ฑ ์ ์ธํฐ์ ํฐ๋ ๊ธฐ๋ณธ ํค๋์ ํด๋น ์ ๋ณด๋ฅผ ์ถ๊ฐํ ์ ์์ต๋๋ค.
์๋๋ Axios๋ฅผ ํ์ฉํ์ฌ API๋ฅผ ํธ์ถํ๊ณ , ์๋ต ๋ฐ์ดํฐ์ blob์ ๋ฐ์์ค๋ ์์ ์ฝ๋์ ๋๋ค.
// axios ์ธ์คํด์ค ์์ฑ ์์ (์ธ์ฆ ํ ํฐ ํฌํจ)
import axios from "axios";
const authAxios = axios.create({
baseURL: "/api/v1",
headers: {
"Content-Type": "application/json",
// ํ ํฐ์ด ํ์ํ ๊ฒฝ์ฐ ์๋์ ๊ฐ์ด Authorization ํค๋ ์ถ๊ฐ
// "Authorization": `Bearer ${token}`
}
});
์์ ๊ฐ์ด Axios ์ธ์คํด์ค๋ฅผ ์์ฑํ ํ, ์์ ํ์ผ ๋ค์ด๋ก๋๋ฅผ ์ํ API ํธ์ถ์ ์งํํฉ๋๋ค. ์ด๋ responseType์ "blob"์ผ๋ก ์ค์ ํด์ผ ์๋ฒ๋ก๋ถํฐ ํ์ผ ๋ฐ์ดํฐ๋ฅผ ์ ๋๋ก ๋ฐ์์ฌ ์ ์์ต๋๋ค.
ํ์ผ ๋ค์ด๋ก๋ ์ FileSaver ๋ผ์ด๋ธ๋ฌ๋ฆฌ ํ์ฉ
์์ ํ์ผ ๋ค์ด๋ก๋ ๊ธฐ๋ฅ์ ๋ณด๋ค ์์ฝ๊ฒ ๊ตฌํํ๊ธฐ ์ํด FileSaver ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํฉ๋๋ค. FileSaver๋ ๋ธ๋ผ์ฐ์ ์์ blob ๋ฐ์ดํฐ๋ฅผ ํ์ผ๋ก ์ ์ฅํ ์ ์๋๋ก ๋์์ฃผ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๋๋ค. ํ๋ก์ ํธ์ FileSaver๋ฅผ ์ค์นํ๋ ค๋ฉด ์๋์ ๊ฐ์ด ๋ช ๋ น์ด๋ฅผ ์คํํฉ๋๋ค.
yarn add file-saver
์ค์น ํ, ์ปดํฌ๋ํธ ๋ด์์ FileSaver๋ฅผ ์ํฌํธํ์ฌ ์ฌ์ฉํ ์ ์์ต๋๋ค.
import FileSaver from "file-saver";
์ด์ FileSaver๋ฅผ ํ์ฉํ์ฌ ๋ฐ์์จ blob ๋ฐ์ดํฐ๋ฅผ ์ฌ์ฉ์์ ์ปดํจํฐ์ ํ์ผ๋ก ์ ์ฅํ ์ ์์ต๋๋ค.
์๋ต ํค๋์์ ํ์ผ๋ช ์ถ์ถ์ ๋ฌธ์ ์ ๊ณผ ํด๊ฒฐ ๋ฐฉ๋ฒ
๋๋ถ๋ถ์ ๋ฐฑ์๋ ์๋ฒ๋ ํ์ผ์ ์ ์กํ ๋ HTTP ์๋ต ํค๋์ content-disposition ํญ๋ชฉ์ ํ์ผ๋ช ์ ํฌํจ์ํต๋๋ค. ์ผ๋ฐ์ ์ธ ํํ๋ ์๋์ ๊ฐ์ต๋๋ค.
Content-Disposition: attachment; filename="sample.xlsx"
Angular์ ๊ฐ์ด ์ผ๋ถ ํ๋ ์์ํฌ์์๋ response.headers.get('content-disposition')์ ๊ฐ์ด ๊ฐ๋จํ ํ์ผ๋ช ์ ์ถ์ถํ ์ ์์ต๋๋ค. ๊ทธ๋ฌ๋ ๋ฆฌ์กํธ์์ Axios๋ฅผ ์ฌ์ฉํ์ฌ ์๋ต์ ๋ฐ์ ๊ฒฝ์ฐ, response.headers๋ ๋จ์ํ ๊ฐ์ฒด ํํ๋ก ์ ๊ณต๋๋ฉฐ, ํ์ดํ(-)์ด ํฌํจ๋ key ๊ฐ์ ์ง์ ์ ๊ทผํ๋ ๋ฐ ์ ์ฝ์ด ์์ต๋๋ค. ์๋ฅผ ๋ค์ด, ์๋์ ๊ฐ์ด ์๋ํ๋ฉด ์ ๋๋ก ๋ ๊ฐ์ ์ป๊ธฐ ์ด๋ ต์ต๋๋ค.
let filename = response.headers.content-disposition; // Syntax Error ๋ฐ์
๋๋
let filename = response.headers.get("content-disposition"); // null ๋ฐํ
์ด๋ฌํ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด, JSON.stringify()๋ฅผ ์ฌ์ฉํ์ฌ headers ๊ฐ์ฒด๋ฅผ ๋ฌธ์์ด๋ก ๋ณํํ ํ, ๋ฌธ์์ด ๋ด์์ "filename=" ๋ถ๋ถ์ ์ฐพ์ ์ถ์ถํ๋ ๋ฐฉ์์ผ๋ก ํด๊ฒฐํ ์ ์์ต๋๋ค. ์ค์ ๊ตฌํ ์์๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
let filename = JSON.stringify(res.headers)
.split("filename=")[1]
.split('",')[0];
์ด ๋ฐฉ์์ headers ๊ฐ์ฒด๋ฅผ ๋ฌธ์์ด๋ก ๋ณํํ ํ, ํน์ ํจํด("filename=")์ ๊ธฐ์ค์ผ๋ก ๋ถ๋ฆฌ(split)ํ์ฌ ํ์ผ๋ช ๋ง ์ถ์ถํ๋ ๊ผผ์์ ๋๋ค. ๋ฌผ๋ก ์ด ๋ฐฉ์์ ๋ค์ ์ฐํ์ ์ธ ๋ฐฉ๋ฒ์ผ ์ ์์ผ๋, ๋ฆฌ์กํธ์์ ํด๋น ๋ฌธ์ ๋ฅผ ์ฐํํ๊ธฐ ์ํ ํจ๊ณผ์ ์ธ ํด๊ฒฐ์ฑ ์ผ๋ก ๋ง์ด ์ฌ์ฉ๋๊ณ ์์ต๋๋ค.
์์ ๋ค์ด๋ก๋ ๋ฒํผ ๊ตฌํ ์์ ์ฝ๋ ์์ธ ๋ถ์
๋ค์์ ์ค์ ๋ก ์์ ํ์ผ ๋ค์ด๋ก๋ ๋ฒํผ์ ํด๋ฆญํ ๋ ๋์ํ๋ ํธ๋ค๋ฌ ํจ์์ ์ ์ฒด ์ฝ๋์ ๋๋ค. ์ด ์ฝ๋๋ ๊ฒ์ ์กฐ๊ฑด์ ๋ฐ๋ผ API ํธ์ถ ํ๋ผ๋ฏธํฐ๋ฅผ ๊ตฌ์ฑํ๊ณ , Axios๋ก ์์ ํ์ผ ๋ฐ์ดํฐ๋ฅผ blob ํ์์ผ๋ก ๋ฐ์์จ ํ, FileSaver๋ฅผ ์ด์ฉํ์ฌ ํ์ผ๋ก ์ ์ฅํ๋ ๋ก์ง์ ํฌํจํ๊ณ ์์ต๋๋ค.
// ์์
๋ค์ด๋ก๋ ํธ๋ค๋ฌ ํจ์
const handleDown = async (e) => {
// ๊ฒ์ ์กฐ๊ฑด ๋ฐ์ดํฐ (์: ์บ ํ์ธ ์ ๋ชฉ, ๋ฐ์ก ์์์ผ, ๋ฐ์ก ์ข
๋ฃ์ผ)
const { cmpgn_title, send_start_dt, send_end_dt } = campaignSearchData;
// ๋ค์ด๋ก๋ ํ๋ผ๋ฏธํฐ ๊ฐ์ฒด ์์ฑ
let downXls = { send_start_dt: "", send_end_dt: "" };
downXls.cmpgn_title = cmpgn_title;
// ๋ ์ง ํ์ ๋ณํ: null ์ฒดํฌ ํ YYYY-MM-DD ํฌ๋งท์ผ๋ก ๋ณ๊ฒฝ
if (send_start_dt !== null) {
downXls.send_start_dt = changeFormat(send_start_dt, "YYYY-MM-DD");
}
if (send_end_dt !== null) {
downXls.send_end_dt = changeFormat(send_end_dt, "YYYY-MM-DD");
}
try {
// API ํธ์ถ: ์์
ํ์ผ ๋ค์ด๋ก๋ ์์ฒญ, responseType์ blob์ผ๋ก ์ค์
const res = await authAxios.get(
`/api/v1/campaign-report/download?send_start_dt=${downXls.send_start_dt}&send_end_dt=${downXls.send_end_dt}&cmpgn_title=${downXls.cmpgn_title}`,
{ responseType: "blob" }
);
console.log("์๋ต ํค๋:", res.headers);
// ์๋ต ํค๋์์ ํ์ผ๋ช
์ ์ถ์ถํ๋ ๋ก์ง
let filename = JSON.stringify(res.headers)
.split("filename=")[1]
.split('",')[0];
// FileSaver๋ฅผ ์ด์ฉํ์ฌ blob ๋ฐ์ดํฐ๋ฅผ ํ์ผ๋ก ์ ์ฅ
FileSaver.saveAs(res.data, filename);
} catch (error) {
// ์๋ฌ ์ฒ๋ฆฌ: ํ์์ ๋ฐ๋ผ ์ฌ์ฉ์์๊ฒ ์๋ฆผ ๋๋ ๋ก๊น
์ฒ๋ฆฌ
console.error("์์
ํ์ผ ๋ค์ด๋ก๋ ์ค ์ค๋ฅ ๋ฐ์:", error);
}
};
์ ์ฝ๋์์ ์ฃผ๋ชฉํ ์ ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
- ๊ฒ์ ์กฐ๊ฑด ๋ฐ์ดํฐ ์ฒ๋ฆฌ
์ฌ์ฉ์ ์ ๋ ฅ ๋๋ ๊ฒ์ ์กฐ๊ฑด์ ๋ฐ๋ผ API ํธ์ถ ํ๋ผ๋ฏธํฐ๋ฅผ ๋์ ์ผ๋ก ๊ตฌ์ฑํฉ๋๋ค. ์บ ํ์ธ ์ ๋ชฉ๊ณผ ๋ฐ์ก ๋ ์ง(์์์ผ, ์ข ๋ฃ์ผ)๋ฅผ ๋ฐ์์์, ๋ ์ง ํ์์ ๋ณ๋์ changeFormat ํจ์๋ฅผ ํตํด "YYYY-MM-DD" ํ์์ผ๋ก ๋ณ๊ฒฝํฉ๋๋ค. - Axios API ํธ์ถ ์ responseType ์ค์
์์ ํ์ผ๊ณผ ๊ฐ์ด ์ด์ง ๋ฐ์ดํฐ(blob)๋ฅผ ๋ฐ์์ฌ ๋์๋responseType: "blob"
์ ์ค์ ํ์ฌ ์๋ฒ๋ก๋ถํฐ ์ฌ๋ฐ๋ฅธ ๋ฐ์ดํฐ ํ์์ ๋ฐ์์ต๋๋ค. ์ด ์ค์ ์ด ์์ผ๋ฉด ํ์ผ ๋ค์ด๋ก๋ ์ ๋ฐ์ดํฐ๊ฐ ๊นจ์ง๊ฑฐ๋ ์๋ฌ๊ฐ ๋ฐ์ํ ์ ์์ต๋๋ค. - ์๋ต ํค๋์์ ํ์ผ๋ช
์ถ์ถ
์๋ฒ๊ฐ ์ ์กํ HTTP ์๋ต ํค๋์๋Content-Disposition
ํญ๋ชฉ์ ํ์ผ๋ช ์ด ํฌํจ๋์ด ์์ต๋๋ค. ๊ทธ๋ฌ๋ ๋ฆฌ์กํธ์ Axios ์๋ต ๊ฐ์ฒด์์๋ ํด๋น ํค๋์ ์ง์ ์ ๊ทผํ๋ ๋ฐ ๋ฌธ์ ๊ฐ ๋ฐ์ํ๋ฏ๋ก, JSON.stringify๋ฅผ ์ด์ฉํด ๋ฌธ์์ด๋ก ๋ณํํ ํ ๋ฌธ์์ด ์กฐ์์ ํตํด ํ์ผ๋ช ์ ์ถ์ถํฉ๋๋ค. - FileSaver๋ฅผ ํตํ ํ์ผ ์ ์ฅ
ํ์ผ ๋ค์ด๋ก๋๋ฅผ ์ํด FileSaver.saveAs() ํจ์๋ฅผ ํธ์ถํฉ๋๋ค. ์ด๋ ์ฒซ ๋ฒ์งธ ์ธ์๋ก blob ๋ฐ์ดํฐ๋ฅผ, ๋ ๋ฒ์งธ ์ธ์๋ก ์ ์ฅํ ํ์ผ๋ช ์ ์ ๋ฌํ์ฌ ์ฌ์ฉ์๊ฐ ํ์ผ์ ๋ค์ด๋ก๋ ๋ฐ์ ์ ์๋๋ก ํฉ๋๋ค.
ํ์ผ๋ช ์ถ์ถ ๋ฐฉ์์ ํ๊ณ์ ๋ณด์์
์์ ์๊ฐํ ๋ฌธ์์ด ์กฐ์ ๋ฐฉ์์ ํตํ ํ์ผ๋ช ์ถ์ถ์ ๋ฆฌ์กํธ ํ๊ฒฝ์ ํ๊ณ๋ฅผ ๊ทน๋ณตํ๊ธฐ ์ํ ์์๋ฐฉํธ์ ๊ฐ๊น์ต๋๋ค. ์ค์ ์ด์ ํ๊ฒฝ์์๋ ๋ค์๊ณผ ๊ฐ์ ๋ณด์์ ์ ๊ณ ๋ คํด๋ณผ ์ ์์ต๋๋ค.
- ๋ฐฑ์๋์์ ํ์ผ๋ช
์ ์ก ๋ฐฉ์ ๊ฐ์
๋ง์ฝ ๊ฐ๋ฅํ๋ค๋ฉด ๋ฐฑ์๋์์ ํ์ผ๋ช ์ ๋ณ๋์ HTTP ํค๋(์: custom-header: filename)๋ก ์ ์กํ๋ ๋ฐฉ๋ฒ์ ๊ณ ๋ คํ ์ ์์ต๋๋ค. ์ด๋ ๊ฒ ํ๋ฉด ํด๋ผ์ด์ธํธ ์ธก์์ ๋ณด๋ค ์์ ์ ์ผ๋ก ํ์ผ๋ช ์ ์ถ์ถํ ์ ์์ต๋๋ค. - Axios ์๋ต ์ธํฐ์
ํฐ ํ์ฉ
Axios์ ์๋ต ์ธํฐ์ ํฐ๋ฅผ ์ฌ์ฉํ์ฌ ๋ชจ๋ ์๋ต์ ๋ํด ๊ณตํต์ ์ผ๋ก ํ์ผ๋ช ์ถ์ถ ๋ก์ง์ ์ ์ฉํ ์ ์์ต๋๋ค. ์ด ๊ฒฝ์ฐ, ์ฝ๋ ์ค๋ณต์ ์ค์ด๊ณ ์ ์ง๋ณด์๋ฅผ ์ฉ์ดํ๊ฒ ํ ์ ์์ต๋๋ค. - ์ ๊ท ํํ์ ์ฌ์ฉ
๋ฌธ์์ด ๋ถ๋ฆฌ(split) ๋ฐฉ์ ๋์ ์ ๊ท ํํ์์ ์ฌ์ฉํ๋ฉด, ํ์ผ๋ช ์ด ํฌํจ๋ ๋ฌธ์์ด์์ ๋ณด๋ค ์ ์ฐํ๊ฒ ๊ฐ์ ์ถ์ถํ ์ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด, ์๋์ ๊ฐ์ด ๊ตฌํํ ์ ์์ต๋๋ค.์ด ๋ฐฉ์์ ํ์ผ๋ช ์ด ์๋ ๊ฒฝ์ฐ ๊ธฐ๋ณธ ํ์ผ๋ช ("download.xlsx")์ ์ฌ์ฉํ๊ณ , ์ ๊ท ํํ์์ ํ์ฉํ์ฌ ๋ณด๋ค ์ ํํ๊ฒ ํ์ผ๋ช ์ ์ถ์ถํฉ๋๋ค. const contentDisposition = res.headers["content-disposition"] || ""; const filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/; const matches = filenameRegex.exec(contentDisposition); const filename = matches != null && matches[1] ? matches[1].replace(/['"]/g, '') : "download.xlsx";
๋ฆฌ์กํธ ์ปดํฌ๋ํธ ๋ด ๊ตฌํ ์์
๋ค์์ ์์์ ์ค๋ช ํ ๋ด์ฉ์ ๋ฐํ์ผ๋ก ๋ฆฌ์กํธ ์ปดํฌ๋ํธ ๋ด์์ ์์ ๋ค์ด๋ก๋ ๋ฒํผ์ ๊ตฌํํ ์์ ์ ๋๋ค. ์ด ์์ ๋ ์ํ ๊ด๋ฆฌ์ ์ด๋ฒคํธ ํธ๋ค๋ฌ๋ฅผ ํฌํจํ์ฌ ์ค์ ๋์ํ๋ ํํ๋ก ๊ตฌ์ฑ๋์ด ์์ต๋๋ค.
import React, { useState } from "react";
import axios from "axios";
import FileSaver from "file-saver";
const ExcelDownloadComponent = () => {
const [campaignSearchData, setCampaignSearchData] = useState({
cmpgn_title: "",
send_start_dt: null,
send_end_dt: null,
});
// ๋ ์ง ํ์์ YYYY-MM-DD๋ก ๋ณ๊ฒฝํ๋ ์ ํธ๋ฆฌํฐ ํจ์ ์์
const changeFormat = (date, format) => {
// ์ค์ ๊ตฌํ ์ moment.js๋ date-fns์ ๊ฐ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ํ์ฉํ์ฌ ํ์ ๋ณํ ๊ฐ๋ฅ
const d = new Date(date);
const year = d.getFullYear();
const month = ("0" + (d.getMonth() + 1)).slice(-2);
const day = ("0" + d.getDate()).slice(-2);
return `${year}-${month}-${day}`;
};
// Axios ์ธ์คํด์ค ์์ฑ (์ธ์ฆ ํ ํฐ์ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ ํค๋ ์ถ๊ฐ ๊ฐ๋ฅ)
const authAxios = axios.create({
baseURL: "/api/v1",
headers: {
"Content-Type": "application/json",
// "Authorization": `Bearer ${token}`,
},
});
// ์์
๋ค์ด๋ก๋ ํธ๋ค๋ฌ ํจ์
const handleDown = async (e) => {
const { cmpgn_title, send_start_dt, send_end_dt } = campaignSearchData;
let downXls = { send_start_dt: "", send_end_dt: "" };
downXls.cmpgn_title = cmpgn_title;
if (send_start_dt !== null) {
downXls.send_start_dt = changeFormat(send_start_dt, "YYYY-MM-DD");
}
if (send_end_dt !== null) {
downXls.send_end_dt = changeFormat(send_end_dt, "YYYY-MM-DD");
}
try {
const res = await authAxios.get(
`/campaign-report/download?send_start_dt=${downXls.send_start_dt}&send_end_dt=${downXls.send_end_dt}&cmpgn_title=${downXls.cmpgn_title}`,
{ responseType: "blob" }
);
console.log("์๋ต ํค๋:", res.headers);
// ์๋ต ํค๋์์ ํ์ผ๋ช
์ถ์ถ (์ ๊ทํํ์ ์ฌ์ฉ)
const contentDisposition = res.headers["content-disposition"] || "";
const filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
const matches = filenameRegex.exec(contentDisposition);
const filename =
matches != null && matches[1]
? matches[1].replace(/['"]/g, "")
: "download.xlsx";
// FileSaver๋ฅผ ์ฌ์ฉํ์ฌ ํ์ผ ๋ค์ด๋ก๋ ์คํ
FileSaver.saveAs(res.data, filename);
} catch (error) {
console.error("์์
ํ์ผ ๋ค์ด๋ก๋ ์ค ์ค๋ฅ ๋ฐ์:", error);
}
};
return (
<div>
<h2>์์
ํ์ผ ๋ค์ด๋ก๋</h2>
<div>
{/* ์ฌ์ฉ์ ์
๋ ฅ์ ์ํ ๊ฐ๋จํ ํผ ์์ */}
<input
type="text"
placeholder="์บ ํ์ธ ์ ๋ชฉ"
value={campaignSearchData.cmpgn_title}
onChange={(e) =>
setCampaignSearchData({
...campaignSearchData,
cmpgn_title: e.target.value,
})
}
/>
<input
type="date"
placeholder="๋ฐ์ก ์์์ผ"
onChange={(e) =>
setCampaignSearchData({
...campaignSearchData,
send_start_dt: e.target.value,
})
}
/>
<input
type="date"
placeholder="๋ฐ์ก ์ข
๋ฃ์ผ"
onChange={(e) =>
setCampaignSearchData({
...campaignSearchData,
send_end_dt: e.target.value,
})
}
/>
</div>
<button onClick={handleDown}>์์
๋ค์ด๋ก๋</button>
</div>
);
};
export default ExcelDownloadComponent;
์ ์ปดํฌ๋ํธ๋ ์ฌ์ฉ์๋ก๋ถํฐ ์บ ํ์ธ ์ ๋ชฉ๊ณผ ๋ฐ์ก ๋ ์ง๋ฅผ ์
๋ ฅ๋ฐ์, ํด๋น ์กฐ๊ฑด์ ๋ง๋ ๋ฐ์ดํฐ๋ฅผ ์์
ํ์ผ๋ก ๋ค์ด๋ก๋ ๋ฐ๋๋ก ๊ตฌํ๋์ด ์์ต๋๋ค. ํนํ, API ํธ์ถ ํ response.headers์ ํฌํจ๋ content-disposition
์์ ํ์ผ๋ช
์ ์ถ์ถํ๋ ๋ถ๋ถ์ ์ค์ ์๋น์ค ํ๊ฒฝ์์๋ ์์ฃผ ํ์ํ ๊ธฐ์ ๋ก, ๋ณธ ์์ ๋ฅผ ์ฐธ๊ณ ํ์ฌ ์ปค์คํฐ๋ง์ด์ง ํ ์ ์์ต๋๋ค.
๊ตฌํ ์ ๊ณ ๋ คํด์ผ ํ ๋ณด์ ๋ฐ ์ฑ๋ฅ ์ด์
์ค์ ํ๋ก์ ํธ์์ ํ์ผ ๋ค์ด๋ก๋ ๊ธฐ๋ฅ์ ๊ตฌํํ ๋๋ ๋จ์ํ ๊ธฐ๋ฅ ๊ตฌํ ์ธ์๋ ๋ช ๊ฐ์ง ์ถ๊ฐ์ ์ธ ๊ณ ๋ ค ์ฌํญ์ด ์์ต๋๋ค.
- ์ธ์ฆ ๋ฐ ๊ถํ ๊ด๋ฆฌ
API ํธ์ถ ์ ํ ํฐ ๊ธฐ๋ฐ ์ธ์ฆ์ด ํ์์ ์ ๋๋ค. Axios ์ธ์คํด์ค ์์ฑ ์ Authorization ํค๋๋ฅผ ์ถ๊ฐํ์ฌ ์ฌ์ฉ์ ์ธ์ฆ์ ์ฒ ์ ํ ๊ด๋ฆฌํ๊ณ , ๊ถํ์ด ์๋ ์ฌ์ฉ์๊ฐ ํ์ผ ๋ค์ด๋ก๋๋ฅผ ์๋ํ ๊ฒฝ์ฐ ์ ์ ํ ์๋ฌ ์ฒ๋ฆฌ๋ฅผ ๊ตฌํํด์ผ ํฉ๋๋ค. - ํ์ผ ํฌ๊ธฐ ๋ฐ ๋ค์ด๋ก๋ ์๊ฐ
๋์ฉ๋ ํ์ผ์ ๋ค์ด๋ก๋ํ ๊ฒฝ์ฐ, ๋ธ๋ผ์ฐ์ ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋๊ณผ ๋ค์ด๋ก๋ ์๊ฐ์ ์ฃผ์ํด์ผ ํฉ๋๋ค. ํ์์ ๋ฐ๋ผ ๋ค์ด๋ก๋ ์งํ ์ํ(progress)๋ฅผ ์ฌ์ฉ์์๊ฒ ์๊ฐ์ ์ผ๋ก ์ ๊ณตํ๋ ๊ฒ๋ ์ข์ UX๋ฅผ ์ํด ๊ณ ๋ คํด๋ณผ ์ ์์ต๋๋ค. - ์๋ฌ ์ฒ๋ฆฌ ๋ฐ ์ฌ์ฉ์ ํผ๋๋ฐฑ
ํ์ผ ๋ค์ด๋ก๋ ์ค ๋คํธ์ํฌ ์ค๋ฅ๋ ์๋ฒ ์ค๋ฅ๊ฐ ๋ฐ์ํ ์ ์์ต๋๋ค. ์ด๋ฌํ ๊ฒฝ์ฐ, ์ฌ์ฉ์์๊ฒ ๋ช ํํ ์๋ฌ ๋ฉ์์ง๋ฅผ ์ ๊ณตํ๊ณ , ์ฌ์๋ ์ต์ ์ด๋ ๋์ฒด ๊ฒฝ๋ก๋ฅผ ์๋ดํ๋ ๊ฒ์ด ์ค์ํฉ๋๋ค. - ํฌ๋ก์ค ๋ธ๋ผ์ฐ์ ํธํ์ฑ
FileSaver ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ ๋๋ถ๋ถ์ ์ต์ ๋ธ๋ผ์ฐ์ ์์ ์ง์๋์ง๋ง, ํน์ ํ๊ฒฝ์์๋ ๋์ํ์ง ์์ ๊ฐ๋ฅ์ฑ๋ ์์ต๋๋ค. ํฌ๋ก์ค ๋ธ๋ผ์ฐ์ ํ ์คํธ๋ฅผ ํตํด ๋ค์ํ ํ๊ฒฝ์์ ํ์ผ ๋ค์ด๋ก๋ ๊ธฐ๋ฅ์ด ์ํํ ์๋ํ๋์ง ํ์ธํด์ผ ํฉ๋๋ค.
๊ฒฐ๋ก
๋ฆฌ์กํธ ํ๊ฒฝ์์ ์์
ํ์ผ ๋ค์ด๋ก๋ ๋ฒํผ์ ๊ตฌํํ๋ ๊ฒ์ ๋จ์ํ API๋ฅผ ํธ์ถํ๋ ๊ฒ ์ด์์ ๋ณต์กํ ๊ณผ์ ์ ๋ดํฌํ๊ณ ์์ต๋๋ค. Axios๋ฅผ ์ด์ฉํ์ฌ ์๋ฒ๋ก๋ถํฐ blob ๋ฐ์ดํฐ๋ฅผ ๋ฐ์์ค๊ณ , FileSaver๋ฅผ ์ฌ์ฉํ์ฌ ํ์ผ์ ์ ์ฅํ๋ ๊ณผ์ ์์ HTTP ํค๋์ ํฌํจ๋ ํ์ผ๋ช
์ ์ถ์ถํ๋ ๋ถ๋ถ์ ํนํ ์ฃผ์๊ฐ ํ์ํฉ๋๋ค.
์ด๋ฒ ํฌ์คํ
์์๋ ํ์ผ๋ช
์ถ์ถ์ ์ํด JSON.stringify()์ ๋ฌธ์์ด ์กฐ์, ๋๋ ์ ๊ท ํํ์์ ํ์ฉํ ๋ฐฉ๋ฒ์ ์๊ฐํ์์ผ๋ฉฐ, ์ค์ ์ฝ๋ ์์ ๋ฅผ ํตํด ๋ณด๋ค ๊ตฌ์ฒด์ ์ผ๋ก ๊ตฌํ ๋ฐฉ๋ฒ์ ์ค๋ช
๋๋ ธ์ต๋๋ค. ์ด์ ๊ฐ์ ๊ธฐ์ ์ ํ์ฉํ๋ฉด, ๋ฆฌ์กํธ ํ๋ก์ ํธ ๋ด์์๋ ์์
ํ์ผ ๋ค์ด๋ก๋ ๊ธฐ๋ฅ์ ์์ฝ๊ฒ ๊ตฌํํ ์ ์์ผ๋ฉฐ, ๋ค์ํ ์กฐ๊ฑด์ ๋ฐ๋ฅธ ๊ฒ์ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ๋ก ์์
ํ์ผ๋ก ์ ์ฅํ๋ ๊ธฐ๋ฅ์ ์ ๊ณตํ์ฌ ์ฌ์ฉ์ ๊ฒฝํ์ ํฌ๊ฒ ํฅ์์ํฌ ์ ์์ต๋๋ค.
์์ผ๋ก๋ ๋ค์ํ ํ์ผ ์ฒ๋ฆฌ ๋ฐ ๋ค์ด๋ก๋ ๊ด๋ จ ๊ธฐ๋ฅ์ ๋ํด ์ง์์ ์ผ๋ก ํ์ตํ๊ณ ์ ์ฉํด ๋๊ฐ์๊ธธ ๋ฐ๋ผ๋ฉฐ, ์ด๋ฒ ํฌ์คํ ์ด ์ฌ๋ฌ๋ถ์ ๊ฐ๋ฐ ์ ๋ฌด์ ๋์์ด ๋๊ธธ ๋ฐ๋๋๋ค.
๋๊ธ