他們希望在一個分類上,Call一次AJAX上傳所有的圖片檔案與圖片檔案的說明文字
{
"id": "分類id",
"title": "分類標題",
"Files":[
{
"FileName":"檔案1",
"ImageIndex": 1,
"ImageFile":"../image/5.jpg", /* input=file 欄位 */
"ImageRemark":"備註說明",
},
{
"FileName":"檔案2",
"ImageIndex": 2,
"ImageFile":"../image/5.jpg", /* input=file 欄位 */
"ImageRemark":"備註說明",
},
]
}
因為有檔案上傳的部分,所以必須需要使用FormData
但是請求裡面有JsonArray,而且JsonArray混入input file的欄位和非input file的欄位
想請問該怎麼從Client端透過JS實作AJAX?或者如果不可行的話,是否該跟開規格的溝通其他方式?
感謝大大的協助。
FormData混Json 和 File 雖然不是不可行
可以根據淺水員大大的回答實現
但是在設計上,根據 fillano 大大說的
規格的原設計會有問題。
一個可能性:
https://developer.mozilla.org/zh-TW/docs/Web/API/FileReader
然後把檔案內容用DataUrl字串或TypedArray的方式讀取放進JSON,不過後端需要針對這個內容做一次解碼。
fillano
如果我沒理解錯的話
這樣子的做法是不是在淺水員大大提問前的Request
input file 的欄位選完檔案之後轉成Data Url的字串
這樣子的好處是不需要FormData包住AJAX的Request
不過開規格的希望可以丟File,圖片的檔案相關處理讓後端來處理
但是這樣的作法讓我疑惑的是,FormData 怎麼處理像這樣子的Request格式?
通靈亡
你說的處理 是指這樣嗎
var formdata = new FormData();
var blobData = Blob([/* {uploadfile} */], {type: "image/jpeg"})
formdata.append('file', blobdata)
listennn08
如果是前端轉成Data Url
可以用FileReader轉換後存到JsonArray
不過如果要丟到後端接type=file的檔案欄位,可以用Blob處理後存到JsonArray嗎?
post json時,request的body是json字串,並不是multipart form,這兩個不能混在一起用。非要混用不可的話,可以考慮把json字串改成當做form的一欄。
fillano
我看到也覺得Request格式JsonArray裡面混 File 跟一般的文字內容的設計真的讓人很困擾
我印象中是不是可以用一個陣列分開獨立放所有的 File
例如:
formData.append('pic[]', myFileInput1.files[0], 'chris1.jpg');
formData.append('pic[]', myFileInput2.files[0], 'chris2.jpg');
如果是的話,我可能必須跟開規格的溝通改設計了...
如果你熟各種form的body格式就知道,他的要求真的有問題XD
multi part格式的request,會在標頭先定義一個part的分隔字串,然後在request body中,分隔字串之間,會有各個part的header跟body。欄位名稱、檔案名稱等會放在part的header,欄位值或檔案內容會放在part body。這裡面不可能空降一個json字串,還有辦法各自剖析。但是當作欄位的值是沒問題的。
content-type是application/json的request,request body就只有json字串,加上其他東西剖析就會失敗(不合法的json),不可能用form的方式再來上傳檔案。
感謝 fillano 大大 協助
今天一整個早上都在檢視這個需求
如果要混一般資料跟 File 一起發送 AJAX,FormData + Fetch可以做到
下面是範例:
html
<input type='file' id='fileupload'>
<div id='result'></div>
<script src="main.js"></script>
javascript
document.querySelector('#fileupload').addEventListener('change', function(evt){
const files=evt.target.files;
let info={
"id": "分類id",
"title": "分類標題",
"Files": [
{
"FileName":"檔案1",
"ImageIndex": 1,
"ImageFile":"../image/5.jpg", /* input=file 欄位 */
"ImageRemark":"備註說明",
}
]
};
let theFormData=new FormData();
theFormData.append('info',JSON.stringify(info));
theFormData.append('../image/5.jpg', files[0]);
fetch('test.php',{
method: 'post',
body: theFormData
}).then((response)=>{
return response.text();
}).then((html)=>{
document.querySelector('#result').innerHTML=html;
});
});
test.php
$infoData=json_decode($_POST['info'], true);
echo '<pre>'.print_r($infoData, true).'</pre>';
echo '<pre>'.print_r($_FILES, true).'</pre>';
PS. File 的 key 好像會把 ..
自動轉成底線,這部分可能要注意一下。
先謝謝淺水員大大
我補充實際的操作情境:
新增一個分類,分類名稱叫分類1
分類1底下可以讓使用者,新增多組圖片,圖片1、圖片2、圖片3
每個圖片可輸入各自的說明文字
上傳幾張的檔案選擇欄位、說明文字欄位會由JS動態產生
當我按下送出的時候,包成Request,傳到後端
讓後端新增分類1,並同時上傳分類1的所有圖片檔案以及圖片說明文字
因此跟你程式碼轉成stringfy又不太一樣
Files會長下面這個樣子:
[
{
"FileName":"檔案1",
"ImageIndex": 1,
"ImageFile": 檔案1 input file的內容, /* input=file 欄位 */
"ImageRemark":"圖片說明1",
},
{
"FileName":"檔案2",
"ImageIndex": 2,
"ImageFile": 檔案2 input file的內容, /* input=file 欄位 */
"ImageRemark":"圖片說明2",
}
]
其中Files裡面的數量,會根據上傳的圖片數量而改變
{
"FileName":"檔案1",
"ImageIndex": 1,
"ImageFile": 檔案1 input file的內容, /* input=file 欄位 */
"ImageRemark":"圖片說明1",
}
我先確認一下,ImageFile 放的是「input=file 實際的內容」還是只是「作為一個 key ,這個 key 可以讓後端從 $File
找到檔案內容」?
另外我只放一個檔案只是範例,如果是多的檔案就:
(下面這是把 ImageFile 當作 key的做法)
let info={
"id": "分類id",
"title": "分類標題",
"Files": [
{
"FileName":"檔案1",
"ImageIndex": 1,
"ImageFile":"key1", /* input=file 欄位 */
"ImageRemark":"備註說明",
},{
"FileName":"檔案2",
"ImageIndex": 2,
"ImageFile":"key2", /* input=file 欄位 */
"ImageRemark":"備註說明",
}
]
};
let theFormData=new FormData();
theFormData.append('info',JSON.stringify(info));
theFormData.append('key1', files[0]);
theFormData.append('key2', files[1]);
依此類推你可以自己改
我上面是假設你們傳的是 multipart form
如果只是單純傳 JSON ,那就要訂好 ImageFile 使用的編碼了
(這時可能還是用 base64 比較好)
是input=file 的實際的內容
這樣子是不是在FormData直接拉兩個陣列,一個專門放File,另一個放圖片文字資訊的Json會比較恰當?
{
Files: [圖片1的input file內容, 圖片2的input file內容],
ImagesInfo: [
{
"FileName":"檔案1",
"ImageIndex": 1,
"ImageRemark":"圖片1說明",
},
{
"FileName":"檔案2",
"ImageIndex": 2,
"ImageRemark":"圖片2說明",
},
]
}
是的話,我就必須跟開規格的溝通改設計了
雖然問題好像解決了,不過我還是回一下
原先 ImageFile 欄位要放檔案內容是可以的
只是要轉成 base64 編碼
然後後端再把 base64 編碼還原後儲存起來就可以
PS. 這邊我也不用 multipart form 傳送了,直接送 JSON
html
<input type='file' id='fileupload' multiple>
javascript
function readFileAsPromise(file)
{
return new Promise((success, error)=>{
let frd=new FileReader();
frd.onload=function(){
success({
name: file.name,
buffer: this.result.split(';base64,')[1]
});
}
frd.readAsDataURL(file);
});
}
document.querySelector('#fileupload').addEventListener('change', function(evt){
let readFiles=Array.from(evt.target.files).map(f=>readFileAsPromise(f));
Promise.all(readFiles).then((readerArray)=>{
let filesData=readerArray.reduce((arr,rd,idx)=>{
arr.push({
FileName: rd.name,
ImageIndex: idx,
ImageFile: rd.buffer,
ImageRemark: '備註說明'+idx
});
return arr;
},[]);
console.log(filesData);
return fetch('reciver.php', {
method:'post',
body: JSON.stringify({
time: Date.now(), //可以放一些其他資訊
files: filesData
})
});
});
});
後端我不知道你們用甚麼語言,這是 PHP 的範例
<?php
//只是示範,忽略錯誤處理跟檢查
$jsonStr=file_get_contents('php://input');
$requestData=json_decode($jsonStr, true);
$timestamp=$requestData['time'];
foreach($requestData['files'] as $fileInfo) {
$name=$timestamp.$fileInfo['FileName'];
$data=base64_decode($fileInfo['ImageFile']);
file_put_contents($name, $data);
}
感謝淺水員
最後請開規格的將設計修改成開兩個陣列:
一個陣列放所有input type=file的欄位,做檔案的相關處理
另一個陣列放Json Array的字串,傳到後端轉換處理
這樣做是因為開規格的不想讓後端處理url encode
算是你提供的轉Json字串方案+我的想法。