Hướng dẫn tích hợp Ckfinder vào Ckeditor trong Spring Boot Java

Sharing now

4.8/5 - (5 bình chọn)

Cách tích hợp Ckfinder vào Ckeditor trong project Spring Boot Java

Trong bài viết này mình sẽ hướng dẫn tích hợp Ckfinder và Ckeditor trong project Spring Boot Java. Mình sẽ dùng Ckfinder 2 do Ckfinder 3 chỉ hỗ trợ cho PHP và ASP.NET mà không hỗ trợ cho Java.
CkfinderJava (1)
Đối với với những web developer việc ứng dụng các WYSIWYG editor vào việc quản trị nội dung cho website dường như là điều bắt buộc. Người dùng có thể dễ dàng cập nhật và định dạng nội dung theo ý muốn một cách dễ dàng như dùng MS Word.
Hiện tại có rất nhiều WYSIWYG editor được các nhà phát triển website sử dụng xem tại đây. Top 10 Jquery HTML5 WYSIWYG Plugins Editor

Giới thiệu Ckeditor

CKEditor (còn gọi là FCKeditor) là một trình soạn thảo mã nguồn mở theo kiểu WYSIWYG (tay làm – mắt thấy) của CKSource.
CkfinderJava (7)
Cũng giống các trình soạn thảo dành cho web khác, CKEditor sử dụng JavaScript là nền tảng, riêng việc tương tác với server thì CKEditor sử dụng các ngôn ngữ phổ biến sau: ASP, ASP.NET, ColdFusion, JavaJavaScript, Perl, PHP và Python.
CKEditor tương thích với hầu hết các trình duyệt Internet, gồm có: Internet Explorer 6.0+ (Windows), Firefox 2.0+, Safari3.0+, Google Chrome (Windows), Opera 9.50+…

Giới thiệu Ckfinder

CKfinder là 1 trình quản lý file, nó cho phép chúng ta quản lý file, folder trên server bao gồm phân quyền sử dụng, upload file từ client. Hiện tại nếu bạn chỉ sử dụng CKEditor thì chức năng upload ảnh từ client và chèn vào bài viết không sử dụng được
.CkfinderJava (11)

Hướng dẫn tích hợp Ckfinder vào Ckeditor trong Spring Boot Java

Vào phần chính nào, hầu hết đa số trên mạng có rất nhiều hướng dẫn tích hợp Ckfinder vào Ckeditor trên PHP, ASP.NET. Thậm chí là Java nhưng hầu như chỉ hướng dẫn tích hợp Ckfinder cho Java web JSP hay Spring Framework với web.xml. Ngay cả document của trang chủ Ckfinder cũng chỉ hướng dẫn theo dạng web.xml. Vì thế mình quyết định viết bài Hướng dẫn tích hợp Ckfinder vào Ckeditor trong Spring Boot Java.
Các công nghệ sử dụng:

  • Spring Boot + Thymeleaf

Công cụ yêu cầu:

  • Ckfinder 2
  • Ckeditor 3
  • Eclipse + Plugin spring suite tool

Quá trình làm gồm các bước cụ thể dưới đây! Để chi tiết bạn nên xem video cuối bài !

Tải thư viện Ckfinder và Ckeditor cần thiết

Download Ckfinder java
Download Ckeditor
CkfinderJava (10)Cấu trúc thư mục

Tạo Spring Boot project

Eclipse bạn đã cài đặt Plugin spring suite tool. File > New > Spring Starter Project.
CkfinderJava (5)
Next, sau đó chọn Thymeleaf và Web. Sau đó chọn Finish.

Tích hợp thư viện vào Spring Boot project (pom, static, lib)

Sau khi tải 2 thư viện phía trên về, bạn hãy giải nén ra. Copy 2 folder cần thiết vào thư mục static trong project spring boot:

  • ckeditor trong ckeditor_4.8.0_basic
  • ckfinder trong ckfinder_java_2.6.2.1\ckfinder\_source\CKFinder for Java\WebApp\src\main\webapp

Copy file file config.xml trong ckfinder_java_2.6.2.1\ckfinder\_source\CKFinder for Java\WebApp\src\main\webapp\WEB-INF và thư mục static.
Đồng thời bạn tạo thư mục uploadmedia trong thư mục static.
Sau đó bạn import CKFinderJava-2.6.2.1.war vào eclipse và lấy 1 số thư viện jar sau:
CkfinderJava (6)
Sau đó bỏ vào thư mục lib nằm ở ngoài project.
Sau đó copy lib vào spring boot project.
File pom.xml



	4.0.0
	com.qlam
	ckspringbootqlam
	0.0.1-SNAPSHOT
	jar
	ckspringbootqlam
	Demo project for Spring Boot
	
		org.springframework.boot
		spring-boot-starter-parent
		1.5.9.RELEASE
		 
	
	
		UTF-8
		UTF-8
		1.8
	
	
		
			org.springframework.boot
			spring-boot-starter-thymeleaf
		
		
			org.springframework.boot
			spring-boot-starter-web
		
		
			org.springframework.boot
			spring-boot-starter-test
			test
		
		
		
			com.ckeditor
			ckeditor-java-core
			3.5.3
		
		
			com.finder
			fileeditor
			2.6.2.1
			system
			${basedir}/lib/CKFinderPlugin-FileEditor-2.6.2.1.jar
		
		
			com.finder
			imgresize
			2.6.2.1
			system
			${basedir}/lib/CKFinderPlugin-ImageResize-2.6.2.1.jar
		
		
			com.finder
			watermark
			2.6.2.1
			system
			${basedir}/lib/CKFinderPlugin-Watermark-2.6.2.1.jar
		
		
			com.finder
			ckfinder
			2.6.2.1
			system
			${basedir}/lib/CKFinder-2.6.2.1.jar
		
		
			net.coobird
			thumbnailator
			0.4.8
			system
			${basedir}/lib/thumbnailator-0.4.8.jar
		
		
			commons-fileupload
			commons-fileupload
			1.3.3
		
		
			commons-io
			commons-io
			2.5
		
		
		
			javax.activation
			activation
			1.1.1
		
		
			javax.mail
			mail
			1.4.7
		
		
			org.projectlombok
			lombok
			true
		
	
	
		
			
				org.springframework.boot
				spring-boot-maven-plugin
			
		
	

Tạo class Cấu hình project

Tạo package com.qlam.demo.config
Trong này sẽ có 3 file dùng để cấu hình như sau:
File CKFinderConfig.java dùng để đọc file ckfinder.xml

package com.qlam.demo.config;
import com.ckfinder.connector.configuration.Configuration;
import com.ckfinder.connector.configuration.Events;
import com.ckfinder.connector.utils.PathUtils;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import javax.servlet.ServletConfig;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Scanner;
public class CKFinderConfig extends Configuration {
    public CKFinderConfig(ServletConfig servletConfig) {
        super(servletConfig);
    }
    @Override
    public void init() throws Exception {
        DefaultResourceLoader loader = new DefaultResourceLoader();
        Resource resource = loader.getResource(this.xmlFilePath);
        Class clazz = getClass().getSuperclass();
        Field field = clazz.getDeclaredField("lastCfgModificationDate");
        Method method = clazz.getDeclaredMethod("clearConfiguration");
        method.setAccessible(true);
        method.invoke(this);
        field.setAccessible(true);
        field.set(this, System.currentTimeMillis());
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        DocumentBuilder db = dbf.newDocumentBuilder();
        Document doc = db.parse(resource.getInputStream());
        doc.normalize();
        Node node = doc.getFirstChild();
        if (node != null) {
            NodeList nodeList = node.getChildNodes();
            //Duyet qua .xml
            for (int i = 0; i < nodeList.getLength(); ++i) {
                Node childNode = nodeList.item(i);
                if (childNode.getNodeName().equals("enabled"))
                    this.enabled = Boolean.valueOf(childNode.getTextContent().trim()).booleanValue();
                if (childNode.getNodeName().equals("baseDir")) {
                    if (servletConf.getInitParameter("baseDir") == null) { //baseDir cua application.prop la null
                        this.baseDir = childNode.getTextContent().trim(); //lay the ben ckfinder.xml, khi do phai thiet lap ben ckfinder.xml
                    } else { //nguoc lai lay ben application.prop
                        this.baseDir = servletConf.getInitParameter("baseDir");
                    }
                    this.baseDir = PathUtils.escape(this.baseDir);
                    this.baseDir = PathUtils.addSlashToEnd(this.baseDir);
                }
                if (childNode.getNodeName().equals("baseURL")) {
                    if (servletConf.getInitParameter("baseURL") == null) {
                        this.baseURL = childNode.getTextContent().trim();
                    } else {
                        this.baseURL=servletConf.getInitParameter("baseURL")+"/public/image/";
                    }
                    this.baseURL = PathUtils.escape(this.baseURL);
                    this.baseURL = PathUtils.addSlashToEnd(this.baseURL);
                }
                if (childNode.getNodeName().equals("licenseName"))
                    this.licenseName = childNode.getTextContent().trim();
                if (childNode.getNodeName().equals("licenseKey"))
                    this.licenseKey = childNode.getTextContent().trim();
                String value;
                if (childNode.getNodeName().equals("imgWidth")) {
                    value = childNode.getTextContent().trim();
                    value = value.replaceAll("//D", "");
                    try {
                        this.imgWidth = Integer.valueOf(value);
                    } catch (NumberFormatException var13) {
                        this.imgWidth = null;
                    }
                }
                if (childNode.getNodeName().equals("imgQuality")) {
                    value = childNode.getTextContent().trim();
                    value = value.replaceAll("//D", "");
                    method = clazz.getDeclaredMethod("adjustQuality", new Class[]{String.class});
                    method.setAccessible(true);
                    this.imgQuality = Float.parseFloat(method.invoke(this, value).toString());
                }
                if (childNode.getNodeName().equals("imgHeight")) {
                    value = childNode.getTextContent().trim();
                    value = value.replaceAll("//D", "");
                    try {
                        this.imgHeight = Integer.valueOf(value);
                    } catch (NumberFormatException var12) {
                        this.imgHeight = null;
                    }
                }
                if (childNode.getNodeName().equals("thumbs")) {
                    method = clazz.getDeclaredMethod("setThumbs", new Class[]{NodeList.class});
                    method.setAccessible(true);
                    method.invoke(this, childNode.getChildNodes());
                }
                if (childNode.getNodeName().equals("accessControls")) {
                    method = clazz.getDeclaredMethod("setACLs", new Class[]{NodeList.class});
                    method.setAccessible(true);
                    method.invoke(this, childNode.getChildNodes());
                }
                if (childNode.getNodeName().equals("hideFolders")) {
                    method = clazz.getDeclaredMethod("setHiddenFolders", new Class[]{NodeList.class});
                    method.setAccessible(true);
                    method.invoke(this, childNode.getChildNodes());
                }
                if (childNode.getNodeName().equals("hideFiles")) {
                    method = clazz.getDeclaredMethod("setHiddenFiles", new Class[]{NodeList.class});
                    method.setAccessible(true);
                    method.invoke(this, childNode.getChildNodes());
                }
                if (childNode.getNodeName().equals("checkDoubleExtension"))
                    this.doubleExtensions = Boolean.valueOf(childNode.getTextContent().trim()).booleanValue();
                if (childNode.getNodeName().equals("disallowUnsafeCharacters"))
                    this.disallowUnsafeCharacters = Boolean.valueOf(childNode.getTextContent().trim()).booleanValue();
                if (childNode.getNodeName().equals("forceASCII"))
                    this.forceASCII = Boolean.valueOf(childNode.getTextContent().trim()).booleanValue();
                if (childNode.getNodeName().equals("checkSizeAfterScaling"))
                    this.checkSizeAfterScaling = Boolean.valueOf(childNode.getTextContent().trim()).booleanValue();
                Scanner sc;
                if (childNode.getNodeName().equals("htmlExtensions")) {
                    value = childNode.getTextContent();
                    sc = (new Scanner(value)).useDelimiter(",");
                    while (sc.hasNext()) {
                        String val = sc.next();
                        if (val != null && !val.equals(""))
                            this.htmlExtensions.add(val.trim().toLowerCase());
                    }
                }
                if (childNode.getNodeName().equals("secureImageUploads"))
                    this.secureImageUploads = Boolean.valueOf(childNode.getTextContent().trim()).booleanValue();
                if (childNode.getNodeName().equals("uriEncoding"))
                    this.uriEncoding = childNode.getTextContent().trim();
                if (childNode.getNodeName().equals("userRoleSessionVar"))
                    this.userRoleSessionVar = childNode.getTextContent().trim();
                if (childNode.getNodeName().equals("defaultResourceTypes")) {
                    value = childNode.getTextContent().trim();
                    sc = (new Scanner(value)).useDelimiter(",");
                    while (sc.hasNext())
                        this.defaultResourceTypes.add(sc.next());
                }
                if (childNode.getNodeName().equals("plugins")) {
                    method = clazz.getDeclaredMethod("setPlugins", new Class[]{Node.class});
                    method.setAccessible(true);
                    method.invoke(this, childNode);
                }
                if (childNode.getNodeName().equals("basePathBuilderImpl")) {
                    method = clazz.getDeclaredMethod("setBasePathImpl", new Class[]{String.class});
                    method.setAccessible(true);
                    method.invoke(this, childNode.getTextContent().trim());
                }
            }
        }
        method = clazz.getDeclaredMethod("setTypes", new Class[]{Document.class});
        method.setAccessible(true);
        method.invoke(this, doc);
        field = clazz.getDeclaredField("events");
        field.setAccessible(true);
        field.set(this, new Events());
        this.registerEventHandlers();
    }
}

File CKFinderServletConfig.java dùng để thiết lập thuộc tính trên file application.properties

package com.qlam.demo.config;
import com.ckfinder.connector.ConnectorServlet;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class CKFinderServletConfig {
    @Value("${ckeditor.storage.image.path}")
    private String baseDir;
    @Value("${ckeditor.access.image.url}")
    private String baseURL;
    @Bean
    public ServletRegistrationBean connectCKFinder(){
        ServletRegistrationBean registrationBean=new ServletRegistrationBean(new ConnectorServlet(),"/ckfinder/core/connector/java/connector.java");
        registrationBean.addInitParameter("XMLConfig","classpath:/static/ckfinder.xml");
        registrationBean.addInitParameter("debug","false");
        registrationBean.addInitParameter("configuration","com.qlam.demo.config.CKFinderConfig");
        //ckfinder.xml
        registrationBean.addInitParameter("baseDir",baseDir);
        registrationBean.addInitParameter("baseURL",baseURL);
        return registrationBean;
    }
}

File WebMvcConfig.java dùng để thiết lập đường dẫn thư viện và file upload theo Spring MVC

package com.qlam.demo.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
@Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter{
	@Override
	public void addResourceHandlers(ResourceHandlerRegistry registry) {
		//   file:D:\\data\\file\\image\\
		registry.addResourceHandler("/public/image/**").addResourceLocations("classpath:/static/uploadmedia/");
		registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/");
		registry.addResourceHandler("/ckfinder/**").addResourceLocations("classpath:/static/ckfinder/");
		super.addResourceHandlers(registry);
	}
}

File application.properties

# ===============================
# THYMELEAF
# ===============================
spring.thymeleaf.cache=false
# ===============================
# DATASOURCE
# ===============================
# Set here configurations for the database connection
# config dir so that ckeditor and ckfinder loading to media upload, write null so that use ckfinder.xml
ckeditor.storage.image.path=src/main/resources/static/uploadmedia/
ckeditor.access.image.url=http://localhost:8080

Xử lý với Controller class

Tạo package com.qlam.demo.controller
Chúng ta cần 1 class Controller để chạy trang chủ.
File QController.java đơn giản là mình cho nó dùng giao diện file adpost.html để hiện cho trang chủ.

package com.qlam.demo.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class QController {
	@GetMapping("/")
	public String home() {
		return "adpost";
	}
}

Thiết kế giao diện với thymeleaf engine

Mình tạo giao diện web đơn giản chia làm 2 phần:

  • Dùng Ckeditor tích hợp Ckfinder khi upload ảnh
  • Chạy Ckfinder khi nhấn một nút

Trong phần src/main/resources/templates tạo file adpost.html
File adpost.html






Ckfinder


	

Integrate ckfinder into ckeditor in spring boot project

ShareEverythings.com

A. Integrate ckfinder into ckeditor

B. Ckfinder Button

Selected Image URL

Note: You should select square image !

Chạy project

Kết quả thành công!
CkfinderJava (1)CkfinderJava (8)CkfinderJava (9)CkfinderJava (2)
Nhờ các plugin bạn có thể thay đổi kích thước ảnh.
CkfinderJava (3)CkfinderJava (4)

Video chi tiết Hướng dẫn tích hợp Ckfinder vào Ckeditor trong Spring Boot Java

Download Full Project Java

Lưu ý: nếu bạn build jar war file lỗi thì hãy thêm dòng sau vào mục plugins của pom.xml nhé:


            org.apache.maven.plugins
            maven-compiler-plugin
            
                1.8
                1.8
            

Sharing now

29 bình luận về “Hướng dẫn tích hợp Ckfinder vào Ckeditor trong Spring Boot Java”

  1. Em chào anh,
    Cảm ơn anh vì bài viết khá hữu ích. Trong phần cấu hình:
    “` @Value(“${ckeditor.storage.image.path}”)
    private String baseDir;
    “`
    Em nghĩ nên sử dụng “`ClassLoader“` để load đường dẫn đến folder “`uploadmedia“` thì tiện hơn. Khi deploy sẽ không phải config lại.
    “`
    private ClassLoader classLoader = CKFinderServletConfig.class.getClassLoader();
    // @Value(“${ckeditor.storage.image.path}”)
    private String baseDir = classLoader.getResource(“static/uploadmedia”).getPath() + “/”;
    “`
    A cho em hỏi chút, trong class “`CKFinderConfig“ dựa vào mà a biết cách để config như vậy. A có thể cho em tài liệu tham khảo/chỉ giúp em được không ạ. Em cảm ơn 😀

    Trả lời
      • Thanks b, nhưng mình thấy rằng mất khá nhiều bước để parse từ xml rồi transfer qua lại như vậy.
        Vì cách đấy thấy phụ thuộc vào nhiều APIs -> tighed coupling.
        Thay vào đó sao không config “XMLConfig” trỏ trực tiếp đến “config.xml” -> Code sẽ ngắn hơn và nhanh hơn rất nhiều mà 😀

        Trả lời
          • Bạn có thể chỉnh “CKFinderServletConfig” như sau:

            @Configuration
            public class CKFinderServletConfig {
            private ClassLoader classLoader = CKFinderServletConfig.class.getClassLoader();
            private String ckfinderXMLPath = classLoader.getResource(“static”).getPath() + “/ckfinder.xml”;
            @Bean
            public ServletRegistrationBean connectCKFinder() {
            ServletRegistrationBean registrationBean = new ServletRegistrationBean(new ConnectorServlet(),
            “/ckfinder/core/connector/java/connector.java”);
            registrationBean.addInitParameter(“XMLConfig”, ckfinderXMLPath);
            registrationBean.addInitParameter(“debug”, “false”);
            return registrationBean;
            }
            }

            Khi deploy bạn có thể config đường dẫn trỏ đến “uploadmeadia” như sau:

            E:\workspace\your-project\target\classes\static\uploadmedia
            http://localhost:8080/public/resources/

            (Nếu muốn có thể bind value trực tiếp luôn)

  2. Hi admin, em có làm theo tài liệu nhưng khi chạy lên nó báo “không hồi đấp dk file xml” và báo ko tìm thấy “/ckfinder/core/connector/java/connector.java”.
    Em cũng chưa hiểu là link java ở đâu a.
    em có tải bản code về chạy thì thấy vẫn dk, nhưng ckeditor ko full dk toolBar. Cao thủ nào giải thích cho em với ạ.

    Trả lời
    • Connector.java thuộc thư viện CKFinder-2.6.2.1.jar. Bạn đã copy thư viện lib jar chưa ? Có full toolbar cho ckeditor hay không là tùy vào lúc đầu bạn chọn tải bản Full package ở trang chủ ckeditor ấy!

      Trả lời
      • It was not possible to properly load the XML response from the web server.
        Raw response from the server:
        {“timestamp”:1525409041533,”status”:500,”error”:”Internal Server Error”,”exception”:”java.lang.Exception”,”message”:”Configuration wasn’t initialized correctly. Check server logs.”,”path”:”/ckfinder/core/connector/java/connector.java”}
        em copy CKFinder-2.6.2.1.jar lib ở project của bác vào rồi. mà trong file jar chỉ có file class thôi bác. file java em ko biết tại sao lại ở đấy nhỉ.

        Trả lời
        • lỗi này do nó không tìm thấy file trong server bạn kiểm tra lại file config.xml và class xử lý nó có giống bên mình ko? Còn jar có file .class sẽ được java dịch ngược lại .java nên thông thường bạn ko thấy file .java

          Trả lời
  3. Dùng CKFinder free sẽ có trademark, nếu mua thì cần 17$.
    Mình có đọc custom image upload adapter của CK nhưng complicated.
    Bạn có biết solution nào cho popular rich editor nào mà support image upload easy/free ko?

    Trả lời
  4. Cho em hỏi tại sao phần demo project em tải về run thì thanh toolbar trong ckeditor lại không có phần ảnh. Làm sao để nó hiện giống như video. Thanks a

    Trả lời

Viết một bình luận