在編寫實現“天雲圖牀”這個程序的時候,想要實現批量上傳圖片的操作,便於自己和廣大站長的使用,其中遇到了一些簡單但又不常見的問題,網上資料良莠不齊,遂總結一遍
一開始,天雲圖牀只有單圖上傳的功能,開源到技術QQ羣中,大家都建議小東寫一個多圖上傳的功能,於是趁着週六有時間,開始編寫代碼。
本以爲是一個非常簡單的工作,哪知道弄了一整天的時間。
0x01一開始的思路
一開始,後臺API服務僅支持單圖上傳,想着,不動服務器的代碼,嘗試從客戶端來實現。
開發前的環境:
新浪圖牀服務端僅支持單圖(form表單形式)上傳圖片即可
前端(客戶端 https://api.top15.cn/picbed/)使用 ajax,實例 form表單 內容,發送給服務端,服務端接受數據,讀取$_FILES中的文件
服務端(PHP),讀取post過來的文件,然後模擬POST遠程上傳新浪服務器,新浪圖牀的接受參數爲`base64`編碼過的圖片資源。
0x02 客戶端的問題
在這之前,參考了一些開源項目,比如 ssi-uploader
、zuypload
這些基於 jquery
的三方插件,他們的共同功能是,能夠讀物本地文件,並將本地圖片展示在前端頁面上。
在站長之家的網站上找到如上的多圖上傳案例,支持拖拽和多圖上傳,想着自己是否可以嘗試寫一下吶?
首先客戶端中 HTML
代碼需要注意 input
的 name
屬性值要爲數組,否則上傳了數據,在服務端值會接受一個文件數據
如下是我在實現過程中的方式,並設置了隱藏,因爲本身 input
文件組件不夠美觀,用另外的按鈕來控制此組件。
<form id="form" enctype="multipart/form-data">
<input type="file" name="files[]" multiple="multiple" id="files" accept="image/png,image/gif,image/jpg,image/jpeg" style="display: none;">
</form>
與此同時,想要實現讀取 files
中的圖片並顯示在界面上,那麼就需要監聽 input[type=file]
組件的變換
<script src="https://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
<script type="javascript/text">
$(function(){
$("#files").change(function(){
console.log($("#files")[0].files) //得到一個FileList對象
})
})
</script>
上述代碼在控制檯打印上傳文件的信息,可以看到如下情況:
在files
中,包含了文件的大小,名字,時間和相對路徑
0x03 如何展示預覽圖片?
在 HTML5
中有一個 FileReader()
的東西,能夠讀取文件內容,
// javascript
function loadImg(){
//獲取文件
var file = $("#files")[0].files[0];
//創建讀取文件的對象
var reader = new FileReader();
//創建文件讀取相關的變量
var imgFile;
//爲文件讀取成功設置事件
reader.onload=function(e) {
imgFileUrl = e.target.result;
$("#gallery").append('<img src="'+imgFileUrl+'" width="200px" height="200px">');
};
reader.readAsDataURL(file);
}
//////////////////////////////////////////////////////////////
FileReader對象的方法
方法名 參數 描述
readAsBinaryString file 將文件讀取爲二進制編碼
readAsText file,[encoding] 將文件讀取爲文本
readAsDataURL file 將文件讀取爲DataURL
abort (none) 終端讀取操作
在控制檯中執行上述代碼,然後運行 loadImg()
函數,即可打印準備上傳文件中的第一張圖片內容到控制檯,類似這樣的字符串 data:image/png;base64,iVBORw0KGgoAAAANSUhEU...
,這是將圖片進行了base64
編碼,服務端只需要解碼base64
即可恢復原文件。將內容複製瀏覽器的地址欄打開是沒法兒預覽的,小東現在想了一下,其原因主要是:瀏覽器地址欄,類似GET請求,限制了URL的長度和大小,故無法正常顯示圖片(此處浪費了較多時間,一直以爲讀取出來的數據不對!!!其實是完全正確的)。
然後本地寫了一個測試文件,如下:
不同文件的數據頭部是不一樣的:data:image/png;base64
,這是上傳了一個 png
圖片的案例,上傳 jpeg
,gif
等等頭部都會改變,真正有效的是,
(逗號)後面的內容,纔是文件內容的編碼,客戶端直接提交整個參數即可無需處理,處理交給服務端即可。
服務端支持一個圖片上傳,那麼我們循環讀取,然後上傳即可,至於返回的數據,以json
方式傳輸,將單個上傳信息直接array_push()
到一個數組即可。
服務端代碼的改寫(部分):
<?php
$result = [];
foreach ($_POST as $key => $value) {
$file = $value; //BASE64
$cookie = $config['cookie'];
$file = explode(',', $file);
$imgarr = upload($file[1], $type, $cookie); //TYPE是GET得到,值爲bs64,避免post內容混淆
array_push($result, $imgarr);
}
echo json_encode($result);
exit();
?>
至此,客戶端 base64
版本及服務端改造完成。
0x04 FormData版本
在Javascript
中,有Jquery
中的 FormData
方法,使得 Form
表單以 ajax
方法傳輸變得可行。
博主個人覺得這種方式更好,代碼具有簡潔性,同時後端按照傳統方式來處理
前端代碼(javascript
):
<script type="text/javascript">
$(function(){
//選擇圖片
$('.btn-primary').click(function(){
$('#files').trigger('click');
})
$('#files').change(function(){
var fileNum = $("#files")[0].files.length;
$('.btn-primary').text('上傳文件('+fileNum+')');
})
//點擊上傳圖片
$('#upload_btn').click(function(){
$("#upload_btn").text("上傳中...");
$('#upload_btn').attr('disabled', true);
var fileNum = $("#files")[0].files.length;
if(fileNum && fileNum <= 10){
$.ajax({
url : "https://api.top15.cn/picbed/picApi.php?type=multipart&num=multiple", //此API可供大家免費使用,且行且珍惜
type : 'POST',
cache: false,
data : new FormData($('#form')[0]),
processData : false,
contentType : false,
async : false,
success : function(data) {
if(data){
var str = '';
var str2 = '';
$.each(data, function(index, obj){
str += '<a href="'+ obj.url2+'" target="_blank">'+obj.url2+'</a><br>';
str2 += '<img src="'+obj.url2+'" width="'+obj.width+'" height="'+obj.height+'">';
})
$(".imgs_src").html(str);
$(".textarea").text(str2);
}
}
});
$("#upload_btn").attr('disabled', false);
$('#files').val('');
$('.btn-primary').text('上傳文件(0)');
}
else{
alert("請選擇上傳文件,且上傳文件數不超過10!");
$('#files').val('');
$('.btn-primary').text('上傳文件(0)');
$("#upload_btn").attr('disabled', false);
}
$("#upload_btn").text("一鍵上傳");
})
})
</script>
後端處理:
<?php
$result = [];
foreach ($_FILES['files']['tmp_name'] as $key => $value) { //從$_FILES中讀取文件
$file = $value;
$cookie = $config['cookie'];
$imgarr = upload($file, $type, $cookie);
array_push($result, $imgarr);
}
echo json_encode($result);
exit();
?>
0x05 最終完成:
完成項目的展示網站:https://api.top15.cn/picbed/
開源地址:https://github.com/dyboy2017/sinaPictureBed
0x06 總結
本次程序實現中,input
控件中的 name
屬性值要爲數組,其次 HTML5
功能 FileReader()
是一個不錯的方法,有利於及時展示即將上傳的圖片,一些多圖上傳的插件,使用的就是此種方式,但是會耗費客戶端較多的計算資源,可能導致客戶端的卡頓(可以限制上傳圖片的大小以及數量是一種解決方案),簡單的直接使用 Jquery
中的 FormData()
即可使用 ajax
異步傳輸數據,最後測試,發現在 Android
手機瀏覽器上還是無法多圖上傳,但是在 Iphone
手機上可以多圖上傳,在PC端,360瀏覽器運行該頁面會卡頓但不影響正常操作,在 Firfox
瀏覽器上表現正常!HTML5
特性在需要的查詢文檔使用即可。