最近学习了文件上传,想做个笔记。写的简陋,勿打!
客户端
1.先看html结构
<div class="container">
<div class="item">
<h3>单一文件上传「FORM-DATA」</h3>
<section class="upload_box" id="upload1">
<!-- accept=".png" 限定上传文件的格式 已display:none -->
<input type="file" class="upload_inp" accept=".png,.jpg,.jpeg">
<div class="upload_button_box">
<button class="upload_button select">选择文件</button>
<button class="upload_button upload">上传到服务器</button>
</div>
<div class="upload_tip">只能上传 PNG/JPG/JPEG 格式图片,且大小不能超过2MB</div>
<ul class="upload_list">
<!-- <li>
<span>文件:...</span>
<span><em>移除</em></span>
</li> -->
</ul>
</section>
</div>
</div>
html效果
2.配置axios实例
let instance = axios.create(); //创建单独的实例
instance.defaults.baseURL = 'http://127.0.0.1:8888';
instance.defaults.headers['Content-Type'] = 'multipart/form-data'; //默认form-data格式
instance.defaults.transformRequest = (data, headers) => {
const contentType = headers['Content-Type'];
if (contentType === "application/x-www-form-urlencoded") return Qs.stringify(data);
return data;
};
//transformRequest处理post参数,data为要传的文件,Qs.stringify将对象转为query参数
//截拦响应结果,直接返回主体信息。
instance.interceptors.response.use(response => {
return response.data;
});
3.发起请求操作
/* 基于FORM-DATA实现文件上传 */
(function () {
let upload = document.querySelector('#upload1'), //上传的大框
upload_inp = upload.querySelector('.upload_inp'), //input框,被display:none
upload_button_select = upload.querySelector('.upload_button.select'), //选择文件按钮
upload_button_upload = upload.querySelector('.upload_button.upload'), //上传文件按钮 不加空格是两个类名都具备
upload_tip = upload.querySelector('.upload_tip'), //提示语
upload_list = upload.querySelector('.upload_list'); //文件列表
let _file = null; //初始要上传的file
// 点击选择文件按钮,触发上传文件INPUT框选择文件的行为
upload_button_select.addEventListener('click', function () {
if (upload_button_select.classList.contains('disable') || upload_button_select.classList.contains('loading')) return;
upload_inp.click();//触发文件选择
});
// 上传文件到服务器时的按钮状态
const changeDisable = flag => {
//flag为true,代表正在上传文件将按钮禁用,显示loading图片
if (flag) {
upload_button_select.classList.add('disable');
upload_button_upload.classList.add('loading');
return;
}
upload_button_select.classList.remove('disable');
upload_button_upload.classList.remove('loading');
};
//文件判空
upload_button_upload.addEventListener('click', function () {
if (upload_button_upload.classList.contains('disable') || upload_button_upload.classList.contains('loading')) return;
if (!_file) {
alert('请您先选择要上传的文件~~');
return;
};
// 监听用户选择文件的操作
upload_inp.addEventListener('change', function () {
// 获取用户选中的文件对象
// + name:文件名
// + size:文件大小 B
// + type:文件的MIME类型
// console.log(this.files);
let file = upload_inp.files[0]; //因为是单文件上传,所以获取第一个文件
if (!file) return;
// 限制文件上传的大小
if (file.size > 2 * 1024 * 1024) {
alert('上传的文件不能超过2MB~~');
return;
}
_file = file; //_file才是我们要上传的文件
// 显示上传的文件列表
upload_tip.style.display = 'none';
upload_list.style.display = 'block';
upload_list.innerHTML = `<li>
<span>文件:${file.name}</span>
<span><em>移除</em></span>
</li>`;
});
//将按钮禁用,显示loading图片的函数
changeDisable(true);
// 把文件传递给服务器:FormData
let formData = new FormData(); //创建form-data格式
formData.append('file', _file); //文件对象
formData.append('filename', _file.name); //文件名
//请求接口/upload_single,开始上传文件
instance.post('/upload_single', formData).then(data => {
if (+data.code === 0) {
alert(`文件已经上传成功~~,您可以基于 ${data.servicePath} 访问这个资源~~`);
return;
}
return Promise.reject(data.codeText);
}).catch(reason => {
alert('文件上传失败,请您稍后再试~~');
}).finally(() => {
clearHandle(); //文件列表复原操作
changeDisable(false); //按钮复原操作
});
});
// 移除按钮的点击处理
const clearHandle = () => {
_file = null;
upload_tip.style.display = 'block';
upload_list.style.display = 'none';
upload_list.innerHTML = ``;
};
upload_list.addEventListener('click', function (ev) {
let target = ev.target;
if (target.tagName === "EM") {
// 点击的是移除按钮
clearHandle();
}
});
})();
选择文件时的状态展示
服务端node-express
1.导入必要的模块
const express = require('express'),
fs = require('fs'),
multiparty = require('multiparty'); //解析form-data格式的插件
2.创建一个express服务器
const app = express(),
PORT = 8888,
HOST = 'http://127.0.0.1',
HOSTNAME = `${HOST}:${PORT}`;
app.listen(PORT, () => {
console.log(`服务器开启:http://127.0.0.1:8888`);
});
3.解决跨域问题
app.use((req, res, next) => {
res.header("Access-Control-Allow-Origin", "*");
req.method === 'OPTIONS' ? res.send('当前服务支持跨域请求!') : next();
});
4.为了更加真实,这里添加了延迟函数
// 延迟函数
const delay = function delay(interval) {
typeof interval !== "number" ? interval = 1000 : null;
return new Promise(resolve => {
setTimeout(() => {
resolve();
}, interval);
});
};
5.使用multiparty处理上传的文件
// 使用multiparty插件实现文件上传处理 & form-data解析
const uploadDir = `${__dirname}/upload`; //上传文件后放置的目录
const multiparty_upload = function multiparty_upload(req, auto) {
typeof auto !== "boolean" ? auto = false : null;
let config = {
maxFieldsSize: 200 * 1024 * 1024,
};
//auto为true就传到/upload目录下
if (auto) config.uploadDir = uploadDir;
return new Promise(async (resolve, reject) => {
await delay(); //延迟函数,模拟上传时间
//解析数据
//fields---->{ filename: [ 'IMG_20210315_110600_polarr.jpg' ] }
//files----->
// {
// file: [
// {
// fieldName: 'file',
// originalFilename: 'IMG_20210315_110600_polarr.jpg',
// path: 'E:\\****\\upload\\IBzQNI15x1kZd5f8GWW3SF_V.jpg',
// headers: [Object],
// size: 1063973
// }
// ]
// }
new multiparty.Form(config)
.parse(req, (err, fields, files) => {
if (err) {
reject(err);
return;
}
resolve({
fields,
files
});
});
});
};
multiparty会在field事件中,将数据信息的字段名和值返回。在file事件中,将文件的字段名和信息返回。 上传成功后,会在指定的文件夹创建一个上传的文件,并会将文件重命名(如:IBzQNI15x1kZd5f8GWW3SF_V.jpg),以防止重名。 若上传出现失败,已保存的文件会自动删除。
6.请求的接口
// 单文件上传处理「FORM-DATA」
app.post('/upload_single', async (req, res) => {
try {
let {
fields,
files
} = await multiparty_upload(req, true);
let file = (files.file && files.file[0]) || {};
res.send({
code: 0,
codeText: 'upload success',
originalFilename: file.originalFilename,
servicePath: file.path.replace(__dirname, HOSTNAME)
});
} catch (err) {
res.send({
code: 1,
codeText: err
});
}
});
最终效果
如有错误的地方,欢迎指出!