쇼핑몰에서 상품 사진을 업로드한다는 전제 하에 작업을 진행한다.
CKEditor4 다운로드
CKEditor4에서 Standard Package를 다운로드한다. 압축을 풀면 나오는 ckeditor 파일을 복사해 사용하려는 프로젝트의 static 폴더에 붙여넣기한다.
📝 어떤 버전, 어떤 패키지를 받아도 상관은 없다.
그리고 C 드라이브 원하는 위치에 /upload/ckupload
폴더를 만들어 준다. 임시로 사진 파일을 저장해 둘 곳이다.
add.html
생성<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<title>Insert title here</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css">
<link rel="stylesheet" href="/css/main.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"></script>
<!-- sweetalert (alert창 예쁘게) -->
<script src="//cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<script src="/ckeditor/ckeditor.js"></script>
<script>
$(document).ready(function() {
// 파일 업로드를 할 경로를 지정하자. (with csrf)
const $ckUploadPath = "/product/image?_csrf=" + $('#_csrf').val();
CKEDITOR.replace('info', {
filebrowserUploadUrl : $ckUploadPath
})
})
</script>
</head>
<body>
<div id="page">
<!-- 시맨틱 마크업 -->
<header th:replace="/fragments/header.html">
</header>
<nav th:replace="/fragments/nav.html">
</nav>
<div id="main">
<aside th:replace="/fragments/aside.html">
</aside>
<section>
<input type="hidden" name="_csrf" id="_csrf" th:value="${_csrf.token}">
<div class="form-group">
<!-- 이 textarea를 ckeditor에 넘겨 준다. -->
<textarea class="form-control" rows="5" id="info" name="info"></textarea>
</div>
</section>
</div>
<footer th:replace="/fragments/footer.html">
</footer>
</div>
</body>
</html>
src/main/java - com.example.demo.controller.mvc - ProductController
생성
package com.example.demo.controller.mvc;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class ProductController {
// 상품 추가
@GetMapping("/product/add")
public void add() {
}
}
src/main/java- com.example.demo.controller.rest - ProductRestController
생성
package com.example.demo.controller.rest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import com.example.demo.dto.CKResponse;
import com.example.demo.service.ProductService;
@RestController
public class ProductRestController {
@Autowired
private ProductService service;
// 'add.html'에서 지정해 두었던 주소
@PostMapping("/product/image")
// MultipartFile 파라미터명 고정!
public ResponseEntity<CKResponse> ckImageUpload(MultipartFile /* 파라미터명 upload로 고정 */ upload) {
CKResponse ckResponse = service.ckImageUpload(upload);
return ResponseEntity.ok(ckResponse);
}
}
📝 ck를 이용해 서버에 사진을 업로드하면 특정 형식으로 응답해야 한다.
🚨 MultipartFile 파라미터명은
upload
로 고정해야 한다.
src/main/java - com.example.demo.dto - CKResponse
생성
package com.example.demo.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class CKResponse {
// 업로드한 이미지 개수
private Integer uploaded;
// 파일명 - 이미지 이름이 겹칠 수 있으니까 이름을 바꿔서 저장한다.
private String fileName;
// 이미지를 볼 수 있는 주소
private String url;
}
src/main/java - com.example.demo.service - ProductService
생성
package com.example.demo.service;
import java.io.File;
import java.io.IOException;
import java.util.UUID;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import com.example.demo.dto.CKResponse;
@Service
public class ProductService {
// 임시로 직접 만든 폴더를 연결해 준다.
@Value("c:/java/upload/ckupload")
private String CKImageFolder;
@Value("/images/")
private String ckImagepath;
public CKResponse ckImageUpload(MultipartFile image){
if (/* ★ 여기 순서 바뀌지 않도록 주의! */ image != null && image.isEmpty() == false) {
// 파일명 바꾸기
String imageName = UUID.randomUUID() + "-" + image.getOriginalFilename();
// 하드디스크에 이미지를 저장할 파일을 생성
File file = new File(CKImageFolder, imageName);
// 이미지를 파일로 이동
try {
image.transferTo(file);
} catch (IllegalStateException | IOException e) {
e.printStackTrace();
}
return new CKResponse(1, imageName, ckImagepath + imageName);
}
return null;
}
}
📝 스프링에서는
/member/find_id?username=spring
을/member/find_id/spring
으로 적을 수 있다./images/
라고 적을 수 있는 게 그 이유다.
📝 url-encoded 형식은 null 체크 또는 ""(빈 문자열) 체크가 필요하지만, form-data는 null 또는 빈 객체 체크가 필요하다.
💡 UUID (Universally Unique IDentifier)
범용 고유 식별자를 말한다. 실용적인 목적을 위해 고유성이 보장되는 id를 만드는 표준 규약이다. 128비트의 숫자이며 32자리의 16진수로 표현된다.
application.properties
에 코드 추가### 파일 업로드하는 방법 : apache commons fileupload, servlet 3.1 이후 직접 지원
spring.servlet.multipart.enabled=true
spring.servlet.multipart.max-request-size = 30MB
spring.servlet.multipart.max-file-size = 10MB