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