专业,创造力,激情,设计。京东用户体验设计部门,致力于创造更美好的电子商务购物体验。 |
由于现在身份比较敏感,所以很多情况下你在网购时都会涉及到需要实名认证,此时就需要上传身份证照片。为了让大家能够在任何情况下都可以进行实名认证,前端的上传工作必不可少,如何进行图片上传的功能呢?下面会一步一步的深入到上传的海洋中,让你畅游其中。
京东推出了整站京东户簿组件,来统一管理用户的实名认证信息。此组件支持不同业务线跨域接入并兼容到 IE7+ 的不同场景。 (黑科技) 上传的图片支持 OCR 图像识别,可以与输入的证件号码做匹配,防止随便录入信息。
户簿系统组件的界面如下图:
接下来我们来逐步深入的剖析一下此组件的是如何来实现的。上传主要使用了 Ajax 与 XMLHttpRequest(文章中简称为 XHR ) 技术来实现。Ajax 介绍以及背景请参考 Ajax 背景 。
(如不了解 XHR ,可参考 w3.org 的一些文章基础教程文章即可快速了解,可参考如下连接地址:https://www.w3.org/TR/XMLHttpRequest/.)
代码实现:
1 2 3 4 5 6 7 8 |
var param = new FormData (); param . append ( 'imgData' , file ); xhr = new XMLHttpRequest (); xhr . addEventListener ( "load" , _self . uploadComplete , false ); xhr . addEventListener ( "error" , _self . uploadFailed , false ); xhr . addEventListener ( "abort" , _self . uploadCanceled , false ); xhr . open ( "POST" , url ); xhr . send ( param ); |
以上代码中,文件需要通过
FormData
来承载并通过 xhr 发送给服务端,
FormData
会有哪些隐患呢?来看一下 XHR
的发展历程。
XHR 一开始只是微软浏览器提供的一个接口,后来各大浏览器纷纷效仿也提供了这个接口,再后来W3C对它进行了标准化,提出了 XHR 标准 。XHR 标准又分为Level 1 和 Level 2。
XHR Level 1 中,XHR 有以下三个缺点:
只支持文本数据的传送,无法用来读取和上传二进制文件。
传送和接收数据时,没有进度信息,只能提示有没有完成。
受到 “同域限制” (Same Origin Policy),只能向同一域名的服务器请求数据。
XHR Level 2 新版本针对老版本做出了大幅改进:
可以设置 http 请求的时限。
可以使用 FormData 对象管理表单数据。
可以上传文件。
可以请求不同域名下的数据(跨域请求)。
可以获取服务器端的二进制数据。
可以获得数据传输的进度信息。
FormData 参数为 XHR Level 2 中新增接口,所以会存在或多或少的兼容性隐患问题,兼容下如下图:
(数据来源: caniuse.com)
根据以上的兼容性,基本上 IE10 以下的浏览器就不用考虑了。
在 XHR 对象中有一个存在这么一个对象:upload。(来个传送门: https://xhr.spec.whatwg.org/#the-upload-attribute )
upload是一个XMLHttpRequestUpload,可以将代码优化成以下方法:
1 2 3 4 5 6 7 8 9 10 |
var param = new FormData (); param . append ( 'imgData' , file ); xhr = new XMLHttpRequest (); /*新增进度条监听函数*/ xhr . upload . addEventListener ( "progress" , _self . uploadProgress , false ); xhr . addEventListener ( "load" , _self . uploadComplete , false ); xhr . addEventListener ( "error" , _self . uploadFailed , false ); xhr . addEventListener ( "abort" , _self . uploadCanceled , false ); xhr . open ( "POST" , url ); xhr . send ( param ); |
针对以上需求,我们可以提供2种跨域解决方案: JSONP 与 CORS。
JSONP:实现原理参考: https://en.wikipedia.org/wiki/JSONP 。由于 JSONP 无法使用 post 请求,故 PASS。
CORS :全程“跨域资源共享”(Cross-origin Resource Sharing),也是一个 Level2 中新增加的功能。兼容性可参考 caniuse 中 Level2兼容性 . 以下提供一张截图
所以只能采用此种方式来支持跨域 post 请求的图片上传功能。
CORS 的 配置需要前端与后端共同来完成 ,服务器端需要配置可以访问接入的域。发送请求的时候也会在 http 的响应头中添加 Access-Control-Allow-Origin:*。
* 表明,该资源可以被任意外域访问。如果服务端仅允许来自 http://foo.example 的访问,该首部字段的内容如下:Access-Control-Allow-Origin: http://foo.example
身为前端开发人员,我相信大家此时都在诅咒微软为啥还不倒闭,微软已经严重的阻碍的大前端的发展脚步,但是抱怨归抱怨,用户群体还是很大的,只能想法来解决了。
根据以上的步骤其实 IE10+ 浏览器已经没问题了,但是因为使用了FormData,CORS 等一些 Level2 的新标准,所以无法再低版本浏览器更好的运行。
解决方案:使用 Flash 来支持低版本浏览器。
市面上比较好的插件有 webUploader ,可以根据浏览器类型来切换 flash 跟 XHR ,单因为此插件需要依赖于 jquery1.10+ 才可以,故无法使用。
最后使用了 SWFUploader 来支持,注:Flash 使用也是存在跨域问题的。
Flash文件的使用方式:
swf放在自己项目中,创建一个 XML,将文件放在与接口同域的服务器上,Crossdomain.xml 文件内容如下,(以下例子为允许所有网站访问)
将 SWF 文件放在与接口同域的服务器上,使用对应 JS 引用 SWF 即可。
以上两种方式均可, SWFUploader 配置调用方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 0 31 32 33 34 35 36 37 38 39 40 41 42 43 |
var setting = { debug : false , upload_url : url , //服务器接受文件路径 flash_url : 'flashurl/swfupload.swf?ver=' + Math . random (), //上传的swf文件路径 //文件配置 file_post_name : 'imgData' , //服务器接受的文件数据key值 post_params : { sessionId : opts . param . sessionId }, //上传文件时带的参数 file_types : '*.bmp;*.jpeg;*.jpg;*.gif;*.png' , //文件类型过滤配置 file_types_description : '浏览器图片格式' , //上传文件类型描述 file_size_limit : opts . fileSizeLimit , //指定要上传的文件的最大体积, //可以带单位,合法的单位有:B、KB、MB、GB,如果省略了单位,则默认为KB。该属性为0时,表示不限制文件的大小。 file_upload_limit : "200" , file_queue_limit : "1" , prevent_swf_caching : false , preserve_relative_urls : false , //按钮配置 button_placeholder_id : opts . id , button_width : ( opts . type == 'pop' ) ? 160 : 175 , button_height : ( opts . type == 'pop' ) ? 100 : 109 , button_text : " " , button_text_style : "" , button_text_left_padding : 0 , button_text_top_padding : 0 , button_action : SWFUpload . BUTTON_ACTION . SELECT_FILES , button_disabled : false , button_cursor : SWFUpload . CURSOR . HAND , button_window_mode : "transparent" , custom_settings : { index : index }, // 事件 file_dialog_start_handler : _self . fileDialogStart , file_queued_handler : _self . fileQueued , file_queue_error_handler : _self . fileQueueError , file_dialog_complete_handler : _self . fileDialogComplete , upload_progress_handler : _self . uploadProgress , upload_error_handler : _self . uploadError , upload_success_handler : _self . uploadSuccess , upload_complete_handler : _self . uploadComplete } var swfupload = new SWFUpload ( setting ) |
函数配置好就行了,具体使用方式可以看一下文档:
http://www.leeon.me/upload/other/swfupload.html
配置好以后,问题出现:发现在高版本浏览器是没问题的,但是在低版本浏览器是获取不到返回值的。
低版本IE;
高版本:
接受参数是不同的,所以会出现刚刚的问题,服务端需要按照这个类型来做兼容支持。这样既可实现 IE7+ 的图片上传功能。
看似简单而合理的需求,实则坑道无限。问题,无论低版本或者高版本都是无法上传成功,都会被登录拦截器拦截。
原因:
XHR 在 CORS 时,浏览器认为请求是不安全的,所以不会自动携带 cookie 信息。
Flash 的请求不是在页面发送的,所以也无法携带 cookie 信息。
解决方法:
高版本浏览器:
需要前端后端一直修改来支持此需求,前端改动:
需要给 XHR 的 withCredentials 设置为 true ,并且服务器端也需要打开开关配置才可以正常的传递 cookie。文档描述: https://xhr.spec.whatwg.org/#the-withcredentials-attribute
代码如下:
1 2 3 4 5 6 7 8 9 10 11 |
var param = new FormData (); param . append ( 'imgData' , file ); xhr = new XMLHttpRequest (); /*新增进度条监听函数*/ xhr . upload . addEventListener ( "progress" , _self . uploadProgress , false ); xhr . addEventListener ( "load" , _self . uploadComplete , false ); xhr . addEventListener ( "error" , _self . uploadFailed , false ); xhr . addEventListener ( "abort" , _self . uploadCanceled , false ); xhr . open ( "POST" , url ); xhr . withCredentials = true ; xhr . send ( param ); |
注意:当设置 withCredentials 时,Access-Control-Allow-Origin 不能设置为 * 。必须设置为具体的域名。
IE 浏览器
Flash 无法获取参数,但是可以通过 JS 来获取到 cookie ,并且将 cookie 作为 param 跟随请求一起发送。可以自己手动写,也可以使用插件,swfupload.cookies.js 。此插件是自动添加 cookie 的,只需要引入即可。
解决了以上问题之后,又出现了另外一个问题:cookie 成功传递过去了,依旧无法通过登录过滤器。
经过排查发现,在XHR上传图片的时候都会发送2次请求:如下图
来仔细对比一下有什么区别?
第一个请求:
第二次请求:
仔细看发现多了一次类型为 OPTIONS 的请求,而且重要的是不携带 cookie。造成此类请求的原因:当使用 CORS 方式跨域请求时,如果信息为“复杂请求”,那么浏览器会自动发送一条“预检请求”,以获知服务器是否允许该实际请求。“预检请求”的使用,可以避免跨域请求对服务器的用户数据产生未预期的影响,那么什么样的请求为复杂请求呢?
当请求满足下述任一条件时,则会提前发送预检请求
一、用了下面任一 http 方法:
PUT
DELETE
CONNECT
OPTIONS
TRACE
PATCH
二、人为设置了对 CORS 安全的首部字段集合之外的其他首部字段。该集合为:
Accept
Accept-Language
Content-Language
Content-Type(but note the additional requirements below)
DPR
Downlink
Save-Data
Viewport-Width
Width
三、Content-Type 的值不属于下列之一:
application/x-www-form-urlencoded
multipart/form-data
text/plain
以下为一个复杂请求的请求过程:
由于上传图片属于复杂请求,所以在请求前,浏览器会先发送一次 OPTIONS 请求,服务端需要处理 OPTIONS 类型的请求,以避免被过滤器拦截掉。
到此为止,一个支持高低版本浏览器,以及支持跨域请求的上传图片组件即将开发完成。
考虑到位了让不同业务线使用,我们需要暴露不同的接口已支持不同的需求:
位于整个dom中的位置,(组件插入的容器选择器)
样式自定义(组件最外层设定的ID)
类型可默认(组件中身份类型的默认值)
操作错误可自定义弹出样式(可以重写弹出错误的函数)
上传图片的服务器url(方便后续维护)
操作错误有回调(方便业务线对不同错误作出相应)
提交保存回调,可让用户独立控制提交(可以让业务线获取保存的结果值,并可灵活的操作提交时机)。
最后来看一下如何来调用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
seajs . use ([ 'componentURL' ], function ( auth ) { auth . page ({ componentKey : '' , // 组件key , transactionId : '1' , // 上传交易ID , container : '.renzheng' , title : '' , id : 'authP' , //可以通过此ID来重写样式 showMsgFn : function ( msg ) { alert ( msg ); }, onReady : function ( el ) {}, onSubmit : function ( data ) { } }); $( ".submit-btn" ). bind ( "click" , function () { auth . submit ( function ( data ) { console . log ( JSON . stringify ( data )); }); }); }) |
通过此组件开发,可以深刻理解跨域的一些常用的解决方案,CORS的用法以及遇到问题时的解决方案。最后希望此篇博客对于正在开发上传功能的同学有一定的帮助。
参考资料列表:
XMLHttpRequest: https://xhr.spec.whatwg.org/ 以及 https://segmentfault.com/a/1190000004322487
XMLHttpRequest Level1与Level2区别介绍: http://www.ruanyifeng.com/blog/2012/09/xmlhttprequest_level_2.html
CORS以及OPTIONS: https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS
JSONP文档: https://en.wikipedia.org/wiki/JSONP
|
拳皇98终极之战OL · 【拳民攻略】奥义精华在跨服争夺战商店性价比分析 8 年前 |
|
河南新闻广播 · 银监会两周连发7文 看看如何影响你的“钱袋子” 7 年前 |
|
健康圈 · 女人常喝三个汤,清火气,排毒解便秘,皮肤变白皙 7 年前 |
|
安在 · 《超能黑客团》重庆开机 安在黑客团队强势上线 7 年前 |
|
央视财经 · 【实用】手机为啥电量剩30%就提示要充电?原来是这样! 7 年前 |