【Nodejs】文件上传

在这里插入图片描述

1.初始化准备


1.1 安装依赖

首先创建一个express-multer-upload工程项目,然后在项目中下好各种依赖包。

multer中间件
Multer 是一个 node.js 中间件,用于处理 multipart/form-data 类型的表单数据,它主要用于上传文件。

注意: Multer 不会处理任何非 multipart/form-data 类型的表单数据。

下面是我下载的依赖以及版本。
image-20221231143824464

1.2 项目结构划分

本着以比较规范的形式去完成这个项目,所以有必要进行合理的项目结构划分。如下:
image-20221231144220130

2.multer上传逻辑


2.1 multer配置

在 multer 目录下创建 multerConfig.js,编写如下代码:

  • 引入依赖
  • 封装处理路径函数
  • 设置 multer 的配置对象
  • 为 multer 添加配置
// 1. 引入依赖
const multer = require('multer')
const path = require('path') 

// 2. 封装处理路径函数
const handlePath = (dir) => {
  return path.join(__dirname, './', dir)
}

// 3. 设置 multer 的配置对象
const storage = multer.diskStorage({
  // 3.1 存储路径
  destination: function(req, file, cb) {
    if (file.mimetype === 'image/jpeg' || file.mimetype === 'image/png' || file.mimetype==='image/gif') {
      cb(null, handlePath('../../public'))
    } else {
      cb({ error: '仅支持 jpg/png/gif 格式的图片!' })
    }
  },
  //  3.2 存储名称
  filename: function (req, file, cb) {
    // 将图片名称分割伪数组,用于截取图片的后缀
    const fileFormat = file.originalname.split('.')
    // 自定义图片名称
    cb(null, Date.now() + '.' + fileFormat[fileFormat.length - 1])
  }
})

// 4. 为 multer 添加配置
const multerConfig = multer({
  storage: storage,
  limits: { fileSize: 2097152 } // 2M
})

module.exports = multerConfig
在该配置中可以设置文件保存的地址、文件名称、限制上传的文件格式、文件大小

2.2 upload上传逻辑
在 multer 目录下创建 upload.js,编写如下代码:

// 引入配置好的 multerConfig
const multerConfig = require('./multerConfig')

// 上传到服务器地址
const BaseURL = 'http://localhost:3001' 
// 上传到服务器的目录
const imgPath = '/public/'

// 封装上传图片的接口
function uploadAvatar(req, res) {
  return new Promise((resolve, reject) => {
    multerConfig.single('file')(req, res, function (err) {
      if (err) {
        // 传递的图片格式错误或者超出文件限制大小,就会reject出去
        reject(err)
      } else {
        // 拼接成完整的服务器静态资源图片路径
        resolve(BaseURL + imgPath + req.file.filename)
      }
    })
  })
}

module.exports = uploadAvatar

上述代码主要是封装了一个上传文件的方法,当图片上传成功时就将拼接好的图片链接 resolve 出去。该方法会在控制器中被调用。

注意:上面的 multerConfig.single(‘file’) 表示单文件上传,并且字段名为 “file”,后面上传图片的字段必须保持一致

3.编写控制器,定义路由

3.1 编写控制器

controllers 目录下创建 UserController.js,编写如下代码:

const uploadAvatar = require('../multer/upload')

// 用户的逻辑控制器
const UserController = {
  // 头像图片上传
  async upload(req, res) {
    try {
      const uploadRes = await uploadAvatar(req, res)
      res.send({
        meta: { code: 200, msg: '上传成功!' },
        data: { img_url: uploadRes}
      })
    } catch (error) {
      res.send(error)
    }
  }
}

module.exports = UserController

上述代码主要是编写了一个用户控制器类 UserController,以及一个图片上传的方法 upload。在 upload 中调用了上传图片的接口 uploadAvatar,得到成功或失败的结果,在响应给客户端。

3.2 定义路由

① 在 routers 目录下创建 index.js,编写如下代码:

const express = require('express')

// 导入用户逻辑
const userController = require('../controllers/UserController')

// 创建路由对象
const router = express.Router()

// 设置路由
router.post('/upload/avatar', userController.upload)

// 导入路由对象
module.exports = router

② 定义了路由之后还需要在 app.js 中注册路由,添加如下代码:

// 导入定义的路由
const router = require('./src/routers/index')

// 注册路由
app.use('/user', router)
在 app.js 中,增加上面两行代码即可完成路由注册

4.上传图片

接下来进入测试环节,借助 postman 工具进行测试
image-20221231144808803
可以看到,成功的拿到了响应的数据,里面也包含了图片的链接地址
注意点:

  • 表单必须是 form-data 格式
  • 文件的字段必须与后端保持一致

5.图片名称优化


由于这是一个用户上传头像图片的功能,当用户第二次上传头像时,需要将原先的图片删除掉,否则旧的图片会一直保存在服务器中。一开始的想法是使用用户id作为图片名称,这样每一次上传图片,都会把原来的图片覆盖掉。但是这样会有两个问题:

  • 不同格式的图片会残留(jpg、png、gif),不会被覆盖掉
  • 如果可以覆盖,但是图片链接地址不会有变化,存入数据库时也是跟上一次的图片地址是相同的,这样会导致前端页面不会根据静态资源中头像图片变化而变化

所以这里采用的做法是先对图片的名称进行拼接优化,改为如下的形式:时间戳.用户id.jpg;这样既能保证每一张图片都不重复,而且还附带了用户的id

注:可以使用 md5 对时间戳进行加密,以确保唯一性。这里为了方便就直接使用时间戳。

6.图片名称优化实现


这个过程其实就是删除旧图片,重命名新图片为规定的格式,可以编写一个函数来实现。

6.1 图片去重删除和重命名

  • 查找指定路径下的所有图片文件,进行遍历
  • 先查询该id命名的文件是否存在,存在则删除
  • 根据新存入的文件名(时间戳.jpg),找到对应文件,然后重命名为: 时间戳.id.jpg
    在upload.js,编写如下代码:
const fs = require('fs')

// 对图片进行去重删除和重命名
const hanldeImgDelAndRename = (id, filename, dirPath) => {
  // TODO 查找该路径下的所有图片文件
  fs.readdir(dirPath, (err, files) => {
    for (let i in files) {
      // 当前图片的名称
      const currentImgName = path.basename(files[i])
      // 图片的名称数组:[时间戳, id, 后缀]
      const imgNameArr = currentImgName.split('.')

      // TODO 先查询该id命名的文件是否存在,有则删除
      if (imgNameArr[1] === id) {
        const currentImgPath = dirPath + '/' + currentImgName
        fs.unlink(currentImgPath, (err) => { })
      }

      // TODO 根据新存入的文件名(时间戳.jpg),找到对应文件,然后重命名为: 时间戳.id.jpg
      if (currentImgName === filename) {
        const old_path = dirPath + '/' + currentImgName
        const new_path = dirPath + '/' + imgNameArr[0] + '.' + id  + path.extname(files[i])
        // 重命名该文件
        fs.rename(old_path, new_path, (err) => { })
      }
    }
  })
}

函数执行过程分析:

  • 该函数主要调用了 fs 内置模块中的 readdir 进行指定路径查询文件,再进行遍历
  • 将图片名称分割为数组,取出id与传入的id进行判断,符合条件则调用 fs 内置模块中的 fs.unlink() 方法删除文件
  • 根据新存入的文件名(时间戳.jpg),找到对应文件,然后重命名为: 时间戳.id.jpg。然后调用 fs 内置模块中的 fs.rename() 方法重命名文件

6.2 修改 uploadAvatar 接口

完成图片去重删除和重命名 hanldeImgDelAndRename 方法后,还需要在 upload.js 中原先的上传接口方法 uploadAvatar 中进行调用,修改为如下代码:

const path = require('path')
// 封装处理路径函数
const handlePath = (dir) => {
  return path.join(__dirname, './', dir)
}

// 上传接口的 请求参数req  响应参数res
function uploadAvatar(req, res) {
  return new Promise((resolve, reject) => {
    multerConfig.single('file')(req, res, function (err) {
      if (err) {
        reject(err)
      } else {
        // 对图片进行去重删除和重命名
        hanldeImgDelAndRename(req.body.id, req.file.filename, handlePath('../../public'))
        const img = req.file.filename.split('.')
        resolve({
          id: req.body.id,
          // 重新返回符合规定的图片链接地址
          img_url: BaseURL + imgPath + img[0] + '.' + req.body.id + '.' + img[1]
        })
      }
    })
  })
}

注意:在上传文件时,必须携带 id 字段,这样 req.body.id 才能获取到传入的 id。

7.最终测试


7.1 第一次上传

image-20221231145520966

可以看到,图片上传成功,并且图片的名称也是按照我们的规定进行拼接,后端服务器也成功保存了上传的图片。
image-20221231145533537

7.2 第二次上传

image-20221231145555139

第二次上传,成功的将相同 id 的旧图片进行了删除,并且重命名了图片名称。

7.3 第三次上传

这里还可以上传不同 id 以表示不同用户上传头像来进行测试,如下:

image-20221231145616872
可以看到,不同 id 之间上传的图片是互不干扰的,只有当 id 匹配时才会进行替换和重命名。最后只需要在控制器当中,把获取到的图片链接地址保存到数据库即可,这里可以根据用户 id 进行保存。

8.ajax上传

<div class="ajax">
  <p>ajax上传</p>
  <form>
    <input type="text" name="username" />
    <input type="password" name="password" />
    <input type="file" name="avatar" />
    <button type="button">上传</button>
  </form>
  <img />
</div>

<script>
  let btn = document.querySelector('.ajax [type=button]');
  var username = document.querySelector('.ajax [name=username]');
  var password = document.querySelector('.ajax [name=password]');
  var avatar = document.querySelector('.ajax [name=avatar');

  avatar.addEventListener('change', () => {
    // 创建预览地址
    let httpUrl = window.webkitURL.createObjectURL(new Blob(avatar.files));
    document.querySelector('img').src = httpUrl;
  });

  btn.addEventListener('click', () => {
    // 要处理成表单对象上传
    const formsdata = new FormData();
    formsdata.append('username', username.value);
    formsdata.append('password', password.value);
    // 追加name值,和文件对象
    formsdata.append('avatar', avatar.files[0]);

    axios
      .post('/user/upload/avatar', formsdata, {
        headers: {
          'Content-Type': 'multipart/form-data',
        },
    })
      .then(res => {
      document.querySelector('img').src = res.data.imgPath;
    });
  });
</script>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/45699.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Ajax】笔记-JQuery发送请求与通用方法

Get请求 语法格式&#xff1a; $.get(url, [data], [callback], [type]) url:请求的 URL 地址。data:请求携带的参数。callback:载入成功时回调函数。type:设置返回内容格式&#xff0c;xml, html, script, json, text, _default。 准备三个按钮分别测试Get 、Post、通用型方…

概率论的学习和整理21:用EXCEL来做假设检验(未完成草稿)

目录 1 EXCEL可以用来做假设检验 1.1 如何打开 数据分析 和 规划求解 1.2 EXCEL里关于正态分布的准备知识 2 基本的假设检验 2.1 最基本的假设检验&#xff0c;单边的Z检验 2.1 双样本F检验 2.1.1 例题 2.1.2 进行F检验之前需要满足一些假设条件 2.1.3 计算步骤 2.1…

Langchain 新手完全指南

原文&#xff1a;Langchain 新手完全指南 Langchain 可能是目前在 AI 领域中最热门的事物之一&#xff0c;仅次于向量数据库。 它是一个框架&#xff0c;用于在大型语言模型上开发应用程序&#xff0c;例如 GPT、LLama、Hugging Face 模型等。 它最初是一个 Python 包&#x…

实战攻防Demo|如何轻松形成自动响应的安全闭环?

从威胁阻断角度来说&#xff0c;拦住黑客的第一步攻击尤为重要。同样&#xff0c;对于攻击者来说&#xff0c;第一步攻击的成本也往往是最高的。日常工作中人们会遇到很多类型的攻击&#xff0c;但暴力破解或者撞库攻击往往被作为黑客的第一步攻击。这主要源于其技术含量低&…

【网络教程】如何快速的解决WordPress“另一更新正在进行”的问题

文章目录 WordPress提示“另一更新正在进行”解决方案手动删除数据库记录使用插件WordPress提示“另一更新正在进行” 当我们在更新WordPress的插件或者升级WordPress时会出现后台提示“另一更新正在进行”,如下图 当我们点击更新后,出现下图提示 出现上述问题是由于在升级Wo…

平板用的触控笔什么牌子好?ipad第三方电容笔推荐

随着技术的发展&#xff0c;出现了各种各样的平板电容笔。一支好的电容笔&#xff0c;不但可以极大地提升我们的工作效率&#xff0c;还可以极大地提升我们的学习效果。平替的电容笔&#xff0c;无论是在技术方面&#xff0c;还是在质量方面&#xff0c;都还有很大的提升空间&a…

在家下载论文使用哪些论文下载工具比较好

在家下载论文如果不借助论文下载工具是非常艰难的事情&#xff0c;因为很多查找下载论文的数据库都是需要账号权限才可使用的。 例如&#xff0c;我们查找中文论文常用的知网、万方等数据库以及众多国外论文数据库。 在家下载知网、万方数据库论文可用下面的方法&#xff1a;…

Apache pulsar 技术系列-- 消息重推的几种方式

导语 Apache Pulsar 是一个多租户、高性能的服务间消息传输解决方案&#xff0c;支持多租户、低延时、读写分离、跨地域复制&#xff08;GEO replication&#xff09;、快速扩容、灵活容错等特性。在很多场景下&#xff0c;用户需要通过 MQ 实现消息的重新推送能力&#xff0c…

RTI无线电层析成像Matlab仿真数据生成

文章目录 概述初始化环境参数 概述 无线电层析成像是一种通过获取一定区域内多对相对固定的无线通信节点间的某种测量数据后,按照一定的数学处理方法,对区域内的障碍物目标以图像的形式 展现出来的成像技术。 开山之作&#xff1a; J. Wilson and N. Patwari, “Radio tomogra…

教师ChatGPT的23种用法

火爆全网的ChatGPT&#xff0c;作为教师应该如何正确使用&#xff1f;本文梳理了教师ChatGPT的23种用法&#xff0c;一起来看看吧&#xff01; 1、回答问题 ChatGPT可用于实时回答问题&#xff0c;使其成为需要快速获取信息的学生的有用工具。 从这个意义上说&#xff0c;Cha…

天气越热越不能开空调,这是什么道理?

如今正值盛夏&#xff0c;炎热的太阳仿佛要把人烤化。相信很多小伙伴一回到家都会迫不及待地打开空调&#xff0c;在干爽的凉风中完成“自我复活”。然而需要警惕的是&#xff0c;相对密闭的空调房其实早已“暗藏杀机”&#xff0c;VOC、细菌、灰尘等室内“健康杀手”在房间里不…

Latex | 将MATLAB图并导入Latex中的方法

一、问题描述 用Latex时写paper时&#xff0c;要导入MATLAB生成的图进去 二、解决思路 &#xff08;1&#xff09;在MATLAB生成图片的窗口中&#xff0c;导出.eps矢量图 &#xff08;2&#xff09;把图上传到overleaf的目录 &#xff08;3&#xff09;在文中添加相应代码 三…

[ 容器 ] consul 容器服务更新与发现

目录 什么是服务注册与发现什么是consulconsul 部署consul 服务器 registrator服务器consul-templateconsul 多节点 什么是服务注册与发现 服务注册与发现是微服务架构中不可或缺的重要组件。起初服务都是单节点的&#xff0c;不保障高可用性&#xff0c;也不考虑服务的压力承…

ftp和sftp区别,以及xftp的使用

网上找链接找的很辛苦对吧&#xff01; 网上下载的破解版还不用。而且用没多久又说要更新了&#xff0c;又得重新找。 这下直接把官方免费获取链接发给你&#xff0c;就不用在被这种事情麻烦了。 家庭/学校免费 - NetSarang Website (xshell.com):家庭/学校免费 - NetSarang W…

CAN bus off ——ISO11898

什么是can bus off&#xff1f; CAN总线关闭&#xff08;CAN bus off&#xff09;是指CAN节点进入一种错误状态&#xff0c;无法继续正常的数据通信。当一个CAN节点的错误计数器超过了设定的阈值时&#xff0c;该节点将进入CAN总线关闭状态。在这种状态下&#xff0c;该节点将停…

NoSQL之Redis配置与优化

目录 关系数据库和非关系数据库 关系型数据库 非关系型数据库 关系数据库和非关系数据库的区别 Redis安装部署 优点 Redis数据库常用命令 Redis持久化 Redis性能管理 非关系数据库产生背景 总结关系与非关系 了解redis redis优点 redis为什么这么快 1、线程池优化…

Doris(二) -通过外部表同步数据

前言 参考网址 1.官网 2.ODBC External Table Of Doris 3.Apache doris ODBC外表使用方式 第一步 创建 RESOURCE DROP RESOURCE IF EXISTS mysql_test_odbc; CREATE EXTERNAL RESOURCE mysql_test_odbc PROPERTIES ( "type" "odbc_catalog", "…

java 支持jsonschema

入参校验产品化 schema_xsd可视化编辑器_个人渣记录仅为自己搜索用的博客-CSDN博客 jsonchema的生成 支持v4的jackson-jsonSchema GitHub - mbknor/mbknor-jackson-jsonSchema: Generate JSON Schema with Polymorphism using Jackson annotations jackson-module-jsonSchema …

WPF实现DiagramChart

1、文件架构 2、FlowChartStencils.xaml <ResourceDictionary xmlns"http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x"http://schemas.microsoft.com/winfx/2006/xaml"xmlns:s"clr-namespace:DiagramDesigner"xmlns:c&…

大语言模型LLM技术赋能软件项目管理和质量保障︱微软中国高级研发经理步绍鹏

微软中国高级研发经理步绍鹏先生受邀为由PMO评论主办的2023第十二届中国PMO大会演讲嘉宾&#xff0c;演讲议题&#xff1a;大语言模型LLM技术赋能软件项目管理和质量保障。大会将于8月12-13日在北京举办&#xff0c;敬请关注&#xff01; 议题内容简要&#xff1a; 本次分享将…