본문 바로가기
Backend/SpringBoot

SpringBoot 파일 업로드 & 다운로드 코드 분석

by YERIEL_염주둥 2020. 6. 16.
728x90

현재 팀프로젝트 진행 중~

우리 팀 주제에는 파일 업로드 다운로드는 없지만 다른 팀은 많은지 알려주심

 

https://spring.io/guides/gs/uploading-files/

 

Uploading Files

this guide is designed to get you productive as quickly as possible and using the latest Spring project releases and techniques as recommended by the Spring team

spring.io

 

Spring Boot 친절하게 어떻게 하는지 알려주지만 왜케 어려운 거 ㅠㅠㅠ

 

Maven으로 진행함 git-hub에서 다운 받으면 코드 그대로 제공해주기 때문에 별도의 코드 작성은 없었다.

내가 아는 만큼 알 수 있는 만큼 분석한 것이기 때문에 정확하다고 확정할 수는 없다..ㅋㅋ

 

Application Class

package com.example.uploadingfiles;

import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;

import com.example.uploadingfiles.storage.StorageProperties;
import com.example.uploadingfiles.storage.StorageService;

@SpringBootApplication
@EnableConfigurationProperties(StorageProperties.class)
public class UploadingFilesApplication {

	public static void main(String[] args) {
		SpringApplication.run(UploadingFilesApplication.class, args);
	}

	@Bean
	CommandLineRunner init(StorageService storageService) {
		return (args) -> {
			storageService.deleteAll();
			storageService.init();
		};
	}
}

 

@EnableConfigurationProperties 

스프링 부트 커스텀 설정 프로퍼티 클래스를 빈으로 등록할 수 있는 어노테이션

@EnableConfigurationProperties을 설정 클래스에 추가하고 @EnableConfigurationProperties의 값으로 @ConfigurationProperties를 적용한 클래스를 지정하면 된다. 이 경우 @EnableConfigurationProperties는 해당 클래스를 빈으로 등록하고 프로퍼티 값을 할당한다.

package com.example.uploadingfiles.storage;

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties("storage")
public class StorageProperties {

	/**
	 * Folder location for storing files
	 * 부팅 될때 이 정보가 올라감
	 */
	private String location = "upload-dir";

	public String getLocation() {
		return location;
	}

	public void setLocation(String location) {
		this.location = location;
	}

}

 

File Upload Controller

package com.example.uploadingfiles;

import java.io.IOException;
import java.util.stream.Collectors;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import com.example.uploadingfiles.storage.StorageFileNotFoundException;
import com.example.uploadingfiles.storage.StorageService;

@Controller
public class FileUploadController {

	private final StorageService storageService;

	@Autowired
	public FileUploadController(StorageService storageService) {
		this.storageService = storageService;
	}

	/* loadAll() ; 생성 메서드 ; StorageService.java
	 * map() ; 제공 메서드 ; Stream.class
	 * MvcUriComponentsBuilder ; 제공 메서드 ; requestMapping 메서드
	 * 파일이 있으면 리스트로 보내주기 위해서 addAttribute에 file로 보냄
	 *  */
	@GetMapping("/")
	public String listUploadedFiles(Model model) throws IOException {

		model.addAttribute("files", storageService.loadAll().map(
				path -> MvcUriComponentsBuilder.fromMethodName(FileUploadController.class,
						"serveFile", path.getFileName().toString()).build().toUri().toString())
				.collect(Collectors.toList()));

		return "uploadForm";
	}
	
	/*파일 다운로드
	 * @ResponsBody ; 포워드 필요 없이 본인 스스로 응답하는 것
	 * @PathVariable ; {키 : + 값} ; get방식으로 받을때 ?로 받기 번거스러워서 사용하는 어노테이션
	 * ResponseEntity ; 스프링 제공 메서드 ; HttpEntity를 상속받음으로써 HttpHeader와 body를 가질 수 있다. 
	 * */
	@GetMapping("/files/{filename:.+}")
	@ResponseBody
	public ResponseEntity<Resource> serveFile(@PathVariable String filename) {

		Resource file = storageService.loadAsResource(filename);
		return ResponseEntity.ok().header(HttpHeaders.CONTENT_DISPOSITION,
				"attachment; filename=\"" + file.getFilename() + "\"").body(file);
	}
	
	/* 파일 업로드
	 * RequestParam으로 file을 받아서 MultipartFile 타입으로 file에 담는다.
	 * redirectAttributes는 post방식에서 리다이렉트 할 수 있음
	 * 피일이 올라가면 attribute에 성공했다고 메세지를 담아 보내쥼*/
	@PostMapping("/")
	public String handleFileUpload(@RequestParam("file") MultipartFile file,
			RedirectAttributes redirectAttributes) {

		storageService.store(file);
		redirectAttributes.addFlashAttribute("message",
				"You successfully uploaded " + file.getOriginalFilename() + "!");

		return "redirect:/";
	}

	@ExceptionHandler(StorageFileNotFoundException.class)
	public ResponseEntity<?> handleStorageFileNotFound(StorageFileNotFoundException exc) {
		return ResponseEntity.notFound().build();
	}

}

 

File Upload Service

package com.example.uploadingfiles.storage;

import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
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.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.stereotype.Service;
import org.springframework.util.FileSystemUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;

@Service
public class FileSystemStorageService implements StorageService {

	private final Path rootLocation;

	@Autowired
	public FileSystemStorageService(StorageProperties properties) {
		this.rootLocation = Paths.get(properties.getLocation());
	}

	@Override
	public void store(MultipartFile file) {
		/* 불필요한 패스 정리 
		 * file에서 클라이어트 파일 시스템에 저장된 파일 이름을 가져와서 path 주소를 "path/.."으로 정리 해줌
		 * */
		String filename = StringUtils.cleanPath(file.getOriginalFilename());
		try {
			if (file.isEmpty()) {
				throw new StorageException("Failed to store empty file " + filename);
			}
			/* 그래서 여기 조건문에 ..이 들어감
			 * contains메서드는 boolean 리턴 타입 매개변수값이 맞다면 true를 반한
			 *  */
			if (filename.contains("..")) {
				// This is a security check
				throw new StorageException(
						"Cannot store file with relative path outside current directory "
								+ filename);
			}
			//결국 실행 부분은 여기
			try (InputStream inputStream = file.getInputStream()) {
				Files.copy(inputStream, this.rootLocation.resolve(filename),
						StandardCopyOption.REPLACE_EXISTING); /* 옵션값 */
				/* 여기서 중요한 거는 copy . 버전 마다 다르지만 앞으로 높은 버전은 일단 copy*/
			}
		}
		catch (IOException e) {
			throw new StorageException("Failed to store file " + filename, e);
		}
	}
	
	/* walk ; 파일의 주소 중 시작 파일을 리턴 (시작파일, 몇번 째 파일) ; 이건가 본대
	 * relativize ; 현재 path와 주는 path 사이의 실제 path
	 * :: ; Applies this function to the given argument. 함수의 결과를 반환 (람다식이여서 함수라고 표현하나?)
	 * */
	@Override
	public Stream<Path> loadAll() {
		try {
			return Files.walk(this.rootLocation, 1)
				.filter(path -> !path.equals(this.rootLocation))
				.map(this.rootLocation::relativize);
		}
		catch (IOException e) {
			throw new StorageException("Failed to read stored files", e);
		}

	}

	@Override
	public Path load(String filename) {
		return rootLocation.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 StorageFileNotFoundException(
						"Could not read file: " + filename);

			}
		}
		catch (MalformedURLException e) {
			throw new StorageFileNotFoundException("Could not read file: " + filename, e);
		}
	}

	@Override
	public void deleteAll() {
		FileSystemUtils.deleteRecursively(rootLocation.toFile());
		/* toFile로 파일 가져오고 Spring이 제공해주는 메서드 deleteRecursively로 제거*/
	}

	@Override
	public void init() {
		try {
			Files.createDirectories(rootLocation);
		}
		catch (IOException e) {
			throw new StorageException("Could not initialize storage", e);
		}
	}
}

서비스는 잘 모르겠엉 ㅠㅠ

 

반응형

'Backend > SpringBoot' 카테고리의 다른 글

에러 일기  (0) 2020.07.21
Spring Boot - 외부 API xml 방식으로 호출 하기  (0) 2020.07.06
Spring Boot 리다이렉트하기  (0) 2020.05.26
Get방식 parameter 넘기기  (0) 2020.05.26
Baeldung  (0) 2020.05.26

댓글