10/10/2018

스프링 쇼핑몰 만들기 #13. 위지윅(WYSIWYG) 에디터 적용

스프링 쇼핑몰 만들기
- 깃허브 링크

위지윅 에디터란, 에디터에서 작성한 형태와 거의 똑같은 형태로 볼 수 있게되는 에디터입니다. 위지윅 에디터가 없다면, 태그를 하나하나 입력해야겠지요. 이미지를 하나 추가하려면 FTP등을 이용해 이미지를 업로드하고, URL을 알아내어 <img> 태그로 추가해야됩니다.

하지만 위지윅 에디터가 있다면 매우 편리하게 글을 작성할 수 있습니다.

제가 사용할 위지윅 에디터는 CK에디터입니다.

다양한 기능을 가지고 있고, 간편히 사용할 수 있으며, 조건만 만족된다면 무료로 사용할 수 있는 오픈소스 에디터입니다.

CK에디터를 다운로드받아서, src/main/webapp/resources 에 복사/붙여넣기를 했습니다.

가장 먼저 register.jsp의 <head> ~ </head> 에 스크립트를 추가합니다.

<script src="/resources/ckeditor/ckeditor.js"></script>

그리고 상품소개의 텍스트에어리어(textarea) 아래에 스크립트를 추가합니다.

<script>
 var ckeditor_config = {
   resize_enaleb : false,
   enterMode : CKEDITOR.ENTER_BR,
   shiftEnterMode : CKEDITOR.ENTER_P,
   filebrowserUploadUrl : "/admin/goods/ckUpload"
 };
 
 CKEDITOR.replace("gdsDes", ckeditor_config);
</script>

Json형태의 변수인 ckeditor_config를 선언및 설정하고, 마지막줄의 코드 CKEDITOR.replace([텍스트에어리어의 ID, 변수]); 를 이용해 텍스트에어리어를 CK에디터로 교체합니다.

이때 중요한 부분은 fileborwerUploadUrl이라는 부분입니다. 파일을 업로드할 경우, 해당 부분에서 설정한 URL로 전송되므로 컨트롤러에서 만들어줄 필요가 있습니다.

프로젝트를 실행해보면, 밋밋한 텍스트에어리어 대신 여러가지 기능이 추가된 CK에디터가 보입니다.

이제 대충 내용을 입력하고 등록을 누르면

에러가 발생합니다. 이 에러는 CK에디터와 무관한 에러입니다.

파일 등록과 관련된 에러인데, 이전에 작성한 코드가 잘못 작성했습니다.

파일을 추가하지 않더라도 file은 값을 가지고 있습니다. 그 값이 아무것도 없이 공간만 차이한다고해도 말이죠.

마찬가지로 용량도 값이 있는데, 유일하게 값이 없는건 파일 이름(OriginalFilename)입니다.

그렇기 때문에 조건문을 file != null 처럼 file 자체를 비교하는게 아니라, 파일 이름을 기준으로 비교해야합니다.

file.getOriginalFilename() != null && file.getOriginalFilename() != ""

다시 아무 내용이나 입력한 뒤 등록 버튼을 클릭합니다.

데이터 베이스에서 확인하면 태그가 추가된 형식으로 저장되어있는걸 확인할 수 있습니다.

조회 화면에서 밋밋하게 보이기 떄문에 잘 알아보기 어렵지만, F12를 눌러 관리자 모드로 살펴보면 태그가 들어가있는걸 알 수 있습니다.

CK에디터의 메뉴중에 소스를 클릭해보면, 어떤 태그가 들어가있는지 알 수 있습니다.

CK에디터의 기능중 하나는 이미지 첨부 기능이 있습니다.

이렇게 업로드 탭에서 파일을 선택하고, 서버로 전송 버튼을 클릭하면 업로드가 됩니다.

하지만 위에서 설정한 CK에디터의 파일 업로드 주소(admin/goods/ckUpload)는 아직 컨트롤러에 없기 때문에 404에러가 발생합니다.

먼저 src/main/webapp/resources에 ckUpload 폴더를 생성합니다. 이 폴더는 CK에디터에서 업로드하는 파일이 저장될 폴더입니다.

이미지 첨부 기능 구현에서 했던것과 마찬가지로, servlet-context.xml에 경로를 설정합니다.

<!-- ck에디터 파일 업로드 경로 -->
<resources mapping="/ckUpload/**" location="/resources/ckUpload/" />

컨트롤러에 CK에디터 업로드용 메서드를 추가합니다.

// ck 에디터에서 파일 업로드
@RequestMapping(value = "/goods/ckUpload", method = RequestMethod.POST)
public void postCKEditorImgUpload(HttpServletRequest req,
          HttpServletResponse res,
          @RequestParam MultipartFile upload) throws Exception {
 logger.info("post CKEditor img upload");
 
 // 랜덤 문자 생성
 UUID uid = UUID.randomUUID();
 
 OutputStream out = null;
 PrintWriter printWriter = null;
   
 // 인코딩
 res.setCharacterEncoding("utf-8");
 res.setContentType("text/html;charset=utf-8");
 
 try {
  
  String fileName = upload.getOriginalFilename();  // 파일 이름 가져오기
  byte[] bytes = upload.getBytes();
  
  // 업로드 경로
  String ckUploadPath = uploadPath + File.separator + "ckUpload" + File.separator + uid + "_" + fileName;
  
  out = new FileOutputStream(new File(ckUploadPath));
  out.write(bytes);
  out.flush();  // out에 저장된 데이터를 전송하고 초기화
  
  String callback = req.getParameter("CKEditorFuncNum");
  printWriter = res.getWriter();
  String fileUrl = "/ckUpload/" + uid + "_" + fileName;  // 작성화면
  
  // 업로드시 메시지 출력
  printWriter.println("<script type='text/javascript'>"
     + "window.parent.CKEDITOR.tools.callFunction("
     + callback+",'"+ fileUrl+"','이미지를 업로드하였습니다.')"
     +"</script>");
  
  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; 
}

이미지 첨부 기능 구현과 비슷하지만 약간 다릅니다.

저장하는 폴더가 연/월/일로 나누어지지 않고, 그냥 ckUpload 폴더에 모두 들어가게됩니다. 모든 이미지가 한 폴더에 들어가게될 경우 파일명이 똑같아서 덮어쓰기가 될 가능성이 있으니, UUID를 이용하여 중복되지 않도록 했습니다. 이미지 업로드가 완료되면 메시지를 띄우도록 설정했습니다.

이미지를 선택하고, 서버로 전송 버튼을 클릭하면 이렇게 미리보기까지 보이게됩니다. 이미지의 크기가 크다보니 부분적으로만 보이는군요.

실제 경로에도 파일이 복사되었습니다.

에디터에도 이미지가 표시되며, 등록 버튼을 누르게 되면

이미지의 주소가 있는 태그가 저장됩니다.

상품 조회로 보게되면, 이미지의 크기가 너무 크기 때문에 제대로 보이지 않습니다.

상품 설명이 들어가는 부분을 <span> 에서 <div> 로 변경했고, 클래스도 추가했습니다.

<div class="gdsDes">${goods.gdsDes}</div>

그리고 스타일을 추가합니다.

.gdsDes img { max-width:600px; height:auto; }

max-width 는 최대 가로 크기이며, 600픽셀보다 작다면 작은 크기를 기준으로 표시되고 이 600픽셀보다 크다면 600픽셀로 표시됩니다. 가로 크기만 변경되고 세로 크기만 고정되어있으면 이미지가 이상하게 표시될테네, height는 자동(auto)로 설정합니다.

하지만 가로만 600픽셀로 표시되고 세로는 아직도 처음 그대로 나오게 됩니다.

이렇게 표시되는 이유는 이미지 태그 자체에 가로 크기와 세로크기가 설정되어있기 때문입니다. 그렇기 때문에 태그를 수정해야합니다.

상품을 수정하려고하니... 아직 수정 화면, modify.jsp에는 CK에디터가 적용되지 않았네요.

<head> ~ </head> 에 스크립트를 추가하고

<script src="/resources/ckeditor/ckeditor.js"></script>

텍스트에어리어 밑에도 스크립트를 추가합니다.

<script>
 var ckeditor_config = {
   resize_enaleb : false,
   enterMode : CKEDITOR.ENTER_BR,
   shiftEnterMode : CKEDITOR.ENTER_P,
   filebrowserUploadUrl : "/admin/goods/ckUpload"
 };
 
 CKEDITOR.replace("gdsDes", ckeditor_config);
</script>

이제 상품수정에서도 CK에디터를 사용할 수 있게되었습니다.

소스보기를 선택하여, style=" ~ " 부분을 삭제합니다.

태그 자체에 있는 스타일이 가장 우선적으로 설정되기 때문에, 이 부분이 있다면 CSS를 아무리 추가해도 변하지 않습니다.

이제 이미지가 적당한 크기로 나옵니다.

게시물 수정
  1. 에디터 적용시 값이 null도 아니고 공백(String)으로 넘어오는데, 혹 문제가 무엇일까요?
    이전에 Ajax로 구현한 질문자입니다!

    답글삭제
  2. 목록에서 이 파트를 누르면 11번 파트로 연결이 됩니다~

    답글삭제
    답글
    1. 아이고; 감사합니다. 수정했습니다.

      삭제
  3. 저 혹시 @Requestparam MultipartFile upload 에서 upload 이름은 어디서 지어주는건가요 ??
    upload 이름말고 input type="file" name = "" 에서 name 값을 가져오니 에러가 뜨더라구요;;
    무조건 upload로 해줘야 하는거 같은데 어디서 가져오는건지 궁금합니다

    답글삭제
    답글
    1. 안녕하세요? 방문해주셔서 감사합니다.

      기간이 좀 지나서 가물가물한데.. ck에디터의 업로드 기능의 고유한 이름이었던걸로 기억합니다.

      삭제
  4. 안되는게 있어서 질문합니다 ....
    ck에디터를 활용해서 위와 비슷하게 이미지 업로드를 구현했습니다.
    그런데 문제는 이미지가 저장도 ck에디터 내용안에 이미지가 올라오는것을 확인했습니다.
    그 후 게시판을 작성하였고 작성한 게시글에 들어가봤는데 이미지가 나오지 않더라구요
    경로가 잘못됬나 db에서 경로에 맞게 바꿔줬는데 똑같이 안나옵니다.
    img src="../resources/ckUpload/z.png"
    단순 위에 코드를 jsp에 뿌려주면 이미지는 잘 나옵니다.
    그런데 db에 img src="../resources/ckUpload/z.png" 를 넣어버리면 이미지가 나오질 않네요....
    뭐가 문제일까요 ㅠ?...

    답글삭제
    답글
    1. 안녕하세요? 방문해주셔서 감사합니다.

      db에 실제 경로를 입력했음에도 안된다면, 브라우저에서 소스보기 또는 관리자 도구를 통해 img 태그의 src속성에 어떤 값이 들어가있는지 확인해보셔야 할 것 같습니다.

      삭제
    2. ㄹㄴㅇ

      삭제
  5. 익명6/23/2020

    안녕하세요 쿠즈로님 많은 도움이 되고 있습니다.
    // 업로드시 메시지 출력
    * printWriter.println("<script type='text/javascript'>"
    + "window.parent.CKEDITOR.tools.callFunction("
    + callback+",'"+ fileUrl+"','이미지를 업로드하였습니다.')"
    +"</script>");*/
    이 부분이 ck 에디터가 업데이트 되면서
    printWriter.println("{\"filename\" : \""+fileName+"\", \"uploaded\" : 1, \"url\":\""+fileUrl+"\"}");
    이렇게 json형식으로 리턴해줘야 서버에 업로드가 됩니다.

    답글삭제
  6. 익명6/26/2020

    안녕하세요 이미지 저장 호출을 하면
    호출 url 다확인하였고 브라우져에
    치면 서버 요청까지 확인이 되는데
    파일업로드하면 서버까지 접근조차 못하고 404 뜹니다

    답글삭제
    답글
    1. 안녕하세요? 방문해주셔서 감사합니다.

      ck에디터의 이미지 업로드에서 404 에러가 생긴 경우.. ck에디터의 설정문제일 가능성이 높습니다.
      ck에디터 사이트에서 경로 설정을 확인해보신 뒤, 적용해보시는걸 추천드립니다.

      삭제
  7. 안녕하세요. 글 잘봤습니다.
    혹시 이미지 업로드면 이미지가 local 파일에 저장이 되긴 하는데 저장하고, 해당 게시물을 볼 때 text랑 이미지를 불러오려면 DB에 저장해야하는 거 아닌가요? 자동으로 불러와지나요..?

    답글삭제
    답글
    1. 안녕하세요? 방문해주셔서 감사합니다.

      이미지를 업로드하면, 업로드하는 파일을 복제하여 서버에 저장됩니다.(=스프링에서 지정된 경로)
      이때, 동시에 이미지 경로를 db로 저장합니다.

      게시물을 불러올때는 이미지가 아닌 이미지 경로를 불러와서 <img src="[db에서 가져온 이미지 경로]">에 넣어서 사용됩니다.

      다른 방법으로는 이미지 파일 자체를 문자열(바이너리)로 바꿔서 그걸 통채로 db에 저장하고, 불러올때는 문자열을 이미지로 변환하는 작업을 거쳐서 <img src="[이미지]"> 이미지 태그를 이용하여 넣어줍니다.

      삭제
  8. <div class="form-group">
    <label>내용</label>
    <textarea rows="10" name="content" id="content" class="form-control"></textarea>

    <script>
    var ckeditor_config = {
    resize_enaleb : false,
    enterMode : CKEDITOR.ENTER_BR,
    shiftEnterMode : CKEDITOR.ENTER_P,
    filebrowserUploadUrl : "/admin/goods/ckUpload"
    };

    CKEDITOR.replace("content", ckeditor_config);
    </script>
    </div>

    이렇게 했는데 에디터에 글을 입력해도 데이터베이스에는 null값으로만 들어갑니다
    왜그럴까요... 정말 해결하고싶어요...

    답글삭제
    답글
    1. 헉 했습니다. 익명으로 남겨서 삭제를 못하네요ㅠ 댓글남겨서 죄송합니다

      삭제
    2. 안녕하세요? 방문해주셔서 감사합니다.

      작성하신 댓글은 굳이 삭제하지 않으셔도 됩니다~

      삭제
  9. CK 에디터의 버전이 업데이트되면서 기존 문구가 정상작동되지 않는 것 같습니다.

    printWriter 부분을

    printWriter.println("{\"filename\" : \""+fileName+"\", \"uploaded\" : 1, \"url\":\""+fileUrl+"\"}");

    제 경우에는 위 문구로 바꾸니 해결되었습니다.

    답글삭제
    답글
    1. 안녕하세요? 방문해주셔서 감사합니다.

      알려주셔서 감사합니다(__) 나중에 게시판에 추가할때 참고하겠습니다.

      삭제
    2. printWriter 부분을 위와 같이 수정하면 그 바로 위에 있는 String fileUrl = "/resources/ckUpload/" + uid + "_" + fileName;으로 수정해야 합니다. ckUpload 폴더 앞 경로에 resources 폴더를 추가해 줘야 브라우저에서 제대로 인식해요

      삭제
    3. 아이고 감사합니다. 버전바뀌어서 안됐는데 아무리찾아도 안되길레 답답했는데

      삭제
  10. 안녕하세요 ck에디터를 불러오는 과정에서 화면에 나타나지않아, 개발자모드로 보니, GET http://localhost:8087/resources/ckeditor/config.js net::ERR_ABORTED 404 (Not Found) 라고 뜨며,
    콘솔창에는 WARN : org.springframework.web.servlet.PageNotFound - No mapping found for HTTP request with URI [/resources/ckeditor/config.js] in DispatcherServlet with name 'appServlet' 로 표시되어있네요ㅠ

    어제는 잘 나온거 같은데, 오늘은 아무리해도 뜨지 않아서 한참 해보다가 문의드려요ㅠ
    제가 어느 사이트에서 찾아보기로는 servlet url-pattern이 "/" 또는 "/.*" 으로 되어있는 경우, css나 js까지도 DispatcherServlet을 타기 때문에 에러가 발생하게 된다고 하는데, url-pattern을 어떻게 해놓으신건지 알려주실 수 있나요?
    아니면 다른 방법이 있을까요? 감사합니다.

    답글삭제
    답글
    1. 안녕하세요? 방문해주셔서 감사합니다.

      음.. 가물가물한데(...)
      저는 servlet-context.xml에 <resources mapping="/ckUpload/**" location="/resources/ckUpload/" /> 라고 입력해두었고, web.xml의 패턴은 별도로 설정하지 않았습니다

      삭제
  11. 익명1/18/2023

    파일 업로드 이거 빡세네요.. ckeditor 버전도 똑같은거 찾아서 했는데 안되네요.

    ckeditor에서 이미지 서버로 전송하고 미리보기가 떠야 하는데 안뜨거든요. controller post메소드에서 선택한 이미지를 다시 ckeditor에 미리보기 뜨도록 전달해주는 코드가 어느부분인지 알 수잇을까요? 콘솔에서 callback 부분이 null로 나오기도 하네요.

    답글삭제
  12. 익명1/19/2023

    구글링을 해보니 4.8.0 부터 json 방식으로 바뀌었는데 여기 사진에 4.11.1 로 나온거 보고 4.11.1로 했었는데 그래서 안된거였네요. 근데 찾아보는게 오래걸리긴 해도 확실히 공부가 됐습니다. 복붙만 해대다가 왜 안되나 파일입출력 코드도 읽어보게 되고 공식문서들도 찾아봤습니다.

    답글삭제
  13. 익명5/25/2023

    https://ckeditor.com/ckeditor-4/download/#ckeditor-4

    4버전 다운로드 링크입니다
    5버전도 스프링에 적용 자체는 가능 한거 같은데 도저히 방도를 못찾겠더군요

    답글삭제
    답글
    1. 익명5/25/2023

      아마 다운로드가 아닌 링크를 복사 붙여 넣기 해서 쓰는 방식으로 해야 하지 않을까 싶습니다

      삭제