【前言】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的反向代理,都在一個域下了還跨什麼呢^_^