上一篇文章我们实现了小黄书后台框架中的鉴权服务。今天我们会开始实现文件上传的服务,因为我们参考的小红书中有很多地方是需要上传图片的,比如商品的图片等。

1. Express Multer中间件


Express框架下进行文件上传的一个很好用的中间件就是Multer:
https://github.com/expressjs/multer

它提供的Readme有很好的例子指导我们如何使用该中间件来进行文件的上传。

之所以叫Multer,顾名思义,应该就是为了支持Multipart/Form的http请求,也就是文件上传的请求。

1.1. 一个简单的例子

最简单的一个使用方法就是:

var express = require('express')
var multer  = require('multer')
var upload = multer({ dest: 'uploads/' })var app = express()app.post('/profile', upload.single('avatar'), function (req, res, next) {// req.file is the `avatar` file// req.body will hold the text fields, if there were any
})

这里有几点需要说下的是:

  • multer的构造函数接受一个json接口的参数,我们可以指定存储的位置,修改文件名的方法(multer默认会生成一个唯一的文件id来作为文件名来进行存储,但有时我们需要修改成其他名字)等。比如我们这里指定的存储位置就是项目根目录下的的uploads目录。

  • 代码中的路由的写法和我们之前的路由写法有点不一样,这里是一个堆叠的路由:先用multer中间件进行文件上传的处理,处理好后再传给我们自己编写的路由中间件。

  • upload.single(‘avatar’),代表只接受上传一个文件,且指定的文件名是’avatar’。

  • 中间件执行完后,会在req.file中加入上传后的文件的文件名,文件大小,文件类型,和文件存储路径等信息:

{ fieldname: 'avatar',originalname: '跑车.jpg',encoding: '7bit',mimetype: 'image/jpeg',destination: '/Users/apple/Develop/wechatapp/XiaoHuangShuServer/server/routes/uploads/',filename: '1c61d2999a0f3d94c1461badbf22522e',path: '/Users/apple/Develop/wechatapp/XiaoHuangShuServer/server/routes/uploads/1c61d2999a0f3d94c1461badbf22522e',size: 348524}
  • req.body中会存储传过来的body中的其他键值信息,比如我们定义一个标签Tag。
{ tag: 'public' }

因为我们这里上传的是图片,所以我们简单修改下,在routes/file.js上实现以下代码:

var express = require('express')
var multer  = require('multer')
var upload = multer({ dest: 'uploads/' })var app = express()app.post('/upload', upload.single('image'), function (req, res, next) {res.send('Upload completed');
})

1.2. 修改文件名

Multer允许我们在初始化multer时,传入storage参数来更好的控制我们的文件存储方式,比如修改文件名等。

这里我们希望将文件名修改为由uuid作为文件名,且保留图片的后缀,然后存储到项目根目录的uploads上面。所以我们的multer的初始化可以做以下修改:

const storage = multer.diskStorage({destination: function (req, file, cb) {cb(null, "uploads/");},filename: function (req, file, cb) {const fileFormat = (file.originalname).split(".");const random = uuid.v4();cb(null, random  + "." + fileFormat[fileFormat.length - 1]);},});const upload = multer({storage: storage,
});

其中回调函数中传进来的file对象会包含解析前的基本信息:

{ fieldname: 'image',originalname: '跑车.jpg',encoding: '7bit',mimetype: 'image/jpeg'}

经过multer中间件后,req.file的内容将会变成下面这样:

{ fieldname: 'image',originalname: '跑车.jpg',encoding: '7bit',mimetype: 'image/jpeg',destination: 'uploads/',filename: '3e1e9cc8-ab8f-44c9-b874-8ed53368875e.jpg',path: 'uploads/3e1e9cc8-ab8f-44c9-b874-8ed53368875e.jpg',size: 348524
}

1.3. 过滤文件类型

我们这里只接受图片类型文件的上传,查看multer的README,我们知道在初始化multer时除了storage还有一个fileFilter,可以用来满足我们这个需求。

function fileFilter (req, file, cb) {// The function should call `cb` with a boolean// to indicate if the file should be accepted// To reject this file pass `false`, like so:cb(null, false)// To accept the file pass `true`, like so:cb(null, true)// You can always pass an error if something goes wrong:cb(new Error('I don\'t have a clue!'))}

如代码注释所言,主要是通过cb中的第二个参数来做控制,如果是我们可以接受的图片文件内容,就设置成true,否则设置成false。设置成false的话,req.file就会变成undefined,我们就可以在我们自己写得路由中间件中做处理。

multer初始化代码修改如下:

const upload = multer({storage: storage,fileFilter: (req, file, cb) => {if (file.mimetype === 'image/png' || file.mimetype === 'image/jpeg' || file.mimetype === 'image/gif') {cb(null, true);} else {cb(null, false);}}
});

路由中间件代码修改如下:

router.post('/', upload.single('image'), async (req, res, next) => {try {if (!req.file) {res.status(400).send('Missing file content');return;}res.send('Upload completed');} catch (e) {next(e);}
});

如果客户端上传的不是图片文件,就返回个400 Bad Request给客户端。

1.4 限制上传文件大小

同时为了网络传输和客户端展示效率,我们应该限制上传的文件大小。查看multer的文档,我们在初始化multer时可以用limits参数来进行限制,默认使用的单位时比特。

limits参数有以下的选项,我们这里只限制文件大小。

Key Description Default
fileSize For multipart forms, the max file size (in bytes) Infinity
fieldNameSize Max field name size 100 bytes
fieldSize Max field value size 1MB
fields Max number of non-file fields Infinity
files For multipart forms, the max number of file fields Infinity
parts For multipart forms, the max number of parts (fields + files) Infinity
headerPairs For multipart forms, the max number of header key=>value pairs to parse 2000

以下是实现代码,限制最大3MB的文件大小。如果超过限制,multer中间件会抛出File Too Large异常。

const upload = multer({storage: storage,fileFilter: (req, file, cb) => {if (file.mimetype === 'image/png' || file.mimetype === 'image/jpeg' || file.mimetype === 'image/gif') {cb(null, true);} else {cb(null, false);}},limits: {fileSize: 3 * 1024 * 1024}, // 3MB file size limits
});

2. 保存图片文件信息


图片上传到服务器之后,我们需要记录下文件的一些关键信息,比如访问路径等。这样的话我们就可以通过查询数据库知道某个商品相关的图片的具体信息。

我们到时的商品数据库的collections中很有可能就有一个field是专门用来存储文件id的,然后可以根据这个文件id来查询图片的collections来获得该文件的具体信息。

2.1. 增加图片信息记录

所以,我们这里先定义好我们的图片文件的模型。在models下建立Images.js文件,仿效之前的User.js,实现代码如下:

'use strict';const Schema = require('mongoose').Schema;const db = require('../libs/mongodb');const ImageSchema = new Schema({original: { type: String, required: true },      // 原始文件名filename: { type: String, required: true },      // 目标文件名url: String,                                     // 访问 URL 地址mime_type: String,                               // 文件类型size: Number,                                    // 文件大小
}, {timestamps: { createdAt: 'created_at', updatedAt: 'updated_at' },
});ImageSchema.options.toJSON = {transform: (doc, ret) => {delete ret.__v;return ret;},
};module.exports = db.model('Image', ImageSchema);

然后,我们需要在文件上传的路由中实现Image存储的业务逻辑,完整代码如下:

router.post('/', upload.single('image'), async (req, res, next) => {try {if (!req.file) {res.status(400).send('Missing file content');return;}let image = {};let filename = '';filename = `${req.file.filename}`;image = {filename,original: req.file.originalname,size: req.file.size,mime_type: req.file.mimetype,url: 'http://localhost:3000/v1/uploads/' + filename,};const imageFile = new Image(image);await imageFile.save();res.send(imageFile);} catch (e) {next(e);}
});

每上传一个图片,我们都会在Image这个collections上增加一条记录。

2.2. 删除图片和数据库记录

既然允许用户增加文件,当然也应该允许用户删除文件。比如删除一个商品信息时,应该要把相关的文件也删除掉。

以下就是具体实现:

router.delete('/:id', async (req, res, next) => {try {const query = {};query._id = req.params.id;const file = await Image.findOne(query);if (!file) {throw new ClientError.NotFoundError();}fs.unlink(`uploads/${file.filename}`, (fserr) => {if (fserr) {log.error('Failed to remove upload tmp file', `uploads/${file.filename}`);res.status(500).send('Failed to del file');return;}});await file.remove();res.status(204).end();} catch (e) {next(e);}
});

先是从磁盘中将文件删除掉,然后从数据库中将对应的文件记录给清掉。

最后,我们不需要将uploads文件夹下的图片上传到github,所以在.gitignore中加入下面这行:

uploads/

3. 访问文件


文件上传后,我们可以在浏览器中输入:http://localhost:3000/v1/uploads/文件名 来进行图片访问。

但是,访问之前,我们有几个地方需要改一下:

  • 需要在server.js后面加上这一行:
app.use('/v1/uploads', express.static('uploads/'));

express.static是express的内置中间件,专门用来提供静态文件资源服务的。所有"/v1/uploads"开始的资源请求,都会在uploads文件夹下提供。

  • 然后在鉴权中间件中开放对“/v1/uploads”开始的路由的访问,我们访问服务器上的图片是不需要提供访问令牌的:
...// APIS need no authenticationif (req.path === '/'|| req.path === '/v1/auth/login'|| req.path.startsWith('/v1/uploads')) {log.debug('no auth required');next();return;}
...

这时从浏览器访问还是会因为我们没有提供favicon.ico而失败,我们这里不做这个文件的提供,直接在鉴权中间件中对此请求飘过:

      if (req.path === '/favicon.ico') {res.status(404).end();return;}

4. 结语


重构后和完整的代码请从github中获取。

  • git clone https://github.com/zhubaitian/XiaoHuangShuServer.git
  • cd XiaoHuangShuServer/
  • git checkout CH06
  • npm install
  • gulp dev

这一系列文章其实我写了有段时间了,后来忙起来忘了发布了

[小黄书后台]文件上传到服务器相关推荐

  1. [小黄书后台]文件上传到CDN

    上一篇文章我们通过multer这个中间件将图片顺利的上传到了我们的服务器上面,且将图片的元数据存储到了Image这个mongodb的collections里面. 这一章我们看下应该如何将文件上传到cd ...

  2. 小程序 图片上传php后台,微信小程序图片选择、上传到服务器、预览(PHP)实现实例...

    微信小程序图片选择.上传到服务器.预览(php)实现实例 小程序实现选择图片.预览图片.上传到开发者服务器上 后台使用的tp3.2 图片上传 请求时候的header参考时可以去掉(个人后台验证权限使用 ...

  3. 微信小程序开发之文件上传下载应用场景(附Demo源码)

    微信小程序开发之文件上传下载应用场景(附Demo源码),Demo为小相册应用,源码在附件中,本示例需要腾讯云支持. http://www.henkuai.com/forum.php?mod=viewt ...

  4. 漏洞案例之z-blog后台文件上传

    z-blog后台文件上传 后台登录地址 php版本后台登录地址:你的域名/zb_system/login.php asp版本后台登录地址:后缀为/zb_system/login.asp zblogge ...

  5. 微信小程序+SpringBoot实现文件上传与下载

    微信小程序+SpringBoot实现文件上传与下载 1.文件上传 1.1 后端部分 1.1.1 引入Apache Commons FIleUpload组件依赖 1.1.2 设置上传文件大小限制 1.1 ...

  6. Apache Axis2 后台文件上传getshell 漏洞复现

    0x00 前言 Apache Axis2是一个Web服务的核心支援引擎.AXIS2对旧有的AXIS重新设计及重写,并提供两种语言Java及C的开发版本. 事实上AXIS2 不只为WEB应用程序提供We ...

  7. asp.net ftp上传文件到服务器,.net 文件上传到服务器上

    详解 Linux 下 SSH 远程文件传输命令 scp 3.将本地文件上传到服务器上 scp-P 2222/home/lnmp0.4.tar.gz root@www.vpser.net:/root/l ...

  8. php 点击选择图片上传,微信小程序图片选择、上传到服务器、预览(PHP)实现实例...

    微信小程序图片选择.上传到服务器.预览(PHP)实现实例 小程序实现选择图片.预览图片.上传到开发者服务器上 后台使用的tp3.2 图片上传 请求时候的header参考时可以去掉(个人后台验证权限使用 ...

  9. 微信图片 自动上传到服务器,微信小程序怎样使图片上传至服务器

    这次给大家带来微信小程序怎样使图片上传至服务器,微信小程序使图片上传至服务器的注意事项有哪些,下面就是实战案例,一起来看一下.-wxml 发布项目 /**选择图片 */ choose: functio ...

最新文章

  1. 我的Java开发学习之旅------gt;Java经典排序算法之希尔排序
  2. hdu3724 字典树(商品条形码)
  3. 【云炬大学生创业基础笔记】第1章第1节 测试
  4. mysql消除重复行的关键字_MySQL 消除重复行的一些方法
  5. git stash命令的用法
  6. 系统设计基础:系统设计基本任务相关知识
  7. 可编程交换时代就在这里
  8. Cow Contest【最短路-floyd】
  9. 2017级C语言大作业 - 元气骑士
  10. BCELoss忽视某个类别
  11. webbench 压力测试软件
  12. Hadoop出现core-site.xml not found的解决办法
  13. 爬虫日记-采集 快代理 免费 代理ip 并 清洗 ip 附源码gitee,可运行
  14. ethtool修改网卡mac地址流程
  15. 程序猿之国庆有空吗?
  16. Google mediapipe 人脸识别应用
  17. 八层高速PCB板叠层设计
  18. 用LU_ASR语音控制板和Arduinonano做一个桌面老婆(1)
  19. glibc==2.17 报错
  20. Solo.io发布Gloo Mesh Enterprise 2.0

热门文章

  1. Python3.8安装Pytorch
  2. lightroom最新版本下载_Lightroom CC 2019|Lightroom CC 2019 正式版下载_太平洋下载中心...
  3. JAVA在鼠标点击位置绘制圆,单击鼠标后在JPanel上绘制圆圈
  4. 新版kettle学习
  5. push进队列的C2075错误
  6. Idea 控制台console 不能搜索日志 CTRL F 快捷键无效
  7. Android ViewGroup介绍+实例,大厂架构师经验分享
  8. 也谈智能手机游戏开发中的分辨率自适应问题
  9. 使用potplayer播放器看直播
  10. WPS 无法覆盖选中文字