ElementUI + express实现头像上传及后台图片保存
记录大创项目中的解决方式。只说明基本的实现方法,不代表实际代码。如果你需要在后台保存头像图片的话。
当然也可以直接使用base64格式保存头像,虽然有一些缺陷,这样后台就不需要保存图片了,直接保存base64的文本就行。
文章仅是自己个人在项目中的总结,如有不妥和遗漏还请多多指教。
1. 前端部分
先照搬elementUI的官方文档,再删删补补一下(样式部分暂且省略):
<!-- 设定的属性在官方文档里都有说明,就不再赘述 -->
<!--
上传的地址:http://localhost/uploadAvatar 必须
上传的文件字段名: avatar 可使用
附带的数据:id(可用于验证),也可以不加
用户在点击并选择好图片后会立即上传
-->
<el-upload
class="avatar-uploader"
action="http://localhost/uploadAvatar"
name="avatar"
:data="{ id }"
:show-file-list="false"
:before-upload="beforeAvatarUpload"
:on-success="handleAvatarSuccess"
:on-error="handleAvatarError"
>
<img v-if="imageUrl" :src="imageUrl" class="avatar" />
<i v-else class="el-icon-plus avatar-uploader-icon"></i>
</el-upload>
<script>
export default {
data() {
return {
imageUrl: "",
id: '1234'
}
},
methods: {
//上传成功的方法
handleAvatarSuccess(res) {
if (res.err) return this.$message.error(res.msg);
this.$message.success("头像上传成功!");
},
//上传失败的方法
handleAvatarError() {
this.$message.error("未知错误");
},
//上传之前的处理
beforeAvatarUpload(file) {
const isJPG = file.type === "image/jpeg" || file.type === "image/png";
const isLt2M = file.size / 1024 / 1024 < 2;
if (!isJPG) {
this.$message.error("上传头像图片只能是 JPG 或者 PNG 格式!");
}
if (!isLt2M) {
this.$message.error("上传头像图片大小不能超过 2MB!");
}
return isJPG && isLt2M;
}
}
}
</script>
2. 后台接口部分
需要使用images模块:
npm i images --save
该模块的文档地址:https://www.npmjs.com/package/images
我使用的是formdata格式传输的数据。
const express = require('express');
const images = require('images');
const router = express.Router(); //二级路由
router.use('/uploadAvatar', function (req, res) {
//前端的参数使用req.fields获取,如果获取不到,请使用express-formidable模块并提取挂载
const id = req.fields.id; //先获取,实际情况有可能使用
//这里提前定义一个图片保存的路径,为防止图片名有可能冲突,我做了一些处理
const imgPath = `./img/avatars/${new Date().getTime() + Math.random().toFixed(3) * 1000}.jpg`;
return new Promise((resolve, reject) => {
try {
//前端的文件使用req.files获取,因为前面的name属性,文件的字段名改成了avatar
//根据文档保存图片:
images(req.files.avatar.path)
.size(1920)
.save(imgPath, {
quality: 80
})
resolve()
}
catch {
reject()
}
}).then(() => {res.send({ err: 0, msg: '头像已上传'})})
.catch(() => res.send({ err: 1, msg: '上传失败' }))
});
module.exports = router;
到此为止,基本的前端头像上传和后端图片保存功能就实现了。再作一些补充。
补充:使用数据库保存用户的头像链接
如果是在自己的后台保存了用户上传的图片,同时你开启了静态资源服务,希望用户能直接访问服务器上的图片。
这时可能就需要在数据库的用户信息表中保存对应的头像链接。
//前面注释过的都删了,补充的部分作了注释
const express = require('express');
const images = require('images');
const mysql = require('mysql'); //引入数据库模块
const router = express.Router();
router.use('/uploadAvatar', function (req, res) {
const id = req.fields.id;
const imgPath = `./img/avatars/${new Date().getTime() + Math.random().toFixed(3) * 1000}.jpg`;
return new Promise((resolve, reject) => {
try {
images(req.files.avatar.path)
.size(1920)
.save(imgPath, {
quality: 80
})
resolve()
}
catch {
reject()
}
}).then(() => {
//对前面图片的保存路径作一些处理,加上前缀:
const link = imgPath.replace('.', 'http://localhost'); //这样就产生了头像的http路径,我们可以直接根据该地址访问图片,如果你开启了静态资源服务的话
//接下来就是常规的修改信息:
//将图片链接存入数据库
const connection = mysql.createConnection({
host: "localhost",
user: "root",
password: "",
database: "test",
});
connection.connect(function (err) {
err ? console.log("数据库链接失败") : console.log("链接成功!");
});
connection.query(
`update user_info set profile='${link}' where id=${id}`, //根据前端上传时附带的id找到对应用户
(err, row) => {
if (err) return res.send({ err: 1, msg: '上传失败' });
res.send({ err: 0, msg: '头像已上传'})
}
})
.catch(() => res.send({ err: 1, msg: '上传失败' }))
});
module.exports = router;
补充:后台设置用户上传时限
设置用户多长时间能够上传一次头像。虽然前端也可以做,但后端更加保险。
同时因为后台是要保存头像图片,不排除一些用户可能恶意刷头像上传,如果没有及时清理会极大的占据并浪费硬盘空间。
当然我也并不知道正规公司是怎么做的,所以这可能有点多余。仅当作一个思路吧。
const timeLimt = {}; //一个全局对象变量
router.use('/uploadAvatar', function (req, res) {
const id = req.fields.id; //1.接收用户id
// 4.提前判断当前用户是否正在时限中,如果是则直接结束
if (timeLimit[idCode]) return res.send({ err: 1, msg: '同一用户三分钟只能上传一次!' });
const imgPath = `./img/avatars/${new Date().getTime() + Math.random().toFixed(3) * 1000}.jpg`;
return new Promise((resolve, reject) => {
try {
images(req.files.avatar.path)
.size(1920)
.save(imgPath, {
quality: 80
})
resolve()
}
catch { reject() }
}).then(() => {
res.send({ err: 0, msg: '头像已上传'})
//2.每次上传成功,给全局变量添加一个当前用户id的属性并设置值:
timeLimit[id] = true; //代表当前用户正在时限中,无法更改
//3. 三分钟后解除时限
setTimeout(() => {
timeLimit[id] = false;
}, 180000)
})
.catch(() => res.send({ err: 1, msg: '上传失败' }))
});
module.exports = router;
写完之后再想想,似乎也可以在每次用户上传的时候,在数据库中查询对应用户的头像链接,更新之后再将原先保存的图片直接删除。
又或者按照设置时限的思路,每次用户上传就将头像路径和id绑定,再次上传时就先根据前一次绑定的头像路径将该图片删除,再绑定到对应id。
这样就保证服务器上每个用户只会保存一张头像图片,也就不存在多余的头像图片,也不用按时清理了。不过不知道这样是否会对后台产生一些压力。所以暂且保存这个想法吧,毕竟也只是个小项目,我也只是个写前端的。
补充:确保一个用户只会保存一张头像
根据前面的想法,每次用户上传就将头像路径和id绑定,同一用户再次上传时就先根据前一次绑定的头像路径将该图片删除,再将新头像路径绑定到对应id。以此反复,保证服务器上每个用户只会保存一张头像图片。
//思路同设置时限
const id_avatarFile = {}; //一个全局对象变量
router.use('/uploadAvatar', function (req, res) {
const id = req.fields.id;
//2.异步删除前一次的图片
if (id_avatarFile[idCode]) //如果存在的话
fs.unlinkSync(id_avatarFile[idCode], (err) => {
if (err) throw err;
console.log('删除成功');
})
const imgPath = `./img/avatars/${new Date().getTime() + Math.random().toFixed(3) * 1000}.jpg`;
return new Promise((resolve, reject) => {
try {
images(req.files.avatar.path)
.size(1920)
.save(imgPath, {
quality: 80
})
resolve()
}
catch { reject() }
}).then(() => {
res.send({ err: 0, msg: '头像已上传'})
//1. 保存第一次或前一次上传的头像链接
id_avatarFile[idCode] = imgPath;
})
.catch(() => res.send({ err: 1, msg: '上传失败' }))
});
module.exports = router;
实测可以成功,但是这样会出现一个新问题:每次用户重新打开界面时,首次上传时肯定是没有这条绑定的数据的,也就是为什么我需要先判断是否存在这样的数据才能执行删除文件的操作。这样每次一定会残余一张图片。
所以每次前端新上传图片时,第一次需要查询数据库并保存“id—路径”这样的数据,相当于初始化。之后直接执行上述的流程就可以了,也不用提前判断是否存在该条数据。
当然代码也很简单,就不再赘述了。