# MultiPartFile์„ ์ด์šฉํ•œ ํŒŒ์ผ ์—…๋กœ๋“œ, ๋‹ค์šด๋กœ๋“œ
Study Repository

MultiPartFile์„ ์ด์šฉํ•œ ํŒŒ์ผ ์—…๋กœ๋“œ, ๋‹ค์šด๋กœ๋“œ

by rlaehddnd0422

์Šคํ”„๋ง์ด ์ œ๊ณตํ•˜๋Š” MultiPartFile์„ ์ด์šฉํ•ด ํŒŒ์ผ ์ „์†ก ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์ „์†กํ•œ ํŒŒ์ผ์„ ๋‹ค์šด๋ฐ›๋Š” ์˜ˆ์ œ ์‹ค์Šต

์š”๊ตฌ์‚ฌํ•ญ 

  • ์ƒํ’ˆ(Item)์„ ๋“ฑ๋กํ•˜๊ณ  ์กฐํšŒ
  • ์ƒํ’ˆ์˜ ์š”์†Œ๋กœ๋Š” ์ƒํ’ˆ ์•„์ด๋””, ์ƒํ’ˆ์ด๋ฆ„, ์ƒํ’ˆ ํŒŒ์ผ, ํŒŒ์ผ ๋ฆฌ์ŠคํŠธ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์ƒํ’ˆ ํŒŒ์ผ์€ ๋‹ค์šด๋กœ๋“œ ๊ฐ€๋Šฅ
  • ์—…๋กœ๋“œํ•œ ํŒŒ์ผ ๋ฆฌ์ŠคํŠธ๋Š” ์›น ๋ธŒ๋ผ์šฐ์ €์—์„œ ํ™•์ธ๊ฐ€๋Šฅ 

Domain

Item

@Data
public class Item {
    private Long itemId;
    private String itemName;
    private UploadFile attachFile;
    private List<UploadFile> imageFiles;
}
@Data
public class UploadFile {
    private String uploadFileName;
    private String storeFileName;

    public UploadFile(String uploadFileName, String storeFileName) {
        this.uploadFileName = uploadFileName;
        this.storeFileName = storeFileName;
    }
}
  • ์ƒํ’ˆ ์•„์ด๋””, ์ƒํ’ˆ ์ด๋ฆ„, ํŒŒ์ผ, ํŒŒ์ผ ๋ฆฌ์ŠคํŠธ
  • ํŒŒ์ผ์€ UploadFileName๊ณผ StorFileName์œผ๋กœ ๊ตฌ๋ถ„ํ•˜๊ธฐ ์œ„ํ•ด UploadFile ํด๋ž˜์Šค๋ฅผ ์ƒ์„ฑํ•ด์„œ ํƒ€์ž…์œผ๋กœ ์ง€์ •
    • ์„œ๋ฒ„์—๋Š” ๊ฐ™์€ ํŒŒ์ผ์˜ ์ด๋ฆ„์ด ์™€๋„ ๊ฐ ํŒŒ์ผ์„ ์‹๋ณ„ํ•  ์ˆ˜ ์žˆ๋„๋ก UUID๋ฅผ ํ†ตํ•ด ์—…๋กœ๋“œ ์‹œ์— StoreFileName๊ณผ, UploadFileName์œผ๋กœ ๊ตฌ๋ถ„์ง€์—ˆ์Šต๋‹ˆ๋‹ค.
    •  ์„œ๋กœ ๋‹ค๋ฅธ ๊ณ ๊ฐ์ด ๊ฐ™์€ ํŒŒ์ผ์ด๋ฆ„์„ ์—…๋กœ๋“œ ํ•˜๋Š” ๊ฒฝ์šฐ ๊ธฐ์กด ํŒŒ์ผ ์ด๋ฆ„๊ณผ ์ถฉ๋Œ์ด ๋‚  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๊ณ ๊ฐ์ด ์—…๋กœ๋“œํ•œ ํŒŒ์ผ๋ช…์œผ๋กœ ์„œ๋ฒ„ ๋‚ด๋ถ€์— ํŒŒ์ผ์„ ์ €์žฅํ•˜๋ฉด ์•ˆ๋ฉ๋‹ˆ๋‹ค.

ItemForm

@Data
public class ItemForm {
    private Long itemId;
    private String itemName;
    private MultipartFile attachFile;
    private List<MultipartFile> imageFiles;
}
  • Item์„ ์—…๋กœ๋“œ ํ•  ๋•Œ ์‚ฌ์šฉํ•  Form
  • Item ๋„๋ฉ”์ธ๊ณผ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ์ƒํ’ˆ ์•„์ด๋””, ์ด๋ฆ„, ํŒŒ์ผ, ํŒŒ์ผ๋ฆฌ์ŠคํŠธ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ํŒŒ์ผ์„ ์ „์†ก ํ•  ๋•Œ MultipartFile ํƒ€์ž… ์‚ฌ์šฉํ•  ์˜ˆ์ •

Item Repository

@Repository
public class ItemRepository {
    private final Map<Long, Item> store = new HashMap<>();
    private long sequence = 0L;
    public Item save(Item item)
    {
        item.setItemId(++sequence);
        store.put(item.getItemId(),item);
        return item;
    }
    public Item findById(Long id)
    {
        return store.get(id);
    }
}
  • Item์„ ์กฐํšŒ, ์ €์žฅ. HashMap ์‚ฌ์šฉ

 

File Store  

package hello.upload.file;

import hello.upload.domain.UploadFile;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

@Component
public class FileStore {

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

    // ์—…๋กœ๋“œ ๋˜๋Š” ๊ฒฝ๋กœ + ํŒŒ์ผ์ด๋ฆ„
    public String getFullPath(String fileName)
    {
        return fileDir + fileName;
    }


    // ์—…๋กœ๋“œ ํŒŒ์ผ(์˜ค๋ฆฌ์ง€๋„ ํŒŒ์ผ์ด๋ฆ„, ์Šคํ† ์–ด์— ์˜ฌ๋ผ๊ฐˆ ํŒŒ์ผ์ด๋ฆ„)
    // Form์œผ๋กœ ๋ถ€ํ„ฐ POST๋œ MultipartFile์„ ์„œ๋ฒ„์— ์ €์žฅํ•˜๊ณ  UploadFile(์˜ค๋ฆฌ์ง€๋„ํŒŒ์ผ์ด๋ฆ„, ์Šคํ† ์–ด์— ์˜ฌ๋ผ๊ฐˆ ํŒŒ์ผ์ด๋ฆ„) ๋ฆฌํ„ด
    public UploadFile storeFile(MultipartFile multipartFile) throws IOException {

        if(multipartFile.isEmpty())
        {
            return null;
        }

        String originalFilename = multipartFile.getOriginalFilename();
        String storeFilename = createStoreFileName(originalFilename);

        // transferTo๋ฅผ ํ†ตํ•ด ํŒŒ์ผ ์„œ๋ฒ„์— ์ €์žฅ (์„œ๋ฒ„์— ์ €์žฅ ํ•  ๋•Œ๋Š” UUID+.+ํ™•์žฅ์ž )
        multipartFile.transferTo(new File(getFullPath(storeFilename)));

        return new UploadFile(originalFilename,storeFilename);
    }
    private String createStoreFileName(String originalFilename) {

        int pos = originalFilename.lastIndexOf(".");
        String uuid = UUID.randomUUID().toString();
        String ext = originalFilename.substring(pos+1);
        return uuid + "." + ext; 
    }


    // ItemForm์œผ๋กœ๋ถ€ํ„ฐ POST ๋œ ํŒŒ์ผ ๋ฆฌ์ŠคํŠธ๋กœ๋ถ€ํ„ฐ ํ•˜๋‚˜์”ฉ iterate ํ•ด์„œ ์„œ๋ฒ„์— ์ €์žฅํ•˜๊ณ  Item List์— ์ถ”๊ฐ€ํ•˜๊ณ  ๋ฆฌํ„ด
    public List<UploadFile> storeFiles(List<MultipartFile> multipartFiles) throws IOException {
        List<UploadFile> storeFileResult = new ArrayList<>();

        for (MultipartFile multipartFile : multipartFiles) {
            if(!multipartFile.isEmpty())
            {
                storeFileResult.add(storeFile(multipartFile));
            }
        }
        return storeFileResult;
    }
}
  •  storeFile : Form์œผ๋กœ๋ถ€ํ„ฐ ์ „์†ก๋œ ํŒŒ์ผ -> ( UUID.ํ™•์žฅ์ž )๋กœ ๋ณ€๊ฒฝํ•ด์„œ ํŒŒ์ผ์„ ์„œ๋ฒ„(file.dir)์— ์ €์žฅํ•˜๊ณ , Item Repository์—๋Š” Item ์ €์žฅํ•ด์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— UploadFile ํ˜•์‹์œผ๋กœ ๋ณ€๊ฒฝํ•ด์„œ ๋ฆฌํ„ด 
  • storeFiles : Form์œผ๋กœ๋ถ€ํ„ฐ ์ „์†ก๋œ ํŒŒ์ผ ๋ฆฌ์ŠคํŠธ ->  iterator๋ฅผ ์‚ฌ์šฉํ•ด ( UUID.ํ™•์žฅ์ž )๋กœ ๋ณ€๊ฒฝํ•ด์„œ ํŒŒ์ผ์„ ์„œ๋ฒ„์— ํ•˜๋‚˜์”ฉ ์ €์žฅํ•˜๊ณ , Item Repository์—๋Š” Item ์ €์žฅํ•ด์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— UploadFile ํ˜•์‹์œผ๋กœ ๋ณ€๊ฒฝํ•ด์„œ List์— ์ถ”๊ฐ€ํ•ด์„œ ๋ฆฌํ„ด 
  • Controller์˜๋ฅผ ์ฐธ๊ณ .

Controller

ItemController

@Slf4j
@Controller
@RequiredArgsConstructor
public class ItemController {
    private final ItemRepository itemRepository;
    private final FileStore fileStore;

    // Item ๋“ฑ๋ก ํผ Get
    @GetMapping("/items/new")
    public String newItem(@ModelAttribute ItemForm form)
    {
        return "item-form";
    }

    // Item ๋“ฑ๋ก ํผ์„ ํ†ตํ•ด POST
    @PostMapping("/items/new")
    public String saveItem(@ModelAttribute ItemForm form, RedirectAttributes redirectAttributes) throws IOException {

        UploadFile uploadFile = fileStore.storeFile(form.getAttachFile());
        List<UploadFile> uploadFiles = fileStore.storeFiles(form.getImageFiles());

        // ๋ฆฌํฌ์ง€ํ† ๋ฆฌ์— ์ €์žฅ
        Item item = new Item();
        item.setItemName(form.getItemName());
        item.setAttachFile(uploadFile);
        item.setImageFiles(uploadFiles);
        itemRepository.save(item);

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

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

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

    @ResponseBody
    @GetMapping("/images/{fileName}")
    public Resource downloadImage(@PathVariable String fileName) throws MalformedURLException {
        return new UrlResource("file:" + fileStore.getFullPath(fileName));
    }

    // ๋‹ค์šด๋กœ๋“œ
    @GetMapping("/attach/{itemId}")
    public ResponseEntity<Resource> downloadAttach(@PathVariable Long itemId) throws MalformedURLException {
        Item item = itemRepository.findById(itemId);
        String uploadFileName = item.getAttachFile().getUploadFileName();
        String storeFileName = item.getAttachFile().getStoreFileName();

        UrlResource resource = new UrlResource("file:" + fileStore.getFullPath(storeFileName));

        log.info("uploadFileName={}",uploadFileName);

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

        log.info("contentDisp = {}",contentDisposition);

        return ResponseEntity.ok()
                .header(HttpHeaders.CONTENT_DISPOSITION,contentDisposition)
                .body(resource);
    }
}
  • Item ๋“ฑ๋ก ํผ, ์กฐํšŒ ํผ ์ œ๊ณต
  • ๋“ฑ๋ก ํผ์„ ํ†ตํ•œ Item ๊ฐ์ฒด ๋“ฑ๋ก
  • ์กฐํšŒ ํผ์„ ํ†ตํ•œ Item ํŒŒ์ผ ์กฐํšŒ ๋ฐ ๋‹ค์šด๋กœ๋“œ

- saveItem

// Item ๋“ฑ๋ก ํผ์„ ํ†ตํ•ด POST
@PostMapping("/items/new")
public String saveItem(@ModelAttribute ItemForm form, RedirectAttributes redirectAttributes) throws IOException {

    // Form์œผ๋กœ๋ถ€ํ„ฐ Post๋œ ํŒŒ์ผ์„ ์Šคํ† ์–ด์— ์ €์žฅํ•˜๊ณ  UploadFile ๋ฆฌํ„ด -> ๋ฆฌํฌ์ง€ํ† ๋ฆฌ์— ์ €์žฅ
    UploadFile uploadFile = fileStore.storeFile(form.getAttachFile());

    // Form์œผ๋กœ๋ถ€ํ„ฐ Post๋œ ํŒŒ์ผ๋ฆฌ์ŠคํŠธ๋ฅผ ์Šคํ† ์–ด์— ์ €์žฅํ•˜๊ณ  UploadFile List ๋ฆฌํ„ด ->  ๋ฆฌํฌ์ง€ํ† ๋ฆฌ์— ์ €์žฅ
    List<UploadFile> uploadFiles = fileStore.storeFiles(form.getImageFiles());

    // ๋ฆฌํฌ์ง€ํ† ๋ฆฌ์— ์ €์žฅ
    Item item = new Item();
    item.setItemName(form.getItemName());
    item.setAttachFile(uploadFile);
    item.setImageFiles(uploadFiles);
    itemRepository.save(item);

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

    return "redirect:/items/{itemId}";
}
  • 1. @PostMapping saveItem์—์„œ ItemForm์„ ํ†ตํ•ด ๋ฐ›์€ ๋ฐ์ดํ„ฐ๋“ค์„ @ModelAttribute ItemForm์— ํ”„๋กœํผํ‹ฐ ์‚ฝ์ž…
  • 2. fileStore.storeFile, fileStore.storeFiles์„ ํ†ตํ•ด 1๋ฒˆ์—์„œ ์‚ฝ์ž…๋œ ํŒŒ์ผ๋“ค์„ ์ผ์„ ์„œ๋ฒ„(file.dir)์— ์ €์žฅํ•˜๊ณ  ๋ฆฌํ„ด๋ฐ›์€ UploadFile, UploadFiles List๋ฅผ ๋ฆฌํฌ์ง€ํ† ๋ฆฌ์— ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.
  • ์ €์žฅํ•˜๊ณ  ๋‚œ๋’ค  /items/{itemId} (๋ฆฌ๋‹ค์ด๋ ‰์…˜ ์–ดํŠธ๋ฆฌ๋ทฐํŠธ์— ์ถ”๊ฐ€๋œ itemId) ๋กœ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ

- itemView

@GetMapping("/items/{itemId}")
public String itemView(@PathVariable Long itemId, Model model)
{
    Item item = itemRepository.findById(itemId);
    model.addAttribute("item",item);
    return "item-view";
}
  • ์ œ์ถœ ๋ฒ„ํŠผ ํด๋ฆญ ํ›„ saveitem ๋ฉ”์†Œ๋“œ ํ˜ธ์ถœ ํ›„ ๋ฆฌ๋‹ค์ด๋ ‰์…˜ ๋œ /Item/{ItemId} URI  
  • ๊ฒฝ๋กœ ๋ณ€์ˆ˜๋กœ ๋„˜์–ด์˜จ Id๊ฐ’์„ ํ†ตํ•ด ํ•ด๋‹น Id๊ฐ’์— ๋Œ€ํ•œ Item ์ƒ์„ธ ์ •๋ณด๋ฅผ ๋ณด์—ฌ์ฃผ๋Š” item view ๋ทฐํ…œํ”Œ๋ฆฟ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค.

- downloadImage

  • ํŒŒ์ผ ๋ฆฌ์ŠคํŠธ์— ์žˆ๋Š” ํŒŒ์ผ๋“ค์„ ํผ์—์„œ th:each๋ฅผ ํ†ตํ•ด th:src๋ฅผ URI๋กœ ๋ฟŒ๋ ค์ฃผ๋Š”๋ฐ ์ปจํŠธ๋กค๋Ÿฌ์—์„œ๋Š” ๋ฟŒ๋ ค์ค€ ์ด URI๋ฅผ @ResponseBody๋กœ ์ฒ˜๋ฆฌํ•ด์„œ ํ•ด๋‹นํŒŒ์ผ์„ UrlResource๋กœ ํ™”๋ฉด์— ๋ณด์—ฌ์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค.

- downloadAttach

item-view.html

// ๋‹ค์šด๋กœ๋“œ
@GetMapping("/attach/{itemId}")
public ResponseEntity<Resource> downloadAttach(@PathVariable Long itemId) throws MalformedURLException {
    Item item = itemRepository.findById(itemId);
    String uploadFileName = item.getAttachFile().getUploadFileName();
    String storeFileName = item.getAttachFile().getStoreFileName();

    UrlResource resource = new UrlResource("file:" + fileStore.getFullPath(storeFileName));

    log.info("uploadFileName={}",uploadFileName);

    String encode = UriUtils.encode(uploadFileName, StandardCharsets.UTF_8);

    // ๋‹ค์šด๋กœ๋“œ ํ•˜๊ธฐ ์œ„ํ•ด์„œ attachment; filename="๋‹ค์šด๋ฐ›์„ ํŒŒ์ผ์ด๋ฆ„"์„ Header CONTENT_DISPOSITION์— ์„ค์ •
    String contentDisposition = "attachment; filename=\"" + encode + "\"";

    return ResponseEntity.ok()
            .header(HttpHeaders.CONTENT_DISPOSITION,contentDisposition)
            .body(resource); // resource ๋‹ค์šด
}
  • ๋‹ค์šด๋กœ๋“œ ๋ฐ›์€ ํŒŒ์ผ์„ ์ฐพ๊ธฐ ์œ„ํ•ด ItemId๋ฅผ ํ†ตํ•ด Repository์—์„œ Item์„ ์กฐํšŒํ•˜๊ณ 
  • ์กฐํšŒํ•œ Item์˜ StoreFile์ด๋ฆ„๊ณผ UploadFile ์ด๋ฆ„์„ ์–ป์–ด๋ƒ…๋‹ˆ๋‹ค.
  • ์–ป์–ด๋‚ธ StoreFile์ด๋ฆ„์œผ๋กœ ์„œ๋ฒ„์˜ ๋””๋ ‰ํ† ๋ฆฌ(file.dir)๋กœ ๋ถ€ํ„ฐ ํŒŒ์ผ ๋ฆฌ์†Œ์Šค๋ฅผ ์–ป์–ด ๋ƒ…๋‹ˆ๋‹ค.
  • ๋‹ค์šด๋กœ๋“œ ํ•˜๊ธฐ ์œ„ํ•ด์„œ attachment; filename="์—…๋กœ๋“œ ํ•œ ํŒŒ์ผ์ด๋ฆ„" ์„ Header CONTENT_DISPOSITION์— ์„ค์ •ํ•ด ์ฃผ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ( ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ์‹œ์— ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์—…๋กœ๋“œํ•œ ํŒŒ์ผ ์ด๋ฆ„ ์œผ๋กœ ๋‹ค์šด๋กœ๋“œ ํ•˜๊ธฐ ์œ„ํ•ด์„œ )

 

 

๋ธ”๋กœ๊ทธ์˜ ์ •๋ณด

Study Repository

rlaehddnd0422

ํ™œ๋™ํ•˜๊ธฐ