完整的前后端 实现上传头像、压缩头像

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/Reagan_/article/details/98759816

本文是我在写上传头像功能后的一个详细的总结,用作以后可以回顾,可能较为繁琐,如果明白上传只想看压缩,可直接拉到文章后半部分。

上传头像

上传头像准备工作

在后台/public文件夹里
创建一个文件夹uploads,来存放头像

开始:
1.models/user.js
添加头像字段

userHead:{
    type:String,
   default:url.resolve(Head.baseUrl,'default.png')  //初始化的默认头像
}

为了更有通用性,在unitils/config.js里面添加配置

var Head={
    baseUrl:'http://localhost:3000/uploads/'
}
module.exports={
    Mongoose,
    Email,
    Head
};

baseUrl路径说明:因为express设置的(app.js里设置)静态资源在public目录下,所以public作为根目录是可以省略的。

app.use(express.static(path.join(__dirname, 'public')));

所以可以在网址栏省略public直接访问到头像:
http://localhost:3000/uploads/default.png

到目前,查看数据库,新注册的账号在数据库就有一个默认的userHead字段来表示头像了。

2.前端渲染
views/admin/users.vue
加到第一列

<el-table-column
prop="userHead"
label="用户头像">
    <template slot-scope="scope">
        <img :src="scope.row.userHead">
    </template>
</el-table-column>

到这默认头像就完全都显示在管理端了(因为未上传图片就是是默认的default,所以每一个用户都有默认头像)


有了默认头像之后,我们想到的就是让用户可以自行上传图片来修改头像的操作了。

上传头像

上传头像路由配置

使用express的中间件multer上传头像。
http://www.expressjs.com.cn/en/resources/middleware/multer.html

步骤:
1.安装

$ cnpm install --save multer

2.引入
routes/users.js

var multer  = require('multer');//引入模块
var upload = multer({ dest: 'public/uploads/' }); //指定将要上传的文件的路径

router.post('/uploadUserHead', upload.single('file') , usersController.uploadUserHead);

//参数:文件用post请求,file是指定的key值(表示上传文件,到时候前端也指定key值为file就可以对应起来了),调用函数

要在个人主页渲染出头像:
在登录的时候挂载userHead,从数据库的个人信息里拿到用户的头像字段保存起来

req.session.userHead = result.userHead;

在getUser(这是我写的一个判断登陆态的接口)的时候发送给前台,意思是判断用户在登录状态的时候渲染头像

userHead : req.session.userHead

上传头像接口

models/user.js

var updateUserHead = ( username , userHead )=>{
    return UserModel.update({username},{$set:{userHead}})
    .then(()=>{
        return true;
    })
    .catch(()=>{
        return false;
    });
}

对外暴露

根据用户名来查找,修改成你上传的那个 userHead(头像)字段

到这里,用户登录的时候接口里就有用户的头像字段了。

现在我们要从前端拿到头像字段。
因为管理端也显示头像,个人主页也显示头像,所以我们用状态管理。

前端

//在beforeRouteEnter的vm.$store.commit里
userHead=res.data.data.userHead

因为虽然有两个组件都有头像字段,但是并不存在共享(因为管理端的头像是直接通过表格的属性scope.row.userHead获取的,没有用状态管理获取值。而个人中心是用状态管理获取值的<img class="userHead" :src="$store.state.user.userHead">),所以不localStorage了。只是添加一个空值:
src/stores/user/index.js

userHead:''

步骤:1.利用input [type=”file”]上传文件,这个文件是一个图片

<input type="file" name="file" value="上传头像">

type=“file” file可以对应的调用刚才我们说的值为key的模块。
type 类型为 file 的 input 元素,让用户可以选择一个或多个元素以提交表单的方式上传到服务器上, 或者通过 Javascript 的 File API 对文件进行操作。
这里我们用的是input表单。

2.绑定方法

<input type="file" name="file" value="上传头像" @change="handleToUpload">

这样每次在选择完头像之后都会触发handleToUpload(更新头像)方法。

 handleToUpload(ev){
    var file = ev.target.files[0];           //拿到存储图片的file文件
    var param = new FormData();              //FormData创建出的实例,可以把文件存储起来,然后传输给后台
    param.append('file',file,file.name);     //key值 文件 文件下的name
    var config={
        headers:{                            //想上传文件post需要改成二进制的数据流,所以设置头信息
            'Content-Type':'multipart/form-data'  
        }
    }
    this.axios.post('/api2/users/uploadUserHead',param,config).then((res)=>{
        var status=res.data.status;
        if(status === 0){
            msgBox({                           //我封装的一个消息弹窗组件
                title:'信息',
                content:'上传头像成功',
                ok:'确定',
                handleOk(){ //头像上传成功之后更新状态管理
                    this.$store.commit('user/USER_NAME',{
                        name:_this.$store.state.user.name , 
                        isAdmin:_this.$store.state.user.isAdmin,
                        userHead:res.data.data.userHead
                    });
                }
            })
        }else{
            msgBox({
                title:'信息',
                content:'上传头像失败',
                ok:'确定',
            })
        }
    });
}

handleToUpload(更新头像)方法调用uploadUserHead接口,发送图片文件。后台通过Router路由层走到这一步
router.post('/uploadUserHead', upload.single('file') , usersController.uploadUserHead);,请求后调用uploadUserHead接口,完成头像的修改。

到这一步测试一下,随便上传一个图片,会发现upload文件夹里面会有图片
但是图片的名字是随机生成的一长串数字,我们就无法获取它让它在前端的个人中心显示出来,所以要把图片名变成一个好获取的,我们想的是变成 ‘用户名.jpg’。

修改图片名

使用fs的rename操作(后面的文件名代替前面),保存的图片名是用户名.jpg
server\controlers\users.js\uploadUserHead接口

await fs.rename('public/uploads/'+req.file.filename , 'public/uploads/'+ req.session.username+'.jpg',function(err){});   //修改图片名

var result =await UserModel.updateUserHead(req.session.username , url.resolve(Head.baseUrl , req.session.username+'.jpg'));  //2

对2句说明:

  • UserModel.updateUserHead是我们在models里封装好的,来修改用户头像的,传两个参数:(username , userHead)前面提到过的:用户名,要修改成的头像字段
  • userHead需要处理:url.resolve(Head.baseUrl , req.session.username+’.jpg’)
  • resolve可以修改或者添加末尾字符串。这里用的是添加,两个参数(Head.baseUrl , req.session.username+’.jpg’)第一个是untils/config.js里配置的本地文件,第二个是要加的处理好(处理好是指修改图片名操作)的图片名。

然后判断是否处理好,如果处理好,用data向前端返回userHead,好让前端更新头像。

到这,就可以成功地上传头像了。

但是现在会发现一个问题,发现每次刷新之后只能更改一次头像,再更改前端头像都不变,但是后台头像更新了,这是为什么呢?
这是因为前端有缓存的原因,解决办法:加随机数让每次请求都是新的请求。

userHead:res.data.data.userHead+'?'+Math.random()      //上传成功后点击确定时修改的状态管理值

压缩头像

压缩的原因

首先,为什么要压缩图片呢?
对于大尺寸图片的上传,在前端进行压缩除了省流量外,最大的意义是极大的提高了用户体验。
表现在:

  • 由于上传图片尺寸比较小,因此上传速度会比较快,交互会更加流畅,同时大大降低了网络异常导致上传失败风险。
  • 最重要的体验改进点:省略了图片的再加工成本。很多网站的图片上传功能都会对图片的大小进行限制,尤其是头像上传,限制5M或者2M以内是非常常见的。然而现在的数码设备拍摄功能都非常出众,一张原始图片超过2M几乎是标配,此时如果用户想把手机或相机中的某个得意图片上传作为自己的头像,就会遇到因为图片大小限制而不能上传的窘境,不得不对图片进行再处理,而这种体验其实非常不好的。如果可以在前端进行压缩,则理论上对图片尺寸的限制是没有必要的。

压缩

明白了上传头像一系列操作之后,压缩图片其实很简单,只是在上传之前对图片进行压缩操作。

压缩操作的流程

–> 因为无法直接对文件进行获取宽高等操作,所以创建一个image对象来接收文件的base64编码,通过操作这个image对象达到操作文件的目的
–> 计算等比缩放后的图片宽高
–> 清空画布之后,按照计算好的宽高,使用drawImage(image,x,y,width,height)的方法对图片进行压缩,绘制到新的画布上
–> 把canvas转换成Blob文件进行上传

几个方法的说明
  • FileReader方法 — 读取图片
    • FileReader 对象允许Web应用程序异步读取存储在用户计算机上的文件(或原始数据缓冲区)的内容,使用 File 或 Blob 对象指定要读取的文件或数据。
    • FileReader.readAsDataURL方法读取图片将其转为base64,读取完成之后,result属性中将包含一个data: URL格式的base64字符串以表示所读取文件的内容。
    • FileReader.onload ,处理load事件。该事件在读取操作完成时触发。
  • canvas操作
    • getContext()方法 — 获得渲染上下文
      canvas起初是空白的。为了展示,首先脚本需要找到渲染上下文,然后在它的上面绘制。它元素有一个叫做 getContext() 的方法,这个方法是用来获得渲染上下文和它的绘画功能。getContext()只有一个参数,即上下文的格式。这里用的是’2d’。
    • context.drawImage()压缩方法
      这个方法在文末详细说明
    • canvas.toBlob(callback, mimeType, qualityArgument)
      toBlob方法可以把canvas转换成Blob文件,通常用在文件上传中,因为是二进制的,对后端更加友好。
      toBlob()方法是异步的,因此多了个callback参数,这个callback回调方法默认的第一个参数就是转换好的blob文件信息。
具体代码的详细分析
handleToUpload(ev){
   // console.log(ev);
   var file = ev.target.files[0];     //拿到存储图片的file文件
  
   //压缩图片开始
   var img = new Image();             //img对象,当作一个容器接收file文件转化成的的base64,等于接受了图片,可以进行图片操作了,否则无法对文件进行图片操作
   var reader = new FileReader();     //创建FileReader对象 
   reader.readAsDataURL(file);        //读取存储图片的file文件,返回一个base64字符串存储到result属性中
   reader.onload = function (ev) {    //监听reader读取完成事件
       img.src=this.result;           //当读取完成时,reader.result就是要的base64,将它赋值给src
   }
   
   var _this=this;

   img.onload=function(){             //必须要加这一句,监听img完成之后才能获取到宽和高,否则获取的值都是0
       // console.log(img.width);
       // console.log(img.height);
       var canvas = document.createElement('canvas');
       var context = canvas.getContext('2d');        //为canvas设置上下文
       var originW = img.width;                      //原图的初始宽、高 
       var originH = img.height;
       var maxW = 400 , maxH = 400;                  //容许的最大宽、高
       var targW = originW,targH=originH;            //如果不需要压缩,就还是原始的宽高
       if(originW>maxW || originH>maxH){
           if(originH/originW > maxH/maxW){          //比起想要的比例(当前400/400是1),高/宽更大,说明高更大,设置高作为最大值高,那么宽一定不会超出范围,且是等比缩放
               targH=maxH;
               targW=Math.round(maxH * (originW / originH));   //取整
           }else {
               targW = maxW;
               targH = Math.round(maxW * (originH / originW));
           }
       }
       canvas.width = targW;
       canvas.height = targH;
       context.clearRect(0,0,targW,targH);           //清除画布
       context.drawImage(img,0,0,targW,targH);       //将原图按照比例绘制到新的画布上去
       canvas.toBlob(function(blob){                 // canvas转为blob并上传
       
       //压缩图片到这里就结束了,剩下的操作是把前文将的上传文件放在toBlob的回调函数里执行
       
           var param = new FormData();               //FormData创建出的实例,可以把文件存储起来,然后传输给后台
           param.append('file' , blob , file.name);  //key值 文件 文件下的name
           var config={
               headers:{                             //想上传文件post需要改成二进制的数据流,所以设置头信息
                   'Content-Type':'multipart/form-data'  
               }
           }
           _this.axios.post('/api2/users/uploadUserHead',param,config).then((res)=>{
               var status=res.data.status;
               // var This=this;
               if(status === 0){
                   msgBox({
                       title:'信息',
                       content:'上传头像成功',
                       ok:'确定',
                       handleOk(){ //头像上传成功之后更新状态管理
                           _this.$store.commit('user/USER_NAME',{
                               name:_this.$store.state.user.name , 
                               isAdmin:_this.$store.state.user.isAdmin,
                               userHead:res.data.data.userHead+'?'+Math.random()   //解决前端的缓存
                           });
                       }
                   })
               }else{
                   msgBox({
                       title:'信息',
                       content:'上传头像失败',
                       ok:'确定',
                   })
               }
           });
      },file.type || 'image/png')
   }
}

context.drawImage()压缩方法说明
  • context.drawImage(img, dx, dy);
  • context.drawImage(img, dx, dy, dWidth, dHeight);
  • context.drawImage(img, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);

参数说明:
img
  图片对象,可以是页面上获取的DOM对象,也可以是虚拟DOM中的图片对象。
dx, dy, dWidth, dHeight
  表示在canvas画布上规划出一片区域用来放置图片(拉伸压缩)。dx, dy为canvas元素的左上角坐标,dWidth, dHeight指canvas元素上用在显示图片的区域大小。如果没有指定sx,sy,sWidth,sHeight这4个参数,则图片会被拉伸或缩放在这片区域内。
sx,sy,swidth,sheight
  这4个坐标是针对图片元素的,表示图片在canvas画布上显示的大小和位置(裁剪)。sx,sy表示图片上sx,sy这个坐标作为左上角,然后往右下角的swidth,sheight尺寸范围图片作为最终在canvas上显示的图片内容。
  
需要注意的是:那就是5参数和9参数里面参数位置是不一样的,这个和一般的API有所不同。一般API可选参数是放在后面。但是,这里的drawImage()9个参数时候,可选参数sx,sy,swidth,sheight是在前面的。如果不注意这一点,有些表现会让你无法理解。

举个简单的例子:
对于本文的图片压缩,需要用的是是5参数的语法。
假如一张图片(假设图片对象是img)的原始尺寸是40003000,现在需要把尺寸限制为400300大小,很简单,原理如下代码示意:

var canvas = document.createElement('canvas');
var context = canvas.getContext('2d');
canvas.width = 400;
canvas.height = 300;
// 核心JS就这个
context.drawImage(img,0,0,400,300);

把一张大的图片,直接画在一张小小的画布上。此时大图片就天然变成了小图片,压缩就这么实现了,是不是简单的有点超乎想象。

参考资料:https://www.cnblogs.com/goloving/p/8260206.html

查看是否压缩成功

找一张超过你设置的最大宽高的图(本文是400×400),当作头像上传。
对比原图的宽高 和 存放头像uploads的文件夹里压缩过的图片的宽高:

原图的宽高 700×1244
在这里插入图片描述

压缩过的图片的宽高 225×400
在这里插入图片描述

看来压缩成功了。

猜你喜欢

转载自blog.csdn.net/Reagan_/article/details/98759816
今日推荐