카테고리 없음

서블릿 파일 업로드

전한준 2024. 9. 1. 16:28

 

문자가 아닌 여러가지 문자와 파일을 같이 보낼려면 어떻게 해야 할까?

 

multipart/form-data를 추가하면 파일과 문자를 동시에 보낼 수 있다.

 

 

logging.level.org.apache.coyote.http11=debug

 

디버깅해서 내용을 볼 수 있다.

 

 

 

 

part는 부분을 나눈게 꺼내어서 볼 수 있다.

 

Multipart-form-data의 기본 설정

 

max-file-size: 파일 하나의 최대 사이즈 ,기본 1MB

max-request-size :멀티 파트 사이즈 여러개 업로드가 가능한데 그 전체 합이다. 기본 10MB

 

멀티파트에 관련한 처리를 하지 말아라 .(기본은 true이다.)

 

String itemName = request.getParameter("itemName");
log.info("itemName={}", itemName);

Collection<Part> parts = request.getParts();

 

false이면 이 두개가 처리가 되지 않는다.

true이면 복잡한 멀티 파트 부분을 해결을 할 수 있다.

 

multipartresolver를 실행하게

옵션을 키게 되면 httpServletRequest에서 MultipartServletRequest로 변환이 되서 여러가지 멀티파트와 관련된 추가기능을 수행을 한다. 

 

 

서블릿이 제공하는 Part에 대해 알아보자 

 

 

파일을 업로드 하려면 실제 파일이 저장되는 경로를 설정해 주자 

 

@Slf4j
@Controller
@RequestMapping("/servlet/v2")
public class ServletUploadControllerV2 {

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

    @GetMapping("/upload")
    public String newFile() {
        return "upload-form";
    }

    @PostMapping("/upload")
    public String saveFileV1(HttpServletRequest request) throws ServletException, IOException {
        log.info("request={}", request);

        String itemName = request.getParameter("itemName");
        log.info("itemName={}", itemName);

        Collection<Part> parts = request.getParts();
        log.info("parts={}", parts);

        for (Part part : parts) {
            log.info("==== PART ====");
            log.info("name={}", part.getName());
            Collection<String> headerNames = part.getHeaderNames();
            //Part도 header 와 내용이 따로 분리되어 있다.
            for (String headerName : headerNames) {
                log.info("header {}: {}", headerName, part.getHeader(headerName));
            }
            //편의 메서드
            //content-disposition; filename
            log.info("submittedFilename={}", part.getSubmittedFileName());
            log.info("size={}", part.getSize()); //part body size

            //바디에 있는 데이터 읽기
            InputStream inputStream = part.getInputStream();
            String body = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
            //문자를 바이너리 셋으로 바꿀떄 캐릭터 셋을 언제나 정의를 해 주어야한다.
            log.info("body={}", body);

            //파일에 저장하기
            if (StringUtils.hasText(part.getSubmittedFileName())) {
                //실제 파일이 있는 가
                String fullPath = fileDir + part.getSubmittedFileName();
                //경로를 잡아줌
                log.info("파일 저장 fullPath={}", fullPath);
                part.write(fullPath);
            }
        }

        return "upload-form";
    }
}

 

 

 

파일에 실제 경로를 @Value("${파일경로}")를 통해 넣어주자

Collection part에 part에 헤더와 body 내용이 있는 데 그거를 읽어와서 로그를 찍을 수 있다.

part.getSubmittedFileName 클라이언트가 전달한 파일 이름

part.getInputStream :part의 전송 데이터를 읽음

part.write():part를 통해 전송된 데이터 저장

 

 

 

용량이 큰 파일을 업로드 할 때는 기타 옵션은 꺼주자 

 

 

 

 


기본적인 Spring에 Upload 기능은 무엇이 있나?

 

 

 

 

기본적으로 파일 저장하는 위치를 

@Value("${}")를 가져온다.

 

@Getmapping 처음 화면 들어가기

@Postmapping 데이터를 보내는 곳 

 

 

 

 

파일이 비어 있지 않는 다면 

 

 

fileDir(저장 위치)

@RequestParam MultipartFile file 에 원래 업로드 된 파일명 을 fullPath에 저장을 해준다.

새로운 file.transferTo로 저장을 해준다. 

 

@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()) {
                storeFileResult.add(storeFile(multipartFile));
            }
        }
        return storeFileResult;
    }

    //파일을 저장하는 로직 스프링이 제공하는 Multipartfile을 받고 반환해준다.
    //서버에 저장하는 파일명
    public UploadFile storeFile(MultipartFile multipartFile) throws IOException {
        if (multipartFile.isEmpty()) {
            return null;
        }

        String originalFilename = multipartFile.getOriginalFilename();
        //original +storeFIle name 이 나누어 진다.
        String storeFileName = createStoreFileName(originalFilename);
        multipartFile.transferTo(new File(getFullPath(storeFileName)));
        return new UploadFile(originalFilename, storeFileName);
    }

    private String createStoreFileName(String originalFilename) {
        String ext = extractExt(originalFilename);
        String uuid = UUID.randomUUID().toString();
        return uuid + "." + ext;
        //uuid를 뽑고 "."+확장자를 더해 준다. 저장할 파일 내임
    }

    private String extractExt(String originalFilename) {
        int pos = originalFilename.lastIndexOf(".");
        return originalFilename.substring(pos + 1);
        //.png를 뽑을 수 있다.
    }


}

 

 

이미지를 multipart 다양하게 받아와서 저장한 다음에 반환한다.

 

 

 

 

뒤에 파일 형식 지정자를 뽑고 예를 들어 "ktprices.png"이면 .뒤에 png 형식을 뽑고 uuid를 생성해서 return 해준다.

 

 

 

 

 

 

 

 

 

 

                                      여러개의 파일을 올리기 위해서 MultipartFile 로 만들어준다.

 

파일도 업로드를 여러개 하기 위해서 multiple=multiple을 꼭 해주어야 한다. 

 

 

 

 

form에서 multipart로 여러가지 데이터를 받아준다.

 

 

 

Uploadfilename:고객이 업로드 한 파일이름

storefilename:DB에 저장되는 파일이름

 

따로 저장하는 이유 두개 가 같으면 이게 충돌이 일어난다. 

 

 

mulitple="multiple"을 붙여줘서 여러개의 파일을 보내 줄 수 있다. 

 

 


 

 

 


@Slf4j
@Controller
@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 {
        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 {
        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 storeFileName = item.getAttachFile().getStoreFileName();
        String uploadFileName = item.getAttachFile().getUploadFileName();

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

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

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

        return ResponseEntity.ok()
                .header(HttpHeaders.CONTENT_DISPOSITION, contentDisposition)
                .body(resource);
    }

}

 

 

 

@ModelAttribute를 통해서 form을 전송해준다.

 

 

@ModelAttribute로 form을 가져와서 

다운 받을 파일 1개와 여러가지 파일을 따로 저장한다.

 

item .setItemName .. setAttachFile,...setImageFiles 등등으로 itemRepository에 저장해서 넘기고 (리다이렉트 된 화면으로 정보를 넘기기 위해서 )  redirectAttributes로 바꾼다. (밑에 바뀌는 화면이다.)

 

 

 

@GetMapping("/images/{filename}") : img 태그로 이미지 조회를 할 때 마다 사용한다. @ResponseBody 로 이미지 바이너리를 조회한다. 

 

file: +fileStore.getFullPath로 풀내임을 입력해서 @Responsbody로 리소스를 가져온다.(내가 파일 저장한 위치로 부터)

 

파일을 다운 받는 방법이다. Urlresource를 가져와서 

encodedUploadFileName 인코딩된 방식을 넣는다. (이렇게 안하면 꺠지는 것도 발생)

Response.ok().header(HttpHeaders. xx ,contentDisposition) 이거를 안하면 눌렀을 때 화면을 읽기만 할 수 있다.