最近在作一個先後端徹底分離模式下,ueditor編輯器多圖上傳的優化,遇到一些似是而非的坑。html
以前採用的跨域方案是用postMessage,大體先將圖片上傳事件發回接口所在的同域工程,再將獲得的上傳結果返回給靜態域名下的編輯器,實現編輯器代碼與後端工程徹底分離。前端
此次場景有點不一樣,因爲主體入口文件被後端工程直接引用,但ueditor的不少彈窗頁面都是引用於靜態域名下的頁面(包括多圖片上傳模塊),這形成了單圖上傳OK,多圖上傳模塊跨域的狀況。此次的思路是想經過CORS直接跨域訪問接口,下面把主要問題記錄以下:html5
因爲通常公司的主域都是相同的,能夠經過設置domain爲相同的主域來實現(兩邊都要設置)java
// 彈窗跨域設置 document.domain = window.location.hostname.match(/(\w+.com)$/)[0]; // 正則獲取到主域名
在作demo測試時,用form表單實現跨域圖片提交,無心中發現了一個後端安全攔截策略,代碼以下:jquery
// form所在的iframe的域名是:http://www.xx.com:8000 <form action="http://www.yy.com:9000/upload" enctype="multipart/form-data" method="post"> <!-- 若是接口支持多圖,加上multiple="multiple" --> <input type="file" name="imageFile"> <!-- 其它參數 --> <input type="hidden" name="uploadType" value="p"> </form>
當執行提交時,控制檯若顯示'X-Frame-Options' to 'deny'之類的提示時,後研究發現iframe頁面的請求被後臺框架的iframe安全策略攔截,而緣由是因爲當前請求用iframe下form提交的,然後臺框架正好作了DENY或默認(便是拒絕)的安全策略。web
解決方案:
一、將form表單提交換成下文(3)中的ajax/formData的方法
二、修改後檯安全配置,如java,須要修改spring安全配置文件:src/main/resources/spring-security.xmlajax
policy有三個值:DENY(默認值), SAMEORIGIN(同域), ALLOW-FROM(容許指定值,chrome貌似支持性很差)spring
<frame-options policy="SAMEORIGIN" /> // 或下面這樣設置,但實際測試感受二者效果差很少 <frame-options policy="SAMEORIGIN" strategy="whitelist" value="http://www.xx.com:8000" />
當設置ALLOW-FROM時,因爲chrome支持性很差,控制檯會出現'ALLOW-FROM DENY' is not a recognized directive. The header will be ignored(沒法識別指令,頭信息被忽略),效果同上面的SAMEORIGIN。chrome
注意的是:若是是ajax形式的提交,則不受上面任何策略的影響(包括DENY)。
response.setHeader("Access-Control-Allow-Origin", "http://www.xx.com:8000"); response.setHeader("Access-Control-Allow-Credentials", "true");
通常狀況下,設置上面的頭後,cors跨域基本上能成功了。json
須要注意的是,當設置了Credentials,上面的Origin值不能再爲*了,須要指定一個具體的值。
在跨域時,若出現options類型的請求,大概情形分如下兩種:
1.) 非簡單跨域請求
http簡單請求包括GET、POST、HEAD三種,像PUT、DELETE、OPTIONS就屬於非簡單請求,另外要注意的是,當POST請求時,Content-Type必須是text/plain、application/x-www-form-urlencoded、 multipart/form-data中的一個值,若爲application/json時,則不屬於簡單請求,會出現options預檢。
2.) 自定義HTTP頭部
當前端在跨域時經過xhr.setRequestHeader自定義了一個頭,在發起正式的請求前,會產生一個OPTIONS預檢請求,該請求中會有兩個字段:Access-Control-Request-Method和Access-Control-Request-Headers,分別描述了使用的方法和自定義了哪些頭信息。
舉個粟子,以下(前端使用了delete,自定義頭Test和非簡單請求application/json):
$.ajax({ ... type: 'DELETE', // 此處大小寫均可以,但後臺的Access-Control-Allow-Methods裏必定要大寫 beforeSend: function (xhr) { xhr.setRequestHeader('Test', 'myValue'); // 添加了自定義頭 }, contentType: 'application/json', // contentType內容非簡單請求
發起options預檢請求後,能夠從Request Headers中能夠看出使用的方法和自定義了哪些頭:
Access-Control-Request-Headers: content-type,test Access-Control-Request-Method: DELETE
則後端java添加相應的容許:
response.AddHeader("Access-Control-Allow-Origin": "*"); response.AddHeader("Access-Control-Allow-Methods", "DELETE"); // 此處值必定要大寫,OPTIONS自己值不用加 response.AddHeader("Access-Control-Allow-Headers", "Content-Type, Test"); // 此處值大小寫無所謂
後端有沒有設置成功或有沒有進入方法裏能夠從options預檢請求的Response Headers裏看出。
Access-Control-Allow-Headers:content-type, test Access-Control-Allow-Methods:DELETE
// 經過FormData將文件轉成二進制數據 var formData = new FormData(); formData.append('imageFile', file); $.ajax({ // UE.Editor.prototype.apiDomain把java的接口域名掛在UE上讀取了 url: UE.Editor.prototype.apiDomain + '/upload', type: 'POST', data: formData, // 上傳formdata封裝的數據 dataType: 'JSON', cache: false, // 不緩存 processData: false, // jQuery不要去處理髮送的數據 xhrFields: { withCredentials: true // 前端設置是否帶cookie }, contentType: false, // jQuery不要去設置Content-Type請求頭 success: function(res) { console.log(res); }, error(res) { console.log(res); } });
注意:processData,contentType都需設置false,否則jquery會作處理;另外若接口不支持多文件上傳,可定時循環發送圖片。
實現圖片預覽有兩種方法:
方法一:
FileReader,使用的base64方式實現
<input type="file" name="imageFile"> <script> document.querySelector('input[type=file]').onchange = function(e) { var file = e.target.files[0]; var fileReader = new FileReader(); fileReader.onload = function(e) { var image = new Image(); image.src = e.target.result; document.body.append(image); } fileReader.readAsDataURL(file); } </script>
方法二:
經過html5的window.URL.createObjectURL方法,使用的是blob的方式實現。
使用方法:window.URL.createObjectURL(blob || file);
兼容寫法:window[window.webkitURL ? 'webkitURL' : 'URL'].createObjectURL.(blob || file);
<input type="file" name="uploadFile"> <script> document.querySelector('input[type=file]').onchange = function(e) { var file = e.target.files[0]; var url = window.URL.createObjectURL(file); var image = new Image(); image.src = url; document.body.append(image) } </script>
當圖片臨時url被替換成正式url時,記得銷燬臨時url,方法:
window.URL.revokeObjectURL(url);