START
- 前几天熬夜做了一个基于
nodejs
的后端服务,连接mysql
数据库搞定了。 - 但是最近遇到上传图片一个需求,这如何实现呢?别着急,番茄带你一点一点实现。
- 本文作者:lazy_tomato
- 编写时间:2022/03/31-22/21
前情提要
- 首先我们已经基于
express
搭建了一个后端服务。 - 其次我们使用
mysql5
已经成功连接并可以操作数据库了。
不清楚这些的可以访问我上一篇博客 :https://blog.csdn.net/wswq2505655377/article/details/123805305?spm=1001.2014.3001.5501
思路说明
1.Multer中间件
不清楚上传图片到底要怎么做?第一步就卡住了。不着急,咱们百度。
番茄上来就百度了
express 上传图片
,百度到了很多相关的博客,但是质量参差不齐,番茄并没有仔细查看。每个博客大概浏览一下主体逻辑。发现一个高频词汇multer
花了10分钟左右,看了很多博客,番茄得出一个结论,上传文件肯定是借助于multer
这个工具来的,但是multer
是什么,咱们不知道。走继续百度,别的不管直奔multer
官网。
multer
Github
的README-zh-cn.md
https://github.com/expressjs/multer/blob/master/doc/README-zh-cn.md
Multer官方解释
Multer
是一个 node.js 中间件,用于处理 multipart/form-data
类型的表单数据,它主要用于上传文件。它是写在 busboy 之上非常高效。
注意: Multer
不会处理任何非 multipart/form-data
类型的表单数据。
中间件: 中间件是介于应用系统和系统软件之间的一类软件 (我简单的解释一下这里就是:在我们代码和电脑之间的一个中间层的软件,帮我们去做一些数据处理的软件)。
multipart/form-data:我们接入上传接口经常会见到的数据格式,这个很熟悉,就是我们html表单提交的数据格式。对于接入上传和导出接口这里后面我会单独在写一篇博客用于总结
busboy:用于解析传入 HTML 表单数据的 node.js 模块。
ps:最后的注意,就提醒我们,后面上传数据应该传
form-data
类型的数据
2.制定目标
到这里番茄看了下官网的基本示例,OK,应该可以通过这个工具我们就可以上传图片了。在开始动手做之前。我们再捋一下我们的目标。清楚目标才能更好的完成它。
2.1 目标
实现图片上传到服务器,并存储到数据库中
2.2 拆分目标
2.2.1 图片上传到服务器
其实分三种。番茄之前涉及到过,在这里就直接说结论:
-
第一种是借助
Multer
这个工具直接把图片上传到服务器即可;简单理解,就像把图片存在我们电脑一样
-
第二种是直接将图片转换成Base64以字符串的形式直接存储到数据库中。
简单理解就是把图片转成Base64形式的字符串,直接存储到数据库的表中字段
-
第三种是上传到对象存储OSS服务上
主流的企业级应用都是上传到OSS服务上,此方案暂时未亲自尝试,后期细说
第二种方案呢,我之前做过类似的上传,图片转换成Base64形式会非常大,数据库的一个字段存储,小一点的图片还好,图片稍微多点,数据库的字段长度就不够了,所以我们这里放弃第二种方案;
第三种方案呢,番茄暂未尝试,后面亲自尝试之后再细说,所以暂时不考虑第三种方案
2.2.2 存储到数据库
由上内容可以知道,目前采用第一种方案去实现功能。所以我们后续还需要存储图片的地址,可供访问,这里需要对图片的地址进行处理。
功能实现
1.安装依赖
npm install --save multer
2.依赖版本
"body-parser": "^1.19.2",
"express": "^4.17.2",
"md5": "^2.3.0",
"multer": "^1.4.4",
"mysql": "^2.18.1",
3.文件目录
4.具体代码
为了方便理解,直接粘贴全部文件代码,详细说明参考注释。
server.js
const express = require('express')
const bodyParser = require('body-parser')
const router = require('./app/router/router')
const app = express()
// 跨域请求处理
app.all('*', (req, res, next) => {
res.header('Access-Control-Allow-Origin', '*')
res.header('Access-Control-Allow-Headers', 'X-Requested-With')
res.header('Access-Control-Allow-Headers', 'Content-Type, Content-Length, Authorization, Accept, X-Requested-With, X_Requested_With')
res.header('Access-Control-Allow-Methods', 'PUT, POST, GET, DELETE, OPTIONS')
//允许接收的请求头上加上一个Authorization,这样我们才能够将数据发送过去
res.header('X-Powered-By', '3.2.1')
// OPTIONS类型的请求 复杂请求的预请求
if (req.method == 'OPTIONS') {
res.send(200)
} else {
/*让options请求快速返回*/
next()
}
})
// 公开静态文件夹,匹配`虚拟路径img` 到 `真实路径public` 注意这里 /img/ 前后必须都要有斜杠!!!
app.use('/img/', express.static('./public/'))
// 挂载处理post请求的插件
app.use(bodyParser.urlencoded({ extended: false }))
app.use(bodyParser.json())
// 挂载路由
app.use(router)
// 监听5000端口 启动服务
app.listen('5000', () => {
console.log('Server is running 5000');
})
注意事项:
// 公开静态文件夹,匹配`虚拟路径img` 到 `真实路径public` 注意这里 /img/ 前后必须都要有斜杠!!! `app.use('/img/', express.static('./public/'))`
multerConfig.js
// 1. 引入依赖
const multer = require('multer');
const md5 = require('md5');
// 2. 引入工具
const path = require('path') //
const resolve = (dir) => {
return path.join(__dirname, './', dir)
}
// 3. multer的配置对象
let storage = multer.diskStorage({
// 3.1 存储路径
destination: function (req, file, cb) {
// 3.1.1 允许图片上传
if (file.mimetype === 'image/jpeg' || file.mimetype === 'image/png') {
cb(null, resolve('../../public/images'))
} else {
// 3.1.2 限制其他文件上传类型
cb({ error: 'Mime type not supported' })
}
},
// 3.2 存储名称
filename: function (req, file, cb) {
let fileFormat = (file.originalname).split(".");
cb(null, md5(+new Date()) + "." + fileFormat[fileFormat.length - 1]);
},
});
// 4. 添加配置
const multerConfig = multer({
storage: storage,
});
// 5. 导出配置好的multerConfig
module.exports = multerConfig;
注意事项:
- 引入
md5
是为了给图片编码,确保唯一性,可酌情去除resolve
用于node中的路径拼接mimetype
是文件的mime
类型,我这里只允许上传png
和jpg
格式的图片。mime
类型详细说明可参考https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Basics_of_HTTP/MIME_typesmulterConfig.js
文件存储的呢,是添加了自定义配置的multer
对象
upload.js
// 1. 引入配置好的multerConfig
const multerConfig = require('./multerConfig');
// 2. 定义静态变量
const fileName = "file" // 上传的 fileName 名称
const updateBaseUrl = "http://localhost:5000" // 上传到服务器地址
const imgPath = "/img/images/" // 上传到服务器的虚拟目录
// 上传接口的 请求参数req 响应参数res
function upload(req, res) {
return new Promise((resolve, reject) => {
multerConfig.single(fileName)(req, res, function (err) {
if (err) {
reject(err)
} else {
// `req.file.filename` 请求文件名称后缀
// `updateBaseUrl + imgPath + req.file.filename` 完整的服务器虚拟目录
resolve(updateBaseUrl + imgPath + req.file.filename)
}
});
})
}
module.exports = upload;
注意事项:
- 为了方便调用,这里我将它包裹在了Promise对象中
- 这里需要注意的点呢,就是我抽离出来的三个静态变量。
fileName
后续前端上传文件的时候,定义的字段名需要和它一致(后面前端代码会体现)updateBaseUrl
对应后端服务的ip
地址加端口imgPath
对应代理的公开静态资源地址
db.js
var mysql = require('mysql')
// 创建一个连接池
var pool = mysql.createPool({
host: 'localhost', // ip
user: 'root', // 用户名
password: '123456', // 密码
database: 'healthy' // 数据库名称
});
// 导出查询相关
var query = function (sql, callback) {
pool.getConnection(function (err, conn) {
if (err) {
callback(err, null, null);
} else {
conn.query(sql, function (qerr, vals, fields) {
// 释放连接 *注意:释放连接要在 conn.query*
conn.release();
// 事件驱动回调
callback(qerr, vals, fields);
});
}
});
};
module.exports = query;
router.js
const express = require('express')
const query = require('../modul/db.js')
const upload = require('../multer/upload.js');
const router = express.Router()
// 欢迎
router.get('/', (req, res) => {
res.send('Hello World')
})
// 测试数据库连接接口
router.get('/demo', (req, res) => {
let sql = `SELECT * FROM account`
query({
sql: sql
}, (err, results) => {
if (err) {
console.log(err)
} else {
res.send(results)
}
})
})
// 上传图片接口
router.post('/uploadImage', (req, res) => {
upload(req, res).then(imgsrc => {
// 上传成功 存储文件路径 到数据库中
// swq sql需要修改一下,变成新增,这里测试暂用更新
let sql = `UPDATE account SET imgsrc='${imgsrc}'WHERE id='1' `
query(sql, (err, results) => {
if (err) {
formatErrorMessage(res, err)
} else {
res.send({
"code": "ok",
"message": "上传成功",
'data': {
url: imgsrc
}
})
}
})
}).catch(err => {
formatErrorMessage(res, err.error)
})
})
// 格式化错误信息
function formatErrorMessage(res, message,) {
res.status(500).send({
"code": "error",
"message": message || '',
});
}
module.exports = router
5.前端调用demo
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>lazy_tomato</title>
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>
</head>
<body>
<input type="file" id="file">
<button onclick="doUpload()">开始上传</button>
<img src="" alt="">
<script>
function doUpload() {
let file = $('#file').get(0).files[0];
console.log(file)
//创建空的formData对象
let formdata = new FormData();
// formdata.append的属性名 要和后端保持一致 `file`
formdata.append('file', file);
$.ajax({
url: 'http://localhost:5000/uploadImage',
type: 'POST',
data: formdata,
contentType: false,
success: function (data) {
console.log(data)
$('img').attr('src', data.data.url);
}
}
)
}
</script>
</body>
</html>