【前言】html
项目中涉及将UEditor上传服务器整合进已有的基于Spring MVC的服务中,而且将上传到本地改成上传到七牛,看似简单的一个需求,实际作起来仍是遇到了一些困难。在这里分享一下经验——前端
七牛官网的社区插件里有ueditor的插件java
该插件是《ueditor上传图片到七牛云存储(form api,java)》的做品,也发布了git地址。从图上时间信息可知这是13年末的插件。nginx
自从百度更新UEditor1.4.0以后,将分散的请求接口整合成一个ActionEnter入口,作了一个比较大的变更,原有的方式已经不适用了。git
参考《Ueditor结合七牛云及百度云存储(JAVA版,ueditor-1.4.3)实现图片文件上传》的方法以后,解决了问题。web
【UEditor分析】spring
该方法涉及到对UEditor源码的改动,因此首先要下载UEditor的源码。具体的二次开发细节和常见问题在UEditor开发文档中写的很清楚apache
大体说明一下编辑器涉及上传的几个文件:json
ueditor.all.min.js是编辑器的汇总js,经过压缩ueditor.all.js获得,也是实际调用的js后端
在ueditor.all.js的// core/loadconfig.js中加载了UE.Editor.prototype.loadServerConfig,该结构是根据serverUrl请求config获得的。其实就是获取了服务器的config.json文件,因此该文件在服务器中的路径要格外注意。
config.json里配置了上传的地址等信息
imageUrlPrefix是图片上传的前缀,这里设置成七牛的上传地址。imagePathFormat是文件名,可使用原有模版加入日期信息。imageManagerUrlPrefix是图片加载的前缀,上传成功后会从该地址读取图片插入到编辑器中。
ueditor.config.js是配置文件
值得注意的是var URL = window.UEDITOR_HOME_URL || getUEBasePath();和serverUrl: URL + "jsp/controller.jsp"这两句,一个指定了上传服务器的路径,一个指定了服务接口。
dialog目录管理编辑器中新开的窗口,包括文件上传窗口
internal.js是其入口
【开始】
【修改文件上传接口】
编辑器整合进spring框架,lib能够经过maven自行加载,这部分比较简单,略过不表。
将UEditor的源码加入spring
再新建一个controller,源码以下
@RestController public class FileManagerController { @RequestMapping(value = "controller") public String controller(HttpServletRequest request,HttpServletResponse response) { String rootPath = request.getServletContext().getRealPath("/"); return new ActionEnter( request, rootPath).exec(); } }
这里作了一个名为controller的接口,调用ueditor的ActionEnter对象,注意由于ueditor的GET和POST方法都会经过ActionEnterd进行,只是action参数不一样,因此该controller不要指定GET/POST方式
【修改文件上传方式】
这里由于将jsp调用改为了接口调用,因此文件传输的方法也要作相应修改,其实使用原来的jsp也是能够的
在com.baidu.ueditor.upload.Uploader找到doExec方法中对应的save方法,我这里没有启用Base64,因此走的是com.baidu.ueditor.upload.BinaryUploader的save方法。
作以下修改:
public class BinaryUploader { public static final State save(HttpServletRequest request, Map<String, Object> conf) { 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()); if ( isAjaxUpload ) { upload.setHeaderEncoding( "UTF-8" ); } try { //建立一个通用的多部分解析器 CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver(request.getSession().getServletContext()); //判断 request 是否有文件上传,即多部分请求 if(multipartResolver.isMultipart(request)){ //转换成多部分request MultipartHttpServletRequest multiRequest = (MultipartHttpServletRequest)request; //取得request中的全部文件名 Iterator<String> iter = multiRequest.getFileNames(); while(iter.hasNext()){ //取得上传文件 MultipartFile file = multiRequest.getFile(iter.next()); if(file != null){ //取得当前上传文件的文件名称 String savePath = (String) conf.get("savePath"); String originFileName = file.getOriginalFilename(); System.out.println(originFileName); 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); String physicalPath = (String) conf.get("rootPath") + savePath; System.out.println(physicalPath); InputStream is = file.getInputStream(); 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; } else{ return new BaseState(false, AppInfo.NOTFOUND_UPLOAD_DATA); } } } } catch (IOException e) { return new BaseState(false, AppInfo.PARSE_REQUEST_ERROR); } return new BaseState(false, AppInfo.IO_ERROR); }
这样就可使用servlet接口而不是jsp获取文件
【修改七牛上传】
再找到com.baidu.ueditor.upload.StorageManager的saveTmpFile方法,在这里改的主要缘由是其余传输方式都会通过这一步,方便一步到位
源码以下:
private static State saveTmpFile(File tmpFile, String path) { State state = null; File targetFile = new File(path); if (targetFile.canWrite()) { return new BaseState(false, AppInfo.PERMISSION_DENIED); } String uploadto = QPropertiesUtil.get("jfinal.ueditor.upload_to"); System.out.println(uploadto); boolean uploaderror = false; if(QStringUtil.notEmpty(uploadto)){ String key = path.substring(path.lastIndexOf("/")+1); if("qiniu".equals(uploadto)){ QQiNiuUtil.uploadFile(key, tmpFile.getAbsolutePath());//使用七牛上传 }else{ uploaderror = true; } }else{ uploaderror = true; } if(uploaderror){ try { FileUtils.moveFile(tmpFile, targetFile); } catch (IOException e) { return new BaseState(false, AppInfo.IO_ERROR); } } state = new BaseState(true); state.putInfo( "size", targetFile.length() ); state.putInfo( "title", targetFile.getName() ); return state; }
其中
if("qiniu".equals(uploadto)){ QQiNiuUtil.uploadFile(key, tmpFile.getAbsolutePath()); }else{ uploaderror = true; }
就是使用七牛的核心方法了,QQiNiuUtil.uploadFile是uikoo9实现的方法,也能够改为本身的七牛上传方法
UploadManager uploadManager = new UploadManager(); Response res = uploadManager.put("上传的文件路径", "上传文件保存的文件名", Auth.create(accessKey, secretKey).uploadToken("上传空间名"));
【修改配置文件】
最后将var URL = window.UEDITOR_HOME_URL || getUEBasePath();
改为var URL = "http://www.xxxx.com/YYYY/" || getUEBasePath();
将serverUrl: URL + "jsp/controller.jsp"
改为serverUrl: URL + "controller"
至此就能够上传到七牛了。
【跨域问题】
【先后端分离的跨域问题】
若是要作到ueditor上传图片先后端分离,这里还要解决一个跨域的问题。假设包含编辑器的前端域名为web.ueditor.com,后端上传服务器的域名为server.ueditor.com,他们同属一个ueditor.com主域名。
首先在controller中使用@CrossOrigin(origins = “http://web.ueditor.com”)的CORS注解(Spring4)解决跨域问题,但这依然会在打开文件上传窗口时出现跨域问题。
问题出在internal.js的dialog = parent.$EDITORUI[window.frameElement.id.replace( /_iframe$/, '' )];
由于在修改serverUrl后,加载ueditor时用的已是服务器的JS文件了,但parent仍是客户端的,有兴趣的人能够在这里把parent打出来看看。因此虽然已经解决了客户端到服务器的跨域问题,但依然遇到了iframe的跨域问题,解决方法以下:
打开服务器的dialogs/internal.js,在错误语句前增长域信息
document.domain="ueditor.com";//加一句 dialog = parent.$EDITORUI[window.frameElement.id.replace( /_iframe$/, '' )];
使上传服务器属于ueditor.com域
再在调用这个编辑器的页面加入
<script> document.domain = "ueditor.com"; </script>
使前端也属于ueditor.com域,就能够解决这个问题了
-----------------------------------------------------------------------------------------------------------------------------------------------------------------
更好的跨域解决方法是使用nginx或apache的反向代理,都在一个域下了还跨什么呢^_^