內(nèi)容簡(jiǎn)單說(shuō)明
??文件上傳是 web 開(kāi)發(fā)中比較常見(jiàn)的一個(gè)功能雖然說(shuō)起來(lái)是文件上傳,實(shí)際上,可以看做是對(duì) multipart/form-data 數(shù)據(jù)的處理卑雁。在 npm 中,有很多處理類(lèi)似數(shù)據(jù)的庫(kù),包括周下載量近 2kw 的 form-data,周下載量近 3mw 的 formidable镶奉。
??不過(guò),如果 nodejs 后端使用的 express 框架轿衔,其官方也有一個(gè)自己的文件上傳中間件,用它自己的話來(lái)說(shuō)就是:“Multer 是一個(gè) node.js 中間件睦疫,用于處理 multipart/form-data 類(lèi)型的表單數(shù)據(jù)害驹,它主要用于上傳文件「蛴”
??使用 multer 比較簡(jiǎn)單宛官,一般就是
????1、導(dǎo)入 multer瓦糕,
????2底洗、指定文件上傳地址(如果有必要的話,不指定只是寫(xiě)到內(nèi)存中)咕娄,
????3亥揖、在 router 的路徑后,回調(diào)函數(shù)前圣勒,寫(xiě)一個(gè)upload.single(photo)
(單文件)或者upload.array('photos', 12)
(多文件)费变,在 router 的回調(diào)中,就可以使用req.file 或者 req.files
獲取文件了圣贸。
??在這里挚歧,因?yàn)橹付ǖ纳蟼鞯刂肥窃?multer(opts)中的 opts 配置,所以 opts 配置號(hào)一個(gè)地址之后吁峻,后續(xù)修改就不是那么方便滑负。如果需要對(duì)不同文件不同路由路徑指定不同的文件上傳地址,那應(yīng)該如何處理用含?
??multer 的簡(jiǎn)單使用后文會(huì)給個(gè)示例矮慕,但是最終的目的,是想要在 express 的 router 回調(diào)函數(shù)中啄骇,可以指定文件上傳的路徑痴鳄,而不是所有的文件都上傳到唯一指定的路徑。例如肠缔,路由是“testUpload”夏跷,我在 router 處理時(shí)指定存放到測(cè)試使用的上傳路徑。路由是“formalUpload”明未,我在處理時(shí)可以指定存放到正式的上傳路徑槽华。
express+multer 基本文件上傳示例
??因?yàn)橹饕菧y(cè)試 multer 內(nèi)容,所以一切從簡(jiǎn)趟妥,就在一個(gè)簡(jiǎn)單的 express 項(xiàng)目中測(cè)試就好
1猫态、創(chuàng)建一個(gè) express 項(xiàng)目(前提:已安裝 express-generator),并安裝 multer
express --view=ejs express-mutler-demo
// 進(jìn)入項(xiàng)目根目錄
npm i multer
2、上傳頁(yè)面編寫(xiě)
??修改 views/index.ejs 的<body>標(biāo)簽內(nèi)容如下:
<div>
<h3>Express + multer 簡(jiǎn)陋上傳文件</h3>
<form method="post" action="/upload" id="upload-form" encType="multipart/form-data">
<input id='upload' type="file" name="file" />
<input type="submit" value="上傳">
</form>
<!-- 進(jìn)度條 -->
<progress id="uploadprogress" min="0" max="100" value="0">0</progress>
<p id='msg'></p>
</div>
<!-- 引入jquery.js -->
<script src="https://code.jquery.com/jquery-3.4.1.min.js"
integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo=" crossorigin="anonymous"></script>
<script>
let form = $("#upload-form");
form.on('submit', function (event) {
// 清除提交結(jié)果顯示信息
$("#msg").html("");
// 在原頁(yè)面處理亲雪,不跳轉(zhuǎn)
event.preventDefault();
// 檢查是否支持FormData
if (window.FormData) {
let formData = new FormData();
// 建立一個(gè)file表單項(xiàng)勇凭,值為上傳的文件
formData.append('file', $('#upload').get(0).files[0]);
let xhr = new XMLHttpRequest();
xhr.open('POST', $(this).attr('action'));
// 進(jìn)度條占比計(jì)算
xhr.upload.onprogress = function (event) {
if (event.lengthComputable) {
let complete = (event.loaded / event.total * 100 | 0);
$("#uploadprogress").val(complete);
$("#uploadprogress").innerHTML = complete;
}
};
// 定義上傳完成后的回調(diào)函數(shù)
xhr.onload = function (e) {
if (xhr.status === 200) {
$("#msg").html("上傳成功!");
// alert('上傳成功!');
} else {
// alert('文件上傳出錯(cuò)了义辕!')
$("#msg").html("上傳失敗!");
}
};
// 發(fā)送表單數(shù)據(jù)
xhr.send(formData);
}
});
</script>
??代碼內(nèi)容很簡(jiǎn)單虾标,就是一個(gè) form 用來(lái)模擬文件上傳,為了最簡(jiǎn)單灌砖,直接使用的 XMLHttpRequest 實(shí)現(xiàn)上傳璧函,還沒(méi)事整了個(gè)進(jìn)度條。
??本來(lái)想用原始的方法基显,還是引入了 jquery蘸吓。更簡(jiǎn)略類(lèi)似下面也 ok。
<script>
function PostData() {
$.ajax({
type: "POST",
url: "XXX",
data : "",
success: function(msg) {
}
});
return false;
}
</script>
<form onsubmit="return PostData()">
<input type="text" value="">
<input type="submit">
</form>
??依舊以第一個(gè)為準(zhǔn)撩幽,頁(yè)面大概是這個(gè)樣子(運(yùn)行 express 項(xiàng)目库继,在 localhost:3000 看到):
3、multer 的簡(jiǎn)單配置
??新建一個(gè) util/Upload.js窜醉,編寫(xiě) multer 配置并導(dǎo)出:
const multer = require('multer');
// 文件上傳配置
const fileStorage = multer.diskStorage({
destination: function (req, file, callback) {
callback(null, "/defaultUploadDir");
},
filename: function (req, file, callback) {
callback(null, file.originalname);
}
});
// 導(dǎo)出配置
module.exports = {
fileUpdate: multer({ 'storage': fileStorage }),
}
注意:上傳地址 "/defaultUploadDir"要先手動(dòng)創(chuàng)建宪萄,否則報(bào)錯(cuò)。
4酱虎、在對(duì)應(yīng) router 中使用 multer
??在 routes/index.js 中雨膨,添加以下 router 代碼:
router.post('/upload', upload.fileUpdate.single('file'), function (req, res, next) {
const file = req.file;
console.log(file);
//如果得到了文件擂涛,就返回上傳成功
if (file) {
return res.status(200).json({ success: true });
} else {
return res.status(500).json({ success: false });
}
});
??記得在最上面引入 multer 配置:
const upload = require('../util/Upload');
??幾個(gè)簡(jiǎn)單注意點(diǎn):
????1读串、這個(gè)路由路徑和路由方法,要和前臺(tái)頁(yè)面中的 action 和 method 一致撒妈;
????2恢暖、多文件就要 upload.array(),單文件就用 upload.single()(后續(xù)都是單文件示例中說(shuō)明);
????3、第二點(diǎn)()里面的標(biāo)志字符串要和前臺(tái)頁(yè)面中的<input id='upload' type="file" name="file" />
name 屬性一致狰右。
??如果步驟都正確杰捂,成功上傳,應(yīng)該可以看到前臺(tái)頁(yè)面如下:
??router 的回調(diào)中取得上傳文件的信息棋蚌,如下:
??文件上傳的位置:
關(guān)于使用 multer 文本域數(shù)據(jù)
??multer 的 readme 所說(shuō):”Multer 會(huì)添加一個(gè) body 對(duì)象 以及 file 或 files 對(duì)象 到 express 的 request 對(duì)象中嫁佳。 body 對(duì)象包含表單的文本域信息,file 或 files 對(duì)象包含對(duì)象表單上傳的文件信息谷暮≥锿“
??實(shí)際測(cè)試,在前臺(tái)頁(yè)面 index.ejs 創(chuàng)建 formData 后湿弦,append 一個(gè)文本數(shù)據(jù):
let formData = new FormData();
// 補(bǔ)入此句
formData.append('dest', 'file_upload');
??刷新頁(yè)面之后瓤漏,重新上傳,可以在 multer 配置中,在 diskStorage 的 destination 的 callback 中蔬充,可以得到 req.body 包含了 dest 屬性蝶俱。如下圖:
??這是好事,很好的饥漫,這樣榨呆,在前臺(tái)上傳文件時(shí),就可以把需要上傳的地址放到這里庸队,那么不同的文件上傳就可以存放的不同的地址了愕提。
??那么會(huì)有哪些問(wèn)題呢?
????1皿哨、前端需要知道后臺(tái)的上傳路徑浅侨,不合理。
????2证膨、并不是所有使用 formData.append()添加的屬性都能在文件上傳 destination 生成前如输,在 req.body 中獲取到。
????這是一個(gè)實(shí)際遇到的問(wèn)題央勒,我在使用 angular 時(shí)不见,使用 HttpClient 實(shí)現(xiàn)文件上傳操作,類(lèi)似:
upload(file: any) {
// 文件使用FormData發(fā)送
const formData: FormData = new FormData();
formData.append('file', file, file.name);
formData.append('file_name', file, file.name);
return this.http.post(this.URL + '/upload', formData );
}
??后臺(tái)的 req.body 在獲取到上傳的文件前并不會(huì)有 file_name 屬性的值崔步,即在 multer 配置在 diskStorage 的 destination 的 callback 中稳吮,可以得到 req.body 是空,在對(duì)應(yīng) upload 的 router 回調(diào)中井濒,才取得 req.body 的 file_name 屬性灶似。
在 router 的回調(diào)中,指定文件上傳的路徑瑞你。
??在”關(guān)于使用 multer 文本域數(shù)據(jù)“這部分有講到酪惭,前臺(tái)直接傳入文件上傳的路徑不合理,在接受到上傳的文件前得到指定的上傳路徑也不一定成功者甲,而直接使用配置好的 multer春感,其文件上傳目的地 destination 又只有固定一個(gè)。該如何實(shí)現(xiàn)虏缸?
??把 multer 的配置鲫懒,封裝到一個(gè)返回 promise 的函數(shù),指定傳入一個(gè)文件路徑參數(shù)刽辙,并在 router 的回調(diào)中使用該函數(shù)窥岩,傳入上傳路徑。
??修改 utils/Upload.js 文件扫倡,補(bǔ)入以下內(nèi)容:
// multer文件上傳,可指定上傳路徑,不在router參數(shù)里直接用
let uploadFunction = (req, res, dest) => {
let storage = multer.diskStorage({
destination: function (req, file, cb) {
let newDestination = dest;
let stat = null;
try {
// 檢查傳入的路徑是否存在谦秧,不存在則創(chuàng)件
stat = fs.statSync(newDestination);
} catch (err) {
fs.mkdirSync(newDestination);
}
if (stat && !stat.isDirectory()) {
throw new Error('文件目錄: "' + dest + '已存在竟纳!"');
}
cb(null, newDestination);
},
filename: function (req, file, callback) {
callback(null, file.originalname);
}
});
let upload = multer({
storage: storage
}).single('file');
return new Promise((resolve, reject) => {
upload(req, res, (err) => {
if (err) {
return reject(err);
}
resolve();
})
})
};
??記得導(dǎo)出:
module.exports = {
fileUpdate: multer({ 'storage': fileStorage }),
uploadFunction,
}
??在 router 中使用,修改原 routes/index.js 的 upload 路由如下:
router.post('/upload', /*upload.fileUpdate.single('file'), */ async function (req, res, next) {
// 指定文件上傳路徑
let uploadPath = 'test_upload';
// 等到文件上傳完成
await upload.uploadFunction(req, res, uploadPath);
const file = req.file;
console.log(req.file);
//如果得到了文件疚鲤,就返回上傳成功
if (file) {
return res.status(200).json({ success: true });
} else {
return res.status(500).json({ success: false });
}
});
??當(dāng)然锥累,await 需要在 async 函數(shù)中使用,也最好放到 trycatch 中集歇。
??如果步驟正確桶略,結(jié)果應(yīng)該和第一步中的一樣,文件上傳成功诲宇。在后臺(tái)的項(xiàng)目中會(huì)新建一個(gè) test_upload 文件夾际歼,并有上傳的文件。
??代碼已放到 github姑蓝,有需求可查閱鹅心。
??以上內(nèi)容,全部親測(cè)有效纺荧,如果有問(wèn)題旭愧,請(qǐng)?zhí)岢鼋涣鳎x謝宙暇。