πŸ’‘ κ²Œμ‹œνŒ - 파일 μ—…λ‘œλ“œ 3(κ°œμ„ )

박상민·2023λ…„ 9μ›” 16일
0
post-thumbnail

πŸ’‘ κ²Œμ‹œνŒ - 파일 μ—…λ‘œλ“œ 2μ—μ„œ κ°œμ„ ν•  점으둜

  • 사진을 보기 μœ„ν•΄μ„œλŠ” μ„œλ²„ μž¬μ‹€ν–‰μ΄ ν•„μš”ν•˜λ‹€.
  • 사진을 ν΄λ¦­ν•΄μ„œ λ³΄λŠ” 것이 μ•„λ‹Œ κ²Œμ‹œκΈ€ 상세 νŽ˜μ΄μ§€μ— 보이고 μ‹Άλ‹€.

λ₯Ό 이야기 ν–ˆλ‹€.

이번 κΈ€μ—μ„œλŠ” μž‘μ„±ν•œ 파일 μ—…λ‘œλ“œμ˜ μ½”λ“œλ₯Ό κ°œμ„ ν•˜κ³  이미지λ₯Ό ν΄λ¦­ν•΄μ„œ λ³΄μ΄λŠ” 것이 μ•„λ‹Œ κ²Œμ‹œκΈ€ μƒμ„ΈνŽ˜μ΄μ§€μ—μ„œ 보이도둝 κ°œμ„ ν–ˆλ‹€.

BoardService의 save λ©”μ„œλ“œ

 public void save(Board board, MultipartFile file) throws Exception {

        if(!file.isEmpty()){ //파일 μ—…λ‘œλ“œκ°€ μžˆλŠ” κ²½μš°μ—λ§Œ μ‹€ν–‰
            UploadFile uploadFile = fileStore.storeFile(file);
            board.setFilename(uploadFile.getUploadFilName());

            board.setFilepath(uploadFile.getStoreFileName());
        }

        boardRepository.save(board);
    }

FileStore

@Component
public class FileStore {

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

    public String getFullPath(String filename) {
        return fileDir + filename;
    }

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

        String originalFilename = multipartFile.getOriginalFilename();
        String storeFileName = createStoreFileName(originalFilename); //랜덀의 uuidλ₯Ό μΆ”κ°€ν•œ 파일 이름
        multipartFile.transferTo(new File(getFullPath(storeFileName)));

        return new UploadFile(originalFilename, storeFileName);
    }

    // μ„œλ²„ λ‚΄λΆ€μ—μ„œ κ΄€λ¦¬ν•˜λŠ” 파일λͺ…은 μœ μΌν•œ 이름을 μƒμ„±ν•˜λŠ” UUIDλ₯Ό μ‚¬μš©ν•΄μ„œ μΆ©λŒν•˜μ§€ μ•Šλ„λ‘ ν•œλ‹€.
    private String createStoreFileName(String originalFilename) {
        String ext = extractExt(originalFilename);
        String uuid = UUID.randomUUID().toString(); //파일 이름 쀑볡 방지
        return uuid + "." + ext;
    }

    //ν™•μž₯자λ₯Ό λ³„λ„λ‘œ μΆ”μΆœν•΄μ„œ μ„œλ²„ λ‚΄λΆ€μ—μ„œ κ΄€λ¦¬ν•˜λŠ” 파일λͺ…에도 λΆ™μ—¬μ€€λ‹€.
    //Ex) a.pngλΌλŠ” μ΄λ¦„μœΌλ‘œ μ—…λ‘œλ“œν•˜λ©΄ 2def12-42qd-3214-e2dqda2.png 와 같이 ν™•μž₯자λ₯Ό μΆ”κ°€ν•΄μ„œ μ €μž₯ν•œλ‹€.
    private String extractExt(String originalFilename) {
        int pos = originalFilename.lastIndexOf("."); //파일의 ν™•μž₯자 μΆ”μΆœ ex) .png .img
        return originalFilename.substring(pos + 1);
    }
}

μ›λž˜ BoardService의 save λ©”μ„œλ“œμ—μ„œ μ²˜λ¦¬ν•˜λ˜ 파일 μ—…λ‘œλ“œ 과정을 FileStore 클래슀λ₯Ό λ§Œλ“€μ–΄μ„œ κ°œμ„ μ„ ν–ˆλ‹€. 덕뢄에 save λ©”μ„œλ“œκ°€ κΉ”λ”ν•΄μ‘Œλ‹€.

이제 κ²Œμ‹œκΈ€ 상세 νŽ˜μ΄μ§€μ—μ„œ μ—…λ‘œλ“œ ν•œ 이미지가 보이도둝 처리λ₯Ό ν•˜κ² λ‹€.

이미지 파일 λΆ€λΆ„

<span th:if="${post.filepath != null}">
     <img th:src="@{'/images/' + ${post.filepath}}" width="300" height="300"/><br>
</span>

방법은 κ°„λ‹¨ν•˜λ‹€. κ²Œμ‹œκΈ€ 상세 νŽ˜μ΄μ§€λ₯Ό λ³΄μ—¬μ£ΌλŠ” html에 μœ„μ˜ μ½”λ“œλ₯Ό μΆ”κ°€ν•˜λ©΄ λœλ‹€. post.filepathλ₯Ό ν•΄μ„œ 파일의 경둜λ₯Ό λΆˆλŸ¬μ™€μ„œ 이미지λ₯Ό 보여쀀닀.

적용이 λλŠ”μ§€ ν™•μΈν•΄λ³΄μž.


μ‚¬μ§„μ²˜λŸΌ μž‘μ„±μ„ ν•˜κ³  이미지 νŒŒμΌμ„ μ²¨λΆ€ν•˜κ² λ‹€.


κ²Œμ‹œκΈ€ μž‘μ„± ν›„ κ²Œμ‹œκΈ€ 상세 νŽ˜μ΄μ§€μ— λ“€μ–΄κ°€λ‹ˆ 이미지가 보이지 μ•ŠλŠ”λ‹€.
λ˜ν•œ, κ΄€λ¦¬μž 도ꡬλ₯Ό 보면 이미지 파일이 HTTP Status 404 였λ₯˜κ°€ λ‚œ 것이 보인닀.
μ΄μœ κ°€ 뭘까?



이미지 νŒŒμΌμ„ λˆŒλŸ¬μ„œ λ“€μ–΄κ°„ νŽ˜μ΄μ§€μ΄λ‹€. μ›Ή λΈŒλΌμš°μ €λŠ” 파일의 경둜만 λ§ν¬ν•΄μ£ΌλŠ” 것이지 DBκ°€ νŒŒμΌμ„ 가지고 μžˆλŠ” 것이 μ•„λ‹ˆλ‹€.
λ•Œλ¬Έμ— Files/이미지 κ²½λ‘œμ— λŒ€ν•œ 컨트둀러λ₯Ό λ§Œλ“€μ–΄μ€˜μ•Όν•œλ‹€.


BoardController에 μΆ”κ°€λœ λ©”μ„œλ“œ

@ResponseBody
@GetMapping("/images/{filename}") // images/DB에 μ €μž₯된 파일 경둜
public Resource downloadImage(@PathVariable String filename) throws MalformedURLException {
    // "file:/Users/../0d713e88-4723-4088-bb4b-be039b6f9b47.png"
    
    //κ²½λ‘œμ— μžˆλŠ” νŒŒμΌμ— μ ‘κ·Όν•΄μ„œ νŒŒμΌμ„ 슀트림으둜 λ°˜ν™˜μ„ 함
    return new UrlResource("file:" + fileStore.getFullPath(filename));
    }

μ΄μ „μ˜ μ—λŸ¬ 사진을 보면 κ²½λ‘œκ°€ /images/DB에 μ €μž₯된 파일 경둜둜 λ„˜μ–΄μ˜¨λ‹€. @GetMapping으둜 경둜λ₯Ό λ°›λŠ”λ‹€.

Resource
Spring의 Resource μΈν„°νŽ˜μ΄μŠ€λŠ” Resource에 λŒ€ν•œ 접근을 μΆ”μƒν™”ν•˜κΈ° μœ„ν•œ μΈν„°νŽ˜μ΄μŠ€μ΄λ‹€.

file: http: μ΄λŸ°μ‹μ˜ Prefix둜 ν”„λ‘œν† μ½œμ„ λͺ…μ‹œν•΄μ£Όκ³  ν•΄λ‹Ή λ¦¬μ†ŒμŠ€μ˜ μœ„μΉ˜λ₯Ό μ•Œλ €μ£ΌλŠ” URL방식을 ν†΅ν•΄μ„œ λ¦¬μ†ŒμŠ€μ˜ μœ„μΉ˜λ₯Ό μ•Œλ €μ£ΌλŠ” 방식

Resource resource = new UrlResource("file:{μ ˆλŒ€κ²½λ‘œ}");

downloadImage λ©”μ„œλ“œλŠ” Spring의 Resource μΈν„°νŽ˜μ΄μŠ€λ₯Ό μ‚¬μš©ν•΄μ„œ UrlResource둜 이미지 νŒŒμΌμ„ μ½μ–΄μ„œ @Responsebody둜 이미지 λ°”μ΄λ„ˆλ¦¬λ₯Ό λ°˜ν™˜ν•œλ‹€.

이제 λ‹€μ‹œ κ²Œμ‹œκΈ€μ„ λ“±λ‘ν•˜κ³  이미지 νŒŒμΌμ„ ν™•μΈν•΄λ³΄μž.



이전과 λ™μΌν•˜κ²Œ κ²Œμ‹œκΈ€μ„ μž‘μ„±ν•΄μ€€λ‹€.


κ²Œμ‹œκΈ€ μž‘μ„± ν›„ κ²Œμ‹œκΈ€ 상세 νŽ˜μ΄μ§€λ₯Ό λ³΄λ‹ˆ μ—…λ‘œλ“œν•œ 이미지 파일이 μ •μƒμ μœΌλ‘œ 보인닀. λ˜ν•œ κ΄€λ¦¬μž 도ꡬλ₯Ό 보면 HTTP statusκ°€ 400이 μ•„λ‹Œ 200으둜 μ˜€λŠ” 것이 보인닀.


μ—¬μ „νžˆ λΆ€μ‘±ν•œ 점이 λ§Žλ‹€.

  • 파일 λ‹€μš΄λ‘œλ“œ κΈ°λŠ₯

  • μ—…λ‘œλ“œν•œ νŒŒμΌμ„ μ‚­μ œν•˜κ²Œ λ§Œλ“€κ³  μ‹Άλ‹€.

  • μ²¨λΆ€νŒŒμΌ μ—…λ‘œλ“œ, λ‹€μš΄λ‘œλ“œ

λ“±λ“± 파일 μ—…λ‘œλ“œ 뢀뢄을 μ œμ™Έν•˜κ³  λ‹€λ₯Έ λΆ€λΆ„μ—μ„œλ„ μˆ˜μ •ν•΄μ•Ό ν•  뢀뢄이 λ§Žλ‹€.

항상 뢀쑱함을 λŠλΌμ§€λ§Œ μ‘°κΈˆμ”© μ„±μž₯ν•˜λŠ” μžμ‹ μ„ λ³΄λ‹ˆ λΏŒλ“―ν•¨λ„ 같이 λŠλ‚€λ‹€. μ—¬μ „νžˆ 개발이 μž¬λ°Œλ‹€. μ§€κΈˆκΉŒμ§€μ˜ μΈμƒμ—μ„œ ν–ˆλ˜ 것 쀑 λ‘λ²ˆμ§Έλ‘œ μž¬λ°Œλ‹€. μ²«λ²ˆμ§ΈλŠ” μ—¬μžμΉœκ΅¬λž‘ λ…ΈλŠ”κ±°.. μ•žμœΌλ‘œλ„ λ°œμ „ν•˜λŠ” λ‚΄κ°€ 되길 바라며 였늘의 글은 μ—¬κΈ°μ„œ 끝내겠닀!


κΈ€μ—μ„œ λΆ€μ‘±ν•œ 뢀뢄이 λ§Žμ„ 것 κ°™μŠ΅λ‹ˆλ‹€. λͺ¨λ“  지적을 ν™˜μ˜ν•©λ‹ˆλ‹€!!

더 μžμ„Έν•œ μ½”λ“œλŠ” κΉƒν—ˆλΈŒλ₯Ό μ°Έκ³ ν•΄μ£Όμ„Έμš”!

κΉƒν—ˆλΈŒ: https://github.com/pp8817/ToyProjectBoard

profile
μŠ€ν”„λ§ λ°±μ—”λ“œλ₯Ό 곡뢀쀑인 λŒ€ν•™μƒμž…λ‹ˆλ‹€!

0개의 λŒ“κΈ€