KCMF 의 관리자 페이지의 다운로드 기능에서 페이지에 대한 값인 파라미터(pageName)을 추가하는 작업을 진행했다. 각 페이지마다 첨부파일을 다운로드 받으면 그 이력이 남아 다운로드 이력 페이지 목록에 등록되는 과정이었는데, 백엔드 개발자분이 다운로드가 발생한게 어느 페이지인지도 값을 받아야 작업이 수월할 것 같다고 요청을 주신 것이다.
그래서 현재 페이지가 어디인지 url 에서 추출하여 현재 페이지에 대한 string 값을 pageName 파람키로 전달해주었다.
// 다운로드 할 파일 리스트 컴포넌트
...
// PageNameType: 페이지별로 추가될 파라미터키(pageName)의 value 값 모음
const pageNameTypeText = (path: string) => {
switch (path) {
case 'program-apply':
return PageNameType.PROGRAM_APPLY;
case 'program-apply-fee':
return PageNameType.FEE;
case 'review':
return PageNameType.REVIEW;
case 'review-result':
return PageNameType.REVIEW_RESULT;
case 'internal-review':
return PageNameType.INTERNAL_EVALUATION_APPLY;
case 'internal-review-result':
return PageNameType.INTERNAL_EVALUATION;
case 'file':
return PageNameType.FILE;
case 'legacy-file':
return PageNameType.LEGACY_FILE;
default:
return '';
}
};
const FileDownloadList: React.FC<FileDownloadListProps> = ({ fileList, isLegacyFile = false }) => {
const { downloadFile, downloadLegacyFile } = useFileDownload();
const location = useLocation();
// 파라미터로 보내질, 정해진 페이지 string 값 리스트 불러오기
const enumsSelector = useMemo(() => selectEnums(['page-name']), []);
const enums = useSelector(enumsSelector);
const selectedPageName = useMemo(() => {
const firstSegment = location.pathname.split('/')[1]; // 현재 페이지에 대한 이름 url 에서 추출
const pageName = pageNameTypeText(firstSegment);
const findKeyByValue = (targetKey: string): string | undefined => {
const [pageNameEnum] = enums;
const foundItem = pageNameEnum?.find((item) => item?.key === targetKey);
return foundItem?.key;
};
return findKeyByValue(pageName);
}, [location.pathname, enums]);
const handleDownloadFile = async (id: number, filename: string, selectedPageName?: string) => {
try {
downloadFile(id, filename, selectedPageName);
} catch (error) {}
};
if (!fileList) return null;
const fileArray = Array.isArray(fileList) ? fileList : [fileList];
return (
<div>
{fileArray?.map(({ filename, size, attachId, legacyAttachId }) => (
<Box key={shortid.generate()}>
<AttachFileIcon color="primary" sx={{ width: '20px', height: '20px' }} />
<button
type="button"
onClick={() => handleDownloadFile(attachId, filename, selectedPageName)}
>
...
</Box>
))}
</div>
);
};
export default memo(FileDownloadList);
로그인한 상태에서 분명 요청을 보냈는데 백엔드 측에서 쿠키값이 안내려왔다고 한다. 그래서 계속 권한 관련 에러코드가 내려오는 상황이었다…
다른 페이지에서api 요청을 할 때 아래의 공통으로 쓰이는 HTTP 클라이언트를 사용했지만, 다운로드 요청의 경우 추가 로직을 위해 api 요청하는 부분을 따로 하드코딩 했었다.
결정적으로 withCredentials: true
설정이 걸려있지 않아 쿠키가 보내지지 않은 것이 원인이었고, 어이없게도 해당 설정을 추가했더니 정상적으로 다운로드 요청이 갔다.
// 공통 http 클라이언트
import axios, { AxiosInstance } from 'axios';
const httpClient: AxiosInstance = axios.create({
baseURL: import.meta.env.VITE_API_ENDPOINT,
withCredentials: true,
});
export default httpClient;
import axios from 'axios';
const useFileDownload = () => {
const downloadFile = async (id: number, filename: string, pageName: string = '') => {
try {
const { data } = await axios({
url: `${import.meta.env.VITE_API_ENDPOINT}admin/v1/attach/${id}/download?pageName=${pageName}`, // your url
method: 'GET',
responseType: 'blob',
withCredentials: true, // 추가
});
if (data) { // 브라우저에서 다운받을 수 있도록 설정된 로직
const url = window.URL.createObjectURL(new Blob([data]));
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', filename);
link.style.cssText = 'display:none';
link.click();
setTimeout(() => window.URL.revokeObjectURL(url), 0);
}
} catch (error: any) {
console.error(error.response || error);
}
};
return {
downloadFile
};
};
export default useFileDownload;
결과:
다운로드 실행시, 이력에도 데이터가 잘 들어간 것을 알 수 있다.
(참여프로그램 신청 관리란 페이지에서 파일을 다운로드 했고, 그 결과가 'PROGRAM_APPLY' 라는 이름으로 요청 값에 담김)
권한 제약이 있는 요청에 있어 withCredentials
속성에 대해 넣어야 한다는 내용은 익히 들었지만, 실제로 요청 실패가 뜨는 것을 경험했으니 해당 속성에 대한 중요성을 더 알게된 계기라고 생각한다.