七牛图片上传

-- 网络请求实战

课程概要

本课程主要讲解图片上传案例,在日常开发中,我们会涉及到图片的上传,大部分对静态资源的管理都托管在云存储,本节课程将为大家实战图片是如何与云存储服务以及本服务器交互。涉及到请求和异步的知识点有 XJAX、XHR、Fetch、AJAX、AXIOS 以及 callback、Promise、asyncAwait。通过本课程,完成一次七牛图片的存储交互,大家将网络请求、异步的原理和开发有更深入的了解。

主要包含以下功能:

  • 选择图片
  • 获取七牛令牌
  • 把图片上传到七牛云存储
  • 把七牛云存储的图片地址告诉服务器

相关原理

步骤:

  1. 请求获取 token
  2. 把图片上传到七牛
  3. 获取图片的七牛地址上传

知识点

本课程涉及到的主要知识点有:

  1. AJAX

传统的Web应用允许用户端填写表单(form),当提交表单时就向网页服务器发送一个请求。服务器接收并处理传来的表单,然后送回一个新的网页,但这个做法浪费了许多带宽,因为在前后两个页面中的大部分HTML码往往是相同的。由于每次应用的沟通都需要向服务器发送请求,应用的回应时间依赖于服务器的回应时间。这导致了用户界面的回应比本机应用慢得多。

AJAX即“Asynchronous JavaScript and XML”(异步的JavaScript与XML技术),指的是一套综合了多项技术的浏览器端网页开发技术。Ajax的概念由杰西·詹姆士·贾瑞特所提出。

Ajax 技术的核心是 XMLHttpRequest 对象(简称 XHR),这是由微软首先引入的一个特性,其他 浏览器提供商后来都提供了相同的实现。在 XHR 出现之前,Ajax 式的通信必须借助一些 hack 手段来实 现,大多数是使用隐藏的框架或内嵌框架。XHR 为向服务器发送请求和解析服务器响应提供了流畅的 接口。能够以异步方式从服务器取得更多信息,意味着用户单击后,可以不必刷新页面也能取得新数据。 也就是说,可以使用 XHR 对象取得新数据,然后再通过 DOM 将新数据插入到页面中。另外,虽然名 字中包含 XML 的成分,但 Ajax 通信与数据格式无关。这种技术就是无须刷新页面即可从服务器取得数 据,但不一定是 XML 数据。

  1. XMLHttpRequest

XMLHttpRequest 是 HTML 原生的网络请求对象, XHR

function _XHR(method, url, datas,success) {
  let xhr = new XMLHttpRequest();
  xhr.open(method, url, true);

  let formData = new FormData();
  for(let key in datas){
    formData.append(key, datas[key]);
  }

  xhr.onerror = function(xhr, status, text) {
    console.log(xhr, status, text);
  };

  xhr.onreadystatechange = function(response) {
    if (xhr.readyState == 4 && xhr.status == 200 && xhr.responseText != "") {
      typeof success === 'function' && success(JSON.parse(xhr.response))
    } else if (xhr.status != 200 && xhr.responseText) {
      console.log(xhr, xhr.status, xhr.responseText);
    }
  };

  xhr.send(formData);
}

const URL = 'https://www.jevescript.com/api/qiniu-token';
_XHR('GET',URL,{},(res)=>{
  console.log(res)
})
  1. Fetch

Fetch API 提供了一个获取资源的接口(包括跨域)。任何使用过 XMLHttpRequest 的人都能轻松上手,但新的API提供了更强大和灵活的功能集。

const URL = 'https://www.jevescript.com/api/qiniu-token';
fetch(URL)
  .then(function(response) {
    return response.json();
  })
  .then(function(myJson) {
    console.log(myJson);
  });
  1. jQuery $.ajax

jQuery 执行一个异步的HTTP(Ajax)的请求。$.ajax 使用示例:

HTML 需要引用 jQuery 依赖

<script src="https://lib.baomitu.com/jquery/3.3.1/jquery.min.js"></script>
$.ajax({
  type: 'GET',
  url: URL,
  data: {},
  success: ( res )=>{
    console.log(res)
  },
  error: (err) => {
    console.log(err)
  }
})
  1. axios

Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中。axios 使用示例:

HTML 需要引用 axios 依赖

<script src="https://lib.baomitu.com/axios/0.19.0-beta.1/axios.js"></script>
const URL = 'https://www.jevescript.com/api/qiniu-token';
axios.get(URL).then( res => {
  console.log(res.data)
})
  1. 异步处理

由于 XHR 是一个异步的机制,在处理请求的时候,需要等待服务端返回才能拿到数据。有时候我们需要从服务端返回拿到数据,再用这个数据去请求服务端,再用返回的数据去处理其他逻辑。当嵌套够深的时候,就会陷入一个 callbackhell(回调地狱)。在处理异步事件中,主要有以下几种方法:

  • callback
  • Promise
  • async/await 在此我们统一用 setTimeout 来模拟 xhr 的异步数据返回。

callback

const xhr1 = function(cb){
  setTimeout(()=>{
    const data = 1;
    typeof cb === 'function' && cb(data)
  },2000)
}

const xhr2 = function(cb){
  setTimeout(()=>{
    const data = 2;
    typeof cb === 'function' && cb(data)
  },2000)
}

const xhr3 = function(cb){
  setTimeout(()=>{
    const data = 3;
    typeof cb === 'function' && cb(data)
  },2000)
}

// 执行
xhr1((data1)=>{
  console.log(data1)
  xhr2((data2)=>{
    console.log(data2)
    xhr3((data3)=>{
      console.log(data3)
    })
  })
})

Promise

const xhr1 = function(){
  return new Promise((resolve,reject)=>{
    setTimeout(()=>{
      const data = 1;
      resolve(data)
    },2000)
  })
}

const xhr2 = function(){
  return new Promise((resolve,reject)=>{
    setTimeout(()=>{
      const data = 2;
      resolve(data)
    },2000)
  })
}

const xhr3 = function(){
  return new Promise((resolve,reject)=>{
    setTimeout(()=>{
      const data = 3;
      resolve(data)
    },2000)
  })
}

// 执行
xhr1().then((data1)=>{
  console.log(data1);
  return xhr2();
}).then((data2)=>{
  console.log(data2);
  return xhr3();
}).then((data3)=>{
  console.log(data3)
}).catch( e => {
  console.log(e)
})

// 并行执行
Promise.all([xhr1(),xhr2(),xhr3()]).then((data)=>{
  console.log(data)
}).catch( e => {
  console.log(e)
})

async/await

const xhr1 = function(){
  return new Promise((resolve,reject)=>{
    setTimeout(()=>{
      const data = 1;
      resolve(data)
    },2000)
  })
}

const xhr2 = function(){
  return new Promise((resolve,reject)=>{
    setTimeout(()=>{
      const data = 2;
      resolve(data)
    },2000)
  })
}

const xhr3 = function(){
  return new Promise((resolve,reject)=>{
    setTimeout(()=>{
      const data = 3;
      resolve(data)
    },2000)
  })
}

async function xhr(){
  const data1 = await xhr1();
  console.log(data1);
  const data2 = await xhr2();
  console.log(data2);
  const data3 = await xhr3();
  console.log(data1,data2,data3);
}

xhr();

实现步骤

  1. HTML & CSS
  2. 构建单例对象PAGE
  3. XHR 上传
  4. Axios 上传
  5. Fetch Async/Await 上传

HTML & CSS

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>uploadImage</title>
</head>
<body>
  <input id="upload" type="file" name="fulAvatar">
  <button id="submit">上传</button>
  <script src="https://lib.baomitu.com/axios/0.19.0-beta.1/axios.min.js"></script>
  <script type="text/javascript">

  </script>
</body>
</html>

构建单例对象PAGE

  1. 定义 PAGE
  2. 在 PAGE.data 中存放上传 API
// 1. 定义 PAGE
const PAGE = {
  // 2. 在 PAGE.data 中存放上传 API
  data: {
    TOKEN_API:'https://www.jevescript.com/api/qiniu-token',
    QINIU_API: 'http://upload-z2.qiniup.com',
    UPLOAD_API :'https://www.jevescript.com/api/image-upload'
  },
  init: function() {
    this.bind();
  },
  bind: function() {

  }
}

PAGE.init();

XHR 上传

  1. 封装 PAGE._XHR 公共请求方法
  2. 绑定点击按钮事件
  3. 点击按钮判断是否有选择图片
  4. 获取 token
  5. 上传图片
  6. 上传图片地址
const PAGE = {
  data: {
    TOKEN_API:'https://www.jevescript.com/api/qiniu-token',
    QINIU_API: 'http://upload-z2.qiniup.com',
    UPLOAD_API :'https://www.jevescript.com/api/image-upload'
  },
  init: function() {
    this.bind();
  },
  bind: function() {
    // 2. 绑定点击按钮事件
    let button = document.getElementById('submit');
    button.addEventListener('click',this.XHRuploadImage);
  },
  XHRuploadImage: function() {
    // 3. 点击按钮判断是否有选择图片
    let files = document.getElementById('upload').files;
    let file = files[0];
    if(!file){
      console.log('缺少文件');
      return
    }

    // 4. 获取 token
    let domain;
    PAGE._XHR('GET', PAGE.data.TOKEN_API ,{},(res)=>{
      let token = res.data.uptoken;
      domain = res.data.domain;
      let key = Date.now() + '_' + file.name;
      let formData = {};
      formData['file'] = file;
      formData['key'] = key;
      formData['fname'] = file.name;
      formData['token'] = token;
      formData['x.name'] = file.name;
      formData['form-data'] = true;

      // 5. 上传图片
      PAGE._XHR('POST',  PAGE.data.QINIU_API, formData, (res) => {
        let image_url = domain +'/' + key;

        // 6. 上传图片地址
        PAGE._XHR('POST',  PAGE.data.UPLOAD_API, { image_url }, (res) => {
          if(res.code === 200 ){
            console.log(image_url);
            alert('提交成功!')
          }else{
            alert('提交失败!')
          }
        })
      })
    })
  },
  //1. 封装 PAGE.\_XHR 公共请求方法
  _XHR: function(method,url,datas,success,progress,error,csrf) {
    let xhr = new XMLHttpRequest();
    xhr.open(method, url, true);
    if(csrf){
      xhr.withCredentials = true;
      xhr.setRequestHeader('X-CSRF-TOKEN',csrf);
    }

    let formData;
    if(datas['form-data']){
      formData = new FormData();
      for(let key in datas){
        formData.append(key, datas[key]);
      }
      datas = formData;
    }else{
      formData = JSON.stringify(datas);
      xhr.setRequestHeader('content-type', 'application/json')
    }

    xhr.upload.onprogress = function (event) {
      typeof progress === 'function' && progress(event);
    };

    xhr.onerror = function(xhr, status, text) {
      typeof error === 'function' && error(xhr, status, text);
    };

    xhr.onreadystatechange = function(response) {
      if (xhr.readyState == 4 && xhr.status == 200 && xhr.responseText != "") {
        typeof success === 'function' && success(JSON.parse(xhr.response))
      } else if (xhr.status != 200 && xhr.responseText) {
        typeof error === 'function' && error(xhr, xhr.status, xhr.responseText);
      }
    };

    xhr.send(formData);
  }
}

PAGE.init();

Axios 上传

  1. 引入 axios
  2. 绑定点击按钮事件
  3. 点击按钮判断是否有选择图片
  4. 获取 token
  5. 上传图片
  6. 上传图片地址
<!-- 1. 引入 axios -->
<script src="https://lib.baomitu.com/axios/0.19.0-beta.1/axios.min.js"></script>
const PAGE = {
  data: {
    TOKEN_API:'https://www.jevescript.com/api/qiniu-token',
    QINIU_API: 'http://upload-z2.qiniup.com',
    UPLOAD_API :'https://www.jevescript.com/api/image-upload'
  },
  init: function() {
    this.bind();
  },
  bind: function() {
    let button = document.getElementById('submit');
    // button.addEventListener('click',this.XHRuploadImage);

    // 2. 绑定点击按钮事件
    button.addEventListener('click',this.AXIOSuploadImage);
  },
  AXIOSuploadImage: function() {
    // 3. 点击按钮判断是否有选择图片
    let files = document.getElementById('upload').files;
    let file = files[0];
    if(!file){
      console.log('缺少文件');
      return
    }

    // 4. 获取 token
    let domain;
    let key = Date.now() + '_' + file.name;
    axios.get(PAGE.data.TOKEN_API)
      .then( res => {
        let token = res.data.data.uptoken;
        domain = res.data.data.domain;
        return token
      })
      .then( token => {
        let formData = new FormData();
        formData.append('file', file);
        formData.append('key', key);
        formData.append('fname', file.name);
        formData.append('token', token);
        formData.append('x.name', file.name);

        // 5. 上传图片
        return axios.post(PAGE.data.QINIU_API, formData, {
          headers: {
            'Content-Type': 'multiple/form-data'
          }
        })
      })
      .then( res => {
        let image_url = domain +'/' + key;

        // 6. 上传图片地址
        return axios.post(PAGE.data.UPLOAD_API,{ image_url })
      })
      .then( res => {
        if(res.data.code === 200 ){
          alert('提交成功!')
        }else{
          alert('提交失败!')
        }
      })
  },
  ...
}

Fetch Async/Await 上传

  1. 绑定点击按钮事件
  2. 点击按钮判断是否有选择图片
  3. 获取 token
  4. 上传图片
  5. 上传图片地址
const PAGE = {
  data: {
    TOKEN_API:'https://www.jevescript.com/api/qiniu-token',
    QINIU_API: 'http://upload-z2.qiniup.com',
    UPLOAD_API :'https://www.jevescript.com/api/image-upload'
  },
  init: function() {
    this.bind();
  },
  bind: function() {
    let button = document.getElementById('submit');
    // button.addEventListener('click',this.XHRuploadImage);
    // button.addEventListener('click',this.AXIOSuploadImage);
    
    // 1. 绑定点击按钮事件
    button.addEventListener('click',this.FetchuploadImage);
  },
  FetchuploadImage: async function() {
    // 2. 点击按钮判断是否有选择图片
    let files = document.getElementById('upload').files;
    let file = files[0];
    if(!file){
      console.log('缺少文件');
      return
    }

    // 3. 获取 token
    let tokenFetch = await fetch(PAGE.data.TOKEN_API).then((response) => response.json());
    let token = tokenFetch.data.uptoken;
    let domain = tokenFetch.data.domain;
    let key = Date.now() + '_' + file.name;
    let formData = new FormData();
    formData.append('file', file);
    formData.append('key', key);
    formData.append('fname', file.name);
    formData.append('token', token);
    formData.append('x.name', file.name);

    // 4. 上传图片
    let qiniuFetch = await fetch(PAGE.data.QINIU_API,{
      method: 'POST',
      body: formData
    }).then(response => response.json())
    let image_url =  domain +'/' + key;

    // 5. 上传图片地址
    let uploadFetch = await fetch(PAGE.data.UPLOAD_API,{
      method: 'POST',
      body: JSON.stringify({ image_url }),
      headers: {
        'content-type': 'application/json'
      },
    }).then(response => response.json())
    if(uploadFetch.code === 200 ){
      alert('提交成功!')
    }else{
      alert('提交失败!')
    }
  },
  ...
}