一、内容简介
在开发一些图片比较多的页面时,为了提升一些页面性能,加快页面显示,通常会选择把图片压缩后,上传到七牛后直接使用七牛地址,那么开发中写一张图片总共分两步:
- 将图片压缩(一般使用tinypng)后保存在本地;
- 将压缩后图片上传到七牛获取图片链接后写在代码里
所以,我用node搭了一个简单的服务,用来直接处理图片压缩和上传:
二、效果预览
可以看到我选择的图片大概有700kb左右,那么最后生成的图片有多大呢?
这张图片上传到七牛后只有197kb,压缩效果还是挺明显的
这个效果预览是使用了之前自己开发的一个vsCode上传图片插件,对vsCode插件开发感兴趣的同学可以去查看之前写的文章,一起学习进步,也可以直接在vscode中搜索插件
upload-to-qiniu
进行安装。
三、压缩功能实现
实现功能点介绍好了,顺便还给自己的插件打了一个小广告。下面进入正题,如何实现:
其实准确来说,具体的压缩算法我也写不出来,只是利用了几个第三方的方案来实现我们的功能:
- squoosh: 是谷歌大佬开发的一款图片压缩工具,通过全局安装
npm i -g @squoosh/cli
,执行相关命令后实现图片的压缩,直接贴出github地址,大家可以安装在自己本地尝试一下,毕竟谷歌大佬,压缩效果应该还是很不错的;- imagemin: 也是一个比较主流的压缩库,用的人比较多,所以开发了使用这种方式压缩的功能,不过有一个坑,在安装
imagemin-pngquant
时需要在本地先通过brew
安装libpng
,否则的话会导致这个库安装不上去;- 通过七牛提供的方法
compressImage
压缩: 这种方法是在前端项目中安装qiniu-js
依赖,使用七牛提供的apicompressImage
进行压缩,但是经过测试,发现使用这种方式压缩的效果不是很明显,而且也是在前端调用,所以只提供了七牛的文档;- 通过在图片链接中添加七牛参数, 这种链接后添加图片参数的方式也是我最常用的方式,效用比较明显,使用方式也十分简单,只要阅读文档就很容易掌握啦。
1. 项目初始化
先来安装项目中需要的依赖:
"dependencies": {
"@squoosh/cli": "^0.6.1",
"express": "^4.17.1",
"imagemin": "^7.0.1",
"imagemin-jpegtran": "^7.0.0",
"imagemin-pngquant": "^9.0.1",
"multer": "^1.4.2",
"qiniu": "7.3.2",
"body-parser": "^1.19.0"
}
复制代码
其中multer是用来处理前端表单上传的文件,他可以方便我们直接在服务端获取前端上传的文件,会将我们上传的内容保存在指定目录下,所以我新建了一个文件用来保存这些数据:
compress
文件用来保存压缩后的图片, original
文件用来保存压缩前的原图
安装好依赖后在 src/index.js
中启动服务,并新建一个image的路由:
const express = require('express')
const app = express()
const bodyParser = require('body-parser')
const imageRouter = require('./router/image')
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({ extended: false }))
app.use('/image', imageRouter)
app.listen(3000, () => {
console.log('serve is listen on 3000')
})
复制代码
到这里都是基本操作,简单启动一个node服务
2. 确定图片压缩接口的请求参数,定义辅助方法
最重要的一点,我们要知道,我们这个接口要传哪些参数,返回什么数据,先定义好这些内容才可以方便具体功能上的开发:
我直接就是一个接口文档定义出来:
请求方式: POST
请求 URL: serve.lyaayl.com:3000/image/compr…
请求参数
字段 | 字段类型 | 是否必填 | 字段说明 |
---|---|---|---|
file | File | 是 | 表单上传图片 |
type | String: imagemin, squoosh | 否 | 压缩方式,默认为 imagemin |
qiniuConf | JSON String | 否 | 传入七牛参数 (accessKey, secretKey, scope, domain),默认为个人七牛账号用于 demo 测试 |
返回结果
{
"data": {
"type": "success", // 是否压缩成功
"originalSize": "10kb", // 原始图片尺寸
"compressSize": "1kb", // 压缩图片尺寸
"url": "xxx" // 图片七牛链接
},
"status": {
"code": 0, // 请求成功
"msg": "成功"
}
}
复制代码
定义请求成功和失败的辅助方法(src/utils/common.js
)
// 成功返回
const successRes = (data) => {
return {
status: { code: 0, msg: '成功' },
data,
}
}
// 失败返回
const errorRes = (msg) => {
return {
status: { code: 1, msg },
}
}
复制代码
定义相关常量量(src/config.js
)
const path = require('path')
// 压缩图片的存储路径
const COMPRESS_IMG_PATH = path.resolve(__dirname, './image/compress/')
// 原始图片的存储路径
const ORIGINAL_IMG_PATH = path.resolve(__dirname, './image/original/')
// 七牛云上传参数
const QI_NIU_CONFIG = {
accessKey: 'xxx',
secretKey: 'xxx',
scope: 'xxx',
domain: 'xxx',
}
复制代码
3.实现压缩逻辑
实际思路是很简单的:
- 在接受到请求参数后,通过判断压缩类型,使用不同的压缩方法,生成压缩后的图片并保存在
original
文件下;- 压缩完成后,得到文件名,通过node读取到对应文件上传到七牛,返回七牛地址
先看下我实现图片压缩方法的源码:
router.post('/compress', async function (req, res) {
const { type = 'imagemin', qiniuConf = JSON.stringify(QI_NIU_CONFIG) } = req.body
try {
const data = await compressImage(req.files[0], type, JSON.parse(qiniuConf))
res.send(successRes(data))
} catch (err) {
res.send(errorRes(`压缩失败: ${err}`))
}
})
复制代码
是不是很简单呢,这里因为qiniuConf
接受的是一个json
字符串,所以在设置默认配置时,统一转成字符串。
接下来的方法就是compressImage
方法实现压缩,接收文件参数file
,压缩类型type
,七牛参数qiniuConf
实现imagemin压缩
其实这个逻辑是非常简单的,只要我们使用之前安装的imagemin
库就可以处理:
if (type === 'imagemin') {
// 通过imagemin 进行压缩
const bufferFile = await getBufferFromFile(filePath)
const imageBuffer = await imagemin.buffer(bufferFile, {
plugins: [
imageminJpegtran(),
imageminPngquant({
quality: [0.8, 0.9],
}),
],
})
bufferImageToFile(imageBuffer, fileName)
}
复制代码
其中filePath
是我们上传图片后,通过multer
处理后保存在original
目录下的原图,通过方法getBufferFromFile
(其辅助方法这里先说明功能,方法在之前文章node实现图片压缩有介绍)获取图片的buffer,将buffer传入第三发库中实现压缩,压缩完成后,会返回一个buffer文件,最后将buffer写入对应compress
目录中即可完成。
实现squoosh压缩
这个方案思路简单,但是要想用好还是很复杂的,我只是用了默认的配置参数来压缩图片,@squoosh/cli
这个库压缩图片其实可以转成很多种图片类型,每种不同的图片类型都有不同的压缩质量参数可以设置,先看最重要的代码:
if (type === 'squoosh') {
// 通过 squoosh 进行压缩
const cli = getSquooshCli(filePath, extname)
execSync(cli)
}
复制代码
通过参数拼接一个需要执行的shell
命令,然后使用node模块child_process
去执行他,就把图片压缩在对应目录下了,不对,下面才是最重要的代码:
/**
* 拼接压缩图片命令
* @param {String} imagePath 原图路径
* @return {String}
*/
const getSquooshCli = (imagePath, extname) => {
let imageType = extname === 'jpg' ? 'mozjpeg' : 'oxipng'
let quality = extname === 'jpg' ? `'{quality: 75}'` : 'auto'
return `squoosh-cli --${imageType} ${quality} ${imagePath} -d ${COMPRESS_IMG_PATH}`
}
复制代码
拼接一个需要执行的shell命令,其含义是:通过squoosh-cli
压缩图片imagePath
(其中压缩后图片类型为imageType
,质量为quality
),压缩后放在COMPRESS_IMG_PATH
目录下
imageType
这里我只选择了压缩成两种最常见的图片类型jpg
和png
,quality
则为图片压缩的质量,这里我是直接使用了默认值,当值设置过低时,可能会出现图片变得不清晰甚至十分模糊,具体的压缩效果大家可以通过squoosh这个网页去尝试,我选择相信大佬的默认值,所以没有进行调整,也没有把这个参数设置成动态的,也为了方便我们的接口不会有过多参数。
4.将压缩后的图片上传到七牛云
上传七牛是直接使用七牛云提供的node依赖qiniu
,既然都实现了压缩逻辑,那不如再加一个普通的上传图片到七牛云,反正上传方法是一样的:
// 上传图片到七牛
router.post('/upload', async function (req, res) {
try {
const { filename } = req.files[0]
const { qiniuConf = JSON.stringify(QI_NIU_CONFIG) } = req.body
const filePath = path.join(ORIGINAL_IMG_PATH, filename)
const url = await uploadImageToQiniu(filePath, JSON.parse(qiniuConf))
res.send(
successRes({
url,
})
)
} catch (err) {
res.send(errorRes(err))
}
})
复制代码
这里最重要的就是上传到七牛的方法uploadImageToQiniu
,接受图片的路径和七牛配置参数:
/**
* 上传图片到七牛
* @param {string} imagePath 上传图片的路径
* @param {Object} qiniuConf 七牛参数
* @return {Promise<string>} 返回上传成功后图片地址
*/
async function uploadImageToQiniu(imagePath, qiniuConf) {
return new Promise((resolve, reject) => {
const { accessKey, secretKey, scope, domain } = qiniuConf
const config = new qiniu.conf.Config()
const formUploader = new qiniu.form_up.FormUploader(config)
const putExtra = new qiniu.form_up.PutExtra()
const token = getToken(accessKey, secretKey, scope)
// 上传内容
const key = path.parse(imagePath).name
const uploadItem = path.normalize(imagePath)
formUploader.putFile(token, key, uploadItem, putExtra, function (respErr, respBody) {
if (respErr) {
reject(respErr)
} else {
const { key } = respBody
resolve(`${domain}/${key}`)
// 删除image文件中的图片文件
setTimeout(() => {
clearImageFile()
}, 0)
}
})
})
}
复制代码
其中方法大多都是七牛提供的api,最终返回上传后的图片地址,上传完成后,执行clearImageFile
,将之前压缩图片和原图都从目录中删除,就完成上传七牛的逻辑啦。
四、前端demo展示
在开发中其实是发现了一个坑:通过
squoosh
压缩png类型图片是会非常非常慢,但是压缩jpg的图片就会比较快,也正是因为这个原因,在vscode插件中是使用imagemin
方式进行压缩。如果以后解决了这个问题,也会及时分享出来,并开发一个对应的前端页面方便一些同学直接使用。 到这里主要的压缩上传功能已经实现,接下来就是写一个前端界面测试一下,我们的功能是否好用:简单写了一个前端界面,用来测试,这里只贴出最主要的上传代码,样式什么的相信大家都很熟练了:
handleInputChange(el) {
const files = el.target.files
const file = files[0]
const param = new FormData()
param.append('file', file)
param.append('qiniuConf', JSON.stringify(upConfig))
if (!this.upType) {
axios
.post('/api/image/upload', param, {
headers: { 'Content-Type': 'multipart/form-data' }
})
.then(res => {
console.log('上传到七牛云', res.data)
})
} else {
param.append('type', this.upType)
axios
.post('/api/image/compress', param, {
headers: { 'Content-Type': 'multipart/form-data' }
})
.then(res => {
console.log(`通过${this.upType}方法压缩后`, res.data)
})
}
}
复制代码
这个就是前端上传代码,new FormData()
后,将需要的参数file、type、qiniuConf
传入,看下控制台的打印:
发现已经可以返回压缩后的图片链接
五、上传到服务器
node端的压缩逻辑已经实现,接着就将代码上传到自己的服务器上,运行时有几个点要注意:
- 必须在服务器上安装一个brew,然后安装之前提到过的依赖
libpng
,这个安装需要更新服务器上的git和curl版本- 这个项目中使用了
squoosh
,安装依赖时必须使用npm install
,使用yarn 会报错,虽然不知道为啥,但是确实会失败,如果想本地运行也是一样的,必须使用npm install
这里就不太多介绍如果在服务器上运行了,毕竟也不是很专业。拉下代码,用pm2
跑起来,能请求就可以啦。
六、最后
因为工作中一直都在使用图片压缩后再上传的方法,时间久了容易对写代码不感兴趣,所以专门找了几个可以前端实现压缩的方案,并封装了node代码和vscode插件,以后可以更开心的开发啦^ _ ^。
重要提示:
如果有安装upload-to-qiniu
这个插件来上传图片,虽然不设置七牛的参数也会使用默认账号上传,但因为是个人七牛账号,空间比较小,用作demo演示还行,会经常删除七牛上的图片,所以建议大家如果在实际项目中要使用自己公司的七牛账号~~
最后一句
已经将图片压缩上传的全部代码上传到github,欢迎大家点个start^ ^。感谢阅读🙏