Spring Boot) Form 으로 파일 업로드 및 다운로드 하기

Dokuny·2022년 1월 14일
10

Spring

목록 보기
2/3

스프링 MVC에서는 MultipartResolver를 설정하면 어플리케이션들어오는 Multipart 요청을 처리하여 파일 업로드를 쉽게 할 수 있다.

스프링 부트에서는 기본 빈으로 등록되어있기 때문에 추가적으로 등록해 줄 필요가 없다!


업로드

1. application.properties 작성

spring.servlet.multipart.max-file-size=1MB
spring.servlet.multipart.max-request-size=10MB 

file.dir=D:/test/
  • max-file-size : 파일 하나의 최대사이즈 설정, 아무런 설정을 안하면 기본값이 1MB이다.

  • max-request-size : Multipart 요청 하나에 여러 파일을 업로드 할 수 있는데, 그 전체 합의 크기를 설정해줄 수 있다. 기본값 10MB

  • file.dir : file.dir 이 부분을 원하는 이름으로 적고 원하는 파일저장경로를 설정해주면 된다. 후에 @Value("${file.dir}")로 불러다가 사용할 예정이다. 만약 파일 저장 위치를 각기 다르게 해야할 경우, 원하는 경로로 여러개 만들어서 불러다가 사용하면 편리하다. 관리하기 편하라고 .properties에다가 설정해둔 것.

2. 폼화면 작성

<!DOCTYPE HTML>
<head>
	<meta charset="utf-8">
</head>
<body>
	<h2>파일 업로드</h2>
	
        <form action="/upload" method="post" enctype="multipart/form-data">
     	  
           <h4>단일 파일 업로드</h4>
           <input type="file" name="file">
                   	
           <h4>다중 파일 업로드</h4>
           <input type="file" multiple="multiple" name="files">
			
	   <input type="submit"/>
	</form>
</body>
</html>
  • form 태그의 enctype="multipart/form-data"로 설정을 해주어야 한다.
  • input 태그의 multiple="multiple" 은 원래 type="file"의 input은 기본적으로 하나의 파일만 선택 가능한데 위의 속성을 적용해주면 여러 개의 파일을 선택할 수 있도록 해준다.

3. 파일 정보를 담을 Entity 작성

@NoArgsConstructor
@Table(name = "file")
@Entity
public class FileEntity {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name="file_id")
    private Long id;
    
    private String orgNm;
    
    private String savedNm;
    
    private String savedPath;

    @Builder
    public FileEntity(Long id, String orgNm, String savedNm, String savedPath) {
        this.id = id;
        this.orgNm = orgNm;
        this.savedNm = savedNm;
        this.savedPath = savedPath;
    }
}

4. 파일 정보를 데이터베이스에 저장할 Repository 작성

Spring Data JPA 이용

public interface FileRepository extends JpaRepository<FileEntity,Long> {}

5. Multipart로 넘어온 파일을 처리해 줄 Service 작성

@RequiredArgsConstructor
@Service
public class FileService {

    @Value("${file.dir}")
    private String fileDir;

    private final FileRepository fileRepository;

    public Long saveFile(MultipartFile files) throws IOException {
        if (files.isEmpty()) {
            return null;
        }

        // 원래 파일 이름 추출
        String origName = files.getOriginalFilename();

        // 파일 이름으로 쓸 uuid 생성
        String uuid = UUID.randomUUID().toString();

        // 확장자 추출(ex : .png)
        String extension = origName.substring(origName.lastIndexOf("."));

        // uuid와 확장자 결합
        String savedName = uuid + extension;

        // 파일을 불러올 때 사용할 파일 경로
        String savedPath = fileDir + savedName;

        // 파일 엔티티 생성
        FileEntity file = FileEntity.builder()
                .orgNm(origName)
                .savedNm(savedName)
                .savedPath(savedPath)
                .build();

        // 실제로 로컬에 uuid를 파일명으로 저장
        files.transferTo(new File(savedPath));

        // 데이터베이스에 파일 정보 저장 
        FileEntity savedFile = fileRepository.save(file);

        return savedFile.getId();
    }
}

6. Controller 작성

@RequiredArgsConstructor
@Controller
public class TestController {

    private final FileService fileService;


    @GetMapping("/upload")
    public String testUploadForm() {

        return "test/uploadTest";
    }

    @PostMapping("/upload")
    public String uploadFile(@RequestParam("file") MultipartFile file,@RequestParam("files") List<MultipartFile> files) throws IOException {
        fileService.saveFile(file);

        for (MultipartFile multipartFile : files) {
            fileService.saveFile(multipartFile);
        }

        return "redirect:/";
    }

}

작동 성공!


다운로드

1. Controller 작성

업로드할 때 작성해둔 컨트롤러에 추가로 작성해줄 것 이다.

@RequiredArgsConstructor
@Controller
public class TestController {

    private final FileService fileService;
    private final FileRepository fileRepository;


  ~ 기존 코드 ~

    @GetMapping("/view")
    public String view(Model model) {

        List<FileEntity> files = fileRepository.findAll();
        model.addAttribute("all",files);
        return "view";
    }


    //   이미지 출력
    @GetMapping("/images/{fileId}")
    @ResponseBody
    public Resource downloadImage(@PathVariable("fileId") Long id, Model model) throws IOException{

        FileEntity file = fileRepository.findById(id).orElse(null);
        return new UrlResource("file:" + file.getSavedPath());
    }
    
    // 첨부 파일 다운로드
    @GetMapping("/attach/{id}")
    public ResponseEntity<Resource> downloadAttach(@PathVariable Long id) throws MalformedURLException {

        FileEntity file = fileRepository.findById(id).orElse(null);

        UrlResource resource = new UrlResource("file:" + file.getSavedPath());

        String encodedFileName = UriUtils.encode(file.getOrgNm(), StandardCharsets.UTF_8);

        // 파일 다운로드 대화상자가 뜨도록 하는 헤더를 설정해주는 것
        // Content-Disposition 헤더에 attachment; filename="업로드 파일명" 값을 준다.
        String contentDisposition = "attachment; filename=\"" + encodedFileName + "\"";

        return ResponseEntity.ok().header(HttpHeaders.CONTENT_DISPOSITION,contentDisposition).body(resource);
    }
}
  • UrlResource파일을 읽어서 @ResponseBody 로 이미지 바이너리를 반환
    • UrlResource는 Resource 인터페이스의 구현체로 new UrlResource("file:" + "파일이 저장된 경로") 로 사용하면된다
  • contentDisposition 값에 든 이름으로 파일이 실질적으로 저장된다.
    • 만약 위에서 UriUtils.encode(file.getSavedNm(),~) 로 주었다면 실제 파일을 다운로드 받을 때 파일명이 uuid로 해놓은 파일명으로 나온다.

2. View 작성

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
	<head>
		<meta charset="UTF-8">
		<title>Title</title>
	</head>
	<body>
		<h3>이미지 출력</h3>
		<div th:each="imageFile : ${all} ">
			<img  th:src="|/images/${imageFile.id}|" width="150" height="150">
			<p th:text="${imageFile.orgNm}"></p>
		</div>
		
		<h3>파일 다운로드</h3>
		<div th:each="file : ${all}">
			<a th:href="|/attach/${file.id}|" th:text="${file.orgNm}"></a>
		</div>
	</body>
</html>

실행 결과

파일 업로드는 대충 이런 식으로 흘러간다는 것을 기억하자.

밑의 다운로드는 content dispositionattachment; filename="파일명" 에 변화를 주었을 때 어떻게 저장되는지 헷갈려서 테스트 해본 흔적이다.

References

profile
모든 것은 직접 경험해보고 테스트하자

2개의 댓글

comment-user-thumbnail
2023년 2월 17일

혹시 DB file 테이블 구성좀 알려주실수 있나요?

답글 달기
comment-user-thumbnail
2024년 3월 31일

좋은 정보 감사함니다!!

답글 달기