Notice
Recent Posts
Recent Comments
Link
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Archives
Today
Total
관리 메뉴

개발자되기 프로젝트

[File업로드] 업로드, 다운로드 본문

인프런/[인프런] 스프링 MVC 2

[File업로드] 업로드, 다운로드

Seung__ 2021. 10. 2. 18:16

1. 요구사항


  • 요구사항
  • 상품을 관리
    • 상품 이름
    • 첨부파일 하나
    • 이미지 파일 여러개
  • 첨부파일을 업로드 다운로드 할 수 있다.
  • 업로드한 이미지를 웹 브라우저에서 확인할 수 있다.

 

 

2. Item 상품 도메인


import lombok.Data;

import java.util.List;

@Data
public class Item {

    private Long id;
    private String itemName;
    private UploadFile  attachFile;
    private List<UploadFile> imageFiles;

}

 

 

3. itemRepository


@Repository
public class ItemRepository {

    private final Map<Long, Item> store = new HashMap<>();
    private long sequence = 0L;

    public Item save(Item item){
        item.setId(++sequence);
        store.put(item.getId(), item);
        return item;
    }

    public Item findById(Long id){
        return store.get(id);
    }

}

 

 

 

4. UploadFile 업로드 파일 정보 보관


  • 고객이 업로드하는 파일명은 중복될 수 있음.
@Data
public class UploadFile {

    private String uploadFileName;
    private String storeFileName;

}

 

 

5. FileStore


@Component
public class FileStore {

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

    public String getFullPath(String fileName){

        return fileDir + fileName;
    }

    public List<UploadFile> storeFiles(List<MultipartFile> multipartFiles) throws IOException {
        List<UploadFile> storeFileResult = new ArrayList<>();
        for (MultipartFile multipartFile : multipartFiles) {
            if(!multipartFile.isEmpty()){
                UploadFile uploadFile = storeFile(multipartFile);
                storeFileResult.add(uploadFile);
            }
        }

        return storeFileResult;
    }



    public UploadFile storeFile(MultipartFile multipartFile) throws IOException {
        if(multipartFile.isEmpty()){
            return null;
        }
        String originalFilename = multipartFile.getOriginalFilename();

        //서버에 저장하는 파일명
        String storeFileName = createStoreFileName(originalFilename);

        multipartFile.transferTo(new File(getFullPath(storeFileName)));

        return new UploadFile(originalFilename, storeFileName);
    }


    public String extractExt(String originalFileName){
        int pos = originalFileName.lastIndexOf(".");
        return originalFileName.substring(pos + 1);
    }

    private String createStoreFileName(String originalFilename) {
        String uuid = UUID.randomUUID().toString();
        String ext = extractExt(originalFilename);
        return uuid + "." + ext;
    }
}
  • 멀티파트 파일을 서버에 저장하는 역할을 담당한다.
  • createStoreFileName()
    • 서버 내부에서 관리하는 파일명은 유일한 이름을 생성하는 UUID 를 사용해서 충돌하지 않도록 한다.
  • extractExt() : 확장자를 별도로 추출해서 서버 내부에서 관리하는 파일명에도 붙여준다. 
    • 예를 들어서 고객이 a.png 라는 이름으로 업로드 하면 
    • 51041c62-86e4-4274-801d-614a7d994edb.png 와 같이 저장
    • string.lastIndexOf("String str") : string에서 str이 발생하는 마지막 index를 반환. 

 

 

 

6. ItemForm 


import lombok.Data;
import org.springframework.web.multipart.MultipartFile;

import java.util.List;

@Data
public class ItemForm {
    /**
     * multiPartFile
     */
    private Long itemId;
    private String itemName;
    private MultipartFile attachFile;
    private List<MultipartFile> imageFiles;

    
}
  • 상품 저장용 폼이다.
  • List<MultipartFile> imageFiles : 이미지를 다중 업로드 하기 위해 MultipartFile 를 사용했다.
  • MultipartFile attachFile : 멀티파트는 @ModelAttribute 에서 사용할 수 있다

 

 

 

7. ItemController


import java.io.IOException;
import java.net.MalformedURLException;
import java.nio.charset.StandardCharsets;
import java.util.List;

@Controller
@Slf4j
@RequiredArgsConstructor
public class ItemController {

    private final ItemRepository itemRepository;
    private final FileStore fileStore;

    @GetMapping("/items/new")
    public String newItem(@ModelAttribute ItemForm form){
        return "item-form";
    }

    @PostMapping("/items/new")
    public String saveITem(@ModelAttribute ItemForm form, RedirectAttributes redirectAttributes) throws IOException {
       //파일로 저장. 파일은 db에 저장 안함. storage에 저장.
        UploadFile attachFile = fileStore.storeFile(form.getAttachFile());
        List<UploadFile> storeImageFiles = fileStore.storeFiles(form.getImageFiles());

        //데이터베이스에 저장.
        //파일 자체를 저장하지 않고, 경로를 저장함.
        Item item = new Item();
        item.setItemName(form.getItemName());
        item.setAttachFile(attachFile);
        item.setImageFiles(storeImageFiles);
        itemRepository.save(item);

        redirectAttributes.addAttribute("itemId", item.getId());

        return "redirect:/items/{itemId}";
    }

    @GetMapping("/items/{id}")
    public String items(@PathVariable Long id, Model model){
        Item item = itemRepository.findById(id);
        model.addAttribute("item", item);
        return "item-view";
    }

    @ResponseBody
    @GetMapping("/images/{filename}")
    public Resource downloadImage(@PathVariable String filename) throws MalformedURLException {
        //"file:/Users/~~/filename.xxx"
        //해당 경로에 있는 파일을 찾아서 스트림으로 반환함.
        return new UrlResource("file:" + fileStore.getFullPath(filename));
    }

    @GetMapping("/attach/{itemId}")
    public ResponseEntity<Resource> downloadAttachFile(@PathVariable Long itemId) throws MalformedURLException {
        Item item = itemRepository.findById(itemId);
        String storeFileName = item.getAttachFile().getStoreFileName();
        String uploadFileName = item.getAttachFile().getUploadFileName();

        UrlResource resource = new UrlResource("file:" + fileStore.getFullPath(storeFileName));
        log.info("uploadFileName = {}", uploadFileName);

        String encodeUploadFileName = UriUtils.encode(uploadFileName, StandardCharsets.UTF_8);
        String contentDisposition = "attachment; filename=\"" + encodeUploadFileName + "\"";

        return ResponseEntity.ok()
                .header(HttpHeaders.CONTENT_DISPOSITION, contentDisposition)
                .body(resource);
    }
}
  • @GetMapping("/items/new") : 등록 폼을 보여준다.
  • @PostMapping("/items/new") : 폼의 데이터를 저장하고 보여주는 화면으로 리다이렉트 한다.
  • @GetMapping("/items/{id}") : 상품을 보여준다.
  • @GetMapping("/images/{filename}") : <img> 태그로 이미지를 조회할 때 사용한다. 
    • UrlResource 로 이미지 파일을 읽어서 @ResponseBody 로 이미지 바이너리를 반환한다.
  • @GetMapping("/attach/{itemId}") : 파일을 다운로드 할 때 실행한다. 
    • 파일 다운로드 시 권한 체크같은 복잡한 상황까지 가정한다 생각하고 이미지 id 를 요청하도록 했다. 
    • 파일  다운로드시에는 고객이 업로드한 파일 이름으로 다운로드 하는게 좋다. 
    • 이때는 Content-Disposition  해더에 attachment; filename="업로드 파일명" 값을 주면 된다.
    • Content-Dispositon 헤더가 있어야 다운로드가 가능하다. : 규약임.
    • 헤당 헤더가 없으면 브라우저가 해당 파일 내용을 보여주기만 함.
  • file: 이 붙으면 내부 resource 접근함.

 

 

8. item-form.html


<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="utf-8">
</head>
<body>
<div class="container">
    <div class="py-5 text-center">
        <h2>상품 등록</h2>
    </div>
    <form th:action method="post" enctype="multipart/form-data">
        <ul>
            <li>상품명 <input type="text" name="itemName"></li>
            <li>첨부파일<input type="file" name="attachFile" ></li>
            <li>이미지 파일들<input type="file" multiple="multiple"
                              name="imageFiles" ></li>
        </ul>
        <input type="submit"/>
    </form>
</div> <!-- /container -->
</body>
</html>
  • 다중 파일 업로드를 하려면 multiple="multiple" 옵션을 주면 된다.
  • ItemForm 의 다음 코드에서 여러 이미지 파일을 받을 수 있다.
  • private List<MultipartFile> imageFiles;

 

9. GitHub : 211002 Upload, Download


 

GitHub - bsh6463/FileUpload

Contribute to bsh6463/FileUpload development by creating an account on GitHub.

github.com

 

Comments