文件上传,无论用什么应用程序,浏览器,小程序或者说APP,其实还是逃不出HTTP协议的格式,
文件上传,也就是HTTP请求,对于HTTP请求来说无非也就是请求行,请求头和请求正文
请求行这部分没什么变化
请求方法 请求地址 协议版本组成
请求头中,有一个很重要的内容Content-Type
Content-Type:multipart/form-data; boundary=----分隔符
其中,content-type
为 multipart/form-data
,简单来说,这种格式,我们就可以上传文件。
在HTML表单中的form标签,通过设置:encType
来指定;比如:encType="multipart/form-data"
,在其他客户端采用Content-Type
来指定;比如:Content-Type="multipart/form-data"
content-type:multipart/form-data
要求提交方式必须为POST
boundary后面的,是长度为16的随机base64字符,它是浏览器自动生成的分隔符。
请求正文,生成的数据,都由这段分隔符包裹,它标志着一段数据(当有多个数据内容的时候)的开始和结束
下面是上传的一段请求正文
可以看到上面传递的每一段数据,都由boundary
生成的分隔符进行分割
无论怎么样,文件上传前端提供的信息,也都是这个样子,后端接收到这些数据之后,进行处理。
大多数情况下,前端人员根本不需要纠结后端,不过要实现上传效果,那么肯定需要后端处理。
后端处理文件
为了达到试验效果,后端这里是express框架,使用multer
第三方库,来处理上传文件
npm install --save multer
地址:https://github.com/expressjs/multer/blob/master/doc/README-zh-cn.md
不过由于是上传,肯定是post提交,也可能会出现跨域问题,所以对于express框架的相关配置大家需要提前准备
cors
//安装cors
npm install cors
//使用cors
let express = require('express')
let cors = require('cors')
let app = express()
app.use(cors())
post提交
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
config.js: 上传相关配置文件
const path = require('path');
//文件上传大小限制,单个文件最大2M
let sizeLimit = 2 * 1024 * 1024;
//文件后缀限制
let exts = ['.jpg', '.jpeg', '.bmp', '.webp', '.gif', '.png'];
//文件上传个数
let countLimit = 10;
//文件地址配置
exports.configPath = {
//上传地址
upload: path.resolve(__dirname, '../public/upload'),
};
//单文件上传配置
exports.single = {
sizeLimit,
exts,
fieldName: 'avatar',
};
//多文件上传配置
exports.multi = {
fieldName: 'images',
sizeLimit,
countLimit,
exts,
};
UploadErrorTypes.js 上传文件错误提示
class UploadErrorTypes extends Error {
constructor(code, msg) {
super(msg);
this.code = code;
this.msg = msg;
}
}
class ExtError extends UploadErrorTypes {
constructor() {
super(1, '后缀名不符合要求');
}
}
class SizeLimitError extends UploadErrorTypes {
constructor() {
super(2, '文件过大');
}
}
class CountLimitError extends UploadErrorTypes {
constructor() {
super(3, '文件数量超过要求');
}
}
class UnexpectedRequest extends UploadErrorTypes {
constructor() {
super(4, '未知请求错误');
}
}
exports.UploadErrorTypes = UploadErrorTypes;
exports.ExtError = ExtError;
exports.SizeLimitError = SizeLimitError;
exports.CountLimitError = CountLimitError;
exports.UnexpectedRequest = UnexpectedRequest;
fileUtil.js 文件上传工具函数
const path = require('path');
//上传文件重命名函数
exports.uploadSuffixName = (originalname,filename) => {
let ext = path.extname(filename);
if (ext) { //如果新生成的文件名有后缀名,直接返回新文件名
return filename;
}
ext = path.extname(originalname);
if (!ext) { //如果源文件名本身就没有后缀名,直接返回新文件名
return filename;
}
return filename + ext;
}
//生成后端访问路径函数
exports.generateUrl = function (req, filename, basePath = '/upload') {
return `${req.protocol}://${req.header('host')}${basePath}/${filename}`;
};
单文件上传路由
const express = require('express');
const router = express.Router();
//引入配置文件
const config = require('../utils/config');
//引入multer模块
const multer = require('multer');
//配置multer
const upload = multer({
dest: config.configPath.upload, //上传文件保存路径
limits: {
fileSize: config.single.sizeLimit,//上传文件大小限制
},
fileFilter(req, file, cb) { //文件后缀过滤
const path = require('path');
const ext = path.extname(file.originalname);
if (config.single.exts.includes(ext)) {
cb(null, true);
} else {
const { ExtError } = require('../utils/UploadErrorTypes.js');
cb(new ExtError());
}
},
});
//单文件上传函数
const single = upload.single(config.single.fieldName);
/**
* 单文件上传
* @group upload - 上传操作
* @route POST /upload/single
* @consumes multipart/form-data
* @param {file} avatar.body.required - 上传的文件
* @returns {object} 0 - 返回成功信息{code:0,msg:'上传成功',data:url}
* @returns {json} 1 - 后缀名不符合要求
* @returns {json} 2 - 文件过大
* @returns {json} 3 - 文件数量超过要求
* @returns {json} 4 - 未知请求错误
*/
router.post('/single', async (req, res, next) => {
single(req, res, async (err) => {
if (err instanceof multer.MulterError) {
const { SizeLimitError, UnexpectedRequest } = require('../utils/UploadErrorTypes.js');
if (err.message === 'File too large') {
err = new SizeLimitError();
} else {
err = new UnexpectedRequest();
}
}
if (err) {
next(err);
return;
}
console.log(req.file);
const { uploadSuffixName,generateUrl } = require('../utils/fileUtils');
const filename = uploadSuffixName(req.file.originalname, req.file.filename);
const fs = require('fs');
//为保存文件重命名
await fs.promises.rename(req.file.path, `${req.file.destination}/${filename}`);
//生成后端访问路径
const url = generateUrl(req, filename);
res.send({
code:0,
data: url,
msg:'上传成功'
});
});
});
module.exports = router;
Comments