前言

上篇文章提到了如何用mock.js来模拟接口,方便在后端没有写好接口的时候也能顺利开发,本来计划这篇文章是讲一下用轮播图组件swiper来展示一下模拟接收到的数据和图片,但项目计划发生了变化,这个就推到后面再说。

mock模拟接口虽然很好用,但是在项目开发的时候还是要有一个真的服务器会比较好,比如我最近要实现一个功能,就是从前端上传用户选择的图片和编写的信息,并保存在某处返回一个vue能访问的url,这样单纯的通过mockjs来实现是非常困难的(我不太清楚,可能mockjs都不能实现这样的功能)。

因为我本身并不会什么后端的技能,nodejs也了解的不多,所以我选择了express,几句话就能搭建一个简易的服务器,这次就先说下我是如何从vue发图片给express,然后在上传到阿里云OSS的,下一篇文章我再说下如何保存在本地数据库里(让我在多学一会)

学习过程不易,碰了很多坑,翻了很多资料,我的思路和实现基于以下几篇文章,大家可以去看看:
手把手教你从0到1通过 Express 完成图片上传并保存至阿里云OSS功能(附详细源码)
vue中实现图片上传


一、申请阿里云OSS

具体的申请方法我就不细说了,详细的可以看看这篇文章的内容,保姆级一步步带你申请一个阿里云OSS
手把手教你从0到1通过 Express 完成图片上传并保存至阿里云OSS功能(附详细源码)

我这里就简单说下有那些是比较重要的
1、一定要记住申请bucket时系统给的AccessKey和AccessKey Secret
2、在OSS管理控制台是可以看到自己bucket的名称和地域的,这些都是后面非常重要的参数
在这里插入图片描述
3、如果是打算长期用的,不是说就尝试下能不能上传图片的,可以买个5块钱的套餐,不然传几张图片可能就给你服务停了
4、要把读写权限改成公共读写
在这里插入图片描述
如果以上的都没啥问题了,可以正式进入具体的代码实现了

二、Vue前端读取图片

首先要做到能把本地的图片给读进前端展示出来,这里我们就简单一点,用input的文件选择表单元素来实现这一功能,也可以用element-ui的upload组件来实现,不过这里我们主要是演示,就不考虑什么样式了,之后我再写一篇用这些组件来实现图片保存到服务器本地的文章吧(挖坑)

搭建vue脚手架那些我就不多说了,简单的结构代码如下:

<template>
    <div>
        <input type="file" ref="file"> 
        <div>
            <span>读取本地图片的</span>
            <img src="" alt="" style="height:200px;width:200px;">
        </div>
        <button>点击我发送图片进行测试</button>
        <div>
            <span>读取服务器图片的</span>
            <img src="" alt="" style="height:200px;width:200px;">
        </div>
    </div>
</template>

大概是长这个样子的
在这里插入图片描述
现在点击选择文件是可以从自己电脑上选择文件或图片的,那么读进来的图片我们要怎么处理呢

1、首先先给图片的src绑定个data,让它能根据我们选择的图片动态改变显示内容

export default {
    name: 'App',
    data () {
        return {
            pic: '',
            backPic: ''
        };
    },
}

上面记得给src加:,不然接收到的是个内容为变量名的字符串
在这里插入图片描述
2、然后我们定义一个方法,当input的内容发生改变(也就是选择完图片)就触发该事件
在这里插入图片描述
3、通过FileReader构造函数里的readAsDataURL方法可以把表单列表里的图片转成base64格式的,这样就能在页面上显示出来了

methods: {
     showImg () {
         let fr = new FileReader();
         fr.readAsDataURL(this.$refs.file.files[0]);
         fr.onload = () => {
             this.pic = fr.result;
         }
     },
 },

注意:FileReader生成的实例对象是异步的,有什么操作必须要在onload里面完成,不然在外面怎么调用都是null

我们随便选张图,看看效果
在这里插入图片描述
可以显示出来了!

三、将图片base64转成二进制文件

我们可以打印看一下FileReader转换后的图片内容是咋样的,在对象fr的result里
在这里插入图片描述
我第一次看到这玩意的时候心里想的是,我的妈呀,这啥东西!
这是图片的base64格式,先不说这东西发到服务器怎么处理,这玩意已经有差不多12M的大小,这还只是一张图,所以我们不能直接这样发送给服务器,要处理一下

base64toFile: (dataurl, filename = 'file') => {
    let arr = dataurl.split(',')
    let mime = arr[0].match(/:(.*?);/)[1]
    let suffix = mime.split('/')[1]
    let bstr = atob(arr[1])
    let n = bstr.length
    let u8arr = new Uint8Array(n)
    while (n--) {
        u8arr[n] = bstr.charCodeAt(n)
    }
    let file = new File([u8arr], `${filename}.${suffix}`, {
        type: mime
    })
    return file
},

具体代码就不分析了,基本上都是这么写的,直接用就可以了,感兴趣的可以查下方法名的作用,一行行打印出来看看具体是什么,我这里就直接打印最终结果
在这里插入图片描述
这样前端的准备工作就完成了,接下来就是如何发送给服务器然后上传给阿里云

四、搭建express服务器

因为我不是很懂nodejs,所以我就大致讲一下怎么搭一个前端能访问的服务器
首先要通过npm安装express,这操作就不演示了
然后在一新的js文件里写如下代码,可以先看下整体代码是怎么样的,接下来我再说说每部分是干什么的

// app.js
const express = require('express');
const app = express();
const PORT = 3002

app.get('/server', function (req, res) {
	// 允许跨域访问
	res.header("Access-Control-Allow-Origin", "*");
	
	// 获取url各部分名称
    const protocol = req.protocol;
    const host = req.hostname;
    const url = req.originalUrl;
    const port = process.env.PORT || PORT;
    const fullUrl = `${protocol}://${host}:${port}${url}`

    const responseString = `Full URL is: ${fullUrl}`;
    res.send(responseString);
})

app.listen(PORT, () => console.log('Example app listening on port 3002!'))

1、首先是要引入express,然后赋值给app,我们通过app来开启服务器监听请求,和接收到请求时应该进行什么样的行动
2、PORT是设定端口号
3、app.get是接收处理路径名为/server的get请求,我这里把服务器完整的URL返回给前端了
4、res.header(“Access-Control-Allow-Origin”, “*”);这句如果了解跨域的同学应该清楚这是设置CORS,不然数据是无法返回给前端的,浏览器会因为跨域问题截停服务器的respond,一定要加上这句话
5、app.listen是开启监听,是否有发送给3002端口号的请求

我们来看一下服务器启动的效果
在终端启动express所在的js文件,一般是node xxx.js,但这种方式你修改了js里的代码就要重新启动服务器,有点麻烦,所以可以安装一个supervisor插件,通过这个插件启动服务器他会自动应用你修改的代码,无须重复启动
在这里插入图片描述
只要看到红色箭头指的语句就可以认为服务器已经启动并开始监听请求了

五、通过axios给服务器发送请求

上一篇博客如何封装一个axios已经写的很清楚了,如果不知道代码具体原理的可以看下官方的文档,或者别人的博文,都写的很详细我就不多说了,这里直接上代码

如果不是做项目的,可以不用专门封装一个axios,直接调用axios来发送请求也是没问题的,只不过我后面要用到拦截器来做权限设置,用来检测是否有登录,所以封装成一个组件

import axios from 'axios';

const picAjax = axios.create({
    timeout: 10000
})

picAjax.interceptors.request.use((config) => {
    return config;
})

picAjax.interceptors.response.use((response) => {
    return response.data;
}, (error) => {
    return Promise.reject(error);
})

export default picAjax

我给按钮绑定了一个事件,点击就向服务器发送get请求,获取服务器完整的url
在这里插入图片描述

async submit () {
     let res = await picAjax.get('http://127.0.0.1:3002/server')
     console.log(res);
 }

我们打印看一下服务器返回了什么东西
在这里插入图片描述
就是我们第四节设置的服务器完整的URL,端口号和路径名都能对得上

到这里我们服务器也搭好了,发送服务的手段也准备好了,接下来就是正式向服务器发送图片,然后上传给阿里云了

六、发送图片并上传阿里云

我们首先了解一下,要怎么发送图片给服务器

1、首先要获取图片,我们是通过input中的file表单元素获取图片,并通过FileReader构造的对象将图片转换成了BASE64格式的字符串,然后转换成二进制
2、通过 FormData 接口提供的一种表示表单数据的键值对 key/value 的构造方式,可以轻松的将数据通过axios方法发送出去
3、然后通过axios.post方法往端口号为3002,路径名为upload的服务器发送FormData请求体数据

了解完整个过程,我们看下发送图片的代码,我就直接在按钮绑定的事件上改了

async submit () {
    let file = this.base64toFile(this.pic);
    let formData = new FormData();
    formData.append("file", file);
    let res = await picAjax.post('http://127.0.0.1:3002/upload', formData)
    this.newPic = res.url
}

其中有两点要注意的:
1、我这里是传单个图片,如果要传多张图片设置可能不一样,详细的请自行看FormData的设置,服务器那边也需要做相应的修改
2、因为axios返回的是一个promise,如果要用respond的内容做一些操作可以用async/await直接接收数据,将异步任务当成同步任务来执行

我们把最终效果留到最后一起看,我们先看一下服务器那边的代码是什么样的

// app.js
const express = require('express');
const app = express();
const PORT = 3002
// OSS 相关
const multer = require('multer') //npm i multer
const MAO = require('multer-aliyun-oss'); //npm install --save multer-aliyun-oss

const uplod = multer({
    storage: MAO({
        config: {
            region: 'oss-cn-你的地区',
            accessKeyId: '你的accessKeyId',
            accessKeySecret: 'accessKeySecret',
            bucket: '你的bucket名'
        },
        destination: 'public/images'
    })
});

app.post('/upload', uplod.single('file'), (req, res) => {
    res.header("Access-Control-Allow-Origin", "*");
    // 可以自定义返回结果,推荐打印 req.file 查看,再决定如何返回数据给前端
    const file = req.file;
    res.send({
        status: '上传成功',
        code: 200,
        url: `https://book-recommendation-system.oss-cn-guangzhou.aliyuncs.com/public/images/${file.filename}`
    });
})

app.listen(PORT, () => console.log('Example app listening on port 3002!'))

其实跟刚才搭建服务器时的代码没什么区别,只是增加了点新的东西,接下来我们来说下新增东西的作用

1、从上面看最直观的是多了两个插件,一个multer,另一个是multer-aliyun-oss,我这里简称MAO,这两个的作用就是用来上传的,multer是往你指定的地方传东西,MAO可以看成是访问你阿里云OSS地址的登录窗口
2、中间新增的函数uplod就是我刚才说的,设定multer往MAO返回的阿里云地址传东西,只不过这个东西我们在post请求的方法里再指定
3、MAO里面要设置你的区域,bucket名,accessKeyId和accessKeySecret,就是我第一节让你们重点记下的参数,这里填错了是访问不了你的阿里云OSS的,然后destination参数是往你阿里云哪个文件夹传东西,我这里设定了图片放在public/images文件夹下
4、然后是post请求,它其实跟get没什么区别,只不过我们要写上请求体,也就是FormData,在接受到数据后,调用uplod将FormData键值对里的file也就是图片上传到阿里云
在这里插入图片描述在这里插入图片描述

OK,那么所有的原理都说完了,我们看下最终成品的效果吧,我最后直接通过src访问保存图片的阿里云url在下面那个div里显示图片

在这里插入图片描述
效果跟想像的没有区别,我上传了一张图片,然后能直接通过返回的阿里云url访问并显示该图片
打印出来的结果可以看到我发送图片的时间,可以去阿里云的文件夹下看下是否上传成功了
在这里插入图片描述
确实是在这个时间在该文件夹下上传了一张图片


总结

为了方便一些同学想先看下能否实现在看具体细节,我这里直接把全部代码贴出来,根据文件名来创建文件贴代码就行

服务器代码,文件名app.js,记得MAO里填你自己的数据啊

// app.js
const express = require('express');
const app = express();
const PORT = 3002
// OSS 相关
const multer = require('multer') //npm i multer
const MAO = require('multer-aliyun-oss'); //npm install --save multer-aliyun-oss

const uplod = multer({
    storage: MAO({
        config: {
            region: 'oss-cn-xxx',
            accessKeyId: 'xxx',
            accessKeySecret: 'xxx',
            bucket: 'xxx'
        },
        destination: 'public/images'
    })
});


app.post('/upload', uplod.single('file'), (req, res) => {
    res.header("Access-Control-Allow-Origin", "*");
    // 可以自定义返回结果,推荐打印 req.file 查看,再决定如何返回数据给前端
    const file = req.file;
    console.log(file);
    res.send({
        status: '上传成功',
        code: 200,
        url: `https://book-recommendation-system.oss-cn-guangzhou.aliyuncs.com/public/images/${file.filename}`
    });
})

app.listen(PORT, () => console.log('Example app listening on port 3002!'))

api文件夹下的uploadPic.js,axios封装文件

import axios from 'axios';

const picAjax = axios.create({
    timeout: 10000
})

picAjax.interceptors.request.use((config) => {
    return config;
})

picAjax.interceptors.response.use((response) => {
    return response.data;
}, (error) => {
    return Promise.reject(error);
})

export default picAjax

App.vue

<template>
    <div>
        <input
            type="file"
            ref="file"
            @change="showImg"
        >
        <div>
            <span>读取本地图片的</span>
            <img
                :src="pic"
                alt=""
                style="height:200px;width:200px;"
            >
        </div>
        <button @click="submit">点击我发送图片进行测试</button>
        <div>
            <span>读取服务器图片的</span>
            <img
                :src="backPic"
                alt=""
                style="height:200px;width:200px;"
            >
        </div>
    </div>
</template>

<script>
import picAjax from '@/api/uploadPic'
export default {
    name: 'App',
    data () {
        return {
            pic: '',
            backPic: ''
        };
    },
    methods: {
        base64toFile: (dataurl, filename = 'file') => {
            let arr = dataurl.split(',')
            let mime = arr[0].match(/:(.*?);/)[1]
            let suffix = mime.split('/')[1]
            let bstr = atob(arr[1])
            let n = bstr.length
            let u8arr = new Uint8Array(n)
            while (n--) {
                u8arr[n] = bstr.charCodeAt(n)
            }
            let file = new File([u8arr], `${filename}.${suffix}`, {
                type: mime
            })
            return file
        },
        showImg () {
            let fr = new FileReader();
            fr.readAsDataURL(this.$refs.file.files[0]);
            fr.onload = () => {
                this.pic = fr.result;
            }
        },
        async submit () {
            let file = this.base64toFile(this.pic);
            let formData = new FormData();
            formData.append("file", file);
            let res = await picAjax.post('http://127.0.0.1:3002/upload', formData)
            this.backPic = res.url
        }
    },
}
</script>

<style>
</style>

本文讲的内容还是比较基础的,不过网上没有特别详细的博客把三者联动后进行上传的过程讲清楚的,所以我做一下我的学习记录,切记不要无脑照搬,请理解好其中的过程,再根据自己的项目进行修改会更好

另外提一嘴,感觉保存在云端不是特别好的选择,还是保存在本地数据库里会比较好,我再学习下,如果网上没特别好的教程我考虑下一篇写这个,那么我们下一篇博客再见!

Logo

前往低代码交流专区

更多推荐