首页 > 代码库 > 解决微信内嵌浏览器无法响应上传文件(图片)的思路(2种办法)
解决微信内嵌浏览器无法响应上传文件(图片)的思路(2种办法)
进园一年多来,第一次写博客,好激动。原因主要是自己平时都是有写笔记,不习惯写博客,这次想写博客的原因是,这个问题确实我做了很久,已经做了近两周才解决了这个问题,,而且两周时间里尝试过了很多种办法,然后由于网上又没有多少人分享这个,决定自己写一下。
先自我介绍下,我是惠州学院大二(准大三)的学生,在去年暑假时候加了一个工作室开始写代码,也是在那个时候加入博客园,最近因为工作室要做微信端网页的开发需要一个上传图片功能,然后编码的时候发现用平时的方法做的上传功能在Android的微信端上实现不了,百度一下发现很多人也出现过同样的问题,也有人指出说这是被腾讯给“阉割”掉了的,经过两个星期的尝试后,找到了两种解决办法。
我尝试过的办法
- 首先是<input type=”file”>,这是我们最容易,同时也是最先用的,应该也是我试过那么多的插件都是用这个,这个只有在部分手机的微信版本上会显示upload disable,大部分手机微信还是支持的,但是做完我们会发现我们的微信可以选择文件,但是上传不了,百度了一下很多人说这个被腾讯给“阉割”掉了,一开始我也是这么觉得,所以就放弃了选择这个,后来发现,腾讯“阉割”掉了的不是<input type=”file”>,而是你上传往服务器发请求的那一块,具体怎么解释我是新手,真心不造怎么说,后面会再说,希望知道这个原理的能和我说明一下。
- 放弃了<input type=”file”>,我们工作室的师兄就推荐我一款插件,叫Uploadify,这款插件在它的官网上面有两种版本,一种是flash(免费),一种是HTML5(收费),不过也有大牛把HTML5的版本模仿了出来,网上找得到。首先是flash吧,这个也是另外一篇博文里博主推荐大家的方法(原文地址:http://blog.csdn.net/zz880329/article/details/12652063),我就尝试了一下,发现使用flash实现上传的话,选择文件的“样式”变了,变成了flash的选择文件,大家自己试过的话应该就会发现,flash选择文件的方式你看到的只有文件名,你都不知道自己要选哪张图片好,而且好像还需要手机有flash支持,所以这个flash被我放弃了。然后是HTML5的版本,打开demo的代码,上面确实没有了<input type=”file”>,但是实际运行起来,在网页还是用的是了<input type=”file”>,实际放到微信上好像也是行不通,后来也就放弃了。
- 后面又看到一篇介绍各种HTML5的上传插件的博文,然后又是像无头苍蝇那样各种尝试,最终只保留下了其中一种我觉得是最可行的,但是好像兼容性各方面还有待优化,后面我的第二种可行办法我会再介绍它的,希望大家都说下各自的理解吧,还有好多方面我都不太懂。
第一种可行的js插件
- 尝试过很多插件之后,基本都以失败告终。不过好像每次写代码都很幸运,我舍友在微信上找到一个网站是有实现相关功能的,然后我就去找到那个网站研究他们的上传插件,发现使用的插件我之前没有尝试过,于是就找到了这个插件并且成功实现功能,插件叫ajaxfileupload.js
//这段代码用于解决一些JQuery版本缺少的一个函数jQuery.extend({ handleError: function (s, xhr, status, e) { // If a local callback was specified, fire it if (s.error) { s.error.call(s.context || s, xhr, status, e); } // Fire the global callback if (s.global) { (s.context ? jQuery(s.context) : jQuery.event).trigger("ajaxError", [xhr, s, e]); } },//ajaxfileupload.js源代码 createUploadIframe: function(id, uri) { //create frame var frameId = ‘jUploadFrame‘ + id; var iframeHtml = ‘<iframe id="‘ + frameId + ‘" name="‘ + frameId + ‘" style="position:absolute; top:-9999px; left:-9999px"‘; if(window.ActiveXObject) { if(typeof uri== ‘boolean‘){ iframeHtml += ‘ src="‘ + ‘javascript:false‘ + ‘"‘; } else if(typeof uri== ‘string‘){ iframeHtml += ‘ src="http://www.mamicode.com/‘ + uri + ‘"‘; } } iframeHtml += ‘ />‘; jQuery(iframeHtml).appendTo(document.body); return jQuery(‘#‘ + frameId).get(0); }, createUploadForm: function(id, fileElementId, data) { //create form var formId = ‘jUploadForm‘ + id; var fileId = ‘jUploadFile‘ + id; var form = jQuery(‘<form action="" method="POST" name="‘ + formId + ‘" id="‘ + formId + ‘" enctype="multipart/form-data"></form>‘); if(data) { for(var i in data) { jQuery(‘<input type="hidden" name="‘ + i + ‘" value="http://www.mamicode.com/‘ + data[i] + ‘" />‘).appendTo(form); } } var oldElement = jQuery(‘#‘ + fileElementId); var newElement = jQuery(oldElement).clone(); jQuery(oldElement).attr(‘id‘, fileId); jQuery(oldElement).before(newElement); jQuery(oldElement).appendTo(form); //set attributes jQuery(form).css(‘position‘, ‘absolute‘); jQuery(form).css(‘top‘, ‘-1200px‘); jQuery(form).css(‘left‘, ‘-1200px‘); jQuery(form).appendTo(‘body‘); return form; }, ajaxFileUpload: function(s) { // TODO introduce global settings, allowing the client to modify them for all requests, not only timeout s = jQuery.extend({}, jQuery.ajaxSettings, s); var id = new Date().getTime() var form = jQuery.createUploadForm(id, s.fileElementId, (typeof(s.data)==‘undefined‘?false:s.data)); var io = jQuery.createUploadIframe(id, s.secureuri); var frameId = ‘jUploadFrame‘ + id; var formId = ‘jUploadForm‘ + id; // Watch for a new set of requests if ( s.global && ! jQuery.active++ ) { jQuery.event.trigger( "ajaxStart" ); } var requestDone = false; // Create the request object var xml = {} if ( s.global ) jQuery.event.trigger("ajaxSend", [xml, s]); // Wait for a response to come back var uploadCallback = function(isTimeout) { var io = document.getElementById(frameId); try { if(io.contentWindow) { xml.responseText = io.contentWindow.document.body?io.contentWindow.document.body.innerHTML:null; xml.responseXML = io.contentWindow.document.XMLDocument?io.contentWindow.document.XMLDocument:io.contentWindow.document; }else if(io.contentDocument) { xml.responseText = io.contentDocument.document.body?io.contentDocument.document.body.innerHTML:null; xml.responseXML = io.contentDocument.document.XMLDocument?io.contentDocument.document.XMLDocument:io.contentDocument.document; } }catch(e) { jQuery.handleError(s, xml, null, e); } if ( xml || isTimeout == "timeout") { requestDone = true; var status; try { status = isTimeout != "timeout" ? "success" : "error"; // Make sure that the request was successful or notmodified if ( status != "error" ) { // process the data (runs the xml through httpData regardless of callback) var data =http://www.mamicode.com/ jQuery.uploadHttpData( xml, s.dataType ); // If a local callback was specified, fire it and pass it the data if ( s.success ) s.success( data, status ); // Fire the global callback if( s.global ) jQuery.event.trigger( "ajaxSuccess", [xml, s] ); } else jQuery.handleError(s, xml, status); } catch(e) { status = "error"; jQuery.handleError(s, xml, status, e); } // The request was completed if( s.global ) jQuery.event.trigger( "ajaxComplete", [xml, s] ); // Handle the global AJAX counter if ( s.global && ! --jQuery.active ) jQuery.event.trigger( "ajaxStop" ); // Process result if ( s.complete ) s.complete(xml, status); jQuery(io).unbind() setTimeout(function() { try { jQuery(io).remove(); jQuery(form).remove(); } catch(e) { jQuery.handleError(s, xml, null, e); } }, 100) xml = null } } // Timeout checker if ( s.timeout > 0 ) { setTimeout(function(){ // Check to see if the request is still happening if( !requestDone ) uploadCallback( "timeout" ); }, s.timeout); } try { var form = jQuery(‘#‘ + formId); jQuery(form).attr(‘action‘, s.url); jQuery(form).attr(‘method‘, ‘POST‘); jQuery(form).attr(‘target‘, frameId); if(form.encoding) { jQuery(form).attr(‘encoding‘, ‘multipart/form-data‘); } else { jQuery(form).attr(‘enctype‘, ‘multipart/form-data‘); } jQuery(form).submit(); } catch(e) { jQuery.handleError(s, xml, null, e); } jQuery(‘#‘ + frameId).load(uploadCallback ); return {abort: function () {}}; }, uploadHttpData: function( r, type ) { var data = http://www.mamicode.com/!type; data = type == "xml" || data ? r.responseXML : r.responseText; // If the type is "script", eval it in global context if ( type == "script" ) jQuery.globalEval( data ); // Get the JavaScript object, if JSON is used. if ( type == "json" ) eval("data = "http://www.mamicode.com/+ data); //eval("data = http://www.mamicode.com/" " + data + " \" ");//此处根据网络版本修改的,修改后发现没用用,用于解决json问题 // evaluate scripts within html if ( type == "html" ) jQuery("<div>").html(data).evalScripts(); return data; }})
- 前台代码很简单
<!DOCTYPE html><html><head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, minimum-scale=1, maximum-scale=1"> <script src="jquery-1.7.1.js" type="text/javascript"></script> <script src="ajaxfileupload.js" type="text/javascript"></script> <script type="text/javascript"> function ajaxFileUpload() { $.ajaxFileUpload ( { url: ‘upload.aspx‘, //用于文件上传的服务器端请求地址 secureuri: false, //一般设置为false fileElementId: ‘file‘, //文件上传空间的id属性 <input type="file" id="file" name="file" /> dataType: ‘json‘, //返回值类型 一般设置为json success: function (data, status) //服务器成功响应处理函数 { $("#img1").attr("src", data.imgurl); if (typeof (data.error) != ‘undefined‘) { if (data.error != ‘‘) { alert(data.error); } else { alert(data.msg); } } }, error: function (data, status, e)//服务器响应失败处理函数 { alert(e); } } ) return false; } </script></head><body> <input type="file" id="file" name="file" value="上传" accept="image/*;capture=camera" onchange="ajaxFileUpload()"/> <p><img id="img1" alt="上传成功啦" src="" /></p></body></html>
- 然后是C#的后台代码,这里我就没有放压缩图片大小的代码进来了,压缩图片的功能是根据教程做的,也是可以实现后台压缩图片的接下来是我对这个插件以及这个插件为什么能实现的一点看法:我主要是在我调试的过程中发现了问题,因为之前也是调试其他不同的插件,不过我的解释可能说不太清楚,还是希望有人能帮我解答~,原因应该是在ajaxfileupload.js这个文件里边。在用firebug调试上传功能的时候,可以很明显看到,控制台内并没用任何内容;而当我使用其他插件的时候,firebug里边都会显示我先一般处理程序发送的请求,而且能够看到返回的信息。我觉得这就是为何这个插件能够实现上传的原因。具体的原理我不知道怎么去解释这个原理,还请大牛帮忙,谢谢!
using System;using System.Collections.Generic;using System.Linq;using System.Web;using System.Web.UI;using System.Web.UI.WebControls;public partial class Upload_Upload : System.Web.UI.Page{ protected void Page_Load(object sender, EventArgs e) { HttpFileCollection files = Request.Files;//这里只能用<input type="file" />才能有效果,因为服务器控件是HttpInputFile类型 string filePath = "Upimages"; string msg = string.Empty; string error = string.Empty; string imgurl; if (files.Count > 0) { files[0].SaveAs(Server.MapPath(filePath+"/") + System.IO.Path.GetFileName(files[0].FileName)); msg = " 成功! 文件大小为:" + files[0].ContentLength; imgurl = "Upimages/" + files[0].FileName; string res = "{ error:‘" + error + "‘, msg:‘" + msg + "‘,imgurl:‘" + imgurl + "‘}"; Response.Write(res); Response.End(); } }}
通过这个实现了之后,腾讯“阉割”掉的应该不是<input type="file”>而是你上传的文件的文件流,<input type="file”>并不会失效(除了在部分低端手机和HTC)。而这个插件把这关键的发送文件流的部分“隐藏了起来”,具体隐藏的方法应该就是他js文件写的,所以这个插件成功了。
第二种可行的办法,仅限用于图片,其他类型文件我还没尝试
接下来是第二种可行的办法,也是在找控件的过程中发现的,感觉很棒,我在三星的微信上试过可以实现上传,但是在魅族上页面还是出错了,之后由于第一种方法可以兼容,于是就采用了第一种,没有对第二种进行深究了,不过还是分享一下。它的原理就是把你点击选择完图片后,用js将你选择的图片转为base64格式的数据,而且还能实现使用js对图片进行压缩后保存下压缩后的base64格式的字符串数据,而我们只需要把这串字符串发送到后台,然后在后台将base64格式的数据转为图片就容易啦,这样,腾讯也抓不了。
前台代码,我跟原作者代码有修改了一点,因为发现发送到后台base64转图片的时候,base64编码不能带上“文件头”(/^data:base64,/),还有就是加上了压缩图片所需要修改的参数的位置,这里默认压缩为100*100。(原作者好腻害的赶脚)。
<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0"><title>图片压缩</title> <script src="jquery-1.7.1.js" type="text/javascript"></script><style>body { margin:0; padding:0; }html { font-size:62.5%; }.imgzip { padding:1em; }.imgzip .itm { padding-bottom:1em; word-break:break-all; font-size:1.2rem; line-height:1.5em; }.imgzip .itm .tit { margin-bottom:.5em; background-color:#e71446; color:#FFF; padding:.5rem 1rem; border-radius:3px; }.imgzip .itm .cnt { padding:1rem; }.imgzip .itm .cnt img { display:block; max-width:100%; }.imgzip textarea { width:100%; height:20em; }</style></head><body><input type="file" accept="image/*;capture=camera" class="input"><input type="button" value="上传" onclick="upload();" /><div class="imgzip"></div><script> document.addEventListener(‘DOMContentLoaded‘, init, false); function init() { var u = new UploadPic(); u.init({ input: document.querySelector(‘.input‘), callback: function (base64) { var html = ‘‘; html = ‘<div class="itm"><div class="tit">图片名称:</div><div class="cnt" id="name">‘ + this.fileName + ‘</div></div>‘ + ‘<div class="itm"><div class="tit">原始大小:</div><div class="cnt">‘ + (this.fileSize / 1024).toFixed(2) + ‘KB<\/div><\/div>‘ + ‘<div class="itm"><div class="tit">编码大小:</div><div class="cnt">‘ + (base64.length / 1024).toFixed(2) + ‘KB<\/div><\/div>‘ + ‘<div class="itm"><div class="tit">原始尺寸:</div><div class="cnt">‘ + this.tw + ‘px * ‘ + this.th + ‘px<\/div><\/div>‘ + ‘<div class="itm"><div class="tit">编码尺寸:</div><div class="cnt">‘ + this.sw + ‘px * ‘ + this.sh + ‘px<\/div><\/div>‘ + ‘<div class="itm"><div class="tit">图片预览:</div><div class="cnt"><img src="http://www.mamicode.com/‘ + base64 + ‘"><\/div><\/div>‘ + ‘<div class="itm"><div class="tit">Base64编码:</div><div class="cnt"><textarea id="base64">‘ + this.noHead + ‘<\/textarea><\/div><\/div>‘; document.querySelector(‘.imgzip‘).innerHTML = html; }, loading: function () { document.querySelector(‘.imgzip‘).innerHTML = ‘读取中,请稍候...‘; } }); } function UploadPic() { this.sw = 0; this.sh = 0; this.tw = 0; this.th = 0; this.scale = 0; this.maxWidth = 0; this.maxHeight = 0; this.maxSize = 0; this.fileSize = 0; this.fileDate = null; this.fileType = ‘‘; this.fileName = ‘‘; this.input = null; this.canvas = null; this.mime = {}; this.type = ‘‘; this.callback = function () { }; this.loading = function () { }; this.noHead = ""; } UploadPic.prototype.init = function (options) { this.maxWidth = options.maxWidth || 800; this.maxHeight = options.maxHeight || 600; this.maxSize = options.maxSize || 3 * 1024 * 1024; this.input = options.input; this.mime = { ‘png‘: ‘image/png‘, ‘jpg‘: ‘image/jpeg‘, ‘jpeg‘: ‘image/jpeg‘, ‘bmp‘: ‘image/bmp‘ }; this.callback = options.callback || function () { }; this.loading = options.loading || function () { }; this._addEvent(); }; /** * @description 绑定事件 * @param {Object} elm 元素 * @param {Function} fn 绑定函数 */ UploadPic.prototype._addEvent = function () { var _this = this; function tmpSelectFile(ev) { _this._handelSelectFile(ev); } this.input.addEventListener(‘change‘, tmpSelectFile, false); }; /** * @description 绑定事件 * @param {Object} elm 元素 * @param {Function} fn 绑定函数 */ UploadPic.prototype._handelSelectFile = function (ev) { var file = ev.target.files[0]; this.type = file.type // 如果没有文件类型,则通过后缀名判断(解决微信及360浏览器无法获取图片类型问题) if (!this.type) { this.type = this.mime[file.name.match(/\.([^\.]+)$/i)[1]]; } if (!/image.(png|jpg|jpeg|bmp)/.test(this.type)) { alert(‘选择的文件类型不是图片‘); return; } if (file.size > this.maxSize) { alert(‘选择文件大于‘ + this.maxSize / 1024 / 1024 + ‘M,请重新选择‘); return; } this.fileName = file.name; this.fileSize = file.size; this.fileType = this.type; this.fileDate = file.lastModifiedDate; this._readImage(file); }; /** * @description 读取图片文件 * @param {Object} image 图片文件 */ UploadPic.prototype._readImage = function (file) { var _this = this; function tmpCreateImage(uri) { _this._createImage(uri); } this.loading(); this._getURI(file, tmpCreateImage); }; /** * @description 通过文件获得URI * @param {Object} file 文件 * @param {Function} callback 回调函数,返回文件对应URI * return {Bool} 返回false */ UploadPic.prototype._getURI = function (file, callback) { var reader = new FileReader(); var _this = this; // function tmpLoad() { // 头不带图片格式,需填写格式 var re = /^data:base64,/; var ret = this.result + ‘‘; if (re.test(ret)) ret = ret.replace(re, ‘data:‘ + _this.mime[_this.fileType] + ‘;base64,‘); //此处为自己加上的去掉base64不带“头”的判断 if (ret.indexOf(";base64,") >= 0) { var num = ret.indexOf(";base64,"); num = parseInt(num) + 8; _this.noHead = ret.substring(num); } callback && callback(ret); } reader.onload = tmpLoad; reader.readAsDataURL(file); return false; }; /** * @description 创建图片 * @param {Object} image 图片文件 */ UploadPic.prototype._createImage = function (uri) { var img = new Image(); var _this = this; function tmpLoad() { _this._drawImage(this); } img.onload = tmpLoad; img.src = uri; }; /** * @description 创建Canvas将图片画至其中,并获得压缩后的文件 * @param {Object} img 图片文件 * @param {Number} width 图片最大宽度 * @param {Number} height 图片最大高度 * @param {Function} callback 回调函数,参数为图片base64编码 * return {Object} 返回压缩后的图片 */ UploadPic.prototype._drawImage = function (img, callback) { // this.sw = img.width; // this.sh = img.height; //如果不需要压缩可将上面注释与下面的更换; this.tw = img.width; this.th = img.height; this.sw = 100; this.sh = 100; this.scale = (this.tw / this.th).toFixed(2); if (this.sw > this.maxWidth) { this.sw = this.maxWidth; this.sh = Math.round(this.sw / this.scale); } if (this.sh > this.maxHeight) { this.sh = this.maxHeight; this.sw = Math.round(this.sh * this.scale); } this.canvas = document.createElement(‘canvas‘); var ctx = this.canvas.getContext(‘2d‘); this.canvas.width = this.sw; this.canvas.height = this.sh; ctx.drawImage(img, 0, 0, img.width, img.height, 0, 0, this.sw, this.sh); this.callback(this.canvas.toDataURL(this.type)); ctx.clearRect(0, 0, this.tw, this.th); this.canvas.width = 0; this.canvas.height = 0; this.canvas = null; }; function upload() { var base64 = $("#base64").val(); var name = $("#name").text(); $.ajax( { type: "post", url: "Upload.ashx", data: { base64: base64, name: name }, async: true, success: function fun(rt) { alert(rt); } }); };</script></body></html>
- 然后是后台C#代码,就是简单的转格式,保存文件那些。
<%@ WebHandler Language="C#" Class="Upload" %>using System;using System.Web;using System.Drawing;using System.Drawing.Imaging;using System.IO;public class Upload : IHttpHandler { public void ProcessRequest (HttpContext context) { context.Response.ContentType = "text/plain"; string base64 = context.Request["base64"]; string name = context.Request["name"]; byte[] arr = Convert.FromBase64String(base64); MemoryStream ms = new MemoryStream(arr); Bitmap bmp = new Bitmap(ms); //要保存的目录路径 string filePath = "Upimages"; filePath = context.Server.MapPath(filePath + "/" + name); //bmp.Save("Upimages/"+name + ".jpg", System.Drawing.Imaging.ImageFormat.Jpeg); bmp.Save(filePath); ms.Close(); context.Response.Write("true"); } public bool IsReusable { get { return false; } }}
好啦,第一次写博客就是写到这里,不知道还有什么写差了的,希望各位提下建议,不喜可喷哈~
2014.08.22
解决微信内嵌浏览器无法响应上传文件(图片)的思路(2种办法)