ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Servlet] 파일업로드시 현재날짜로 디렉토리 생성하기, UUID로 파일명 저장하기
    JAVA 2022. 7. 14. 00:41

     

     

    서버에 파일업로드를 할 때, 자동으로 날짜별 폴더로 구분지어놓으면 관리하기 편할 것이다.

    또한 파일명을 UUID로 지정한다면 중복파일명이 생기는 것도 방지할 수 있다. 

     

    UUID(universally unique identifier)란?

    범용고유식별자.
    128비트의 숫자이며, 32자리의 16진수로 표현된다.
    여기에 8-4-4-4-12 글자마다 하이픈을 집어넣어 5개의 그룹으로 구분한다.
    (ex. 550e8400-e29b-41d4-a716-446655440000)
    세션ID를 설정할 때나, 중복되지 않는 고유한 ID값을 생성하고자 할 때 사용한다.

     

     

    이렇게 파일업로드를 하면 자동으로 오늘날짜로 폴더가 생성되고,

    파일명은 '날짜_UUID'로 저장하는 방법을 간단하게 구현해보았다.

     


     

    입력받을 html파일 생성

     

    ...
    <form 
        	action="/Upload" 
        	method="post" 
        	enctype="multipart/form-data">
    
            1. 작성자: <input type="text" name="writer"><br><br>
            2. 업로드 파일: <input type="file" name="uploadFile" multiple><br>
            
            <p></p>
            
            <input type="submit" value="업로드">
            
    </form>
    ...

    먼저 파일업로드를 할 수 있도록 html파일을 하나 생성해 input태그로 파일을 받을 수 있게 만든다.

    form태그의 enctype="multipart/form-data" 속성을 추가하는 것이 중요하다.

    끝에 multiple 속성을 붙이면 여러개 파일을 한번에 업로드할 수 있다.

     

    파일 input태그에서 name을 지정해주면, 해당 이름으로 값이 전송된다.

     


    파일업로드 Servlet 생성하기

     

    ...
    // 1024 = 1kb, 1024 * 1024 = 1mb
    // maxFileSize = 여러개 파일의 합산크기
    // maxRequestSize = 하나의 요청 당 크기
    @MultipartConfig(location="C:/temp/upload", maxFileSize = 1024 * 1024 * 20) 
    
    @WebServlet("/Upload")
    public class UploadServlet extends HttpServlet {
    	private static final long serialVersionUID = 1L;

     

     

    전송할 url로 매핑한 서블릿을 생성하고,

    @MultipartConfig 어노테이션을 붙여준다.

     

    이 어노테이션의 속성으로 몇가지가 있다.

     

    속성명 설명
    location 파일이 저장될 경로를 지정한다. 단, 실제로 존재하는 경로만 지정이 가능하며, 자동으로 폴더를 생성해주진 않는다.
    maxFileSize 업로드할 파일의 크기를 제한한다. 단위는 byte이다. 여러개의 파일을 올리는 경우, 모든 파일을 합산한 크기로 계산된다.
    maxRequestSize 업로드할 파일의 크기를 제한한다. 단위는 byte이다. 파일 하나당 크기를 제한하고 싶을 때 사용한다.

     

    현재설정한 maxFileSize는 1024 * 1024 * 20.

    1024 byte = 1kb이므로, 설정한 파일크기는 20mb가 된다.

     

     


     

    html에서 파일 업로드 후 전송하면, 각각의 데이터들은 Part로 구분되어 날아온다.

    잠시 이해를 돕기 위해 netcat으로 데이터들을 전송해보면,

     

    Content-Type은 multipart/form-data,

    그리고 전송한 항목들이 각각 WebKitFormBoundary로 나뉘어 전송된 것을 확인할 수 있다.

    name은 input태그에서 작성했던 name속성으로 날아온다.

     


    현재 날짜이름으로 폴더 생성하기

     

     

    이 파트들을 모두 받아와서, name이 파일인 것만 추린 후 업로드하면 된다.

     

    그 전에 먼저 현재날짜로 디렉토리를 생성하기로 했으니, 현재날짜를 구해와야한다.

    자바의 DateSimpleDateFormat 클래스를 이용하면 현재날짜를 쉽게 구할 수 있다.

     

    Date date = new Date();
    SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
    String fileDate = sdf.format(date);

     

    형식을 이렇게 지정해주면 '20220714'이렇게 포맷팅된 날짜를 얻을 수 있다.

     

     

    폴더를 생성하는 것은 File클래스의 mkdir()메소드를 사용한다.

     

    // 현재 경로에 현재날짜폴더 생성하기
    String uploadPath = "C:/temp/upload/" + fileDate;
    File uploadDir = new File(uploadPath);
    // 해당 경로가 없으면 폴더를 생성
    if (!uploadDir.exists()) uploadDir.mkdir();

     

    업로드할 경로에 아까 구한 fileDate를 붙여준다.

    만약 해당 경로가 없으면 mkdir()메소드를 호출해서 폴더를 만들어주도록 한다.

     

     


     

    이제 파트들을 받아온다. request객체의 getParts()메소드를 사용하면 모든 파트들을 얻어올 수 있다.

    반환타입은 제네릭 컬렉션이므로, 타입을 맞춰 변수에 저장한다.

     

    Collection<Part> multipart = req.getParts();

     

     

    이제 이 데이터들을 조작하면 된다.

    enhanced for문을 쓰는 방법과 stream을 쓰는 방법 두가지로 구현해봤다.

     

    1. enhanced for문

     

    for(Part part : multipart) {
        if( !part.getName().equals("uploadFile") ) {
            continue;
        } // if
    
        part.write(uploadPath + "/" + fileDate + "_" + part.getSubmittedFileName());
        part.delete();
    } // enhanced for

     

    파트의 이름이 "uploadFile"인 파트에 대해서만 업로드 로직을 수행한다.

    write()메소드에 경로와 파일이름을 지정해주면 해당경로에 지정한 이름으로 파일이 업로드 된다.

     

    getSubmittedFileName()은 원본 파일명을 그대로 얻어온다.

     

     

    UUID로 파일명 지정하기

     

     

    앞서 말했듯 UUID로 파일명을 지정할 것이기 때문에, write 이전에 UUID함수를 사용해 파일명을 새로 지정해줘야한다.

     

    for(Part part : multipart) {
        if( !part.getName().equals("uploadFile") ) {
            continue;
        } // if
    
        // 원래 파일명에서 확장자(.jpg, .png 등등)만 추출
        String extension = fileName.substring(part.getSubmittedFileName().lastIndexOf("."));
    
        // UUID 생성 후 뒤에 추출한 확장자 연결
        String newFileName = UUID.randomUUID().toString() + extension;
    
        part.write(uploadPath + "/" + fileDate + "_" + newFileName);
        part.delete();
    } // enhanced for

     

    원본파일명에서 확장자(.jpg, .png 등)을 추출하기 위해 substring을 사용한다. 확장자는 맨 뒤에 .으로 구분되기 때문에,

    "."이 들어간 가장 마지막 인덱스부터 끝까지 잘라주면 된다.

     

    이후, UUID난수 생성 후 구한 확장자를 붙여주면 새 파일명이 만들어진다.

     

    write메소드에 이 새로운 파일명을 지정하면 끝이다.

    문자열 연결연산자(+)를 지양하려면 StringBuilder나 StringBuffer객체를 생성해 append해준 후 지정하면 된다.

    delete()메소드는 파일을 삭제하는 것이 아니라, 임시저장공간을 비우는 메소드이다.

     

     


     

    2. Stream

     

    같은 코드를 스트림을 사용해서도 구현해봤다.

     

    multipart.stream()
        .filter(part -> part.getName().equals("uploadFile"))
        .forEach(part -> {
        
            String extension = fileName.substring(part.getSubmittedFileName().lastIndexOf("."));
            String newFileName = UUID.randomUUID().toString() + extension;
    
            try {
                part.write(uploadPath + "/" + fileDate + "_" + newFileName);
                part.delete();
            } catch (IOException e) {;;} // try-catch
        });

     

    filter를 사용해 추린 후 forEach를 사용했다.

    이 때 write메소드가 IOException예외를 발생시키는데, 람다식 내부에서 다시 try-catch로 예외처리를 해줘야한다.

     

    보통 스트림을 쓰는 코드가 간결하고 명확하다고 하지만, 이 상황에선 enhanced for문을 이용하는 것이 나아보인다.

    forEach에서는 간단한 동작만 수행하는 것이 좋기 때문이다.

     

    System.currentTimeMills()로 두 방법 간 소요시간을 측정해본 결과, 속도엔 거의 차이가 없었다.

     

     

     

    아래는 전체 코드이다.

     

    import java.io.File;
    import java.io.IOException;
    import java.text.SimpleDateFormat;
    import java.util.Collection;
    import java.util.Date;
    import java.util.UUID;
    
    import javax.servlet.ServletException;
    import javax.servlet.annotation.MultipartConfig;
    import javax.servlet.annotation.WebServlet;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import javax.servlet.http.Part;
    
    import lombok.NoArgsConstructor;
    import lombok.extern.log4j.Log4j2;
    
    @Log4j2
    @NoArgsConstructor
    
    // 1024 = 1kb, 1024 * 1024 = 1mb
    // maxFileSize = 여러개 파일의 합산크기
    // maxRequestSize = 하나의 요청 당 크기
    @MultipartConfig(location="C:/temp/upload", maxFileSize = 1024 * 1024 * 20) 
    
    @WebServlet("/Upload")
    public class UploadServlet extends HttpServlet {
    	private static final long serialVersionUID = 1L;
    
        @Override
        protected void service(HttpServletRequest req, HttpServletResponse res)
        	throws ServletException, IOException {
            log.trace("service(req, res) invoked.");
    
            req.setCharacterEncoding("utf8");
    
            // 현재 날짜 얻어서 파일명에 붙이기
            Date date = new Date();
            SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
            String fileDate = sdf.format(date);
    
            // 현재 경로에 현재날짜폴더 생성하기
            String uploadPath = "C:/temp/upload/" + fileDate;
            File uploadDir = new File("C:/temp/upload/" + fileDate);
            // 해당 경로가 없으면 폴더를 생성
            if (!uploadDir.exists()) uploadDir.mkdir();
    
            Collection<Part> multipart = req.getParts();
    
        	// 두 방법 중 하나만 선택
            // 1. enhanced for사용
            for(Part part : multipart) {
                if( !part.getName().equals("uploadFile") ) continue;
    
                // 원래 파일명에서 확장자(.jpg, .png 등등)만 추출
                String extension = part.getSubmittedFileName().substring(
                    part.getSubmittedFileName().lastIndexOf(".")
                );
    
                // UUID 생성 후 뒤에 추출한 확장자 연결
                String newFileName = UUID.randomUUID().toString() + extension;
    
                part.write(uploadPath + "/" + fileDate + "_" + newFileName);
                part.delete();
            } // enhanced for
    
            // 2. stream 사용
            multipart.stream()
                .filter(part -> part.getName().equals("uploadFile"))
                .forEach(part -> {
                    String extension = part.getSubmittedFileName().substring(
                        part.getSubmittedFileName().lastIndexOf(".")
                    );
    
                    // UUID 생성 후 뒤에 추출한 확장자 연결
                    String newFileName = UUID.randomUUID().toString() + extension;
    
                    try {
                        part.write(uploadPath + "/" + fileDate + "_" + newFileName);
                        part.delete();
                    } catch (IOException e) {;;} // try-catch
                });
        } // service
    
    } // end class

    댓글

Designed by Tistory.