之前,就听过“跨域上传”图片的问题,只是疏于研究,也就一再搁置,直至今天再次遇见这个不能避免的“坑”,才不得不思考一下,怎么“跨域上传”图片或者文件?
问题来源:
何为“跨域”? ——就是给你一个接口,外面暴露的url(并非是自己项目中的url),然后你发post()请求,请求给你的接口,请求成功,接口就会返回给你想要的结果。
实际情况:
我们公司自己做的项目一般都是使用nodejs的thinkjs框架(ThinkJS 是一款使用 ES6/7 特性全新开发的 Node.js MVC 框架,使用 ES7 中 async/await
,或者 ES6 中的 */yield
特性彻底解决了 Node.js 中异步嵌套的问题。)之前我们的上传图片都是显示在自己项目本地,而这次的需求却加上了请求另一个人的接口地址,然后正常上传图片。
页面HTML主要代码:
... <label style="fmargin-top: 10px; 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.这次需求改动总结的小经验:
点击“添加图片”的时候,自动往后面添加一个相同的上传图片的点击框,即:
自己写的代码总是冗杂繁余,而其他人写的代码一看却是那么的简洁明了,不得不怀疑自己的能力。而自己想长期快乐的继续自己的这份工作时,就应该好好的沉淀自己,把自己培养成像同事一样的大神。