1.安装multer模块
npm i multer -S
multer是node.js的中间件,用于处理multipart/form-data类型的表单数据,主要用于上传文件
2.前端处理文件的上传、预览、下载
这里使用的vue+elementui
<template>
<el-upload
class="avatar-uploader"
action="#"
:before-upload="beforeAvatarUpload"
:http-request="httpRequest"
:show-file-list="false"
>
<img v-if="imageUrl" :src="imageUrl" class="avatar" />
<i v-else class="el-icon-plus avatar-uploader-icon"></i>
</el-upload>
<el-button @click="handlePreview" v-show="showPreviewBtn" type="primary"
>预览</el-button
>
<el-button @click="updateAvatar" v-show="showPreviewBtn"
>上传头像</el-button
>
<el-button @click="downloadFile" v-show="showPreviewBtn" type="primary"
>下载头像</el-button
>
</el-form-item>
</template>
<script>
export default{
data(){
return{
imageUrl: "",
previewImage: false,
showPreviewBtn: false, //默认隐藏预览按钮
fileId: "", //文件的二进制名称
currentFileId: "", //判断上传后的fileId是否相同 如果相同则提示图片重复上传
currentFileNameCN: "", //当前图片的名称
}
},
computed: {
...mapState(["userInfo", "userId"]),
},
methods:{
/**上传之前 */
beforeAvatarUpload(file) {
console.log(file);
let { name, size } = file;
let nameArr = name.split(".");
//文件后缀名
let fileType = nameArr[nameArr.length - 1];
//文件大小
let fileSize = (size / 1024 / 1024).toFixed(4);
console.log(fileType, fileSize);
//文件大小不超过2M且格式必须是jpg/png
let fileTypeList = ["jpg", "png"];
if (!(fileSize > 2 || fileTypeList.includes(fileType))) {
this.$message.error("文件类型只能为jpg或png且文件大小不能超过2M");
return;
}
},
/**上传文件 及 预览*/
async httpRequest(params) {
const { file } = params;
const formData = new FormData();
formData.append("file", file);
const fileData = await uploadFile(formData);
let { filename, fileNameCN, originalname } = fileData;
//返回的fileId 用于上传时用
this.fileId = filename;
//用于下载时的文件名
this.currentFileNameCN = fileNameCN || originalname;
const { data } = await previewFile(filename);
this.imageUrl = `${baseUrl}/uploadFile/${data.fileNewName}`;
//显示预览 上传 下载 按钮
this.showPreviewBtn = true;
},
/**预览图片 */
handlePreview() {
this.previewImage = true;
},
/**上传头像 上传成功则用户头像*/
async updateAvatar() {
let params = {
fileId: this.fileId,
userId: this.userInfo.userId,
};
const { code, msg } = await uploadAvatar(params);
if (code == 200) {
this.$message.success(msg);
this.$parent.$parent.$parent.init();
} else {
this.$message.error(msg);
}
},
/**下载头像 */
async downloadFile() {
const data = await downloadAvatar(this.fileId);
let a = document.createElement("a");
a.download = this.currentFileNameCN;
a.style.display = "none";
let blob = new Blob([data]);
const url = URL.createObjectURL(blob);
a.href = url;
a.click();
window.URL.revokeObjectURL(url);
},
},
},
created() {
//获取用户信息
this.formData = this.userInfo;
this.fileId = this.userInfo.fileId;
this.currentFileNameCN = this.userInfo.fileNewName;
//判断是否存在用户图片
if (this.userInfo.fileId) {
this.imageUrl = `${baseUrl}/uploadFile/${this.userInfo.fileNewName}`;
this.showPreviewBtn = true;
} else {
this.showPreviewBtn = false;
}
},
}
</script>
3.后端处理文件的上传、预览、下载
后端用的express ,用multer处理文件上传
为了将文件路径或二进制名称存入数据库存储,将源文件名称及二进制文件名都存入数据库中(避免相同的源文件名称)
由于img的src属性可以直接读取二进制文件,所以在存取时可以直接存入src属性中,通过对文件的读写操作,保存上传文件的二进制文件及上传的源文件,但是为了防止文件名重复,上传的文件名按照年月日时分秒_文件名格式存入数据库
后端代码:
var express = require('express');
var router = express.Router();
let fs = require('fs')
let multer = require('multer')
let upload = multer({ dest: './public/uploadFile/' }) //创建文件夹
/**上传 + 预览*/
router.post('/uploadFile', upload.any(), (req, res, next) => {
//var des_file = "./public/uploadFile/" + req.files[0].originalname;
//读取文件 fs.readFile(path,callback) => path 为路径 callback为回调函数,err:错误 data:
// fs.readFile( req.files[0].path, function (err, data) {
// fs.writeFile(des_file, data, function (err) {
// if( err ){
// console.log( err );
// }else{
// let response = {
// filename: req.files[0].filename,
// path: req.files[0].path
// };
// res.send( response );
// }
// });
// });
/*鉴于官方建议使用异步方法,比起同步,异步方法性能更高,速度更快,而且没有阻塞。读写后会生成二进制文件及源文件*/
//fs.writeFileSync(des_file,fs.readFileSync(req.files[0].path)) //先读文件,然后重新定义文件路径写入文件
//直接改名且不生成二进制文件
//文件重新命名
let fileNameArr = file.originalname.split('.');
let suffix = fileNameArr[fileNameArr.length - 1];
let currentDate = dateFormat(new Date(), 'YYYYmmddHHMMSS')
//避免文件名重复 格式为 年月日时分秒_文件名
let fileNewName = `${currentDate}_${fileNameArr[0]}.${suffix}`
fs.renameSync('./public/uploadFile/' + file.filename, `./public/uploadFile/${fileNewName}`)
/**如果存在相同的文件 则不添加 根据图片名称、二进制名称、文件类型及大小判断 */
//上传成功后在fileInfo表中插入文件数据
let select = `select * from fileinfo where fileName='${file.originalname}' and fileType='${suffix}' and fileSize='${file.size}'`
console.log(select)
connection.query(select, (selectErr, selectData) => {
//当文件相同时
if (selectData.length > 0) {
let response = {
filename: selectData[0].fileId,
path: selectData[0].filePath,
fileNameCN: selectData[0].fileNewName,
}
// console.log(response, '111111111')
res.send(response)
} else {
//上传新文件 则直接添加一条数据
let sql = "insert into fileinfo(fileId,fileName,fileNewName,fileType,filePath,fileSize) values(?,?,?,?,?,?)"
let sqlParams = [file.filename, file.originalname, fileNewName, suffix, file.path, file.size]
connection.query(sql, sqlParams, (err, doc) => {
if (err) res.json({ code: 500, data: null, msg: err })
let response = {
filename: req.files[0].filename,
path: req.files[0].path,
fileNameCN: fileNewName,
}
// console.log(response, '2222222222222')
res.send(response)
})
}
})
})
前端代码:
<template>
<el-upload
class="avatar-uploader"
action="#"
:before-upload="beforeAvatarUpload"
:http-request="httpRequest"
:show-file-list="false"
>
<img v-if="imageUrl" :src="imageUrl" class="avatar" />
<i v-else class="el-icon-plus avatar-uploader-icon"></i>
</el-upload>
<el-button @click="handlePreview" v-show="showPreviewBtn" type="primary"
>预览</el-button
>
<el-button @click="updateAvatar" v-show="showPreviewBtn"
>上传头像</el-button
>
<el-button @click="downloadFile" v-show="showPreviewBtn" type="primary"
>下载头像</el-button
>
</el-form-item>
</template>
<script>
export default{
data(){
return{
imageUrl: "",
previewImage: false,
showPreviewBtn: false, //默认隐藏预览按钮
fileId: "", //文件的二进制名称
currentFileId: "", //判断上传后的fileId是否相同 如果相同则提示图片重复上传
currentFileNameCN: "", //当前图片的名称
}
},
computed: {
...mapState(["userInfo", "userId"]),
},
methods:{
/**上传之前 */
beforeAvatarUpload(file) {
console.log(file);
let { name, size } = file;
let nameArr = name.split(".");
//文件后缀名
let fileType = nameArr[nameArr.length - 1];
//文件大小
let fileSize = (size / 1024 / 1024).toFixed(4);
console.log(fileType, fileSize);
//文件大小不超过2M且格式必须是jpg/png
let fileTypeList = ["jpg", "png"];
if (!(fileSize > 2 || fileTypeList.includes(fileType))) {
this.$message.error("文件类型只能为jpg或png且文件大小不能超过2M");
return;
}
},
/**上传文件 及 预览*/
async httpRequest(params) {
const { file } = params;
const formData = new FormData();
formData.append("file", file);
const fileData = await uploadFile(formData);
let { filename, fileNameCN, originalname } = fileData;
//返回的fileId 用于上传时用
this.fileId = filename;
//用于下载时的文件名
this.currentFileNameCN = fileNameCN || originalname;
const { data } = await previewFile(filename);
this.imageUrl = `${baseUrl}/uploadFile/${data.fileNewName}`;
//显示预览 上传 下载 按钮
this.showPreviewBtn = true;
},
/**预览图片 */
handlePreview() {
this.previewImage = true;
},
/**上传头像 上传成功则用户头像*/
async updateAvatar() {
let params = {
fileId: this.fileId,
userId: this.userInfo.userId,
};
const { code, msg } = await uploadAvatar(params);
if (code == 200) {
this.$message.success(msg);
this.$parent.$parent.$parent.init();
} else {
this.$message.error(msg);
}
},
/**下载头像*/
async downloadFile() {
const data = await downloadAvatar(this.fileId);
let a = document.createElement("a");
a.download = this.currentFileNameCN;
a.style.display = "none";
let blob = new Blob([data]);
const url = URL.createObjectURL(blob);
a.href = url;
a.click();
window.URL.revokeObjectURL(url);
},
},
},
created() {
//获取用户信息
this.formData = this.userInfo;
this.fileId = this.userInfo.fileId;
this.currentFileNameCN = this.userInfo.fileNewName;
//判断是否存在用户图片
if (this.userInfo.fileId) {
this.imageUrl = `${baseUrl}/uploadFile/${this.userInfo.fileNewName}`;
this.showPreviewBtn = true;
} else {
this.showPreviewBtn = false;
}
},
}
</script>
注意:这里做的下载返回类型是blob(responseType:‘blob’)