본문 바로가기
Java

[SpringBoot] RestApi 만들기 (6) File Upload / Download / List

by bryan.oh 2021. 7. 11.
반응형

Spring Boot
File Upload / Download / Delete / List

 


1. File Upload

file 관련 설정은 

src/main/resources/application.yml ( 또는 application.properties ) 에 합니다.

spring: 
  servlet:
    multipart:
      max-file-size: 10MB
      max-request-size: 10MB
      location: d:\\temp\\spring_uploaded_files
application.properties 라면 spring.servlet.multipart.max-file-size=10MB 와 같이 쓰면 됩니다.
  • spring.servlet.multipart.max-file-size : 총 파일 사이즈가 설정한 크기를 넘지 못합니다.
  • spring.servlet.multipart.max-request-size : multipart/form-data 의 request 사이즈가 설정한 크기를 넘지 못합니다.

 

InfoController.java

// @ResponseBody 생략가능. class 에 @RestController
@PostMapping(value="uploadFile")
public ResponseEntity<String> uploadFile(MultipartFile file) throws IllegalStateException, IOException{
    
    if( !file.isEmpty() ) {
        log.debug("file org name = {}", file.getOriginalFilename());
        log.debug("file content type = {}", file.getContentType());
        file.transferTo(new File(file.getOriginalFilename()));
    }
    
    return new ResponseEntity<>("", HttpStatus.OK);
}

일단은 Controller 에서 file 관련된 내용을 보기위해 테스트해봤습니다.

form-data 의 KEY 와 메소드의 파라메터이름이 같아야 합니다.

로그 찍히고 파일이 저장되었습니다.

2021-07-11 13:31:30.771[DEBUG] : file org name = images.jpg
2021-07-11 13:31:30.771[DEBUG] : file content type = image/jpeg

 

이번엔 File 관련 서비스를 만들어서 처리하도록 하겠습니다.

StorageService

info package 아래에 storage package 를 만듭니다.

그 아래에 StorageService.javainterface 로 생성합니다.

StorageService.java

package com.bryan.hello.preword.info.storage;

import java.nio.file.Path;
import java.util.stream.Stream;

import org.springframework.core.io.Resource;
import org.springframework.web.multipart.MultipartFile;

public interface StorageService {
    
    void init();

    void store(MultipartFile file);

    Stream<Path> loadAll();

    Path load(String filename);

    Resource loadAsResource(String filename);

    void deleteAll();
    
}

 

interface 를 구현해야겠죠. 같은 package 에

FileSystemStorageService.java 를 만듭니다. 일단 파일 저장하는 것 까지만,

package com.bryan.hello.preword.info.storage;

import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.stream.Stream;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

@Service
public class FileSystemStorageService implements StorageService {

    @Value("${spring.servlet.multipart.location}")
    private String uploadPath;
    
    @Override
    public void init() {
        try {
            Files.createDirectories(Paths.get(uploadPath));
        } catch (IOException e) {
            throw new RuntimeException("Could not create upload folder!");
        }
    }

    @Override
    public void store(MultipartFile file) {
        try {
            if (file.isEmpty()) {
                throw new Exception("ERROR : File is empty.");
            }
            Path root = Paths.get(uploadPath);
            if (!Files.exists(root)) {
                init();
            }

            try (InputStream inputStream = file.getInputStream()) {
                Files.copy(inputStream, root.resolve(file.getOriginalFilename()),
                    StandardCopyOption.REPLACE_EXISTING);
            }
        } catch (Exception e) {
            throw new RuntimeException("Could not store the file. Error: " + e.getMessage());
        }
    }

    @Override
    public Stream<Path> loadAll() {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public Path load(String filename) {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public Resource loadAsResource(String filename) {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public void deleteAll() {
        // TODO Auto-generated method stub
        
    }

}
@Value 는 application.yml 의 값을 가져와서 주입해줍니다.

Controller 에서 StorageService 를 사용하기 위해 생성자 주입을 해줍니다.

Controller 에 PostMapping method 추가

@PostMapping(value="upload")
public ResponseEntity<String> upload(MultipartFile file) throws IllegalStateException, IOException{
	storageService.store(file);
	return new ResponseEntity<>("", HttpStatus.OK);
}

실행

결과는.. 잘 됩니다.


2. File Download

 

위에서 implement 했던 메소드 중에 load() 와 loadAsResource() 를 작성합니다.

FileSystemStorageService.javaload(), loadAsResource()

@Override
public Path load(String filename) {
    return Paths.get(uploadPath).resolve(filename);
}

@Override
public Resource loadAsResource(String filename) {
    try {
        Path file = load(filename);
        Resource resource = new UrlResource(file.toUri());
        if (resource.exists() || resource.isReadable()) {
            return resource;
        }
        else {
            throw new RuntimeException("Could not read file: " + filename);
        }
    }
    catch (MalformedURLException e) {
        throw new RuntimeException("Could not read file: " + filename, e);
    }
}

그리고 Controller 에서 다운로드 api 를 만듭니다.

InfoController.java 에 추가

// import 에 추가
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;

// method 추가
    @GetMapping(value="download")
    public ResponseEntity<Resource> serveFile(@RequestParam(value="filename") String filename) {

        Resource file = storageService.loadAsResource(filename);
        return ResponseEntity.ok().header(HttpHeaders.CONTENT_DISPOSITION,
                "attachment; filename=\"" + file.getFilename() + "\"").body(file);
    }
브라우저에서 호출해보기 쉽게 GET 방식으로 mapping 해줍니다.

실행

웹 브라우저에 아래와 같이 호출

localhost:8000/info/download?filename=images.jpg

그럼 file 이 다운로드 됩니다.

 


3. File Delete

전체 파일 삭제 입니다.

 

FileSystemStorageService.javadeleteAll() 을 구현해줍니다.

import org.springframework.util.FileSystemUtils;

    @Override
    public void deleteAll() {
        FileSystemUtils.deleteRecursively(Paths.get(uploadPath).toFile());
    }

컨트롤러에서 호출

@PostMapping(value="deleteAll")
public ResponseEntity<String> deleteAll(){
    storageService.deleteAll();;
    return new ResponseEntity<>("", HttpStatus.OK);
}

사용 전 주의하세요 !!

application.yml 에 설정한 location 이 삭제됩니다.

파일을 하나만 삭제하려면 StorageService Interface 에 추상 메소드delete() 를 추가하고
FileSystemStorageService.java 에 Override 해서 구현하고 사용하면 됩니다.

 

4. Files List

file들의 정보를 list 에 담아서 리턴해야 하기 때문에, FileData 라는 class 를 만들겠습니다.

model package 에 FileData.java

package com.bryan.hello.preword.info.model;

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

@Getter
@Setter
@ToString
public class FileData {
    private String filename;
    private String url;
    private Long size;
}

FileSystemStorageService.java 의 loadAll() 을 구현해줍니다.

@Override
public Stream<Path> loadAll() {
    try {
        Path root = Paths.get(uploadPath);
        return Files.walk(root, 1)
            .filter(path -> !path.equals(root));
    }
    catch (IOException e) {
        throw new RuntimeException("Failed to read stored files", e);
    }
}

InfoController.java 에 추가

 

import java.util.stream.Collectors;
import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder;

    @GetMapping("fileList")
    public ResponseEntity<List<FileData>> getListFiles() {
        List<FileData> fileInfos = storageService.loadAll()
          .map(path ->{
              FileData data = new FileData();
              String filename = path.getFileName().toString();
              data.setFilename(filename);
              data.setUrl(MvcUriComponentsBuilder.fromMethodName(InfoController.class,
                        "serveFile", filename).build().toString());
              try {
                data.setSize(Files.size(path));
            } catch (IOException e) {
                log.error(e.getMessage());
            }
              return data;
          })
          .collect(Collectors.toList());

        return ResponseEntity.status(HttpStatus.OK).body(fileInfos);
    }

실행

http://localhost:8000/info/fileList

결과

 

 

Spring Boot Tutorial 시리즈

Spring Boot Tutorial 부록

 

728x90
반응형

댓글