本系列的目标快速上手 Express 框架, 基于官方文档归纳
参考中文文档:https://nodejs.cn/express/4x/api/
项目: https://github.com/eezd/cli-template/tree/main/nodejs-express-template
环境: Express 4.16
文件下载 res.download
注意, 是用根目录开始
router.get("/download", function (req, res, next) {
res.download("./server/public/my_pdf.pdf", "my_pdf.pdf", function (err) {
if (err) {
console.log(err);
} else {
}
});
});
multer 文件上传
文档:https://github.com/expressjs/multer/blob/master/doc/README-zh-cn.md
安装依赖
pnpm i multer
⚠️ 需要注意:
.single('fieldname')
: 是对应<input type="file" name="fieldname">
中的 fieldname,不然它会获取不到文件。
.array(fieldname[, maxCount])
- 接受一个以
fieldname
命名的文件数组。可以配置maxCount
来限制上传的最大数量。这些文件的信息保存在req.files
。
- 接受一个以
.fields(fields)
- 接受指定
fields
的混合文件。这些文件的信息保存在req.files
。 fields
应该是一个对象数组,应该具有name
和可选的maxCount
属性。
- 接受指定
[
{ "name": "avatar", "maxCount": 1 },
{ "name": "gallery", "maxCount": 8 }
]
.none()
- 只接受文本域。如果任何文件上传到这个模式,将发生 “LIMIT_UNEXPECTED_FILE” 错误。这和
upload.fields([])
的效果一样。
- 只接受文本域。如果任何文件上传到这个模式,将发生 “LIMIT_UNEXPECTED_FILE” 错误。这和
.any()
- 接受一切上传的文件。文件数组将保存在
req.files
。 - 警告: 确保你总是处理了用户的文件上传。 永远不要将 multer 作为全局中间件使用,因为恶意用户可以上传文件到一个你没有预料到的路由,应该只在你需要处理上传文件的路由上使用。
- 接受一切上传的文件。文件数组将保存在
每个文件具有下面的信息:
Key 钥匙 | Description 描述 | Note 注意 |
---|---|---|
fieldname | Field name 由表单指定 | |
originalname | 用户计算机上的文件的名称 | |
encoding | 文件编码 | |
mimetype | 文件的 MIME 类型 | |
size | 文件大小(字节单位) | |
destination | 保存路径 | DiskStorage |
filename | 保存在 destination 中的文件名 | DiskStorage |
path | 已上传文件的完整路径 | DiskStorage |
buffer | 一个存放了整个文件的 Buffer | MemoryStorage |
简单案例
destination
: 文件存储位置filename
: 文件保存名称fileFilter
: 通过函数控制什么文件可以上传limits
: 看下面解析
再次强调: upload.single("fieldname")
的 fieldname
是对应 <input type="file" name="fieldname">
再次强调: upload.single("fieldname")
的 fieldname
是对应 <input type="file" name="fieldname">
再次强调: upload.single("fieldname")
的 fieldname
是对应 <input type="file" name="fieldname">
const multer = require("multer");
const upload = multer({
storage: multer.diskStorage({
// 文件存储位置
destination: function (req, file, cb) {
cb(null, "./uploads/");
},
// 文件保存名称
filename: function (req, file, cb) {
const { fieldname, originalname, mimetype } = file;
// 文件名称
const file_name =
fieldname + "_" + Math.random().toString(16).substring(2, 8);
// 后缀
const after = originalname.split(".")[1]
? "." + originalname.split(".")[1]
: "";
cb(null, file_name + after);
},
}),
// 限制接受上传的文件
fileFilter: (req, file, cb) => {
if (file.mimetype === "image/jpeg") {
cb(null, true);
} else {
cb(new Error("Only JPG files are allowed!"), false); // 拒绝上传的文件
}
},
// 限制文件大小
limits: {
fileSize: 1024 * 1024 * 1024,
files: 3,
},
});
router.get("/upload", function (req, res, next) {
res.send(
`<!DOCTYPE html>
<html>
<body>
<form action="upload" method="post" enctype="multipart/form-data">
<h1>选择上传的文件</h1>
<input type="file" name="fieldname">
<input type="submit" value="上传">
</form>
</body>
</html>`
);
});
router.post("/upload", upload.single("fieldname"), function (req, res, next) {
res.send(req.file);
});
limits
Key 钥匙 | Description 描述 | Default 违约 |
---|---|---|
fieldNameSize | field 名字最大长度 | 100 bytes 100 字节 |
fieldSize | field 值的最大长度 | 1MB 1 兆字节 |
fields | 非文件 field 的最大数量 | 无限 |
fileSize | 在 multipart 表单中,文件最大长度 (字节单位) | 无限 |
files | 在 multipart 表单中,文件最大数量 | 无限 |
parts | 在 multipart 表单中,part 传输的最大数量(fields + files) | 无限 |
headerPairs | 在 multipart 表单中,键值对最大组数 | 2000 |
捕获错误
https://github.com/expressjs/multer/blob/master/lib/multer-error.js
const multer = require("multer");
const upload = multer().single("fieldname");
app.post("/profile", function (req, res) {
upload(req, res, function (err) {
if (err instanceof multer.MulterError) {
// 发生错误
} else if (err) {
// 发生错误
}
// 一切都好
});
});
fetch 异步上传
<body>
<input id="postFile" type="file" name="logo" />
<button type="button" onclick="upload()">上传</button>
</body>
<script>
const upload = async () => {
const body = new FormData();
const file = document.getElementById("postFile").files[0];
if (file) {
body.append("fieldname", file);
const response = await fetch("http://localhost:3000/upload", {
method: "post",
body,
});
const res = await response.json();
console.log(res);
} else {
alert("请选择文件!");
}
};
</script>
多文件上传
upload.single
变成 upload.array
router.get("/upload", function (req, res, next) {
res.send(
`<!DOCTYPE html>
<html>
<body>
<input id="postFile" type="file" name="logo" multiple>
<button type="sub" onclick="upload()">上传</button>
</body>
<script>
const upload = async () => {
const body = new FormData()
const postFile = document.getElementById('postFile')
if (postFile.files[0]) {
for (const file of postFile.files) {
body.append('fieldname', file)
}
const response = await fetch('http://localhost:3000/upload', {
method: 'post',
body
})
const res = await response.json()
console.log(res)
} else {
alert('请选择文件!')
}
}
</script>
</html>`
);
});
router.post("/upload", upload.array("fieldname"), function (req, res, next) {
res.send(req.files);
});
Pug 模板引擎
pug
原名jade
,因版权问题更名为pug
,即哈巴狗。
router.get("/", async (req, res, next) => {
res.render("index", {
title: "Hello World",
url: "https://baidu.com",
arr: [
{
id: 1,
name: "Tom",
},
{
id: 2,
name: "Jack",
},
],
});
});
layout
layout.pug
doctype html
html
head
meta(charset="UTF-8")
meta(name="viewport", content="width=device-width, initial-scale=1.0")
title Document
link(rel='stylesheet', href='/stylesheets/style.css')
body
block content
block foot
p foot-默认内容
extends layout
: 导入模板if
: 如果存在, 则显示
index.pug
extends layout
block content
h1= title
p Welcome to #{title}
a(href=url) Go To Baidu
if arr
div#my_id.my_class
each item in arr
p #{item.id}
p #{item.name}
- var day = 1 // 变量
ul
while day < 3
li= day++
prepend foot
p foot-prepend
append foot
p foot-append
对应的 HTML 文件
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<link rel="stylesheet" href="/stylesheets/style.css" />
</head>
<body>
<h1>Hello World</h1>
<p>Welcome to Hello World</p>
<a href="https://baidu.com">Go To Baidu</a>
<div class="my_class" id="my_id">
<p>1</p>
<p>Tom</p>
<p>2</p>
<p>Jack</p>
</div>
<ul>
<li>1</li>
<li>2</li>
</ul>
<script type="text/javascript">
var data = "Test";
console.log(data);
</script>
<p>foot-prepend</p>
<p>foot-默认内容</p>
<p>foot-append</p>
</body>
</html>
Mixin
mixin.pug
mixin menu-item(href, name)
li
a(href=href)= name
实现
extends layout
include mixin
block content
h1= title
p Welcome to #{title}
+menu-item('/Archives','Archives')
html
<li>
<a href="/Archives">Archives</a>
</li>