본문 바로가기
Spring공부/3-파일업로드

파일업로드(2)

by 으노으뇨 2021. 10. 13.
728x90
반응형
SMALL

파일업로드 전 고려 해야할 사항 4가지가 있다 이 4가지를 처리하는 것을 위주로 공부해보겠다.

  1. 파일의 확장자나 크기 사전처리
  2. 중복이름처리
  3. 이미지파일과 일반파일을 구별

이렇게 있다. 최근 포털에서도 특정한 확장자를 제외한 파일들의 업로드를 제한 하는 경우가 많다.

이는 첨부파일을 이용하는 웹 공격을 막기위해 행해지는 조치라고 한다.

첨부파일의 확장자가 exe, sh, zip 등일 경우에는 업로드를 제한하고, 특정 크기 이상의 파일은 업로드 할 수 없도록

제한하는 처리를 JS로 해보자

확장자의 경우는 정규표현식으로 처리가능하다.


upload.jsp일부에 아래와 같은 것을 추가하자

			var regex = new RegExp("(.*?)\.(exe|sh|zip|html|jsp)$");
			var maxSize = 5242880; //5MB

			function checkExtension(fileName, fileSize) {

				if (fileSize >= maxSize) {
					alert("파일 사이즈 초과!! *5mb 이하로 업데이트");
					return false;
				}
				if (regex.test(fileName)) {
					alert("해당 종류의 파일은 업로드할 수 없습니다. *이미지파일만!");
					return false;
				}
				return true;
			}

			$("#uploadBtn").on("click", function(e) {

				var formData = new FormData();

				var formData = new FormData();

				var inputFile = $("input[name='uploadFile']");

				var files = inputFile[0].files;

				//console.log(files);

				for (var i = 0; i < files.length; i++) {

					if (!checkExtension(files[i].name, files[i].size)) {
						return false;
					}

					formData.append("uploadFile", files[i]);

				}

정규표현식을 이용해서 확장자 검사를 하도록 했다.

우선 간단한예를위해서 html, jsp 파일도 오류나도록 설정했다.

이것을 js에서 처리하는 이유는 웹에서 사전적으로 처리하고, 컨트롤 부분으로 보내주기 위함이다. 이미 컨트롤단으로 보내고 그곳에서 판단하고 다시 내보내면 처리속도라던가 문제가 생길것같아서 이다.

그리고 5mb 초과 또는 적용불가 확장자를 구별하기 위해 checkExctension이라는 함수를 새로만들고, 그것에 대해 하나라도 false값을 낸다면 append되지 않도록 구현했다.

다시 톰캣을 시작하고, http://localhost:8080/upload로 확인을 해봤다.

지난 프로젝트의 jsp파일 4개를 이용해서 업로드해보겠다.

upload버튼을 누르는 순간 업로드 할수없다고 경고창으로 알려준다.

첨부파일을 업로드 하면 for 루프에서 checkExtension()을 호출해서 확장자와 파일크기를 체크한다.


중복된 이름의 첨부파일처리와 폴더관리

첨부파일을 저장할 때 신경쓰이는 것중 제일 신경써야할 부분이다.

전에 프로젝트를 진행할때도 사진파일이라던가 동영상파일업로드 기능을 구현할때 고민을 좀 했었다.

그리고 계속 진행을 했을땐 한 폴더 수에 엄청나게 많은 사진파일이 계속 쌓이는 것이었다. 

우선 사진을 사진이름1.jpg, 사진이름2.jpg 이렇게 저장을 했었고, 폴더도 한폴더내에 저장시켰다.

이번 프로젝트에서는

1. UUID방식으로 파일이름저장시키가.
(현재시간을 밀리세컨드로 구분해 파일이름을 생성해서 저장시키거나 기타 알아보기힘들정도로 해서 저장시키는 법)

2. 하루하루 년/월/일 단위의 폴더를 생성해서 파일을 저장시키기

이렇게 구현해보겠다

년/월/일 폴더 생성시키기

첨부파일을 보관하는 폴더를 생성하는 작업은 한 번에 폴더를 생성하거나 존재하는 폴더를 이용하는 방식을 사용한다.

java.io.file에 존재하는 mikdirs()를 이용하면 필요한 상위폴더까지 한번에 생성시킬수있다!

우선 UploadController 에

	private String getFolder() {
		SimpleDateFormat t = new SimpleDateFormat("yyyy-mm-dd");
		Date date = new Date();
		String str = t.format(date);

		return str.replace("-", File.separator);
	}

를 추가한다. 이것은 날짜 형식으로 , 현재의 날짜를 가져오는 것으로, 폴더만들때 사용하도록하는것이다.

@PostMapping("/uploadAjaxAction")
	public void uploadAjaxPost(MultipartFile[] uploadFile) {

		String uploadFolder = "C:\\programing\\upload";

		// make folder --------
		File uploadPath = new File(uploadFolder, getFolder());
		log.info("upload path: " + uploadPath);

		if (uploadPath.exists() == false) {
			uploadPath.mkdirs();
		}
		// make yyyy/MM/dd folder

		for (MultipartFile multipartFile : uploadFile) {

			log.info("-------------------------------------");
			log.info("Upload File Name: " + multipartFile.getOriginalFilename());
			log.info("Upload File Size: " + multipartFile.getSize());

			String uploadFileName = multipartFile.getOriginalFilename();

			// IE has file path
			uploadFileName = uploadFileName.substring(uploadFileName.lastIndexOf("\\") + 1);
			log.info("only file name: " + uploadFileName);
            
			File saveFile = new File(uploadPath, uploadFileName);

			try {

				multipartFile.transferTo(saveFile);
			} catch (Exception e) {
				log.error(e.getMessage());
			} // end catch

		} // end for

	}

그리고 기존의 UploadAjaxPost메서드를 수정해주어야한다.

수정한 것은 크게 없고, 

중간부분 if문을 이용해서 해당 이름의 폴더가 존재하면그곳에 저장하도록 계속진행되고, 만약 없다면

.mkdirs()를 이용해서 폴더가 생성되게 된후 진행 되게된다.

getFolder()는 오늘날짜의 경로를 문자열로 생성한다. 생성된 경로는 폴더 경로로 수정된 뒤에 반환된다.

 uploadAjaxPost()에서는 해당 경로가 있는지 검사하고, 폴더를 생성한다.

동일하게 작동시키고 예제는 언제나동일하게 사진파일로 진행하였다.

C:\programing\upload\2021\47\13

라는 경로로 저장됨을 알 수 가 있다. 성공적으로 저장되었다. 


중복장지를 위한 UUID 적용

파일이름을 생성할 때 동일한 이름으로 업로드되면 기존 파일을 지우게됨 - 즉 덮어씌운다.

파일 네이밍하는 방법은 여러가지가 있지만 이번엔 UUID를 이용하겠다.

java.uit.UUID 의 값을 이용하자

	@PostMapping("/uploadAjaxAction")
	public void uploadAjaxPost(MultipartFile[] uploadFile) {

		String uploadFolder = "C:\\programing\\upload";

		// make folder --------
		File uploadPath = new File(uploadFolder, getFolder());
		log.info("upload path: " + uploadPath);

		if (uploadPath.exists() == false) {
			uploadPath.mkdirs();
		}
		// make yyyy/MM/dd folder

		for (MultipartFile multipartFile : uploadFile) {

			log.info("-------------------------------------");
			log.info("Upload File Name: " + multipartFile.getOriginalFilename());
			log.info("Upload File Size: " + multipartFile.getSize());

			String uploadFileName = multipartFile.getOriginalFilename();

			// IE has file path
			uploadFileName = uploadFileName.substring(uploadFileName.lastIndexOf("\\") + 1);
			log.info("only file name: " + uploadFileName);

			UUID uuid = UUID.randomUUID();

			uploadFileName = uuid.toString() + "_" + uploadFileName;

			File saveFile = new File(uploadPath, uploadFileName);

			try {

				multipartFile.transferTo(saveFile);
			} catch (Exception e) {
				log.error(e.getMessage());
			} // end catch

		} // end for

	}

어려울것없다.

그저 기존 매서드에 딱 두줄이 추가되었을 뿐이다.

uploadFileName = uuid.toString() + "_" + uploadFileName;
File saveFile = new File(uploadPath, uploadFileName);

그리고 UUID 생성자를 이용해서 

작성한다. 더보기를 눌러서 어떻게 이게 저 두줄만에 작동하는지 알아보자.

더보기

UUID 클래스는

public final class UUID implements java.io.Serializable, Comparable<UUID> {

과같이 정의되어있다.

그리고 randomUUID 메서드는

        SecureRandom ng = Holder.numberGenerator;

        byte[] randomBytes = new byte[16];
        ng.nextBytes(randomBytes);
        randomBytes[6]  &= 0x0f;  /* clear version        */
        randomBytes[6]  |= 0x40;  /* set to version 4     */
        randomBytes[8]  &= 0x3f;  /* clear variant        */
        randomBytes[8]  |= 0x80;  /* set to IETF variant  */
        return new UUID(randomBytes);

로 정의 되어있다. 이점을 참고하자

그럼이제 다시 테스트를 진행하자

돌리고 나서 업로드가 성공되었다고 한다. 그런데 이상하게 폴더내에는 생성되지 않았다? 무슨일이지?

아하 그새 또다른 폴더를 생성했다.

이렇게 아주 어려운 이름으로 결과값을 받게된다.


썸네일 이미지 생성

이미지의 경로에 대한 처리와 중복 이름에 대한 처리가 되었다. 

이제 일반 파일과 이미지파일을 구분하는 방법이 남았다.

이미지 파일의 경우에는 화면에 보여지는 작은 이미지를 생성하는 추가적인 처리 방식이다.

페북이나 인스타에 사진을 올릴때 사진의 경우는 해당파일이 작은 사진으로 작게 나타난다. 

그것을 구현해보려고 한다.

섬네일을 제작하는 방법은 여러가지 방식이 있다. JDK1.4부터는 ImageIO를 제공하기 때문에 이를 이용해서 원본 이미지의 크기를 줄일 수도 있고, ImageScalr와 같은 별도의 라이브러리를 이용하는 방식도 있다. 

JDK의 API를 이용하는 방식보다 별도의 라이브러를 사용해보자

Thumbnailator 라이브러리를 이용해서 썸네일 이미지를 생성해보자

우선 pom.xml 에 의존성을 주입한다.

		<!-- https://mvnrepository.com/artifact/net.coobird/thumbnailator -->
		<dependency>
			<groupId>net.coobird</groupId>
			<artifactId>thumbnailator</artifactId>
			<version>0.4.8</version>
		</dependency>

이제 앞으로 해야할 것은 uploadController에

업로드된 파일이 이미지 종류의 파일인지, 이미지 파일의 경우에는 섬네일 이미지 생성 및 저장시키기

를 해야한다.

이미지 파일의 판단

화면에서 약간의 검사를 통해서 업로드되는 파일의 확장자를 검사하기는 하지만, Ajax로 사용하는 호출은 반드시 브라우저만을 통해서 들어오는 것이 아니므로 확인할 필요가 있다.

서버에 업로드된 파일은 조금 시간이 걸리더라도 파일 자체가 이미지인지 정확히 체크한뒤에 저장하도록 해야한다.

말로만 .jpg파일일수있기에..

특정한 파일이 이미지 타입인지 검사하는 별도의 메서드를 생성하자

	private boolean checkImageType(File file) {
		try {
			String contentType = Files.probeContentType(file.toPath());
			return contentType.startsWith("image");
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return false;
	}

파일 매개변수를 받을때 파일이 이미지인지 확인하고 그것을 통해 참/거짓을 넘겨준다. 

더보기

    public static String probeContentType(Path path)
        throws IOException
    {
        // try installed file type detectors
        for (FileTypeDetector detector: FileTypeDetectors.installeDetectors) {
            String result = detector.probeContentType(path);
            if (result != null)
                return result;
        }

        // fallback to default
        return FileTypeDetectors.defaultFileTypeDetector.probeContentType(path);
    }

를 통해 이미지파일인지 확인한다

그리고 기존의 uploadAjaxPost 매서드도 이에 맞게 수정을하자

	@PostMapping("/uploadAjaxAction")
	public void uploadAjaxPost(MultipartFile[] uploadFile) {

		String uploadFolder = "C:\\upload";

		// make folder --------
		File uploadPath = new File(uploadFolder, getFolder());
		log.info("upload path: " + uploadPath);

		if (uploadPath.exists() == false) {
			uploadPath.mkdirs();
		}
		// make yyyy/MM/dd folder

		for (MultipartFile multipartFile : uploadFile) {

			log.info("-------------------------------------");
			log.info("Upload File Name: " + multipartFile.getOriginalFilename());
			log.info("Upload File Size: " + multipartFile.getSize());

			String uploadFileName = multipartFile.getOriginalFilename();

			// IE has file path
			uploadFileName = uploadFileName.substring(uploadFileName.lastIndexOf("\\") + 1);
			log.info("only file name: " + uploadFileName);

			UUID uuid = UUID.randomUUID();

			uploadFileName = uuid.toString() + "_" + uploadFileName;

			try {
				File saveFile = new File(uploadPath, uploadFileName);
				multipartFile.transferTo(saveFile);
				// check image type file
				if (checkImageType(saveFile)) {

					FileOutputStream thumbnail = new FileOutputStream(new File(uploadPath, "thumb_" + uploadFileName));

					Thumbnailator.createThumbnail(multipartFile.getInputStream(), thumbnail, 100, 100);

					thumbnail.close();
				}

			} catch (Exception e) {
				e.printStackTrace();
			} // end catch
		} // end for

	}

UUID 이후 세이브 파일을 만들게하는데 , 변환해서 썸네일로 만든다. 그 썸네일을 100 * 100 사이즈다.

Thumbnailator는 InputStream 과 java.io.File 객체를 이용해서 파일을 생성할 수 있고, 뒤에 사이즈에 대한 부분을 파라미터로 width와 height를 지정할 수 있다.

이제 /upload 를통해서 이미지 파일을 업로드하면 원본파일은 그대로 저장되고, 파일이름이 thumb_로 시작하는 썸네일 파일이 생성될것이다.

총파일 8개, 첨부파일4개, 썸네일파일 4개 이렇게 생성되었다.

그리고 썸네일은 이름앞에 thumb라고 붙여서 나온다.

728x90
반응형
LIST

댓글