kindeditor官網:http://kindeditor.net/demo.php
kindeditor在之前已經用過,現在在springboot項目中使用。並且也在里面使用了圖片上傳以及回顯等功能。
其實主要的功能是圖片的處理:kindeditor對輸入的內容會作為html標簽處理,對於image的做法是先將圖片傳到后台服務器,然后上傳成功之后回傳圖片的URL,之后內容中增加<img src='url'>進行回顯,當然保存到數據庫也是img標簽進行保存的。對於文件上傳是以a標簽的形式進行回顯,數據庫也以a標簽進行保存。
下面的代碼涉及到了:Restful風格的請求、SpringMVC文件的上傳、不配置虛擬路徑的前提下請求圖片資源、kindeditor、thymeleaf模板的使用。
1.首先編寫接收kindeditor圖片上傳和圖片請求的類:
package cn.qs.controller.common; import java.io.File; import java.io.FileInputStream; import java.util.Date; import java.util.HashMap; import java.util.Map; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.IOUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.multipart.MultipartFile; import cn.qs.bean.common.Document; import cn.qs.service.common.DocumentService; import cn.qs.utils.UUIDUtils; import cn.qs.utils.file.FileHandleUtil; import cn.qs.utils.file.TikaUtils; import cn.qs.utils.system.MySystemUtils; @Controller @RequestMapping("document") public class DocumentController { private static final Logger logger = LoggerFactory.getLogger(DocumentController.class); @Autowired private DocumentService documentService; /** * Restful風格獲取文檔 * * @param request * @param response * @param documentId */ @RequestMapping("/getDocument/{documentId}") public void getPicture(HttpServletRequest request, HttpServletResponse response, @PathVariable() String documentId) { FileInputStream in = null; ServletOutputStream outputStream = null; try { Document document = documentService.getById(documentId); String path = document.getPath(); String originName = document.getOriginName(); File fileByName = FileHandleUtil.getFileByName(path); // 判斷文件類型,image、pdf返回閱讀,其他下載 String fileType = TikaUtils.getFileType(fileByName); if (!TikaUtils.TYPE_IMAGE.equals(fileType) && !TikaUtils.TYPE_PDF.equals(fileType)) { response.setContentType("application/force-download"); response.setHeader("Content-Disposition", "attachment;fileName=" + originName); } in = new FileInputStream(fileByName); outputStream = response.getOutputStream(); IOUtils.copyLarge(in, outputStream); } catch (Exception e) { e.printStackTrace(); } finally { IOUtils.closeQuietly(in); IOUtils.closeQuietly(outputStream); } } /** * 文檔上傳 * * @param imgFile * @return */ @RequestMapping("/upload") @ResponseBody public Map<String, Object> uploadPicture(MultipartFile imgFile) { Map<String, Object> result = new HashMap<String, Object>(); result.put("error", 1); if (imgFile == null) { result.put("message", "文件沒接到"); return result; } logger.debug("file -> {},viewId ->{}", imgFile.getOriginalFilename()); String fileOriName = imgFile.getOriginalFilename();// 獲取原名稱 String fileNowName = UUIDUtils.getUUID2() + "." + FilenameUtils.getExtension(fileOriName);// 生成唯一的名字 try { FileHandleUtil.uploadSpringMVCFile(imgFile, fileNowName); } catch (Exception e) { logger.error("uploadPicture error", e); return result; } String id = UUIDUtils.getUUID(); Document document = new Document(); document.setCreatetime(new Date()); document.setPath(fileNowName); document.setId(id); document.setOriginName(fileOriName); document.setUploaderUsername(MySystemUtils.getLoginUsername()); documentService.insert(document); // 回傳JSON結果 result.put("error", 0); result.put("url", "/document/getDocument/" + id); return result; } }
圖片上傳:參數接受名字必須是imgFile,否則接收不到文件。收到文件之后先生成一個全局唯一的名稱然后保存到本地,並保存到數據庫之后返回一個圖片的URL例如: /document/getDocument/94995b51-901c-44e9-87ec-12c109098f5e
圖片獲取:通過restful風格的請求將圖片的ID傳到后台,根據ID查詢到圖片的路徑,然后調用IOUtils將文件流回傳回去實現圖片的src請求顯示。如果是其他類型的文件以下載的方式進行回傳流。
Document實體類如下:
package cn.qs.bean.common; import java.util.Date; import javax.persistence.Entity; import javax.persistence.Id; //系統文檔表 @Entity public class Document { @Id private String id; /** * 原名字 */ private String originName; /** * 上傳者 */ private String uploaderUsername; private String name; private String path; private Date createtime; private String remark1; private String remark2; public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name == null ? null : name.trim(); } public String getPath() { return path; } public void setPath(String path) { this.path = path == null ? null : path.trim(); } public Date getCreatetime() { return createtime; } public void setCreatetime(Date createtime) { this.createtime = createtime; } public String getRemark1() { return remark1; } public void setRemark1(String remark1) { this.remark1 = remark1; } public String getRemark2() { return remark2; } public void setRemark2(String remark2) { this.remark2 = remark2; } public String getOriginName() { return originName; } public void setOriginName(String originName) { this.originName = originName; } public String getUploaderUsername() { return uploaderUsername; } public void setUploaderUsername(String uploaderUsername) { this.uploaderUsername = uploaderUsername; } }
FileHandleUtil類是圖片保存以及獲取,保存到本地的固定文件夾下面。
package cn.qs.utils; import java.io.File; import java.util.Locale; import java.util.ResourceBundle; import org.apache.commons.io.FileUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.web.multipart.MultipartFile; public class FileHandleUtil { public static final String LANGUAGE = "zh"; public static final String COUNTRY = "CN"; private static String getProperties(String baseName, String section) { String retValue = ""; try { Locale locale = new Locale(LANGUAGE, COUNTRY); ResourceBundle rb = ResourceBundle.getBundle(baseName, locale); retValue = (String) rb.getObject(section); } catch (Exception e) { } return retValue; } public static String getValue(String fileName, String key) { String value = getProperties(fileName, key); return value; } public static boolean deletePlainFile(String propertiesFileName, String fileName) { if (fileName == null) { return false; } String fileDir = StringUtils.defaultIfBlank(FileHandleUtil.getValue("path", "picture"), "E:/picture/"); try { FileUtils.deleteQuietly(new File(fileDir + fileName)); } catch (Exception e) { return false; } return true; } public static boolean uploadSpringMVCFile(MultipartFile multipartFile, String fileName) throws Exception { String fileDir = StringUtils.defaultIfBlank(FileHandleUtil.getValue("path", "picture"), "E:/picture/"); if (!new File(fileDir).exists()) { new File(fileDir).mkdirs(); } multipartFile.transferTo(new File(fileDir + fileName));// 保存文件 return true; } public static File getFileByName(String path) { String fileDir = StringUtils.defaultIfBlank(FileHandleUtil.getValue("path", "picture"), "E:/picture/"); return new File(fileDir+path); } }
TikaUtils是提取文件類型的工具類,依賴的jar包如下:
<!--tika解析文本內容 --> <dependency> <groupId>org.apache.tika</groupId> <artifactId>tika-parsers</artifactId> <version>1.17</version> </dependency>
工具類源碼如下:
package cn.qs.utils.file; import java.io.File; import java.io.IOException; import org.apache.commons.lang3.StringUtils; import org.apache.tika.Tika; public class TikaUtils { // 總的文件類型分為下面幾類 public static final String TYPE_OFFICE = "OFFICE"; public static final String TYPE_PDF = "PDF"; public static final String TYPE_IMAGE = "IMAGE"; public static final String TYPE_VIDEO = "VIDEO"; public static final String TYPE_OTHER = "OTHER"; // tika解析的文件信息 private static final String WORD_DOC = "application/msword"; private static final String WORD_PPT = "application/vnd.ms-powerpoint"; private static final String WORD_EXCEL = "application/vnd.ms-excel"; private static final String WORD_DOCX = "application/vnd.openxmlformats-officedocument.wordprocessingml.document"; private static final String WORD_PPTX = "application/vnd.openxmlformats-officedocument.presentationml.presentation"; private static final String WORD_EXCELX = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; private static final String VIDEO = "video/"; private static final String IMAGE = "image/"; private static final String TEXT = "text/plain"; private static final String CSS = "text/css"; private static final String HTML = "text/html"; private static final String PDF = "application/pdf"; private static final String ZIP = "application/zip"; private static final String RAR = "application/x-rar-compressed"; public static String getFileType(File file) { if (file == null || !file.exists()) { return ""; } Tika tika = new Tika(); String filetype = null; try { filetype = tika.detect(file); } catch (IOException ignore) { // ignore return "error"; } if (StringUtils.isBlank(filetype)) { return "error"; } if (WORD_DOC.equals(filetype) || WORD_PPT.equals(filetype) || WORD_EXCEL.equals(filetype) || WORD_DOCX.equals(filetype) || WORD_PPTX.equals(filetype) || WORD_EXCELX.equals(filetype)) { return TYPE_OFFICE; } if (filetype.startsWith(VIDEO)) { return TYPE_VIDEO; } if (filetype.startsWith(IMAGE)) { return TYPE_IMAGE; } if (filetype.equals(PDF)) { return TYPE_PDF; } return TYPE_OTHER; } }
2.前台界面准備富文本編輯器並且保存輸入的信息到數據庫
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"/> <title>歡迎頁面-X-admin2.0</title> <meta name="renderer" content="webkit"/> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/> <meta name="viewport" content="width=device-width,user-scalable=yes, minimum-scale=0.4, initial-scale=0.8,target-densitydpi=low-dpi" /> <link rel="shortcut icon" href="/favicon.ico" type="image/x-icon" /> <link rel="stylesheet" th:href="${#httpServletRequest.getContextPath()+'/static/x-admin/css/font.css'}"/> <link rel="stylesheet" th:href="${#httpServletRequest.getContextPath()+'/static/x-admin/css/xadmin.css'}"/> <script type="text/javascript" th:src="${#httpServletRequest.getContextPath()+'/static/js/jquery.min.js'}" charset="utf-8"></script> <script type="text/javascript" th:src="${#httpServletRequest.getContextPath()+'/static/x-admin/lib/layui/layui.js'}" charset="utf-8"></script> <script type="text/javascript" th:src="${#httpServletRequest.getContextPath()+'/static/x-admin/js/xadmin.js'}"></script> <!-- kindeditor相關 --> <link rel="stylesheet" th:href="${#httpServletRequest.getContextPath()+'/static/kindeditor/themes/default/default.css'}" /> <link rel="stylesheet" th:href="${#httpServletRequest.getContextPath()+'/static/kindeditor/plugins/code/prettify.css'}" /> <script charset="utf-8" th:src="${#httpServletRequest.getContextPath()+'/static/kindeditor/kindeditor-all.js'}"></script> <script charset="utf-8" th:src="${#httpServletRequest.getContextPath()+'/static/kindeditor/lang/zh-CN.js'}"></script> <script> var editor; KindEditor.ready(function(K) { editor = K.create('textarea[name="contentEditor"]', { resizeType : 1, allowPreviewEmoticons : false, uploadJson : '/document/upload.html', allowImageUpload : true, pasteType : 0, //設置能否粘貼 items : [ 'fontname', 'fontsize', '|', 'forecolor', 'hilitecolor', 'bold', 'italic', 'underline', 'removeformat', '|', 'justifyleft', 'justifycenter', 'justifyright', 'insertorderedlist', 'insertunorderedlist', '|', 'emoticons', 'image', 'link','fullscreen'] }); }); </script> <!-- 讓IE8/9支持媒體查詢,從而兼容柵格 --> <!--[if lt IE 9]> <script src="https://cdn.staticfile.org/html5shiv/r29/html5.min.js"></script> <script src="https://cdn.staticfile.org/respond.js/1.4.2/respond.min.js"></script> <![endif]--> </head> <body> <div class="x-body layui-anim layui-anim-up"> <form class="layui-form"> <div class="layui-form-item"> <label for="L_email" class="layui-form-label"> <span class="x-red">*</span>博客標題 </label> <div class="layui-input-inline"> <input type="text" id="L_blogtitle" name="blogtitle" lay-verify="required" autocomplete="off" class="layui-input"/> </div> </div> <div class="layui-form-item"> <label for="L_email" class="layui-form-label"> <span class="x-red">*</span>內容 </label> <div class="layui-input-inline"> <textarea name="contentEditor" id="test" cols="100" rows="8" style="width:700px;height:200px;visibility:hidden;"></textarea> </div> </div> <div class="layui-form-item"> <label for="L_repass" class="layui-form-label"> </label> <button class="layui-btn" lay-filter="add" lay-submit=""> 增加 </button> </div> </form> </div> </body> <script> /*<![CDATA[*/ layui.use(['form','layer'], function(){ $ = layui.jquery; var form = layui.form ,layer = layui.layer; //監聽提交 form.on('submit(add)', function(data){ var data = { "blogtitle":$('[name="blogtitle"]').val(), "content":editor.html() } //異步提交數據 $.post("/blog/doAddBlog.html",data,function(response){ if(response.success == true){ layer.msg("增加成功", {icon: 6},function () { // 獲得frame索引 var index = parent.layer.getFrameIndex(window.name); //關閉當前frame parent.layer.close(index); // 父頁面刷新 parent.location.reload(); }); }else{ layer.alert(response.msg); } }); return false; }); }); /*]]>*/ </script> </html>
界面如下:
最終生成的內容保存到數據庫之后如下:
<p> 測試 </p> <p> <img src="http://localhost:8088/static/kindeditor/plugins/emoticons/images/20.gif" border="0" alt="" /> </p> <p> <img src="/document/getDocument/efe5134f-8a41-4e75-9108-6ab045979db6" alt="" /> </p>
最終經過代碼處理后在界面顯示如下:
@RequestMapping("/getBlogdetail/{blogId}") public String getBlogdetail(ModelMap map, @PathVariable() Integer blogId, HttpServletRequest request) { Blog blog = blogService.getBlogdetail(blogId); // 獲取當前用戶 HttpSession session = request.getSession(); User user = (User) session.getAttribute("user"); String username = user.getUsername(); String blogUsername = StringUtils.defaultIfBlank(blog.getBlogblank(), "admin"); if (blogUsername.equals(username)) { map.put("blog", blog); } else { map.put("blog", new Blog()); } return "blogDetail"; }
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"/> <title th:text="${blog.blogtitle}"></title> <meta name="renderer" content="webkit"/> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/> <meta name="viewport" content="width=device-width,user-scalable=yes, minimum-scale=0.4, initial-scale=0.8,target-densitydpi=low-dpi" /> <link rel="shortcut icon" href="/static/x-admin/favicon.ico" type="image/x-icon" /> <link rel="stylesheet" th:href="${#httpServletRequest.getContextPath()+'/static/x-admin/css/font.css'}"/> <link rel="stylesheet" th:href="${#httpServletRequest.getContextPath()+'/static/x-admin/css/xadmin.css'}"/> <script type="text/javascript" src="https://cdn.bootcss.com/jquery/3.2.1/jquery.min.js"></script> <script type="text/javascript" th:src="${#httpServletRequest.getContextPath()+'/static/x-admin/lib/layui/layui.all.js'}" charset="utf-8"></script> <script type="text/javascript" th:src="${#httpServletRequest.getContextPath()+'/static/x-admin/js/xadmin.js'}"></script> <span th:if="${session.user.username} eq 'admin'"> <script> var admin = true; </script> </span> <style type="text/css"> body{ text-align:center; background-color: #dcdcdc; } #container{ text-align:left; margin:0 auto; width: 80%; background-color: #fff; } </style> </head> <body class="layui-anim layui-anim-up"> <div id="container"> <center> <h1 th:text="${'標題:'+blog.blogtitle}"></h1> <h2 th:text="${'所屬人'+blog.blogblank}"></h2> </center> <hr/> <span th:utext="${blog.content}"></span> </div> </body> </html>
補充:kindeditor可選的顯示插件有好多,如下需要的時候items里面增加對應的選項即可
items : [
'source', '|', 'undo', 'redo', '|', 'preview', 'print', 'template', 'code', 'cut', 'copy', 'paste',
'plainpaste', 'wordpaste', '|', 'justifyleft', 'justifycenter', 'justifyright',
'justifyfull', 'insertorderedlist', 'insertunorderedlist', 'indent', 'outdent', 'subscript',
'superscript', 'clearhtml', 'quickformat', 'selectall', '|', 'fullscreen', '/',
'formatblock', 'fontname', 'fontsize', '|', 'forecolor', 'hilitecolor', 'bold',
'italic', 'underline', 'strikethrough', 'lineheight', 'removeformat', '|', 'image', 'multiimage',
'flash', 'media', 'insertfile', 'table', 'hr', 'emoticons', 'baidumap', 'pagebreak',
'anchor', 'link', 'unlink', '|', 'about'
]
源碼git地址:https://github.com/qiao-zhi/bs-tourism2.git
3.前后端分離項目vue-elementui中使用kindeditor
git地址: https://github.com/ff755/vue-kindeditor
大致安裝步驟如下:
0.設置淘寶鏡像:
npm config set registry https://registry.npm.taobao.org npm config get registry
(如果上面配置正確,會顯示https://registry.npm.taobao.org )
1. 添加vue-kindedtior
在終端項目目錄下執行
npm install vue-kindeditor --save-dev
2.使用vue-kindedtior
第一步,編輯 demo/src/main.js 文件
import Vue from 'vue' import App from './App' // 引入 vue-kikindeditor 需要的文件 import VueKindEditor from 'vue-kindeditor' import 'kindeditor/kindeditor-all-min.js' import 'kindeditor/themes/default/default.css' Vue.config.productionTip = false // 注冊 vue-kikindeditor plugin Vue.use(VueKindEditor) /* eslint-disable no-new */ new Vue({ el: '#app', template: '<App/>', components: { App } })
編輯完配置文件后,就可以在組件中使用了。
第二步,編輯 demo/src/components/Hello.vue 文件
在 Hello.vue 中添加如下代碼
<template> <div class="hello"> <editor id="editor_id" height="500px" width="700px" :content="editorText" pluginsPath="/static/kindeditor/plugins/" :loadStyleMode="false" @on-content-change="onContentChange"></editor> </div> </template> <script> export default { name: 'hello', data () { return { editorText: '' } }, methods: { onContentChange (val) { this.editorText = val } } } </script>
返回瀏覽器就可以看到編輯器了。
使用 editor 標簽使用kindeditor。<editor ...></editor> 參數: id: 設置編輯器的id,kindeditor創建使用需要用到,所以必填。例子:editor_id content: 用來獲取編輯器內容,使用雙向綁定獲取編輯器內容數據。例子:editorText loadStyleMode: 是否自動加載kindeditor需要的css文件,默認是從 / 查找,所以設置為false,搭配pluginsPath使用 pluginsPath: 設置編輯器的plugins目錄。例子中為了方便把kindeditor全部目錄復制到了demo/static中了,實際使用中地址自行設置。
3.關於圖片上傳:(使用該編輯器我主要是使用圖片上傳)
vue.config.js使用的是proxy,代理如下:
proxy: { '/api': { target: 'http://localhost:8088', ws: true, changeOrigin: true, pathRewrite: { '^/api': '' } } }
后台代碼只需要修改返回的地址加上/api 以供前端代理請求,如下:
/** * 文檔上傳 * * @param imgFile * @return */ @RequestMapping("/upload") @ResponseBody public Map<String, Object> uploadPicture(MultipartFile imgFile) { Map<String, Object> result = new HashMap<String, Object>(); result.put("error", 1); if (imgFile == null) { result.put("message", "文件沒接到"); return result; } logger.debug("file -> {},viewId ->{}", imgFile.getOriginalFilename()); String fileOriName = imgFile.getOriginalFilename();// 獲取原名稱 String fileNowName = UUIDUtils.getUUID2() + "." + FilenameUtils.getExtension(fileOriName);// 生成唯一的名字 try { FileHandleUtil.uploadSpringMVCFile(imgFile, fileNowName); } catch (Exception e) { logger.error("uploadPicture error", e); return result; } String id = UUIDUtils.getUUID(); Document document = new Document(); document.setCreatetime(new Date()); document.setPath(fileNowName); document.setId(id); document.setOriginName(fileOriName); document.setUploaderUsername(MySystemUtils.getLoginUsername()); documentService.insert(document); // 回傳JSON結果 result.put("error", 0); result.put("url", "/api/document/getDocument/" + id); return result; }
editor修改為如下:
<editor id="editor_id" height="200px" width="200px" :content="addForm.content" pluginsPath="/static/kindeditor/plugins/" :loadStyleMode="false" uploadJson="/api/blogPicture/uploadPicture.html" filePostName="imgFile" @on-content-change="onContentChange"></editor> </el-form-item>
4.實現文件上傳
kindeditor實現更多的插件,文件上傳和圖片上傳也可以單獨的使用,不在textarea里面使用。
有時候需要實現文件上傳,文件上傳和圖片上傳的思路是一樣的。
我們用到的controller還是上面的controller,只是在items增加文件上傳選項。 allowFileUpload: true 表示允許上傳本地文件、items中增加'insertfile' 表示顯示菜單。
<script> var editor; KindEditor.ready(function(K) { editor = K.create('textarea[name="contentEditor"]', { resizeType : 1, allowPreviewEmoticons : false, uploadJson : '/document/upload.html', allowImageUpload : true, allowFileUpload: true, pasteType : 0, //設置能否粘貼 items : [ 'fontname', 'fontsize', '|', 'forecolor', 'hilitecolor', 'bold', 'italic', 'underline', 'removeformat', '|', 'justifyleft', 'justifycenter', 'justifyright', 'insertorderedlist', 'insertunorderedlist', '|', 'emoticons', 'image', "insertfile", 'link','fullscreen'] }); }); </script>
顯示:
文件上傳之后相當於editor給我們增加了一個a標簽,href屬性為我們返回的url,a標簽的text為說明,如下:
我們點擊的時候會用a標簽訪問我們的URL,當瀏覽器可以解析的文件時會顯示,不解析的會以下載的形式進行下載。
這個文件上傳成功之后會alert(上傳成功),有時候體驗不是很好。通過查看源碼發現在kindeditor\plugins\insertfile目錄的insertfile.js文件的87行左右,如果不想 打印我們去掉打印效果即可,當然也可以自己修改上傳成功的源碼,如下:
如果我們希望文件在線預覽,可以修改后台代碼,在調用URL獲取文件信息的時候轉為PDF返回到流中。當然可以結合kkFileView(git上一個在線預覽,也是基於libreoffice轉換pdf進行預覽,只不過封裝了界面與更多的文件類型判斷)