前端文件index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>文件上传</title>
<script src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js"></script>
</head>
<body>
<form name="uploadFileForm">
<div>
单文件上传
<input id="uploadFile1" accept="*/*" type="file" />
</div>
<div>
多文件上传
<input id="uploadFile2" accept="*/*" type="file" multiple />
</div>
<div>
文件夹上传
<input id="uploadFile3" accept="*/*" type="file" webkitdirectory />
</div>
</form>
<script>
uploadFile1.onchange = uploadFile;
uploadFile2.onchange = uploadFile;
uploadFile3.onchange = uploadFile;
function uploadFile() {
console.log("上传文件中");
console.log("this.files", this.files);
var formData = new FormData();
for (let i = 0; i < this.files.length; i++) {
// 将文件路径(包含文件名)作为formData的key, 避免key重复
let key = this.files[i].webkitRelativePath || "defaultUploadFileKey"; // 如果webkitRelativePath的值不为空说明上传的是文件夹
formData.append(key, this.files[i]);
}
$.ajax({
url: "/uploadFile",
type: "post",
data: formData,
contentType: false, // 取消自动的设置请求头
processData: false, //取消自动格式化数据
enctype: "multipart/form-data",
success(data) {
console.log("data: ", data);
},
error(err) {
console.log("err: ", err);
},
});
}
</script>
</body>
</html>
Node.js入口文件app.js:
const http = require('http');
const path = require('path');
const fs = require('fs');
const formidable = require('formidable');
const log = console.log;
const port = 3000; // 运行端口
const isAllowCoverageFile = false; // 是否允许覆盖同名文件
const uploadDir = path.join(process.cwd(), '/uploads/'); // 保存上传文件的目录, process.cwd()返回当前的工作目录
// 发送页面
function sendPage(res, path, statusCode = 200) {
res.writeHead(statusCode, { 'Content-Type': 'text/html;charset=UTF-8' });
fs.createReadStream(path).pipe(res)
}
/**
* 同步递归创建路径
* fs.mkdirSync(fileDir)要求路径的父级存在才能创建, 否则报错.
* 注: NodeJS 10以后的版本,fs.mkdir已经增加递归选项:
* fs.mkdir('/home/test1/test2', { recursive: true }, (err) => {})
*
* @param {string} dir 处理的文件路径(不是文件夹路径)
* @param {function} cb 回调函数
*/
function mkdirSync(dir, cb) {
let pathinfo = path.parse(dir);
if (!fs.existsSync(pathinfo.dir)) {
mkdirSync(pathinfo.dir, function () {
console.log('创建文件夹: ' + pathinfo.dir);
fs.mkdirSync(pathinfo.dir)
})
}
cb && cb()
}
// 如果uploadDir目录不存在就创建目录
if (!fs.existsSync(uploadDir)) {
fs.mkdirSync(uploadDir)
}
// 上传文件
function uploadFile(req, res) {
console.log("上传文件");
res.writeHead(200, { 'content-type': 'text/plain;charset=UTF-8' });
let form = new formidable.IncomingForm();
form.uploadDir = uploadDir; // 保存上传文件的目录
form.multiples = true; // 设置为多文件上传
form.keepExtensions = true; // 保持原有扩展名
form.maxFileSize = 10 * 1024 * 1024 * 1024; // 限制上传文件最大为10GB
// 文件大小超过限制会触发error事件
form.on("error", function (e) {
console.log("文件大小超过限制, error: ", e);
res.writeHead(400, { 'content-type': 'text/html;charset=UTF-8' });
res.end("文件大小超过10GB, 无法上传")
})
form.on('end', () => {
res.end("文件全部上传成功!")
});
form.parse(req, function (err, fields, files) {
if (err) {
console.log("err: ", err);
res.writeHead(500, { 'content-type': 'text/html;charset=UTF-8' });
res.end('上传文件失败: ' + JSON.stringify(err));
return;
}
// console.log('files:\n', files);
for (let key in files) {
rename(files[key])
}
// 文件会被formidable自动保存, 而且文件名随机, 因此保存后建议重命名
function rename(fileItem) {
// 单文件上传时fileItem为对象, 多文件上传时fileItem为数组,
// 单文件上传时也将fileItem变成数组统一当做多文件上传处理;
let fileArr = fileItem;
if (Object.prototype.toString.call(fileItem) === '[object Object]') {
fileArr = [fileItem];
}
for (let file of fileArr) {
let fileName = file.name; // 上传文件夹时文件名可能包含上传的文件夹路径
console.log("上传文件名: ", fileName);
let suffix = path.extname(fileName); // 文件后缀名
let oldPath = file.path; // formidable自动保存后的文件路径
let newPath = path.join(uploadDir, fileName);
// log('oldPath', oldPath)
// log('newPath', newPath)
// 防止路径不存在
mkdirSync(newPath);
// 如果不允许覆盖同名文件
if (!isAllowCoverageFile) {
// 并且文件已经存在,那么在文件后面加上时间戳和随机数再加文件后缀
if (fs.existsSync(newPath)) {
newPath = newPath + '-' + Date.now() + '-' + Math.trunc(Math.random() * 1000) + suffix;
}
}
fs.rename(oldPath, newPath, function (err) {
if (err) {
log(err)
}
})
}
}
});
}
let server = http.createServer(function (req, res) {
let url = decodeURI(req.url);
console.log("url: ", url);
let method = req.method.toLowerCase()
let parameterPosition = url.indexOf('?')
if (parameterPosition > -1) {
url = url.slice(0, parameterPosition) // 去掉url中的参数部分
}
if (url === '/') {
sendPage(res, './index.html');
return;
}
if (url === '/uploadFile' && method === 'post') {
// 上传文件
uploadFile(req, res)
}
})
server.listen(port);
console.log('http://localhost:' + port)
// 异常处理
process.on("uncaughtException", function (err) {
if (err.code == 'ENOENT') {
console.log("no such file or directory: ", err.path);
} else {
console.log(err);
}
})
process.on("SIGINT", function () {
process.exit()
})
process.on("exit", function () {
console.log("exit");
})
运行方法:
把两个文件放到同一个目录下, 在该目录下输入node app.js,
然后打开浏览器访问http://localhost:3000/