[스프링 부트] 36. 파일 업로드를 JSON으로 주고 받기 - 웹 게시판 v6

KangHo Lee's avatar
Dec 06, 2024
[스프링 부트] 36. 파일 업로드를 JSON으로 주고 받기 - 웹 게시판 v6

1. Upload 엔티티

@NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter @Table(name = "upload_tb") @Entity public class Upload { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; private String username; private String profileUrl; @Builder public Upload(Integer id, String username, String profileUrl) { this.id = id; this.username = username; this.profileUrl = profileUrl; } }

2. UploadRepository

@RequiredArgsConstructor @Repository public class UploadRepository { private final EntityManager em; public Upload findById(Integer id) { return em.find(Upload.class, id); } public void save(Upload upload) { em.persist(upload); } }

3. UploadService

@RequiredArgsConstructor @Service public class UploadService { private final UploadRepository uploadRepository; public Upload 사진보기() { return uploadRepository.findById(1); } @Transactional public void v2사진저장(UploadRequest.V2DTO v2DTO) { // DB에 username이랑 dbUrl 저장 String dbUrl = MyFileUtil.fileSave(v2DTO.getImg()); uploadRepository.save(v2DTO.toEntity(dbUrl)); } }

4. MyFileUtil

public class MyFileUtil { // String public static String fileSave(String base64Data) { // 1. 확장자 추출 String mimeType = base64Data.substring(5, base64Data.indexOf(";base64,")); // image/png String extension = mimeType.split("/")[1]; // png // 2. 사진 파일 명을 롤링 String imgName = UUID.randomUUID() + "." + extension; String profileUrl = "images/" + imgName; String dbUrl = "/upload/" + imgName; // 3. Base64를 Byte 배열로 변환 String base64Image = base64Data.split(",")[1]; byte[] decodedBytes = Base64.getDecoder().decode(base64Image); // 4. 사진을 파일로 저장 (images 폴더) try { Path path = Paths.get(profileUrl); Files.write(path, decodedBytes); return dbUrl; } catch (IOException e) { throw new RuntimeException(e.getMessage()); } } }
  • 확장자(png)를 추츨합니다.
  • UUID로 고유한 식별자를 생성해서 파일 이름 앞에 추가합니다.
  • base64로 인코딩된 문자열을 byte[]로 변환합니다.
  • 이미지를 파일로 저장하고 DB에 기록할 url을 return합니다.

4-1. Base64 인코딩 테스트

Base64Test
public class Base64Test { @Test public void base64Encoding_test() { String base64String = "data:image/png;base64,iVBO"; // 데이터 URI에서 Base64 부분만 추출 // base64String.split(",")[0] -> "data:image/png;base64" String base64Data = base64String.split(",")[1]; // 디코딩 byte[] decodedBytes = Base64.getDecoder().decode(base64Data); // 확인 for(byte b : decodedBytes){ System.out.println(b); } } @Test public void mime_test() { String base64String = "data:image/png;base64,iVBO"; // MIME 타입 추출 함수 String mimeType = base64String.substring(5, base64String.indexOf(";base64,")); System.out.println(mimeType); // image/png String extension = mimeType.split("/")[1]; System.out.println(extension); // png } }
  • base64Encoding_test()
    • 데이터 타입을 알려주는 data:image/png;base64 부분을 잘라내고 base64로 인코딩 된 이미지를 byte[]로 변환합니다.
  • mime_test()
    • mime type과 extension을 추출하는 메서드 입니다.

5. UploadController

@RequiredArgsConstructor @Controller public class UploadController { private final UploadService uploadService; @GetMapping("/file2") public String file2() { return "file2"; } @PostMapping("/v2/upload") public ResponseEntity<?> v2upload(@RequestBody UploadRequest.V2DTO v2DTO) { // 반환값을 Resp<?> 로 하면 @ResponseBody 달아야 해서 불편 uploadService.v2사진저장(v2DTO); Resp resp = new Resp(true, "성공", null); return ResponseEntity.ok(resp); // 자동으로 JSON으로 변환해서 보낸다. } // 업로드한 이미지 체크 @GetMapping("/file2-check") public String file2Check(Model model) { Upload upload = uploadService.사진보기(); model.addAttribute("model", upload); return "file2-check"; } }
  • @RequestBody
    • JSON 문자열인 요청의 body 내용을 Jackson 라이브러리를 통해 자바 객체로 변환합니다.
  • new Resp(true, "성공", null)
    • 클라이언트에게 반환할 body 데이터는 없으므로 null 입니다.
  • ResponseEntity<?>
    • ResponseEntity로 반환할 경우 상태 코드(ResponseEntity.ok일 경우 200) 내용이 포함된 JSON 문자열로 변환해서 클라이언트에게 전달합니다.

5-1. Resp

@AllArgsConstructor @Data public class Resp<T> { private Boolean success; private String msg; private T data; }

6. 업로드에 사용하는 DTO

UploadRequest.V2DTO
public class UploadRequest { @Data public static class V2DTO { private String username; private String img; public Upload toEntity(String profileUrl) { Upload upload = Upload.builder() .username(username) .profileUrl(profileUrl) .build(); return upload; } } }
  • 이미지는 JSON으로 받기 때문에 타입이 String입니다.

7. 파일 전송 Form

file2.mustache
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Document</title> </head> <body> <h1>사진 파일 전송 JSON</h1> <hr> <form> <input type="text" id="username"> <br> <input type="file" id="img"> <br> <button type="button">전송</button> <!-- submit 버튼 아님--> </form> <script> let imgInput = document.querySelector("#img"); imgInput.addEventListener("change", (e) => { let file = imgInput.files[0]; let reader = new FileReader(); // FileReader 안에 onload() 함수가 있다. onload 이벤트가 발생할 때 실행될 함수를 정의 reader.onload = () => { let username = document.querySelector("#username").value; let base64String = reader.result; myUpload(username, base64String); } // file을 비동기적으로 읽고 완료되면 reader.onload 이벤트가 발생하여 콜백 함수를 실행 reader.readAsDataURL(file); }); async function myUpload(username, img) { let user = { username: username, img: img }; let requestBody = JSON.stringify(user); let response = await fetch("/v2/upload", { method: "post", body: requestBody, headers: { "Content-Type":"application/json; charset=utf-8" } }); let responseBody = await response.json(); if (responseBody.success) { location.href="/"; } } </script> </body> </html>
  • form 타입은 file로 한 다음 javascript로 JSON으로 변환 합니다.
  • reader.readAsDataURL(file);
    • 비동기로 file을 읽기 시작하며 파일이 완전히 읽혀지면 onload 이벤트가 발생합니다.
💡
자바스크립트의 change 이벤트는 사용자가 <input>, <select>, <textarea>와 같은 폼 요소의 값을 변경하고 나서 포커스가 다른 요소로 이동할 때 발생합니다.

8. 업로드 된 이미지 파일을 확인하는 화면

file2-check.mustache
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Document</title> </head> <body> <h1>{{model.username}}</h1> <img src="{{model.profileUrl}}"> </body> </html>
 
Share article

devleekangho