express-multer-upload工程项目,然后使用 npm i express 等命令在项目中下好各种依赖包,
下面是我下载的依赖以及版本。
由于本次不涉及数据库操作,所以省略了数据库访问层
const express = require('express')
const bodyParser = require('body-parser')
const cors = require('cors')
// 创建 app 实例
const app = express()
// 开启跨域支持
app.use(cors())
// 解析表单
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({ extended: true }))
// * 简单测试接口,后面会删掉
app.get('/', (req, res) => {
res.send('ok')
})
// 开启监听服务
app.listen('3001', () => {
console.log('express serve running at http://localhost:3001');
})
在终端输入 nodemon app.js 命令开启服务,可以看到服务已经跑起来了:
注意:没有安装 nodemon 也可以使用 node app.js 命令
可以看到成功的拿到了请求响应的数据
// 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
在该配置中可以设置文件保存的地址、文件名称、限制上传的文件格式、文件大小
// 引入配置好的 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",后面上传图片的字段必须保持一致
const uploadAvatar = require('../multer/upload')
// 用户的逻辑控制器
class 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 = new UserController()
上述代码主要是编写了一个用户控制器类 UserController,以及一个图片上传的方法 upload。
在 upload 中调用了上传图片的接口 uploadAvatar,得到成功或失败的结果,在响应给客户端。
const express = require('express')
// 导入用户逻辑
const userController = require('../controllers/UserController')
// 创建路由对象
const router = express.Router()
// 设置路由
router.post('/upload/avatar', userController.upload)
// 导入路由对象
module.exports = router
// 导入定义的路由
const router = require('./src/routers/index')
// 注册路由
app.use('/user', router)
在 app.js 中,增加上面两行代码即可完成路由注册
可以看到,成功的拿到了响应的数据,里面也包含了图片的链接地址
注意点:
显然,这样是看不到图片的,因为我们还没开启静态服务的访问,下面来看看怎么开启吧
app.use('/public/', express.static('./public/'))
然后再打开图片地址,就可以成功访问上传到服务器内的图片了!
时间戳.用户id.jpg
这样既能保证每一张图片都不重复,而且还附带了用户的id
注:可以使用 md5 对时间戳进行加密,以确保唯一性。这里为了方便就直接使用时间戳。
时间戳.id.jpgconst 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 进行指定路径查询文件,再进行遍历
fs 内置模块中的 fs.unlink() 方法删除文件
时间戳.id.jpg。然后调用 fs 内置模块中的 fs.rename() 方法重命名文件
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。
可以看到,图片上传成功,并且图片的名称也是按照我们的规定进行拼接,后端服务器也成功保存了上传的图片。
第二次上传,成功的将相同 id 的旧图片进行了删除,并且重命名了图片名称。
可以看到,不同 id 之间上传的图片是互不干扰的,只有当 id 匹配时才会进行替换和重命名。
最后只需要在控制器当中,把获取到的图片链接地址保存到数据库即可,这里可以根据用户 id 进行保存。
到此为止,这个Node+Express+Multer实现图片上传的功能就完成啦~
每文一句:书山有路勤为径,学海无涯苦作舟。本次的分享就到这里,如果本章内容对你有所帮助的话欢迎点赞+收藏。文章有不对的地方欢迎指出,有任何疑问都可以在评论区留言。希望大家都能够有所收获,大家一起探讨、进步!
我发现ActiveRecord::Base.transaction在复杂方法中非常有效。我想知道是否可以在如下事务中从AWSS3上传/删除文件:S3Object.transactiondo#writeintofiles#raiseanexceptionend引发异常后,每个操作都应在S3上回滚。S3Object这可能吗?? 最佳答案 虽然S3API具有批量删除功能,但它不支持事务,因为每个删除操作都可以独立于其他操作成功/失败。该API不提供任何批量上传功能(通过PUT或POST),因此每个上传操作都是通过一个独立的API调用完成的
我有一个用户工厂。我希望默认情况下确认用户。但是鉴于unconfirmed特征,我不希望它们被确认。虽然我有一个基于实现细节而不是抽象的工作实现,但我想知道如何正确地做到这一点。factory:userdoafter(:create)do|user,evaluator|#unwantedimplementationdetailshereunlessFactoryGirl.factories[:user].defined_traits.map(&:name).include?(:unconfirmed)user.confirm!endendtrait:unconfirmeddoenden
我是Rails的新手,所以请原谅简单的问题。我正在为一家公司创建一个网站。那家公司想在网站上展示它的客户。我想让客户自己管理这个。我正在为“客户”生成一个表格,我想要的三列是:公司名称、公司描述和Logo。对于名称,我使用的是name:string但不确定如何在脚本/生成脚手架终端命令中最好地创建描述列(因为我打算将其设置为文本区域)和图片。我怀疑描述(我想成为一个文本区域)应该仍然是描述:字符串,然后以实际形式进行调整。不确定如何处理图片字段。那么……说来话长:我在脚手架命令中输入什么来生成描述和图片列? 最佳答案 对于“文本”数
我有带有Logo图像的公司模型has_attached_file:logo我用他们的Logo创建了许多公司。现在,我需要添加新样式has_attached_file:logo,:styles=>{:small=>"30x15>",:medium=>"155x85>"}我是否应该重新上传所有旧数据以重新生成新样式?我不这么认为……或者有什么rake任务可以重新生成样式吗? 最佳答案 参见Thumbnail-Generation.如果rake任务不适合你,你应该能够在控制台中使用一个片段来调用重新处理!关于相关公司
我在Rails应用程序中使用CarrierWave/Fog将视频上传到AmazonS3。有没有办法判断上传的进度,让我可以显示上传进度如何? 最佳答案 CarrierWave和Fog本身没有这种功能;你需要一个前端uploader来显示进度。当我不得不解决这个问题时,我使用了jQueryfileupload因为我的堆栈中已经有jQuery。甚至还有apostonCarrierWaveintegration因此您只需按照那里的说明操作即可获得适用于您的应用的进度条。 关于ruby-on-r
华为OD机试题本篇题目:明明的随机数题目输入描述输出描述:示例1输入输出说明代码编写思路最近更新的博客华为od2023|什么是华为od,od薪资待遇,od机试题清单华为OD机试真题大全,用Python解华为机试题|机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为o
一、引擎主循环UE版本:4.27一、引擎主循环的位置:Launch.cpp:GuardedMain函数二、、GuardedMain函数执行逻辑:1、EnginePreInit:加载大多数模块int32ErrorLevel=EnginePreInit(CmdLine);PreInit模块加载顺序:模块加载过程:(1)注册模块中定义的UObject,同时为每个类构造一个类默认对象(CDO,记录类的默认状态,作为模板用于子类实例创建)(2)调用模块的StartUpModule方法2、FEngineLoop::Init()1、检查Engine的配置文件找出使用了哪一个GameEngine类(UGame
C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.
文章目录1.开发板选择*用到的资源2.串口通信(个人理解)3.代码分析(注释比较详细)1.主函数2.串口1配置3.串口2配置以及中断函数4.注意问题5.源码链接1.开发板选择我用的是STM32F103RCT6的板子,不过代码大概在F103系列的板子上都可以运行,我试过在野火103的霸道板上也可以,主要看一下串口对应的引脚一不一样就行了,不一样的就更改一下。*用到的资源keil5软件这里用到了两个串口资源,采集数据一个,串口通信一个,板子对应引脚如下:串口1,TX:PA9,RX:PA10串口2,TX:PA2,RX:PA32.串口通信(个人理解)我就从串口采集传感器数据这个过程说一下我自己的理解,
MIMO技术的优缺点优点通过下面三个增益来总体概括:阵列增益。阵列增益是指由于接收机通过对接收信号的相干合并而活得的平均SNR的提高。在发射机不知道信道信息的情况下,MIMO系统可以获得的阵列增益与接收天线数成正比复用增益。在采用空间复用方案的MIMO系统中,可以获得复用增益,即信道容量成倍增加。信道容量的增加与min(Nt,Nr)成正比分集增益。在采用空间分集方案的MIMO系统中,可以获得分集增益,即可靠性性能的改善。分集增益用独立衰落支路数来描述,即分集指数。在使用了空时编码的MIMO系统中,由于接收天线或发射天线之间的间距较远,可认为它们各自的大尺度衰落是相互独立的,因此分布式MIMO