前端JS中使用XMLHttpRequest 2上傳圖片到服務器,PC端和大部分手機上都正常,但在少部分安卓手機上上傳失敗,服務器上查看圖片,顯示字節數爲0。下面是上傳圖片的核心代碼:html
HTML前端
<input type="file" id="choose" capture="camera" accept="image/*">
JavaScripthtml5
var filechooser = document.getElementById("choose"); filechooser.onchange = function () { var _this = $(this); if (!this.files.length) return; var files = Array.prototype.slice.call(this.files); if (files.length > 1) { alert("一次只能上傳1張圖片"); return; } files.forEach(function (file, i) { if (!/\/(?:jpeg|png|gif)/i.test(file.type)) return; var reader = new FileReader(); reader.onload = function () { var result = this.result; upload(result, file.type); }; reader.readAsDataURL(file); }); }; function upload(basestr, type){ var xhr = new XMLHttpRequest(); var text = window.atob(basestr.split(",")[1]); var buffer = new Uint8Array(text.length); var pecent = 0; for (var i = 0; i < text.length; i++) { buffer[i] = text.charCodeAt(i); } var blob = getBlob(buffer, type); var formdata = new FormData(); formdata.append('imagefile', blob); xhr.open('post', '/uploadtest'); xhr.onreadystatechange = function () { if (xhr.readyState == 4 && xhr.status == 200) { var jsonData = JSON.parse(xhr.responseText); console.log(jsonData); } }; //利用progress事件顯示數據發送進度 xhr.upload.addEventListener('progress', function (e) { pecent = ~~(100 * e.loaded / e.total) / 2; // 利用pecent來顯示上傳進度 }, false); xhr.send(formdata); } function getBlob(buffer, format){ var Builder = window.WebKitBlobBuilder || window.MozBlobBuilder; if(Builder){ var builder = new Builder(); builder.append(buffer); return builder.getBlob(format); } else { return new window.Blob([ buffer ], {type: format}); } }
上述代碼使用FormData來實現表單數據提交。FormData是一種針對XHR2設計的新型數據類型,使用它咱們能夠很方便地實時以JavaScript建立HTML <Form>,而後經過AJAX提交該表單。在上述代碼中,提交的表單中的字段名爲imagefile,值是blob,這是一個經過getBlob函數構造並返回的文件Blob。經過該方法上傳文件簡單直觀。node
而後咱們在服務端接收並保存圖片,並返回已上傳的圖片的信息。下面是Node.js代碼的示例:android
var Q = require('q'); var fs = require('fs'); var path = require('path'); var formidable = require('formidable'); var moment = require('moment');var imageUpload = function (){ }; imageUpload.prototype.useFormParseCallback = function(req){ var deferred = Q.defer(); var form = new formidable.IncomingForm(); form.parse(req, deferred.makeNodeResolver()); return deferred.promise; }; imageUpload.prototype.uploadImageTest = function(req){ var pathName = 'uploadImgs/dealInfo/'; var uploadPath = path.join(__dirname, '../../public/', pathName); return this.useFormParseCallback(req).then(function(files){ var file = files[1].imagefile; var fileType = files[1].imagefile.type.split('/')[1]; var newFileName = 'upload_' + moment().format('x') + Math.random().toString().substr(2, 10) + '.' + fileType; var readStream = fs.createReadStream(file.path); var writeStream = fs.createWriteStream(uploadPath + newFileName); var deferred = Q.defer(); readStream.pipe(writeStream); readStream.on('end', deferred.makeNodeResolver()); return deferred.promise.then(function() { fs.unlinkSync(file.path); return { fileName: newFileName, filePath: '/' + pathName + newFileName, fileSize: file.size/1024 > 1024 ? (~~(10*file.size/1024/1024))/10 + "MB" : ~~(file.size/1024) + "KB" }; }); }); }; module.exports = imageUpload;
咱們使用formidable這個包來接收上傳文件的數據,而後將文件保存到/public/uploadImgs/dealInfo目錄下(假定已在express中將public設置爲static的根目錄),並將圖片按照指定的規則重命名,以保證上傳圖片不會由於名稱相同而被覆蓋。另外,代碼中使用Q來避免直接使用回調函數,以更好地對函數功能進行分離。git
上面的代碼在PC端瀏覽器以及大部分主流移動設備上都能正常工做,可是少部分Android設備上卻會出現上傳的圖片字節數爲0的狀況。具體的緣由你們能夠看下面幾個網頁中的描述:github
http://www.oschina.net/question/2502182_2139420?fromerr=qwYwJQK8web
https://github.com/fex-team/webuploader/issues/185express
https://code.google.com/p/android/issues/detail?id=39882json
就是說這個是Android的一個bug!
那如何解決呢?
其實從上面給出的頁面中能夠找到答案,就是咱們得換一種文件上傳方式。在XHR2中,除了以Blob的方式上傳文件外,還能夠ArrayBuffer的方式上傳文件。下面是修改以後的前端JavaScript代碼:
var filechooser = document.getElementById("choose"); filechooser.onchange = function () { var _this = $(this); if (!this.files.length) return; var files = Array.prototype.slice.call(this.files); if (files.length > 1) { alert("一次只能上傳1張圖片"); return; } files.forEach(function (file, i) { if (!/\/(?:jpeg|png|gif)/i.test(file.type)) return; var reader = new FileReader(); reader.onload = function () { var result = this.result; upload(result, file.type); }; reader.readAsDataURL(file); }); }; function upload(basestr, type){ var xhr = new XMLHttpRequest(); var text = window.atob(basestr.split(",")[1]); var buffer = new Uint8Array(text.length); var pecent = 0; for (var i = 0; i < text.length; i++) { buffer[i] = text.charCodeAt(i); } xhr.open('post', '/uploadtest?filetype=' + type.split('/')[1]); xhr.setRequestHeader('Content-Type', 'application/octet-stream'); xhr.onreadystatechange = function () { if (xhr.readyState == 4 && xhr.status == 200) { var jsonData = JSON.parse(xhr.responseText); console.log(jsonData); } }; //利用progress事件顯示數據發送進度 xhr.upload.addEventListener('progress', function (e) { pecent = ~~(100 * e.loaded / e.total) / 2; // 利用pecent來顯示上傳進度 }, false); xhr.send(buffer.buffer); // 以ArrayBuffer的方式上傳圖片 }
我將有變化的地方加了高亮顯示。以ArrayBuffer方式上傳圖片必須添加'application/octet-stream'的RequestHeader,不然服務器沒法響應請求。另外,經過這種方式上傳圖片咱們也沒法從表單數據中獲取到文件類型,能夠將文件類型以query的方式傳到服務器,而後服務器根據文件類型來生成對應的文件,如下是通過少許修改以後的服務器代碼:
imageUpload.prototype.uploadImageTest = function(req){ var pathName = 'uploadImgs/dealInfo/'; var uploadPath = path.join(__dirname, '../../public/', pathName); return this.useFormParseCallback(req).then(function(files){ var file = files[1].file; var fileType = req.query.filetype ? ('.' + req.query.filetype) : '.png'; var newFileName = 'upload_' + moment().format('x') + Math.random().toString().substr(2, 10) + '.' + fileType; var readStream = fs.createReadStream(file.path); var writeStream = fs.createWriteStream(uploadPath + newFileName); var deferred = Q.defer(); readStream.pipe(writeStream); readStream.on('end', deferred.makeNodeResolver()); return deferred.promise.then(function() { fs.unlinkSync(file.path); return { fileName: newFileName, filePath: '/' + pathName + newFileName, fileSize: file.size/1024 > 1024 ? (~~(10*file.size/1024/1024))/10 + "MB" : ~~(file.size/1024) + "KB" }; }); }); };
修改以後的代碼能夠支持Android手機,包括微信瀏覽器。注意不是全部的Android手機都會存在該問題,若是你發如今Andriod手機上沒法上傳圖片,尤爲是在微信瀏覽器中,則能夠嘗試下上面的方法。
參考頁面: