※ 많은 레퍼런스들을 참고하여 고민하며 구현한 코드지만 저 스스로도 취업준비생인 초보 개발자이기 때문에 코드 상의 문제 혹은 더 바람직한 우수사례들이 있을 수 있습니다. 코드 작성 시 참고 정도로 활용해주시고 문제나 개선 가능한 부분 발견 시 공유해주신다면 감사하겠습니다.
파일명의 String에서 확장자명을 추출하는 방식으로 타입 체크 시에는 위변조 위험에 쉽게 노출되기 때문에 Apache의 Tika를 이용해보겠습니다
Exception
public class NotImageFileException extends RuntimeException {
public NotImageFileException() {
super();
}
}
이미지 파일이 아닐 시 발생될 Custom Exception 입니다
UploadFileUtils
public class UploadFileUtils {
public static List<String> uploadFile(MultipartHttpServletRequest mtfRequest) throws IOException, NotImageFileException {
// multipartFile 리스트
List<MultipartFile> mtfs = mtfRequest.getFiles("image");
List<String> fileList = new ArrayList<>();
for (MultipartFile mtf : mtfs) {
if (!detectFileType(mtf)) {
throw new NotImageFileException();
}
// 파일 이름
String originalFileName = mtf.getOriginalFilename();
// 공백문자를 언더스코어로 교체하기
originalFileName = originalFileName.replace(' ', '_');
// 업로드 경로
String uploadPath = mtfRequest.getSession().getServletContext().getRealPath(File.separator + "WEB-INF" + File.separator);
// 조회시 과부하를 막기 위한 경로 구분용 현재 날짜를 yyyyMMdd 형태로 반환받기
String date = getTodayDate();
// 파일 이름 중복문제를 해결하기 위한 UUID
String uuid = UUID.randomUUID().toString();
// 데이터베이스에 기록될 경로 형태
// 현재날짜 + UUID + "_" + 파일
String dbFile = File.separator + "uploadedImages" + File.separator + date + uuid + "_" + originalFileName;
// 위에서 생성했던 리스트에 첨부 이미지 데이터 담기
fileList.add(dbFile);
// 파일 저장 경로
String saveFile = uploadPath + dbFile;
// 파일 경로에 저장하기
mtf.transferTo(new File(saveFile));
}
return fileList; // 파일 리스트 반환
}
}
이전에 구현해 사용중이던 파일 업로드 관련 클래스 입니다
해당 코드 부분에서 detectFileType 멤버 함수가 false 반환 시 NotImageFileException을 발생 시킵니다
if (!detectFileType(mtf)) {
throw new NotImageFileException();
}
detectFileType
private static boolean detectFileType(MultipartFile mtf) throws IOException {
Tika tika = new Tika();
InputStream fileInputStream = mtf.getInputStream();
String mimeType = tika.detect(fileInputStream);
return mimeType.startsWith("image");
}
해당 멤버 변수에서는 tika.detect 메소드로 file의 mime type을 String으로 반환받아 image인지 확인하여
Boolean 값을 반환 합니다
ImageController
@RestController
@RequestMapping("/file/")
public class ImageController {
@PostMapping(value="/upload")
public ResponseEntity<List<String>> uploadFile(MultipartHttpServletRequest mtfRequest) throws Exception {
// 파일(들)을 지정된 경로에 저장 및 경로 리스트 반환
try {
List<String> attachedImgs = UploadFileUtils.uploadFile(mtfRequest);
// Image DTO 리스트와 함께 OK status response code 반환
return new ResponseEntity<>(attachedImgs, HttpStatus.OK);
} catch(NotImageFileException e) {
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
}
}
}
컨트롤러에서는 NotImageException 발생 시 클라이언트 측으로 400 status code를 반환 합니다