이번 프로젝트에서 multipart를 이용하여 파일 업로드 및 다운로드를 구현하는 작업을 하였다.
기존 blob 형태로 upload를 구현하는 것이 아닌 multipart를 활요한 업로드 및 다운로드를 구현하여 조금 헤맨 느낌이 있어 정리를 해야겠다.
일단 처음에는 api 구현부터.
async clientNoticeUpdate(id: string, formData: FormData): Promise<IOoDataResponse<GetClientNoticeApiDataList>> {
const url = `${baseUrl.rest}${endPoint.clientNotice.clientNoticeUpdate}/${id}`;
return await this.axiosInstance.put<IOoDataResponse<GetClientNoticeApiDataList>>(url, formData, {
// 중간에 기존 content-type을 대체하기 위해 추가
headers: {
'Content-Type': 'multipart/form-data',
},
});
}
const clientNoticeCreateAndUpdateApi = async (type: string): Promise<void> => {
setLoading();
try {
const formData = new FormData();
formData.append('title', title);
formData.append('content', content);
formData.append('isActive', isActive === 0 ? 'true' : 'false');
formData.append('startDate', startDate ? moment(startDate).format('YYYYMMDD') : '');
formData.append('endDate', endDate ? moment(endDate).format('YYYYMMDD') : '');
visibilityTarget.map((visibilityTarget) => {
if (visibilityTarget.selected) formData.append('visibilityTarget', visibilityTarget.target);
});
if (file && type === 'create') {
file.map((file) => {
formData.append('files', file);
});
}
let response: any;
if (type === 'create') {
response = await DoctorOnAPI.shared.hospital.clientNoticeCreate(formData);
} else if (type === 'update') {
response = await DoctorOnAPI.shared.hospital.clientNoticeUpdate(state.id, formData);
}
if (isRequestSucceed(response)) {
toast.success(type === 'create' ? '공지사항이 등록되었습니다.' : '공지사항이 수정되었습니다.');
navigate('/clientNoticeList', { replace: true });
setStopLoading();
} else {
toast.error(type === 'create' ? '공지사항 등록에 실패하였습니다.' : '공지사항 수정에 실패하였습니다.');
setStopLoading();
console.log(response, 'response');
}
} catch (e: any) {
console.log(e, 'error');
setStopLoading();
throw new Error(e);
}
};
최대한 어렵지 않게 수정.
한글이 깨지는 현상이 발생하였으나, 서버에서 UTF-8로 인코딩 및 디코딩하여 내려주는 것으로 결정
파일 추가 로직
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
if (e.target.files) {
const selectedFiles: Array<File> = Array.from(e.target.files);
// 파일 개수 제한
if (file.length + selectedFiles.length > 5) {
toast.error('파일은 최대 5개까지 업로드할 수 있습니다.');
return;
}
// 파일 용량 제한
const oversizedFiles = selectedFiles.filter((file) => file.size > 10 * 1024 * 1024);
if (oversizedFiles.length > 0) {
toast.error('파일 용량은 개당 10MB 미만이어야 합니다.');
return;
}
const difference = _.differenceWith(selectedFiles, file, (selected, upload) => _.isEqual(selected.name, upload.name));
e.target.value = '';
if (_.isEmpty(difference)) {
toast.error('중복된 파일입니다.');
return;
}
setFile([...file, ...difference]);
}
};
const onHandleRemove = (e: any) => {
const filterData = file.filter((item) => item.name !== e.currentTarget.value);
setFile(filterData);
};
파일 다운로드 로직
const downloadFileApi = async (fileId: string, fileName: string) => {
try {
const response = await DoctorOnAPI.shared.hospital.ClientNoticeDownloadFile(state.id, fileId);
const aElement = document.createElement('a');
// 위에서 생성한 aElement변수에 href속성에 넣을 데이터를 설정해준다.
const blobFile = window.URL.createObjectURL(new Blob([response]));
aElement.href = blobFile;
aElement.download = fileName;
aElement.click();
setTimeout(() => {
// 이제 더이상 필요 없으니 생성한 a태그를 1초후 삭제 시켜준다.
aElement.remove();
}, 1000);
} catch (e: any) {
console.log(e, 'error');
throw new Error(e);
}
};
서버에서 준 값을 blob처리를 하여 다운로드.
이름은 원래 헤더에서 오는 값을 넣으려고 했으나, 내가 데이터들을 가지고 있기 때문에 fileName을 내려서 사용하기로 결정.
그 뒤 a tag 삭제까지.
추후에 작업할 때 헷갈리지 않도록 잘 알아두자.