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.
Đố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.
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, Java, JavaScript, 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
.
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
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.
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:
Sau đó bỏ vào thư mục lib nằm ở ngoài project.
Sau đó copy lib vào spring boot project.
File pom.xml
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.qlam</groupId>
<artifactId>ckspringbootqlam</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>ckspringbootqlam</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.9.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--CKFinder start -->
<dependency>
<groupId>com.ckeditor</groupId>
<artifactId>ckeditor-java-core</artifactId>
<version>3.5.3</version>
</dependency>
<dependency>
<groupId>com.finder</groupId>
<artifactId>fileeditor</artifactId>
<version>2.6.2.1</version>
<scope>system</scope>
<systemPath>$basedir/lib/CKFinderPlugin-FileEditor-2.6.2.1.jar</systemPath>
</dependency>
<dependency>
<groupId>com.finder</groupId>
<artifactId>imgresize</artifactId>
<version>2.6.2.1</version>
<scope>system</scope>
<systemPath>$basedir/lib/CKFinderPlugin-ImageResize-2.6.2.1.jar</systemPath>
</dependency>
<dependency>
<groupId>com.finder</groupId>
<artifactId>watermark</artifactId>
<version>2.6.2.1</version>
<scope>system</scope>
<systemPath>$basedir/lib/CKFinderPlugin-Watermark-2.6.2.1.jar</systemPath>
</dependency>
<dependency>
<groupId>com.finder</groupId>
<artifactId>ckfinder</artifactId>
<version>2.6.2.1</version>
<scope>system</scope>
<systemPath>$basedir/lib/CKFinder-2.6.2.1.jar</systemPath>
</dependency>
<dependency>
<groupId>net.coobird</groupId>
<artifactId>thumbnailator</artifactId>
<version>0.4.8</version>
<scope>system</scope>
<systemPath>$basedir/lib/thumbnailator-0.4.8.jar</systemPath>
</dependency>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.3</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.5</version>
</dependency>
<!--CKFinder end -->
<dependency>
<groupId>javax.activation</groupId>
<artifactId>activation</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>javax.mail</groupId>
<artifactId>mail</artifactId>
<version>1.4.7</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
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
<html xmlns:th="http://www.thymeleaf.org">
<head th:fragment="head">
<meta charset="utf-8" />
<meta name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<meta http-equiv="x-ua-compatible" content="ie=edge" />
<title>Ckfinder</title>
</head>
<body>
<div id="page-content-wrapper">
<h1 id="home" class="text-center font-bold mt-1">Integrate
ckfinder into ckeditor in spring boot project</h1>
<h2>ShareEverythings.com</h2>
<!-- Section: Create Page -->
<section class="section mt-5 container-fluid">
<!-- First row -->
<div class="row">
<!-- First col -->
<div class="col-lg-6">
<h3>A. Integrate ckfinder into ckeditor</h3>
<!-- Second card -->
<div class="card mb-r">
<textarea name="content" id="content"></textarea>
</div>
<!-- /.Second card -->
</div>
<!-- /.First col -->
<div class="col-lg-6">
<h3>B. Ckfinder Button</h3>
<div class="avatar">
<img id="imgpreview"
src="https://yt3.ggpht.com/-f6NCDKG2Ukw/AAAAAAAAAAI/AAAAAAAAAAA/MqMm3rgmqCY/s48-c-k-no-mo-rj-c0xffffff/photo.jpg"
class="img-fluid" style="max-width: 300px; max-height: 300px;" />
</div>
<div class="file-field">
<p>
<strong id="xImagePath">Selected Image URL</strong><br /> <input
class="btn btn-primary btn-sm waves-effect waves-light"
type="button" value="Browse Image"
onclick="BrowseServer( 'Images:/', 'xImagePath' );" />
</p>
</div>
<p>Note: You should select square image !</p>
</div>
</div>
<!-- /.First row -->
</section>
<!-- /.Section: Create Page -->
</div>
<script src="ckeditor/ckeditor.js"></script>
<script src="ckfinder/ckfinder.js"></script>
<script>
CKEDITOR
.replace(
'content',
filebrowserBrowseUrl : 'ckfinder/ckfinder.html',
filebrowserImageBrowseUrl : 'ckfinder/ckfinder.html?type=Images',
filebrowserFlashBrowseUrl : 'ckfinder/ckfinder.html?type=Flash',
filebrowserUploadUrl : 'ckfinder/core/connector/java/connector.java?command=QuickUpload&type=Files',
filebrowserImageUploadUrl : 'ckfinder/core/connector/java/connector.java?command=QuickUpload&type=Images',
filebrowserFlashUploadUrl : 'ckfinder/core/connector/java/connector.java?command=QuickUpload&type=Flash'
);
/*Avatar start*/
function BrowseServer(startupPath, functionData)
// You can use the "CKFinder" class to render CKFinder in a page:
var finder = new CKFinder();
// The path for the installation of CKFinder (default = "/ckfinder/").
finder.basePath = '../';
//Startup path in a form: "Type:/path/to/directory/"
finder.startupPath = startupPath;
// Name of a function which is called when a file is selected in CKFinder.
finder.selectActionFunction = SetFileField;
// Additional data to be passed to the selectActionFunction in a second argument.
// We'll use this feature to pass the Id of a field that will be updated.
finder.selectActionData = functionData;
// Name of a function which is called when a thumbnail is selected in CKFinder. Preview img
// finder.selectThumbnailActionFunction = ShowThumbnails;
// Launch CKFinder
finder.popup();
// This is a sample function which is called when a file is selected in CKFinder.
function SetFileField(fileUrl, data)
document.getElementById(data["selectActionData"]).innerHTML = this
.getSelectedFile().name;
document.getElementById("imgpreview").src = fileUrl;
/*Avatar end*/
</script>
</body>
</html>
Chạy project
Kết quả thành công!
Nhờ các plugin bạn có thể thay đổi kích thước ảnh.
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é:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
Học hành, Lập trình, Spring Framework
Java, Lập trình
0 comments:
Post a Comment