前后端交互(二)

1,386 阅读11分钟

前后端交互(二)

  • ajax封装
  • 相册空间实例
    • ajax封装
    • 数据库存储
    • 上存图片处理
    • 跨域请求的三种方法

ajax封装

给ajax函数传递一个对象,对象里面有基本的配置:

  • get/post 方法
  • 路径 url
  • 接收回调的结果 success(){}
    ajax({
        method:'get',
        url:'/getData',
        success(data){
            console.log('data',data)
        }
    });
    
    

    ajax 函数:

    function noop() {}
    
    function ajax(options) {
        //合并配置
          options = {
            ...{
                method:'get',
                url:'',
                success:noop
            },
             ...options
        }
    
        console.log(111)
        //创建ajax对象
        let xhr = new XMLHttpRequest();
    
         //请求回调
        xhr.onload = function () {
            options.success(xhr.responseText);
        }
    
        //打开请求配置
        xhr.open(options.method,options.url,true);
    
    
        //发送请求
        xhr.send();
    }
    
  • query数据处理

    如果我们有query的数据要处理 http://localhost:9527/?a=1&b=2

    这种我们要怎么请求给后端。

    首先我们,应该在接口预定义个query的空对象,然后我们应该定义一处理query对象的函数

    我们理想是把 {a:1,b:2} 这样的一个对象变成a=1&b=2。

    //fn queryParse(obj)
    function queryParse(obj) {
        let arr = [];
        for (let item in obj){
            arr.push(`${item}=${obj[item]}`)
        }
    
        return arr.join('&');
    }
    

    url 处理

    let queryData = queryParse({a:1,b:2})
    if(queryData){
        options.url += '?' + queryData;
    }
    

yusNYd.png

  • body发送数据处理

    post

    body发送的数据区别于url的query,它是有很多格式json 等。我们不肯像 query数据处理简单用函数拼凑。我们可以是使用内置的js的formData对象。

    利用formData对象,遍历传入对象的里面的值,利用append方法加入到formData对象。这样向服务端发送formData数据

    function bodyParse(obj) {
        let fd = new FormData();
    
        for (let item in obj){
            fd.append(item,obj[item]);
        }
        return fd
    }
    //-------------------------------//
    //处理body数据
    let bodyData = null;
    if (options.data){
        bodyData = bodyParse(options.data);
    }
    
    
    //发送请求
    xhr.send(bodyData);
    

yuyihd.png

相册空间实例

  • 前端上存

点击上存文件时候,添加change事件。我们在type = files的input 可以获取到这个文件的信息。他是一个FileList对象,里面有file对象和一个file的length。

好,接下来,我们要传给后端的就是这个file对象。

yucT78.png

uploadFileEl.addEventListener('change',function () {

    console.log(this.files)
     for (let file of this.files)
     {
         uploadFile({
             file
         });
     }
})

//发送数据给客户端
function uploadFile(data) {
    ajax(
        {
            method:'post',
            url:'/upload',
            data,
            success(data){
                console.log(data)
            }
        }

    )
}

yugYut.png

  • 后端接口处理

    • koa-body库

      post过来的数据,我们要使用Koa-body库来处理

      koa-body,根据它的特性,我们就可以用这个库帮我们处理前端传过来的数据。

      特性:

      • can handle requests such as:
        • multipart/form-data
        • application/x-www-urlencoded
        • application/json
        • application/json-patch+json
        • application/vnd.api+json
        • application/csp-report
        • text/xml
      • option for patch to Koa or Node, or either
      • file uploads
      • body, fields and files size limiting
    • 英文版

      options 传递的参数:

      • patchNode {Boolean} Patch request body to Node's ctx.req, default false
      • patchKoa {Boolean} Patch request body to Koa's ctx.request, default true
      • jsonLimit {String|Integer} The byte (if integer) limit of the JSON body, default 1mb
      • formLimit {String|Integer} The byte (if integer) limit of the form body, default 56kb
      • textLimit {String|Integer} The byte (if integer) limit of the text body, default 56kb
      • encoding {String} Sets encoding for incoming form fields, default utf-8
      • multipart {Boolean} Parse multipart bodies, default false
      • urlencoded {Boolean} Parse urlencoded bodies, default true
      • text {Boolean} Parse text bodies, such as XML, default true
      • json {Boolean} Parse JSON bodies, default true
      • jsonStrict {Boolean} Toggles co-body strict mode; if set to true - only parses arrays or objects, default true
      • includeUnparsed {Boolean} Toggles co-body returnRawBody option; if set to true, for form encodedand and JSON requests the raw, unparsed requesty body will be attached to ctx.request.body using a Symbol, default false
      • formidable {Object} Options to pass to the formidable multipart parser
      • onError {Function} Custom error handle, if throw an error, you can customize the response - onError(error, context), default will throw
      • strict {Boolean} DEPRECATED If enabled, don't parse GET, HEAD, DELETE requests, default true
      • parsedMethods {String[]} Declares the HTTP methods where bodies will be parsed, default ['POST', 'PUT', 'PATCH']. Replaces strict option.

      后端我们怎么查看files文件,可以使用ctx.request.files.

      处理文件的物理路径,大小等要是用 options.formidable,里面的参数有

      • maxFields {Integer} Limits the number of fields that the querystring parser will decode, default 1000
      • maxFieldsSize {Integer} Limits the amount of memory all fields together (except files) can allocate in bytes. If this value is exceeded, an 'error' event is emitted, default 2mb (2 * 1024 * 1024)
      • uploadDir {String} Sets the directory for placing file uploads in, default os.tmpDir()
      • keepExtensions {Boolean} Files written to uploadDir will include the extensions of the original files, default false
      • hash {String} If you want checksums calculated for incoming files, set this to either 'sha1' or 'md5', default false
      • multiples {Boolean} Multiple file uploads or no, default true
      • onFileBegin {Function} Special callback on file begin. The function is executed directly by formidable. It can be used to rename files before saving them to disk. See the docs

      中文版:

    参数名描述类型默认值
    patchNode将请求体打到原生 node.js 的ctx.reqBooleanfalse
    patchKoa将请求体打到 koa 的 ctx.requestBooleantrue
    jsonLimitJSON 数据体的大小限制String / Integer1mb
    formLimit限制表单请求体的大小String / Integer56kb
    textLimit限制 text body 的大小String / Integer56kb
    encoding表单的默认编码Stringutf-8
    multipart是否支持 multipart-formdate 的表单Booleanfalse
    urlencoded是否支持 urlencoded 的表单Booleantrue
    text是否解析 text/plain 的表单Booleantrue
    json是否解析 json 请求体Booleantrue
    jsonStrict是否使用 json 严格模式,true 会只处理数组和对象Booleantrue
    formidable配置更多的关于 multipart 的选项Object{}
    onError错误处理Functionfunction(){}
    stict严格模式,启用后不会解析 GET, HEAD, DELETE 请求Booleantrue

    formidable 的相关配置参数

    参数名描述类型默认值
    maxFields限制字段的数量Integer1000
    maxFieldsSize限制字段的最大大小Integer2 * 1024 * 1024
    uploadDir文件上传的文件夹Stringos.tmpDir()
    keepExtensions保留原来的文件后缀Booleanfalse
    hash如果要计算文件的 hash,则可以选择 md5/sha1Stringfalse
    multipart是否支持多文件上传Booleantrue
    onFileBegin文件上传前的一些设置操作Functionfunction(name,file){}

    了解了koa-body后,其实我们可以它作为一个中间件返回,免得多个接口要使用的时候重复多写。还能用传入路径作为一个参数使用。

    // koabody 处理数据
    //upload 中间件
    const koaBody = require('koa-body');
    
    module.exports = function upload(dir) {
        return new koaBody({
            //让他支持 `multipart-formdata` 的表单
            multipart:true,
            //处理文件参数
            formidable:{
                keepExtensions:true,
                uploadDir : __dirname + '/../public/' + dir
            }
        })
    }
    

    app.js

    router.post('/upload',upload('upload'),(ctx)=>{
        // console.log(ctx.request.files);
        ctx.body = '上存成功'
    })
    
  • 前端优化进度条

    在ajax函数中配置onload.onprogress接口。

    ajax(
        {
            method:'post',
            url:'/upload',
            data,
            onprogress(ev){
                progressEl.style.width = (ev.loaded/ev.total) * 100 + '%';
            },
            success(data){
                taskProgressStatusEl.innerHTML = '上存成功'
                setTimeout(function () {
                    li.remove();
    
                },1000)
                console.log(data)
            }
        }
    )
    

    然后ajax函数中封装将onprogress 方法和xhr.upload.onprogress 联系起来

    xhr.upload.onprogress = options.onprogress;
    
  • 图片数据持久化保存

    持久化保存我们要用到mysql2这个库链接Mysql数据库

    mysql2

    //链接数据库
    let db = mysql.createConnection({
        host:'localhost',
        port:3307,
        user:'root',
        password:'123456',
        database: 'ajax_photos'
    
    })
    

    使用:

    // simple query 查询
    connection.query(
      'SELECT * FROM `table` WHERE `name` = "Page" AND `age` > 45',
      function(err, results, fields) {
        console.log(results); // results contains rows returned by server
        console.log(fields); // fields contains extra meta data about results, if available
      }
    );
     
    // with placeholder 如果复杂的话可以使用占位符 ?
    connection.query(
      'SELECT * FROM `table` WHERE `name` = ? AND `age` > ?',
      ['Page', 45],
      function(err, results) {
        console.log(results);
      }
    );
    

    插入数据:

    //插入数据库
    //INSERT INTO table_name (列1, 列2,...) VALUES (值1, 值2,....)
    db.query("insert into `photos` (`name`) values (?)",[pathname],function (err,result) {
        console.log('result',result)
    })
    
  • 跨域访问服务器

    我们这个相册实例的静态服务和路由服务分开。一个是专门提供后端返回的数据的服务器(9527),一个提供静态资源的服务器(3000)

    目录:

    y34gi9.png

    app2 是专门获取后端穿过来的数据

    appStatic是储存静态资源的, html css 等

    //appStatic app.js
    const koa = require('koa');
    const koaStatic = require('koa-static-cache');
    
    const app = new koa();
    //只提供静态服务
    app.use(koaStatic('./public',{
        prefix:'/public',
        gzip:true,
        dynamic:true
    }))
    
    app.listen(3000,()=>{
        console.log('服务启动:http://localhost:3000');
    })
    

    当我们请求,就会报错

    http://localhost:3000/public/index.html

    y35kzq.png

    那我们把ajax 的url 改成 9527 端口,就会出现前端经常的遇到的问题,跨域了

    Access to XMLHttpRequest at 'http://localhost:9527/getPhotos' from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

    这里就需要说到同源策略

    浏览器同源策略:
    • 同源策略是浏览器的一个安全功能,不同源的客户端脚本在没有明确授权的情况下,不能读写对方资源
    • 源 :协议、域名和端口号
    • 所以3000端口的请求9527端口的数据 即使,协议、域名一致了,端口号不一致。他就违背了同源策略,不能请求
    • ajax会受到同源策略影响
    处理方法:
    • jsonp封装
    • ngnix, apache

    • CORS跨域设置(需要后端配合)

      mdn

      CORS(Cross-origin resource sharing),跨域资源共享,是一份浏览器技术的规范,用来避开浏览器的同源策略

      简单来说就是解决跨域问题的除了jsonp外的另一种方法;比jsonp更加优雅。

      1.('Access-Control-Allow-Origin', '*') //这个表示任意域名都可以访问,默认不能携带cookie了。(必须字段)

      简单就是说,往前端发送Access-Control-Allow-Origin这个头信息,第二参数是接收方的。

      res.header('Access-Control-Allow-Origin', 'http://www.baidu.com'); //这样写,只有www.baidu.com 可以访问。
      res.header('Access-Control-Allow-Origin', '*'); //这个表示任意域名都可以访问。
      
      判断黑白名单,根据返过来的ctx 里面的和数据判断,是否可以设置跨域
      
      

      y3IauV.png

      代码:

      创建li时候带上图片资源地址的域,ajax url: 也要修改域

      function createLI(data) {
          let imgLi = document.createElement('li');
          data.name = 'http://localhost:9527/public/upload/' + data.name ;
          imgLi.innerHTML = `
                          <img src="${data.name}" alt="">
                          <div class="imgName">${data.imgName}</div>`
          contentUlEL.appendChild(imgLi);
      }
      
      //getphotos
      //获取全部 图片
      function getAllImage() {
          ajax({
      
              method: 'get',
              url: 'http://localhost:9527/getPhotos',
      

      2、预检请求(post)

      mdn --Access_control_CORS

      • 简单的请求直接发送,不用发送预检

        使用下列方法之一: GET HEAD POST

        除了被用户代理自动设置的首部字段(例如 Connection ,User-Agent)和在 Fetch 规范中定义为 禁用首部名称 的其他首部,允许人为设置的字段为 Fetch 规范定义的 对 CORS 安全的首部字段集合。该集合为:

        以下合集是安全合集,可以设置

        ​ Accept ​ Accept-Language ​ Content-Language ​ Content-Type (需要注意额外的限制) ​ DPR ​ Downlink ​ Save-Data ​ Viewport-Width ​ Width

        Content-Type 的值仅限于下列三者之一: text/plain multipart/form-data application/x-www-form-urlencoded

        请求中的任意XMLHttpRequestUpload 对象均没有注册任何事件监听器;

        XMLHttpRequestUpload 对象可以使用 XMLHttpRequest.upload 属性访问。 请求中没有使用 ReadableStream 对象。

        yGzYFA.png

      • 预检请求

        那要怎么做,需要在路由配置中,判断是否OPTIONS,然后设置请求头为通过。

        PUT
        DELETE
        CONNECT
        OPTIONS
        TRACE
        PATCH
        
        //发送这个头过去
        Access-Control-Allow-Methods: POST, GET, OPTIONS
        首部字段 Access-Control-Request-Method 告知服务器,实际请求将使用 POST 方法
        ..还有其他更多方法
        
    • 代理proxy( koa-server-http-proxy)
      • 跨域是浏览器规范,通过同服务器请求数据,不通过浏览器请求,也能解决浏览器限制;

      • 转发请求

      • 利用http模块实现简单的服务器转发

      • 利用 koa-server-http-proxy中间件实现代理

        app.use(koaServerHttpProxy('/api', {
            target: 'http://localhost:4000',
            pathRewrite: { '^/api': '' }
        }))
        

      node原生

      nodejs在提供静态服务的同时,提供静态代理服务。利用node http模块:

      http.request(options[, callback]) http.request(url, [options] [, callback])

      作用:为每个服务器维护多个连接以发出 HTTP 请求。 此函数允许显式地发出请求。

      因为Node里面没有浏览器的同源策略 , 当我们index.html发起请求时候,通过/api 代理了跨域请求的9527端口的/ getPhotos

      node-官方解析

      官网例子:

      const postData = querystring.stringify({
        'msg': '你好世界'
      });
      
      const options = {
        protocol:'http:',
        hostname: 'nodejs.cn',
        port: 80,
        path: '/upload',
        method: 'POST',
        headers: {
          'Content-Type': 'application/x-www-form-urlencoded',
          'Content-Length': Buffer.byteLength(postData)
        }
      };
      //有可能是异步
      const req = http.request(options, (res) => {
        console.log(`状态码: ${res.statusCode}`);
        console.log(`响应头: ${JSON.stringify(res.headers)}`);
        res.setEncoding('utf8');
        res.on('data', (chunk) => {
          console.log(`响应主体: ${chunk}`);
        });
        res.on('end', () => {
          console.log('响应中已无数据');
        });
      });
      
      req.on('error', (e) => {
        console.error(`请求遇到问题: ${e.message}`);
      });
      
      // 将数据写入请求主体。
      req.write(postData);
      req.end();
      
      
      // Proxy appStatic app.js
      //proxy
      
      //跨域服务代理
      app.use(async (ctx,next)=>{
      
          let data = await proxyRequest(ctx);
      
          ctx.body =data;
      
          await next();
      })
      
      function proxyRequest(ctx) {
          return new Promise(resolve=>{
      
              const options = {
                  protocol:'http:',
                  hostname: 'localhost',
                  port: 9527,
                  path: '/getPhotos',
                  method: 'get',
                  headers: ctx.request.headers
              };
      
      
              const req = http.request(options,(res)=>{
                  let data =''
                  res.on('data', (chunk) => {
                      data += chunk;
                       console.log(`data: ${chunk}`);
                  });
                  res.on('end', () => {
                      resolve(data);
                      console.log('no data query');
                  });
              });
      
              req.on('error', (e) => {
                  console.error(`请求遇到问题: ${e.message}`);
              });
      
              req.write('');
              req.end();
          })
      
      }
      

      利用 koa-server-http-proxy中间件实现代理

      koa-server-http-proxy

      const Koa = require('koa')
       
      const app = new Koa()
       
      const proxy = require('koa-server-http-proxy')
       
      app.use(proxy('/api', {
          //需要跨域的地址
        target: 'https://news-at.zhihu.com',
          //将api重写成目标服务器的地址
        pathRewrite: { '^/api': 'api/4/' },
        changeOrigin: true
      }))
       
      app.listen(3000)
       
      

用户登录鉴权

有用得文章讲解jwt

一、jwt:json web token是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准,推荐放在头信息,防止拦截伪造

二、生成token

  • jsonwebtoken 模块

    •     const token = jwt.sign({
                name: reslut[0].username,
                _id: reslut[0].id
            }, 'my_token', { expiresIn: '2h' });
      

三、缓存token:cookie 或者是 locaStroage

​ 通过ajax对象拿到头信息。

四、token的组成

  • 头部的基本信息;

    {
      "typ": "JWT",
      "alg": "HS256"
    }
    
  • paload :存放自定义信息 ; 预定义信息有如下几个:

    iss: 该JWT的签发者
    sub: 该JWT所面向的用户
    aud: 接收该JWT的一方
    exp(expires): 什么时候过期,这里是一个Unix时间戳
    iat(issued at): 在什么时候签发的
    
  • signature 签名 哈希需要有secret;

Access-Control-Allow-Credentials:布尔值 true允许携带凭证;(可选字段)

//客户端设置允许携带用户凭证
xhr.withCredentials = true;

//服务端设置允许携带凭证
ctx.set("Access-Control-Allow-Credentials",true);

五、前端的认证

koa-jwt 库验证

"Authorization","Bearer " + token

相册实例源码

gitee门