之前,就聽過“跨域上傳”圖片的問題,只是疏於研究,也就一再擱置,直至今天再次遇見這個不能避免的“坑”,才不得不思考一下,怎么“跨域上傳”圖片或者文件?
問題來源:
何為“跨域”? ——就是給你一個接口,外面暴露的url(並非是自己項目中的url),然后你發post()請求,請求給你的接口,請求成功,接口就會返回給你想要的結果。
實際情況:
我們公司自己做的項目一般都是使用nodejs的thinkjs框架(ThinkJS 是一款使用 ES6/7 特性全新開發的 Node.js MVC 框架,使用 ES7 中 async/await
,或者 ES6 中的 */yield
特性徹底解決了 Node.js 中異步嵌套的問題。)之前我們的上傳圖片都是顯示在自己項目本地,而這次的需求卻加上了請求另一個人的接口地址,然后正常上傳圖片。
頁面HTML主要代碼:
... <label style="fmargin-top: 10px;width: 100px" ></label> <div id="addImg"> <span class="addImglist"></span> <img id="addPic" style="float: left;margin-top: 20px;margin-left: 20px" width="150px" height="100" src="/static/admin/img/addimg.jpg" onclick="addImg()"> </div> <input type="file" name="uploadFile" id="fileupload_input" style="display: none"/> <div class="temp" style="float: left;margin-top: 20px;margin-left: 20px"> <img src="" class="showImg" ondblclick="canceImg(this)" width="150px" height="100"/> <input type="hidden" class="imgs" name="imgs"/> </div> ... <script> function addImg() { uploadimg('dynamic'); } function uploadimg(type) {//這里的圖片上傳分為兩種形式:動態以及用戶頭像 var url=''; if(type=='dynamic'){ url="/tools/uploadutils/uploadtonet?type=dynamic&t=" + new Date().getTime();//文件上傳地址 請求接口地址 }else{ url="/article/article/upload?type=portrait&t=" + new Date().getTime();//文件上傳地址 } jQuery('#fileupload_input').click().fileupload({ dataType: 'json', url: url, done: function (e, result) { if (result.result.errno==0) { var data=result.result.data; if(type=='dynamic'){ jQuery(".temp:first").clone().appendTo('#addImg .addImglist'); if(jQuery(".temp").length>=10){ jQuery("#addPic").hide(); } jQuery('#addImg .showImg:last').attr("src",data.path); jQuery('#addImg .imgs:last').val(data.savePath); }else{ jQuery('#portrait').attr("src",imgsite+"/static"+data.path); jQuery('#img').val(data.savePath) } } else { jQuery.messager.alert('提示', "上傳失敗"); } } }); } function canceImg(me) { jQuery(me).parent().remove(); if(jQuery(".temp").length<10){ //只能上傳九張圖 jQuery("#addPic").show(); } } </script>
后台項目中的js代碼:
uploadutils.js(文件路徑:/tools/uploadutils/的uploadtonet方法):
/** * Created by *** on 2016/11/10 */ 'use strict'; import Base from '../base.js'; import imgutil from '../../../common/util/imgutil'; import fs from 'fs'; import request from 'request'; export default class extends Base { /** * 上傳圖片給前台接口(c#程序) * @returns {Promise|*|void|PreventPromise} */ async uploadtonetAction() { let type = this.get("type"); if (!think.isEmpty(this.file('uploadFile'))) { let savePath = "";//保存在數據庫的路徑 let file = think.extend({}, this.file('uploadFile')); let fPath = file.path; let suffix = fPath.substr(fPath.lastIndexOf(".") + 1); if (suffix == "jpg" || suffix == "png" || suffix == "jpeg") { let apiBaseUrl = this.config("apiUrl"); let reqUrl = apiBaseUrl + "/upload.ashx"; //c#接口請求地址 let fileObj = imgutil.getCSharpImageUrl(this.param("type"), suffix); let path = fileObj.path + fileObj.fileName; let dbUri = "/" + path; //數據庫保存的路徑 let req = think.promisify(request.post); let options = { url: reqUrl, method: "post", formData: { file: fs.createReadStream(fPath), path: path } }; let res = await req(options); let result = JSON.parse(res.body); let imgUrl = this.config("apiImgsite") + dbUri; //回顯的路徑 if (result.status == 1) {//上傳成功 savePath = result.data.join(','); return this.success({ path: imgUrl, savePath: savePath }); } else { return this.fail(); } } else { return this.fail("上傳圖片格式有誤,請重新上傳!"); } } } //跨域請求的方法 call = async function (url, fPath, path) { let req = think.promisify(request.post); let reqObj = { url: url, method: "post", formData: { file: fs.createReadStream(fPath), path: path } }; return req(reqObj); }; }
主要的問題出在哪里呢???其實主要知識點就是在下:
這段代碼是老大給的,為此還被罵了一頓(這段代碼很難理解嗎?其實也不然,有時候就是覺得自己的腦子在代碼運行方面實在不怎么靈光!明明自己知道的東西,因為粗心或者不自信總是犯錯,導致一些不可挽回的“形象破壞”):
XMLHttpRequest Level 2添加了一個新的接口FormData
.利用FormData對象
,我們可以通過JavaScript用一些鍵值對來模擬一系列表單控件,我們還可以使用XMLHttpRequest的send()
方法來異步的提交這個"表單".比起普通的ajax,使用FormData
的最大優點就是我們可以異步上傳一個二進制文件。(https://developer.mozilla.org/zh-CN/docs/Web/API/FormData)
在網上找到了一個C#實現http協議GET、POST請求 ,覺得挺好的 http://blog.chinaunix.net/uid-7552018-id-173395.html
const options = { method: 'POST', uri: testData.url + `uploadprofilephoto`, formData: { image: fs.createReadStream('/home/rje/photo.jpg') } }; const json: IResponse<string> = await request(options);
uri:就是要請求的圖片上傳地址;
formData:模擬表單提交,接口需要兩個參數,一個文件路徑,一個文件名,以鍵值對的形式傳給它,最終便會返回給你想要的東西了。
只是在此項目中,使用await request(option)得不到接口返回的結果,於是只能使用thinkjs自帶的 think.promisify() —— think.promisify將一個異步函數自動改造,返回一個promise對象以供調用。
1.npm中request-promise模塊(https://www.npmjs.com/package/request-promise),有具體的用法;
2.這次需求改動總結的小經驗:
點擊“添加圖片”的時候,自動往后面添加一個相同的上傳圖片的點擊框,即:
自己寫的代碼總是冗雜繁余,而其他人寫的代碼一看卻是那么的簡潔明了,不得不懷疑自己的能力。而自己想長期快樂的繼續自己的這份工作時,就應該好好的沉淀自己,把自己培養成像同事一樣的大神。