ueditor编辑器
一、去Ueditor官网看看开发文档
地址:ueditor
官方文档说的不是很明白,去github看看:点击
接下来就是搭建一个Spring工程,使用IDEA来搭建maven工程, 目录如下:
测试一下:
二、spring后台整合
首先解压刚才下载的代码,直接进入jsp文件夹观察:
- 第一点,一看就是依赖的jar包
- 第二点,源码文件
- 第三点,ueditor配置文件
- 第四点,应该是一个类似controller接口的文件
接下来,我在我的项目中加入这些jar包,由于是maven项目,还是加入依赖比较好:
<!-- Ueditor依赖包 -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.5</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.9</version>
</dependency>
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20160807</version>
</dependency>
将源码文件加入项目中:
如果你的包名不是com.baidu.ueditor的话,还需要花点时间改改import中的包名
然后,将配置文件config.json放入resources中
最后就要看一看jsp文件到底写的什么逻辑
<%@ page language="java" contentType="text/html; charset=UTF-8"
import="com.baidu.ueditor.ActionEnter"
pageEncoding="UTF-8"%>
<%@ page trimDirectiveWhitespaces="true" %>
<%
request.setCharacterEncoding( "utf-8" );
response.setheader("Content-Type" , "text/html");
String rootPath = APPlication.getrealpath( "/" );
out.write( new ActionEnter( request, rootPath ).exec() );
%>
这个文件就是调用ActionEnter类的exec()方法,然后执行一系列的操作。写一个mvc接口来代替jsp文件:
DemoController.java
package com.demo.controller;
import com.demo.ueditor.ActionEnter;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import java.io.UnsupportedEncodingException;
/**
* @author xyd
* @version V1.0
* @Package com.demo.controller
* @Description:
* @date 2018/8/6 17:19
*/
@RestController("demo")
@RequestMapping("/ueditor")
public class DemoController {
@RequestMapping(value = "/exec")
@ResponseBody
public String exec(HttpServletRequest request) throws UnsupportedEncodingException {
request.setCharacterEncoding("utf-8");
String rootPath = request.getRealPath("/");
return new ActionEnter( request, rootPath ).exec();
}
}
后台的代码基本上就整合完毕了,现在需要前端页面进行配合测试。
接下来要去官网下载前端页面
地址:点击
下载jsp版本后目录:
之前我们使用了jsp中的代码,现在我们需要的是前端页面,所以我们可以吧jsp文件夹删掉。
通过查看官方文档,可以看出要想整合前端后台,必须配置ueditor.config.js中的serverUrl,
找到文件,将serverUrl替换成有效的url:
这时应该将后台代码打包放入服务器中,但是打包之前还是得测试一下http接口是否生效:
可以了,打包上传代码!这时,后台代码在服务器中,前端代码在本地。
这样,好像基本整合完了!
但是!测试一下图片上传:
结果报错了,上传不了。看到origin “null” ,应该就是源ip为null报的错,那就把前端代码也放入服务器中。
这里看到,整合是没问题了。
三、问题
1、在整合的时候没有配置config.json
就是config.json是个配置文件,我们还没有配置,现在修改一下:
/* 前后端通信相关的配置,注释只允许使用多行方式 */
{
/* 上传图片配置项 */
"imageActionName": "uploadimage", /* 执行上传图片的action名称 */
"imagefieldName": "upfile", /* 提交的图片表单名称 */
"imageMaxSize": 2048000, /* 上传大小限制,单位B */
"imageAllowFiles": [".png", ".jpg", ".jpeg", ".gif", ".bmp"], /* 上传图片格式显示 */
"imageCompressEnable": true, /* 是否压缩图片,默认是true */
"imageCompressBorder": 1600, /* 图片压缩最长边限制 */
"imageInsertAlign": "none", /* 插入的图片浮动方式 */
// 重点是修改路径
"imageUrlPrefix": "http://test.**.com/ueditordemo", /* 图片访问路径前缀 */
// *******
"imagePathFormat": "/www/web/demo/image/{yyyy}{mm}{dd}/{time}{rand:6}", /* 上传保存路径,可以自定义保存路径和文件名格式 */
/* {filename} 会替换成原文件名,配置这项需要注意中文乱码问题 */
/* {rand:6} 会替换成随机数,后面的数字是随机数的位数 */
/* {time} 会替换成时间戳 */
/* {yyyy} 会替换成四位年份 */
/* {yy} 会替换成两位年份 */
/* {mm} 会替换成两位月份 */
/* {dd} 会替换成两位日期 */
/* {hh} 会替换成两位小时 */
/* {ii} 会替换成两位分钟 */
/* {ss} 会替换成两位秒 */
/* 非法字符 \ : * ? " < > | */
/* 具请体看线上文档: fex.baidu.com/ueditor/#use-format_upload_filename */
/* 涂鸦图片上传配置项 */
"scrawlActionName": "uploadscrawl", /* 执行上传涂鸦的action名称 */
"scrawlFieldName": "upfile", /* 提交的图片表单名称 */
// *******
"scrawlPathFormat": "/www/web/demo/image/{yyyy}{mm}{dd}/{time}{rand:6}", /* 上传保存路径,可以自定义保存路径和文件名格式 */
"scrawlMaxSize": 2048000, /* 上传大小限制,单位B */
"scrawlUrlPrefix": "http://test.**.com/ueditordemo", /* 图片访问路径前缀 */
"scrawlInsertAlign": "none",
/* 截图工具上传 */
"snapscreenActionName": "uploadimage", /* 执行上传截图的action名称 */
"snapscreenPathFormat": "/www/web/demo/image/{yyyy}{mm}{dd}/{time}{rand:6}", /* 上传保存路径,可以自定义保存路径和文件名格式 */
"snapscreenUrlPrefix": "http://test.**.com/ueditordemo", /* 图片访问路径前缀 */
"snapscreenInsertAlign": "none", /* 插入的图片浮动方式 */
/* 抓取远程图片配置 */
"catcherlocalDomain": ["127.0.0.1", "localhost", "img.baidu.com"],
"catcherActionName": "catchimage", /* 执行抓取远程图片的action名称 */
"catcherFieldName": "source", /* 提交的图片列表表单名称 */
"catcherPathFormat": "/www/web/demo/image/{yyyy}{mm}{dd}/{time}{rand:6}", /* 上传保存路径,可以自定义保存路径和文件名格式 */
"catcherUrlPrefix": "http://test.**.com/ueditordemo", /* 图片访问路径前缀 */
"catcherMaxSize": 2048000, /* 上传大小限制,单位B */
"catcherAllowFiles": [".png", ".jpg", ".jpeg", ".gif", ".bmp"], /* 抓取图片格式显示 */
/* 上传视频配置 */
"videoActionName": "uploadvideo", /* 执行上传视频的action名称 */
"videoFieldName": "upfile", /* 提交的视频表单名称 */
"videoPathFormat": "/www/web/demo/video/{yyyy}{mm}{dd}/{time}{rand:6}", /* 上传保存路径,可以自定义保存路径和文件名格式 */
"videoUrlPrefix": "http://test.**.com/ueditordemo", /* 视频访问路径前缀 */
"videoMaxSize": 102400000, /* 上传大小限制,单位B,默认100MB */
"videoAllowFiles": [
".flv", ".swf", ".mkv", ".avi", ".rm", ".rmvb", ".mpeg", ".mpg",
".ogg", ".ogv", ".mov", ".wmv", ".mp4", ".webm", ".mp3", ".wav", ".mid"], /* 上传视频格式显示 */
/* 上传文件配置 */
"fileActionName": "uploadfile", /* controller里,执行上传视频的action名称 */
"fileFieldName": "upfile", /* 提交的文件表单名称 */
"filePathFormat": "/www/web/demo/file/{yyyy}{mm}{dd}/{time}{rand:6}", /* 上传保存路径,可以自定义保存路径和文件名格式 */
"fileUrlPrefix": "http://test.**.com/ueditordemo", /* 文件访问路径前缀 */
"fileMaxSize": 51200000, /* 上传大小限制,单位B,默认50MB */
"fileAllowFiles": [
".png", ".jpg", ".jpeg", ".gif", ".bmp",
".flv", ".swf", ".mkv", ".avi", ".rm", ".rmvb", ".mpeg", ".mpg",
".ogg", ".ogv", ".mov", ".wmv", ".mp4", ".webm", ".mp3", ".wav", ".mid",
".rar", ".zip", ".tar", ".gz", ".7z", ".bz2", ".cab", ".iso",
".doc", ".docx", ".xls", ".xlsx", ".ppt", ".pptx", ".pdf", ".txt", ".md", ".xml"
], /* 上传文件格式显示 */
/* 列出指定目录下的图片 */
"imageManagerActionName": "listimage", /* 执行图片管理的action名称 */
"imageManagerListPath": "/www/web/demo/image/", /* 指定要列出图片的目录 */
"imageManagerListSize": 20, /* 每次列出文件数量 */
"imageManagerUrlPrefix": "http://test.**.com/ueditordemo", /* 图片访问路径前缀 */
"imageManagerInsertAlign": "none", /* 插入的图片浮动方式 */
"imageManagerAllowFiles": [".png", ".jpg", ".jpeg", ".gif", ".bmp"], /* 列出的文件类型 */
/* 列出指定目录下的文件 */
"fileManagerActionName": "listfile", /* 执行文件管理的action名称 */
"fileManagerListPath": "/www/web/demo/file/", /* 指定要列出文件的目录 */
"fileManagerUrlPrefix": "http://test.**.com/ueditordemo", /* 文件访问路径前缀 */
"fileManagerListSize": 20, /* 每次列出文件数量 */
"fileManagerAllowFiles": [
".png", ".jpg", ".jpeg", ".gif", ".bmp",
".flv", ".swf", ".mkv", ".avi", ".rm", ".rmvb", ".mpeg", ".mpg",
".ogg", ".ogv", ".mov", ".wmv", ".mp4", ".webm", ".mp3", ".wav", ".mid",
".rar", ".zip", ".tar", ".gz", ".7z", ".bz2", ".cab", ".iso",
".doc", ".docx", ".xls", ".xlsx", ".ppt", ".pptx", ".pdf", ".txt", ".md", ".xml"
] /* 列出的文件类型 */
}
该配置文件主要修改的地方是图片访问路径和图片存储路径。
在服务器相应的地址下建立文件夹:
这时候打包上传
可以看到配置已经生效了。
但是,图片还是不能上传上去,开始分析问题:
1. 从页面访问的action开始
可以看到访问的是exec.action,后面带的是参数。
2.从代码中找到该接口
看的出来是执行了ActionEnter中的exec方法,转到该方法
public String exec () {
String callbackName = this.request.getparameter("callback");
if ( callbackName != null ) {
if ( !validCallbackName( callbackName ) ) {
return new BaseState( false, AppInfo.ILLEGAL ).toJSONString();
}
return callbackName+"("+this.invoke()+");";
} else {
return this.invoke();
}
}
该方法执行了invoke()方法,再转到invoke
public String invoke() {
if ( actionType == null || !ActionMap.mapping.containskey( actionType ) ) {
return new BaseState( false, AppInfo.INVALID_ACTION ).toJSONString();
}
if ( this.configManager == null || !this.configManager.valid() ) {
return new BaseState( false, AppInfo.CONFIG_ERROR ).toJSONString();
}
State state = null;
int actionCode = ActionMap.getType( this.actionType );
Map<String, Object> conf = null;
switch ( actionCode ) {
case ActionMap.CONFIG:
return this.configManager.getAllConfig().toString();
// 上传图片执行到这
case ActionMap.UPLOAD_IMAGE:
case ActionMap.UPLOAD_SCRAWL:
case ActionMap.UPLOAD_VIDEO:
case ActionMap.UPLOAD_FILE:
conf = this.configManager.getConfig( actionCode );
state = new Uploader( request, conf ).doExec();
break;
case ActionMap.CATCH_IMAGE:
conf = configManager.getConfig( actionCode );
String[] list = this.request.getParameterValues( (String)conf.get( "fieldName" ) );
state = new ImageHunter( conf ).capture( list );
break;
case ActionMap.LIST_IMAGE:
case ActionMap.LIST_FILE:
conf = configManager.getConfig( actionCode );
int start = this.getStartIndex();
state = new FileManager( conf ).listFile( start );
break;
}
return state.toJSONString();
}
这里应该就很清楚了,刚才我们action后面跟的参数是uploadimage,也就是上述代码中注释的地方
state = new Uploader( request, conf ).doExec();
这里看出又执行了Uploader类的doExec()方法,转过去:
public final State doExec() {
State state = null;
if ("true".equals(this.conf.get("isbase64"))) {
state = Base64Uploader.save(this.request,
this.conf);
} else {
// 运行到这
state = binaryUploader.save(this.request, this.conf);
}
return state;
}
再转到BinaryUploader.save()方法
public static final State save(HttpServletRequest request,
Map<String, Object> conf) {
FileItemStream filestream = null;
boolean isAjaxUpload = request.getHeader( "X_Requested_With" ) != null;
if (!servletfileupload.isMultipartContent(request)) {
return new BaseState(false, AppInfo.NOT_MULTIPART_CONTENT);
}
ServletFileUpload upload = new ServletFileUpload(
new DiskFileItemFactory());
// 转为utf-8
if ( isAjaxUpload ) {
upload.setHeaderEncoding( "UTF-8" );
}
try {
// 文件Iterator
FileItemIterator iterator = upload.getItemIterator(request);
while (iterator.hasNext()) {
fileStream = iterator.next();
// 如果存在文件
if (!fileStream.isFormField())
break;
fileStream = null;
}
// 不存在就返回
if (fileStream == null) {
return new BaseState(false, AppInfo.notfound_UPLOAD_DATA);
}
// 从配置文件里拿到imagePathFormat的值
String savePath = (String) conf.get("savePath");
// 拿到文件原始名称包括后缀
String originFileName = fileStream.getName();
// 拿到文件的后缀名
String suffix = FileType.getSuffixByFilename(originFileName);
// 拿到文件名
originFileName = originFileName.substring(0,
originFileName.length() - suffix.length());
// 拼接路径和文件后缀
savePath = savePath + suffix;
long maxSize = ((Long) conf.get("maxSize")).longValue();
// 判断文件类型
if (!validType(suffix, (String[]) conf.get("allowFiles"))) {
return new BaseState(false, AppInfo.NOT_ALLOW_FILE_TYPE);
}
savePath = PathFormat.parse(savePath, originFileName);
//modified by Ternence
String rootPath = ConfigManager.getRootPath(request,conf);
String physicalPath = rootPath + savePath;
InputStream is = fileStream.openStream();
State storageState = StorageManager.saveFileByInputStream(is,
physicalPath, maxSize);
is.close();
if (storageState.isSuccess()) {
storageState.putInfo("url", PathFormat.format(savePath));
storageState.putInfo("type", suffix);
storageState.putInfo("original", originFileName + suffix);
}
return storageState;
} catch (FileUploadException e) {
return new BaseState(false, AppInfo.PARSE_REQUEST_ERROR);
} catch (IOException e) {
}
return new BaseState(false, AppInfo.IO_ERROR);
}
3.修改代码
在try catch中的代码就是上传的具体代码,仔细分析代码后,发现代码不太适合,就改成适合自己的:
try {
MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
MultipartFile file = multipartRequest.getFile("upfile");
String savePath = (String) conf.get("savePath");
String localSavePathPrefix = (String) conf.get("localSavePathPrefix");
String originFileName = file.getOriginalFilename();
String suffix = FileType.getSuffixByFilename(originFileName);
originFileName = originFileName.substring(0, originFileName.length() - suffix.length());
savePath = savePath + suffix;
long maxSize = ((Long) conf.get("maxSize")).longValue();
if (!validType(suffix, (String[]) conf.get("allowFiles"))) {
return new BaseState(false, AppInfo.NOT_ALLOW_FILE_TYPE);
}
savePath = PathFormat.parse(savePath, originFileName);
localSavePathPrefix = savePath;
String physicalPath = localSavePathPrefix;
InputStream is = file.getInputStream();
// InputStream is = fileStream.openStream();
// State storageState = StorageManager.saveFileByInputStream(is,
// physicalPath, maxSize);
//上传到本地
State storageState = FileUploadUtils.uploadFileMultiPart(file, physicalPath, null);
is.close();
if (storageState.isSuccess()) {
storageState.putInfo("type", suffix);
storageState.putInfo("original", originFileName + suffix);
}
return storageState;
} catch (Exception e) {
// return new BaseState(false, e.getmessage());
return new BaseState(false, AppInfo.PARSE_REQUEST_ERROR);
}
其中FileUploadUtils是自己写的工具类:
package com.demo.util;
import com.demo.ueditor.define.BaseState;
import com.demo.ueditor.define.State;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
/**
* @author xyd
* @version V1.0
* @Package com.demo.util
* @Description:
* @date 2018/8/7 11:40
*/
public class FileUploadUtils {
public static State uploadFileMultiPart(MultipartFile file, String filePath, State state ){
boolean success = true;
File file1 = new File(filePath);
if(!file1.exists()){
file1.mkdirs();
}
try {
file.transferTo(file1);
} catch (IOException e) {
e.printstacktrace();
}
if (success) {
state = new BaseState(true);
state.putInfo( "size", file1.length() );
state.putInfo( "title", file.getOriginalFilename());//文件名填入此处
state.putInfo( "group", "");//所属group填入此处
state.putInfo( "url", filePath);//文件访问的url填入此处
}else{
state = new BaseState(false, 4);
}
return state;
}
}
这样上传图片就ok了。
2.图片在线管理不能使用
列出的图片结果如图:
分析:
- 地址中没有域名,可能缺少域名拼接
- 地址所有字符被‘/’分割,可能使用了不适用的方法
整体思维步骤和第一点一样,找到代码执行的类中:
package com.demo.ueditor.hunter;
import java.io.File;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map;
import org.apache.commons.io.FileUtils;
import com.demo.ueditor.PathFormat;
import com.demo.ueditor.define.AppInfo;
import com.demo.ueditor.define.BaseState;
import com.demo.ueditor.define.MultiState;
import com.demo.ueditor.define.State;
public class FileManager {
private String dir = null;
private String rootPath = null;
private String[] allowFiles = null;
private int count = 0;
public FileManager ( Map<String, Object> conf ) {
this.rootPath = (String)conf.get( "rootPath" );
this.dir = this.rootPath + (String)conf.get( "dir" );
this.allowFiles = this.getAllowFiles( conf.get("allowFiles") );
this.count = (integer)conf.get( "count" );
}
// 1、代码在此执行
public State listFile ( int index ) {
File dir = new File( this.dir );
State state = null;
if ( !dir.exists() ) {
return new BaseState( false, AppInfo.NOT_EXIST );
}
if ( !dir.isDirectory() ) {
return new BaseState( false, AppInfo.NOT_DIRECTORY );
}
///2、查找文件
Collection<File> list = FileUtils.listFiles( dir, this.allowFiles, true );
if ( index < 0 || index > list.size() ) {
state = new MultiState( true );
} else {
Object[] fileList = Arrays.copyOfRange( list.toArray(), index, index + this.count );
///3、调用getState方法
state = this.getState( fileList );
}
state.putInfo( "start", index );
state.putInfo( "total", list.size() );
return state;
}
///4、继续到这
private State getState ( Object[] files ) {
MultiState state = new MultiState( true );
BaseState fileState = null;
File file = null;
for ( Object obj : files ) {
if ( obj == null ) {
break;
}
file = (File)obj;
fileState = new BaseState( true );
// 5、猜测本段代码有问题,进入this.getPath方法
// this.getPath()返回的路径是服务器物理路径,不适合外部访问,所以需要加上域名
fileState.putInfo( "url", PathFormat.format( this.getPath( file ) ) );
state.addState( fileState );
}
return state;
}
private String getPath ( File file ) {
String path = file.getabsolutePath();
// 6、这个替换有问题
// 进入此方法发现此方法在linux系统下会出错,所以需要去掉此方法调用
return path.replace( this.rootPath, "/" );
}
private String[] getAllowFiles ( Object fileExt ) {
String[] exts = null;
String ext = null;
if ( fileExt == null ) {
return new String[ 0 ];
}
exts = (String[])fileExt;
for ( int i = 0, len = exts.length; i < len; i++ ) {
ext = exts[ i ];
exts[ i ] = ext.replace( ".", "" );
}
return exts;
}
}
问题分析流程在代码注释中,修改后的代码为:
package com.demo.ueditor.hunter;
import java.io.File;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map;
import org.apache.commons.io.FileUtils;
import com.demo.ueditor.PathFormat;
import com.demo.ueditor.define.AppInfo;
import com.demo.ueditor.define.BaseState;
import com.demo.ueditor.define.MultiState;
import com.demo.ueditor.define.State;
public class FileManager {
private String dir = null;
private String rootPath = null;
private String[] allowFiles = null;
private int count = 0;
public FileManager ( Map<String, Object> conf ) {
this.rootPath = (String)conf.get( "rootPath" );
this.dir = this.rootPath + (String)conf.get( "dir" );
this.allowFiles = this.getAllowFiles( conf.get("allowFiles") );
this.count = (Integer)conf.get( "count" );
}
public State listFile ( int index ) {
File dir = new File( this.dir );
State state = null;
if ( !dir.exists() ) {
return new BaseState( false, AppInfo.NOT_EXIST );
}
if ( !dir.isDirectory() ) {
return new BaseState( false, AppInfo.NOT_DIRECTORY );
}
Collection<File> list = FileUtils.listFiles( dir, this.allowFiles, true );
if ( index < 0 || index > list.size() ) {
state = new MultiState( true );
} else {
Object[] fileList = Arrays.copyOfRange( list.toArray(), index, index + this.count );
state = this.getState( fileList );
}
state.putInfo( "start", index );
state.putInfo( "total", list.size() );
return state;
}
private State getState ( Object[] files ) {
MultiState state = new MultiState( true );
BaseState fileState = null;
File file = null;
for ( Object obj : files ) {
if ( obj == null ) {
break;
}
file = (File)obj;
fileState = new BaseState( true );
fileState.putInfo( "url", "http://test.kuyuntech.com/ueditordemo" + PathFormat.format( this.getPath( file ) ) );
state.addState( fileState );
}
return state;
}
private String getPath ( File file ) {
String path = PathFormat.format( file.getAbsolutePath() );
return path;
}
private String[] getAllowFiles ( Object fileExt ) {
String[] exts = null;
String ext = null;
if ( fileExt == null ) {
return new String[ 0 ];
}
exts = (String[])fileExt;
for ( int i = 0, len = exts.length; i < len; i++ ) {
ext = exts[ i ];
exts[ i ] = ext.replace( ".", "" );
}
return exts;
}
}
四、最后测试:
1、上传图片:
2、在线管理:
3、上传视频:
4、上传文件:
5、在线管理:
5、总结
百度Ueditor整合还是算比较简单的
- 修改配置文件的路径
- 修改上传文件的代码
- 修改展示文件的代码
- 注意不能再本地运行前端代码
码云代码:码云
相关阅读
ueditor美化皮肤 angular-ueditor-theme
做cms项目时候发现ueditor太丑了。所以在项目中修改了ueditor,现在分享给大家使用,个人感觉不错。有什么问题可以issue我如果你没有
1::功能齐全 tinymce|TinyMCE | The Most Advanced WYSIWYG HTML Editor 官方网址:https://www.tinymce.com/ TinyMCE是一个轻量级
RichEditor——一款基于RecyclerView实现的富文本编辑
前言 对于富文本编辑器的实现,首先我们肯定会想到实现的编辑器需要支持的几个必要特性: 1.涉及大量文字,图片,文字样式的