Nodejs Express4 入门实战笔记04 multer 文件上传/文件下载 res.download PUG模板简易入门

本文含有: Express 文件上传与下载, multer 模块 limits 多文件上传 fetch异步上传 捕获错误 PUG模板简易入门

本系列的目标快速上手 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([]) 的效果一样。
  • .any()

    • 接受一切上传的文件。文件数组将保存在 req.files
    • 警告: 确保你总是处理了用户的文件上传。 永远不要将 multer 作为全局中间件使用,因为恶意用户可以上传文件到一个你没有预料到的路由,应该只在你需要处理上传文件的路由上使用。

每个文件具有下面的信息:

Key 钥匙Description 描述Note 注意
fieldnameField name 由表单指定
originalname用户计算机上的文件的名称
encoding文件编码
mimetype文件的 MIME 类型
size文件大小(字节单位)
destination保存路径DiskStorage
filename保存在 destination 中的文件名DiskStorage
path已上传文件的完整路径DiskStorage
buffer一个存放了整个文件的 BufferMemoryStorage

简单案例

  • 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 违约
fieldNameSizefield 名字最大长度100 bytes 100 字节
fieldSizefield 值的最大长度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>
Licensed under CC BY-NC-SA 4.0
本博客已稳定运行
发表了53篇文章 · 总计28.17k字
使用 Hugo 构建
主题 StackJimmy 设计