前言:之前做了很多富文本编辑器的工作,不过都是以前了,最近项目突然要加富文本编辑器。心想那就加嘛,没想到springboot整合ueditor这么多坑,一个一个淌过来足足用了整整一天时间。趁着记忆力还算充足赶紧记录一下。
一、ueditor引入SpringBoot
首先请准备进入这个浑水的请务必注意,百度富文本编辑器已经4年没有更新过了。如果不是项目必须要用,你完全可以选择市面上仍在维护的编辑器。
那么开始正常的引入流程:
(1)官网下载适合的版本,这里我选择的是JSP版本
下载后解压缩,是一个如此结构的目录
我们将这部分代码直接复制到项目中的静态文件目录下,我这里的目录是这样的
导入maven依赖及相关的文件引入
ueditor的jar包存放在jsp/lib目录下,自己maven进去即可
相关的maven依赖:(这里ueditor和json包我没在中央仓库下载,而是手动cmd mvn引入的,所以实际依赖可能会和我的不一样,请自行选择,效果是一样的)
<!-- 引入富文本编辑器依赖 -->
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.10</version>
</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.4</version>
</dependency>
<dependency>
<groupId>com.baidu</groupId>
<artifactId>ueditor</artifactId>
<version>1.1.2</version>
</dependency>
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>1.0</version>
</dependency>
在相关页面用themleaf引入对应的JS
<script th:src="@{/ueditor/ueditor.config.js}"></script>
<script th:src="@{/ueditor/ueditor.all.min.js}"></script>
<script th:src="@{/ueditor/lang/zh-cn/zh-cn.js}"></script>
这里请务必注意引入顺序,config.js必须在all.min.js上面,让ueditor先加载相关配置项,不然会JS报错,富文本编辑器初始化失败。
(3)在页面初始化富文本编辑器
html:
JS:
//初始化富文本编辑器
var ue = UE.getEditor("editor");
至此,项目引入富文本编辑器第一步就完成了。
打开对应的页面,出现如图
二、ueditor的图片、附件、视频等相关的配置与结合fastDFS
完成了上面步骤后,富文本编辑器自己已经可以运转了,但附件上传功能还是坏的。
会提示后端配置不正确,上传插件不能使用。
这里ueditor会读取ueditor.config.js中的配置
而JSP中只是个跳转,最终找到的是config.json,这里面是全部富文本编辑器上传所需要的配置项
但是这里有两个问题,
一个是config.json在这个目录下是读取不到的,为此我们需要把它移动到statics目录下,
第二个是config.json读取加载完毕后会走ueditor内置的BinaryUploader文件上传。
这里由于springboot默认配置原因,springboot与BinaryUploader内置的方法冲突,所以用它自带的上传方法是拿不到文件流的,会不停的报未找到上传数据的错误。
三、ueditor重写,并整合进fastDFS
于是为了拿到上传数据,以及整合fastDFS,我们需要重写这部分代码。
首先干掉controller.jsp和config.json,这里我们完全不需要ueditor内置的上传方法了,
新建一个ueditor的controller
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author deer_boom
* @version 1.0
* @date 2020年7月27日
*/
@RequestMapping("ueditor")
@RestController
public class UEditorController {
@RequestMapping("/Config")
public String uploadConfig() {
String s = "/* 前后端通信相关的配置,注释只允许使用多行方式 */\n" +
"{\n" +
" /* 上传图片配置项 */\n" +
" \"imageActionName\": \"uploadimage\", /* 执行上传图片的action名称 */\n" +
" \"imageFieldName\": \"upfile\", /* 提交的图片表单名称 */\n" +
" \"imageMaxSize\": 2048000, /* 上传大小限制,单位B */\n" +
" \"imageAllowFiles\": [\".png\", \".jpg\", \".jpeg\", \".gif\", \".bmp\"], /* 上传图片格式显示 */\n" +
" \"imageCompressEnable\": true, /* 是否压缩图片,默认是true */\n" +
" \"imageCompressBorder\": 1600, /* 图片压缩最长边限制 */\n" +
" \"imageInsertAlign\": \"none\", /* 插入的图片浮动方式 */\n" +
" \"imageUrlPrefix\": \"\", /* 图片访问路径前缀 */\n" +
" \"imagePathFormat\": \"/ueditor/jsp/upload/image/{yyyy}{mm}{dd}/{time}{rand:6}\", /* 上传保存路径,可以自定义保存路径和文件名格式 */\n" +
" /* {filename} 会替换成原文件名,配置这项需要注意中文乱码问题 */\n" +
" /* {rand:6} 会替换成随机数,后面的数字是随机数的位数 */\n" +
" /* {time} 会替换成时间戳 */\n" +
" /* {yyyy} 会替换成四位年份 */\n" +
" /* {yy} 会替换成两位年份 */\n" +
" /* {mm} 会替换成两位月份 */\n" +
" /* {dd} 会替换成两位日期 */\n" +
" /* {hh} 会替换成两位小时 */\n" +
" /* {ii} 会替换成两位分钟 */\n" +
" /* {ss} 会替换成两位秒 */\n" +
" /* 非法字符 \\ : * ? \" < > | */\n" +
" /* 具请体看线上文档: fex.baidu.com/ueditor/#use-format_upload_filename */\n" +
"\n" +
" /* 涂鸦图片上传配置项 */\n" +
" \"scrawlActionName\": \"uploadscrawl\", /* 执行上传涂鸦的action名称 */\n" +
" \"scrawlFieldName\": \"upfile\", /* 提交的图片表单名称 */\n" +
" \"scrawlPathFormat\": \"/ueditor/jsp/upload/image/{yyyy}{mm}{dd}/{time}{rand:6}\", /* 上传保存路径,可以自定义保存路径和文件名格式 */\n" +
" \"scrawlMaxSize\": 2048000, /* 上传大小限制,单位B */\n" +
" \"scrawlUrlPrefix\": \"\", /* 图片访问路径前缀 */\n" +
" \"scrawlInsertAlign\": \"none\",\n" +
"\n" +
" /* 截图工具上传 */\n" +
" \"snapscreenActionName\": \"uploadimage\", /* 执行上传截图的action名称 */\n" +
" \"snapscreenPathFormat\": \"/ueditor/jsp/upload/image/{yyyy}{mm}{dd}/{time}{rand:6}\", /* 上传保存路径,可以自定义保存路径和文件名格式 */\n" +
" \"snapscreenUrlPrefix\": \"\", /* 图片访问路径前缀 */\n" +
" \"snapscreenInsertAlign\": \"none\", /* 插入的图片浮动方式 */\n" +
"\n" +
" /* 抓取远程图片配置 */\n" +
" \"catcherLocalDomain\": [\"127.0.0.1\", \"localhost\", \"img.baidu.com\"],\n" +
" \"catcherActionName\": \"catchimage\", /* 执行抓取远程图片的action名称 */\n" +
" \"catcherFieldName\": \"source\", /* 提交的图片列表表单名称 */\n" +
" \"catcherPathFormat\": \"/ueditor/jsp/upload/image/{yyyy}{mm}{dd}/{time}{rand:6}\", /* 上传保存路径,可以自定义保存路径和文件名格式 */\n" +
" \"catcherUrlPrefix\": \"\", /* 图片访问路径前缀 */\n" +
" \"catcherMaxSize\": 2048000, /* 上传大小限制,单位B */\n" +
" \"catcherAllowFiles\": [\".png\", \".jpg\", \".jpeg\", \".gif\", \".bmp\"], /* 抓取图片格式显示 */\n" +
"\n" +
" /* 上传视频配置 */\n" +
" \"videoActionName\": \"uploadvideo\", /* 执行上传视频的action名称 */\n" +
" \"videoFieldName\": \"upfile\", /* 提交的视频表单名称 */\n" +
" \"videoPathFormat\": \"/ueditor/jsp/upload/video/{yyyy}{mm}{dd}/{time}{rand:6}\", /* 上传保存路径,可以自定义保存路径和文件名格式 */\n" +
" \"videoUrlPrefix\": \"\", /* 视频访问路径前缀 */\n" +
" \"videoMaxSize\": 102400000, /* 上传大小限制,单位B,默认100MB */\n" +
" \"videoAllowFiles\": [\n" +
" \".flv\", \".swf\", \".mkv\", \".avi\", \".rm\", \".rmvb\", \".mpeg\", \".mpg\",\n" +
" \".ogg\", \".ogv\", \".mov\", \".wmv\", \".mp4\", \".webm\", \".mp3\", \".wav\", \".mid\"], /* 上传视频格式显示 */\n" +
"\n" +
" /* 上传文件配置 */\n" +
" \"fileActionName\": \"uploadfile\", /* controller里,执行上传视频的action名称 */\n" +
" \"fileFieldName\": \"upfile\", /* 提交的文件表单名称 */\n" +
" \"filePathFormat\": \"/ueditor/jsp/upload/file/{yyyy}{mm}{dd}/{time}{rand:6}\", /* 上传保存路径,可以自定义保存路径和文件名格式 */\n" +
" \"fileUrlPrefix\": \"\", /* 文件访问路径前缀 */\n" +
" \"fileMaxSize\": 51200000, /* 上传大小限制,单位B,默认50MB */\n" +
" \"fileAllowFiles\": [\n" +
" \".png\", \".jpg\", \".jpeg\", \".gif\", \".bmp\",\n" +
" \".flv\", \".swf\", \".mkv\", \".avi\", \".rm\", \".rmvb\", \".mpeg\", \".mpg\",\n" +
" \".ogg\", \".ogv\", \".mov\", \".wmv\", \".mp4\", \".webm\", \".mp3\", \".wav\", \".mid\",\n" +
" \".rar\", \".zip\", \".tar\", \".gz\", \".7z\", \".bz2\", \".cab\", \".iso\",\n" +
" \".doc\", \".docx\", \".xls\", \".xlsx\", \".ppt\", \".pptx\", \".pdf\", \".txt\", \".md\", \".xml\"\n" +
" ], /* 上传文件格式显示 */\n" +
"\n" +
" /* 列出指定目录下的图片 */\n" +
" \"imageManagerActionName\": \"listimage\", /* 执行图片管理的action名称 */\n" +
" \"imageManagerListPath\": \"/ueditor/jsp/upload/image/\", /* 指定要列出图片的目录 */\n" +
" \"imageManagerListSize\": 20, /* 每次列出文件数量 */\n" +
" \"imageManagerUrlPrefix\": \"\", /* 图片访问路径前缀 */\n" +
" \"imageManagerInsertAlign\": \"none\", /* 插入的图片浮动方式 */\n" +
" \"imageManagerAllowFiles\": [\".png\", \".jpg\", \".jpeg\", \".gif\", \".bmp\"], /* 列出的文件类型 */\n" +
"\n" +
" /* 列出指定目录下的文件 */\n" +
" \"fileManagerActionName\": \"listfile\", /* 执行文件管理的action名称 */\n" +
" \"fileManagerListPath\": \"/ueditor/jsp/upload/file/\", /* 指定要列出文件的目录 */\n" +
" \"fileManagerUrlPrefix\": \"\", /* 文件访问路径前缀 */\n" +
" \"fileManagerListSize\": 20, /* 每次列出文件数量 */\n" +
" \"fileManagerAllowFiles\": [\n" +
" \".png\", \".jpg\", \".jpeg\", \".gif\", \".bmp\",\n" +
" \".flv\", \".swf\", \".mkv\", \".avi\", \".rm\", \".rmvb\", \".mpeg\", \".mpg\",\n" +
" \".ogg\", \".ogv\", \".mov\", \".wmv\", \".mp4\", \".webm\", \".mp3\", \".wav\", \".mid\",\n" +
" \".rar\", \".zip\", \".tar\", \".gz\", \".7z\", \".bz2\", \".cab\", \".iso\",\n" +
" \".doc\", \".docx\", \".xls\", \".xlsx\", \".ppt\", \".pptx\", \".pdf\", \".txt\", \".md\", \".xml\"\n" +
" ] /* 列出的文件类型 */\n" +
"\n" +
"}";
return s;
}
}
这里我们在controller中,直接把ueditor需要的配置项拼接字符串直接返回。
这样可以瞒过ueditor,上传方法就可以用了。
然后在页面上传的JS中,重写文件上传的路径
这个在网络上到处都是,可以直接复制
UE.Editor.prototype.getActionUrl = function(action) {
if (action == 'uploadimage' || action == 'uploadfile') {
return parent.parent.webPath + 'notice/upload';
} else {
return this._bkGetActionUrl.call(this, action);
}
}
注意此处,uploadimage代表的是图片上传,uploadfile代表的是文件上传,如果你的业务需要视频,也可以在条件里自己加上。
此时来到上传方法
/**
* 富文本附件上传的方法(包括图片、附件)
* @date 2020年7月27日
* @author deer_boom
* @param file
* @return java.util.Map
*/
@ResponseBody
@RequestMapping("/upload")
public Map upload(@RequestParam(value = "upfile") MultipartFile file) {
RetBean retBean = new RetBean();
try {
String fileFullName = file.getOriginalFilename();
String extName = fileFullName.split("\\.")[1];
byte[] bytes = null;
bytes = file.getBytes();
String filePath = FastDFSClientUtils.upload(bytes, extName);
String id = StringUtils.getUUID();
String time = StringUtils.getDateTime();
long fileSize = file.getSize();
FileInfo fileInfo = new FileInfo();
fileInfo.setFileName(fileFullName);
fileInfo.setFilePath(filePath);
fileInfo.setId(id);
fileInfo.setFileTime(time);
fileInfo.setFileSuffix(extName);
int code = archiveNoticeService.savaFile(fileInfo);
if (code > 0) {
retBean.setFileInfo(fileInfo);
retBean.setMessage("SUCCESS");
retBean.setCode("0");
//给ueditor的回调参数
Map<String ,Object> result = Maps.newHashMap();
result.put("state","SUCCESS");
result.put("original",extName);
result.put("size",fileSize);
result.put("title",fileFullName);
result.put("type",bytes);
result.put("url", "downLoad?id=" + id);
return result;
} else {
retBean.setMessage("ERROR");
retBean.setCode("1");
}
} catch (IOException e) {
retBean.setMessage("UPLOAD FILE ERROR");
retBean.setCode("1");
}
return null;
}
这里需要注意的是需要手动用map给ueditor返回它需要的格式
//给ueditor的回调参数
Map<String ,Object> result = Maps.newHashMap();
result.put("state","SUCCESS");
result.put("original",extName);
result.put("size",fileSize);
result.put("title",fileFullName);
result.put("type",bytes);
result.put("url", "downLoad?id=" + id);
URL为fastDFS获取图片的方法
这里需要注意的问题有:
1、Springboot获取不到文件流,MultipartFile一直是null
这个原因有俩,一个是springboot自带的org.springframework.web.multipart.MultipartFile和Multipart冲突。
为此需要自己添加配置到启动项
@Configuration
public class UploadConfig {
@Bean(name = "multipartResolver")
public MultipartResolver multipartResolver() {
CommonsMultipartResolver resolver = new CommonsMultipartResolver();
resolver.setDefaultEncoding("UTF-8");
//resolveLazily属性启用是为了推迟文件解析,以在在UploadAction中捕获文件大小异常
resolver.setResolveLazily(true);
resolver.setMaxInMemorySize(40960);
//上传文件大小 5M 5*1024*1024
resolver.setMaxUploadSize(5 * 1024 * 1024);
return resolver;
}
}
2、请重新回头查看刚才拼接的ueditorController,里面写了上传表单名
这里请务必与后台接收名对应好,
至此,附件上传成功,但是回显还是有问题
来简单看一下现在图片的src:
为此需要我们在手动拼的url对应中,写从fastDFS中获取图片的方法
@ResponseBody
@RequestMapping("/downLoad")
public void downLoad(@RequestParam(value = "id") String id, HttpServletResponse response) throws IOException {
PrintWriter writer = response.getWriter();
FileInfo fileInfo = archiveNoticeService.fileInfoById(id);
String fileName = fileInfo.getFileName();
String filePath = fileInfo.getFilePath();
try {
String group = filePath.split("/")[0];
InputStream inStream = FastDFSClientUtils.download(group, fileInfo.getFilePath());
// 设置输出的格式
response.reset();
response.setContentType("application/octet-stream");//设置文件类型
response.addHeader("Content-Disposition", "attachment; filename=\"" + URLEncoder.encode(fileName, "UTF-8") + "\"");
// 循环取出流中的数据
byte[] b = IOUtils.toByteArray(inStream);
response.getOutputStream().write(b);
response.flushBuffer();
inStream.close();
response.getOutputStream().close();
} catch (Exception e) {
writer.println("Download failure!");
e.printStackTrace();
}
}
但是此时保存后还是有问题的,因为ueditor和springboot相关配置的XSS注入拦截,会在新增时传参的时候,把img标签里的src过滤掉,导致回显src缺失没图没文件,为此我们需要做两手解决:
1、ueditor的解决:在ueditor中直接将XSS拦截关闭,统一改成false
担心这样会出现注入问题的话,可以在ueditor.all.js和ueditor.all.min.js中干掉img的判断
case 'img':
//todo base64暂时去掉,后边做远程图片上传后,干掉这个
/*if (val = node.getAttr('src')) {
if (/^data:/.test(val)) {
node.parentNode.removeChild(node);
break;
}
}
node.setAttr('_src', node.getAttr('src'));*/
break;
第二步,如果你的Springboot项目也配置了XSS拦截,那么需要将对应的字符串转义即可,这个根据你自己实际业务随时引入,我把有用的JS都放上了,noticeContent就是你后台传过来的富文本内容
noticeContent = html2Escape(noticeContent);
//特殊字符转义
function html2Escape(sHtml) {
return sHtml.replace(/[<>&"]/g,function(c){
return {'<':'<','>':'>','&':'&','"':'"'}[c];
});
}
由于涉及到了转义,所以请记得回显的时候反转义回来,
noticeContent = escape2Html(noticeContent);
console.log("转义前::"+noticeContent);
ue.setContent(noticeContent);
//特殊字符反转义
function escape2Html(str) {
var arrEntities={'lt':'<','gt':'>','nbsp':' ','amp':'&','quot':'"'};
return str.replace(/&(lt|gt|nbsp|amp|quot);/ig,function(all,t){
return arrEntities[t];
});
}
这样,富文本内回显也没有问题了。
此时新增、修改的上传与回显都是正常的。但是作为一个富文本管理功能,必然有一个专门的展示页,所以回显富文本也是有问题的,不过很简单,稍做记录:
themleaf使用th:text回显会导致页面不渲染,所以这里请使用th:utext回显,就可以正常加载了
<div id="noticeContent" th:utext="${noticeMap.NOTICE_CONTENT}"></div>
至此,SpringBoot+Ueditor+themleaf+fastDFS都全都弄好了,功能正常可以使用。
结尾:我自己水平不高,因为这个东西着实头疼了我一天,而且网上很多方法又碎又不好用,一个问题可能需要三篇博客结合看才能解决问题,所以分享出来权当帮大家伙踩坑,有什么问题可以留言。