괜찮은 에디터로는 tinymce, ckeditor가 있다.
나는 spring framework를 사용, 뷰로는 jsp를 사용할 것이다.
- tinymce
- jsp에서 안 되는 듯 하다.. html 정적으로는 되는데 tomcat 서버를 통해 jsp로 출력이 되지 않는다...그냥 내가 잘 몰라서 그런 걸 수도..
- ckeditor 5
- npm 으로 어찌저찌 js 라이브러리 같은 걸 깔긴 했는데.. 그.. 파일 업로드 방법을 잘 모르겠다.. 아무리해도 사진이 서버에 업로드 되지 않는걸...
- ckeditor 4
- 관련 블로그 글이 많아서 성공! ckeditor5로 해내고 싶었는데.. ㅠㅠ 공식 문서 보는 방법을 더 익힌 후에 시도해보는 걸로 하자!
아래 링크는 참고할만한 블로그 글을 모아놨다...
- tinymce
- https://myhappyman.tistory.com/234
- https://catchdream.tistory.com/152
- https://www.tiny.cloud/docs/tinydrive/libraries/java/
- ckeditor
- 4
- https://kuzuro.blogspot.com/2018/10/13.html
- https://badstorage.tistory.com/36
- https://kuzuro.blogspot.com/2018/10/13.html
- https://github.com/kuzuro/kubg/blob/495e2836c826c6bfca8a71565019c1f3694ced7f/kubg/src/main/resources/mappers/adminMapper.xml
- https://nm-it-diary.tistory.com/30
- https://earscoming.tistory.com/127
- https://devmg.tistory.com/289
- https://codermimi.tistory.com/48
- https://sorrel012.tistory.com/311
- https://gaebalja.tistory.com/40
- https://romworld.tistory.com/110
- https://nelapham.tistory.com/37
- https://dorothy-yang.tistory.com/186
- 5
- https://velog.io/@kchm0224/ckeditor-이미지-업로드부터-db저장-db-read까지
- https://freehoon.tistory.com/123
trouble shootings
- ckeditor 4 LTS인 v4.23.0-lts 사용 시, [CKEDITOR] Error code: invalid-lts-license-key. 에러가 뜨면서 에디터가 보이지 않음.
- 해결방법 - v4.22.1 사용함
나의 코드
깃허브에서 전체 코드 확인하기
https://github.com/thegreatjy/ChunjaeFullStack/tree/main/Spring_Study/ckeditor
실행환경
- java jdk 11
- ckeditor v4.22.1
https://velog.io/@joon1106/CKeditor4-사용사진-업로드#게시판-작성-수정-만든뒤-구현이-잘되는것을-확인한뒤-ckeditor-추가하는것을-추천-1
이미지 업로드 부분은 이 윗 분의 코드를 거의 복사했더니 됐다 !
디렉터리 구조
pom.xml
- 필요없는 것들도 포함되어있다.. 귀찮아서 전체 복사를 한다.
commons-io
,commons-fileupload
,org.json
이건 추가해주어야 한다..아마도?
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>ckeditor</artifactId>
<version>1.0-SNAPSHOT</version>
<name>ckeditor</name>
<packaging>war</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.source>1.8</maven.compiler.source>
<junit.version>5.9.2</junit.version>
<java-version>11</java-version>
<org.springframework-version>5.3.29</org.springframework-version>
<org.aspectj-version>1.9.6</org.aspectj-version>
<org.slf4j-version>1.7.25</org.slf4j-version>
</properties>
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<!-- Spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${org.springframework-version}</version>
<exclusions>
<!-- Exclude Commons Logging in favor of SLF4j -->
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-core -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${org.springframework-version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${org.springframework-version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${org.springframework-version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${org.springframework-version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-tx -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${org.springframework-version}</version>
</dependency>
<!-- AspectJ -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>${org.aspectj-version}</version>
</dependency>
<!-- Logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${org.slf4j-version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>${org.slf4j-version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>${org.slf4j-version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
<exclusions>
<exclusion>
<groupId>javax.mail</groupId>
<artifactId>mail</artifactId>
</exclusion>
<exclusion>
<groupId>javax.jms</groupId>
<artifactId>jms</artifactId>
</exclusion>
<exclusion>
<groupId>com.sun.jdmk</groupId>
<artifactId>jmxtools</artifactId>
</exclusion>
<exclusion>
<groupId>com.sun.jmx</groupId>
<artifactId>jmxri</artifactId>
</exclusion>
</exclusions>
<!--<scope>runtime</scope>-->
</dependency>
<!-- @Inject -->
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>
<!-- Servlet -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<!-- jakarta servlet -->
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>5.0.0</version>
<scope>provided</scope>
</dependency>
<!-- Test -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!-- lombok -->
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
<scope>provided</scope>
</dependency>
<!-- DB -->
<!-- https://mvnrepository.com/artifact/org.mariadb.jdbc/mariadb-java-client -->
<dependency>
<groupId>org.mariadb.jdbc</groupId>
<artifactId>mariadb-java-client</artifactId>
<version>3.3.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.zaxxer/HikariCP -->
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>3.2.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.11</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.6</version>
</dependency>
<!-- fileupload(사진 업로드) -->
<!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.15.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.5</version>
</dependency>
<!-- ckeditor json -->
<!-- https://mvnrepository.com/artifact/org.json/json -->
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20231013</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.3.2</version>
</plugin>
</plugins>
</build>
</project>
- servlet-context.xml
<!-- 사진 업로드 -->
<beans:bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver" />
<!-- ckeditor통해 업로드된 사진의 저장 경로 -->
<resources mapping="/uploads/**" location="/resources/uploads/" />
- ckeditor4.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
<script src="https://code.jquery.com/jquery-3.7.1.slim.js" integrity="sha256-UgvvN8vBkgO0luPSUl2s8TIlOSYRoGFAX4jlCIm9Adc=" crossorigin="anonymous"></script>
<script src="/resources/ckeditor/ckeditor.js"></script>
<!-- <script src="ckeditor.js"></script> -->
</head>
<body>
<div class="mb-3">
<form method="post" action="/upload">
<textarea class="form-control" id="content" name="content" rows="3"></textarea>
<script>
var ckeditor_config = {
width: "100%",
height:"400px",
image_previewText: '이건어디에',
resize_enabled : false,
enterMode : CKEDITOR.ENTER_BR,
shiftEnterMode : CKEDITOR.ENTER_P,
filebrowserUploadUrl : "/food/imageUpload.do"
};
CKEDITOR.replace("content", ckeditor_config);
//이미지 업로드가 끝나고 실행하는 함수
CKEDITOR.on( 'dialogDefinition', function( ev ) {
// Take the dialog name and its definition from the event data.
let dialogName = ev.data.name;
let dialogDefinition = ev.data.definition;
let uploadTab = dialogDefinition.getContents( 'Upload' );
let uploadButton = uploadTab.get('uploadButton');
uploadButton['filebrowser']['onSelect'] = function( fileUrl, errorMessage ) {
}
});
</script>
<input type="submit" value="전송">
</form>
</div>
</body>
</html>
- testController.java
// 에디터 문자열 콘솔에 확인
@PostMapping("/upload")
public void ckeditorUpload(Content content){
System.out.println(content);
}
// 이미지 업로드
@PostMapping("/food/imageUpload.do")
public void imageUpload(HttpServletRequest request,
HttpServletResponse response, MultipartHttpServletRequest multiFile
, @RequestParam MultipartFile upload) throws Exception {
// 랜덤 문자 생성
UUID uid = UUID.randomUUID();
OutputStream out = null;
PrintWriter printWriter = null;
//인코딩
response.setCharacterEncoding("utf-8");
response.setContentType("text/html;charset=utf-8");
try {
//파일 이름 가져오기
String fileName = upload.getOriginalFilename();
byte[] bytes = upload.getBytes();
//이미지 경로 생성
System.out.println("\n\n ===== 현재 경로 : " + request.getContextPath());
//String path = "/Users/Desktop/Spring_Study/ckeditor/src/main/webapp/resources/uploads/"; // 이미지 경로 설정(폴더 자동 생성)
String path = "/resources/uploads/"; // 이미지 경로 설정(폴더 자동 생성)
String ckUploadPath = path + uid + "_" + fileName;
File folder = new File(path);
System.out.println("path:" + path); // 이미지 저장경로 console에 확인
//해당 디렉토리 확인
if (!folder.exists()) {
try {
folder.mkdirs(); // 폴더 생성
} catch (Exception e) {
e.getStackTrace();
}
}
out = new FileOutputStream(new File(ckUploadPath));
out.write(bytes);
out.flush(); // outputStram에 저장된 데이터를 전송하고 초기화
String callback = request.getParameter("CKEditorFuncNum");
printWriter = response.getWriter();
String fileUrl = "/food/ckImgSubmit.do?uid=" + uid + "&fileName=" + fileName; // 작성화면
// 업로드시 메시지 출력
printWriter.println("{\"filename\" : \"" + fileName + "\", \"uploaded\" : 1, \"url\":\"" + fileUrl + "\"}");
printWriter.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (out != null) {
out.close();
}
if (printWriter != null) {
printWriter.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return;
}
// 서버로 전송된 이미지 뿌려주기
@RequestMapping(value="/food/ckImgSubmit.do")
public void ckSubmit(@RequestParam(value="uid") String uid
, @RequestParam(value="fileName") String fileName
, HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//서버에 저장된 이미지 경로
//String path = "/Users/Desktop/Spring_Study/ckeditor/src/main/webapp/resources/uploads/"; // 저장된 이미지 경로
String path = "/resources/uploads/"; // 이미지 경로 설정(폴더 자동 생성)
System.out.println("path:" + path);
String sDirPath = path + uid + "_" + fileName;
File imgFile = new File(sDirPath);
//사진 이미지 찾지 못하는 경우 예외처리로 빈 이미지 파일을 설정한다.
if (imgFile.isFile()) {
byte[] buf = new byte[1024];
int readByte = 0;
int length = 0;
byte[] imgBuf = null;
FileInputStream fileInputStream = null;
ByteArrayOutputStream outputStream = null;
ServletOutputStream out = null;
try {
fileInputStream = new FileInputStream(imgFile);
outputStream = new ByteArrayOutputStream();
out = response.getOutputStream();
while ((readByte = fileInputStream.read(buf)) != -1) {
outputStream.write(buf, 0, readByte);
}
imgBuf = outputStream.toByteArray();
length = imgBuf.length;
out.write(imgBuf, 0, length);
out.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
outputStream.close();
fileInputStream.close();
out.close();
}
}
}
path를 내 로컬 컴퓨터의 절대경로로 했을 때 사진 전송 및 저장이 잘 된다..
하지만 aws 배포를 하게 되거나 다른 팀원이 테스트할 때에는 경로를 바꿔 주어야 하는데 아직 해결하지 못했다.
request.getContextPath로 하면 빈 문자열이 반환되어 어떻게 해야할지 모르겠다..
⇒ 해결 : https://ninedc.tistory.com/entry/JAVA-Spring-위지윅-CK-에디터CKEditor-4-파일업로드
- pom.xml
@Resource 애너테이션 사용을 위함
<!-- ckeditor @Resource -->
<!-- https://mvnrepository.com/artifact/javax.annotation/javax.annotation-api -->
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
</dependency>
- servlet-context.xml에 업로드 경로를 추가
<!-- ckeditor통해 업로드된 사진의 저장 경로 -->
<beans:bean class="java.lang.String" id="uploadPath">
<beans:constructor-arg value="/Users/Desktop/Spring_Study/ckeditor/src/main/webapp/resources/"></beans:constructor-arg>
</beans:bean>
<resources mapping="/uploads/**" location="/resources/uploads/" />
- testController.java에 멤버변수로 String path 설정해준다. @Resource통해 servlet-context.xml에 지정해놓은 경로와 맵핑.
package com.example.ckeditor;
import javax.annotation.Resource;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import java.io.*;
import java.util.UUID;
@Controller
public class testController {
@Resource(name="uploadPath")
private String uploadPath;
... 다른 메서드들 생략...
}
- 경로 사용법
// 이미지 업로드
@PostMapping("/food/imageUpload.do")
public void imageUpload(HttpServletRequest request,
HttpServletResponse response, MultipartHttpServletRequest multiFile
, @RequestParam MultipartFile upload) throws Exception {
ckUploadPath = uploadPath + File.separator + "uploads" + File.separator + uid + "_" + fileName;
ckUploadPath = uploadPath + File.separator + "uploads" + File.separator + uid + "_" + fileName;
이 부분이다. 멤버변수인 uploadPath을 사용하여 경로 지정을 한다. 풀 코드는 바로 위 url에서 얻을 수 있다.
- 결과
System.out.println(uploadPath);
System.out.println(ckUploadPath);
을 했을 때, servlet-context.xml에 지정한 경로가 나온다.