一篇文章全面了解Ajax框架

目录

什么是AJAX?

如何使用AJAX?

语法

url详解

URL的概念

URL的组成

URL查询参数

axios

axios-查询参数

axios-请求配置

常用请求方法

axios-错误处理

HTTP协议

HTTP协议-请求报文

HTTP协议-响应报文

接口文档

AJAX原理

[AJAX原理 - XMLHttpRequest](#AJAX原理 - XMLHttpRequest)

Promise

[Promise - 三种状态](#Promise - 三种状态)

AJAX封装


什么是AJAX?

概念:AJAX是浏览器与服务器进行数据通信的技术。

定义:AJAX是异步的JavaScript和XML(Asynchronous JavaScript And XML)。简单点说,就是使用XMLHttpRequest对象与服务器通信。它可以使用JSON,XML,HTML和text文本等格式发送和接收数据。AJAX最吸引人的就是它的"异步"特性,也就是说它可以在不重新刷新页面的情况下与服务器通信,交换数据,或更新页面。


如何使用AJAX?

语法

  1. 引入axios.js:https://cdn.jsdelivr.net/npm/axios/dist/axios/min.js
  2. 使用axios函数
    1. 传入配置对象
    2. 再用**.then**回调函数接收结果,并做后续处理

接下来让我们通过代码演示看看ajax的作用吧,代码、需求及效果图如下:

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>ajax</title>
</head>
<body>
    <!--
        axios库地址:https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js
        省份数据地址:http://hmajax.itheima.net/api/province

        目标:使用axios库,获取省份列表数据,展示到页面上
    -->
    <!-- 渲染 -->
    <p class="my-p"></p>
    <!-- 1.引入axios库 -->
    <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
    <!-- 2.使用axios函数 -->
    <script>
        axios({
            url:'http://hmajax.itheima.net/api/province'
        }).then(result => {
            // 打印返回结果
            console.log(result)
            // 打印省份列表
            console.log(result.data.list)
            // 打印省份
            console.log(result.data.list.join('<br>'))
            // 插入页面
            document.querySelector('.my-p').innerHTML = result.data.list.join('<br>')
        })
    </script>
</body>
</html>

可以看到,通过axios函数我们可以从url中获取数据资源,并进行处理,那么url是什么意思呢?接下来我们进行详细的了解。


url详解

URL的概念

Internet上的每一个网页都具有一个唯一的名称标识,通常称之为URL(Uniform Resource Locator, 统一资源定位器)。

它是www的统一资源定位标志,简单地说URL就是web地址,俗称"网址"。

URL的组成

URL由三部分组成:资源类型、存放资源的主机域名、资源文件名

也可认为由4部分组成:协议、主机、端口、路径。(很多时候端口都是隐藏的)

还可以认为由7部分组成:协议,域名,端口,虚拟目录,文件名,锚,参数

URL的一般语法格式:(带方括号[]的为可选项):protocol :// hostname[:port] / path / [;parameters][?query]#fragment
**案例:**https://www.baidu.com

协议:https

**域名(IP):**www.baidu.com

端口(port):443

资源路径:当没有资源路径时,可以默认是 / ,在各个网站通常有默认的路径如index.html

URL查询参数

定义:浏览器提供给服务器的额外信息,让服务器返回浏览器想要的数据

语法:http://xxxx.com/xxx/xxx**?**参数名1=值1**\&**参数名2=值2

示例:假如前面的案例,我要获取河北省下辖市的信息,就可以写为:

http://hmajax.itheima.net/api/city?pname=河北省

axios

现在开始,我们对axios进行全面的了解和使用。

axios-查询参数

语法:使用axios提供的params选项

注意:axios在运行时把参数名和值,会拼接到url**?参数名=值**

javascript 复制代码
axios({
    url:'目标资源地址'
    params:{
        参数名:值
    }
}).then(result=>{
    //对服务器返回的数据做后续处理
})

那么让我们来使用查询参数查询一下云南省下辖市吧:

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>查询参数</title>
</head>
<body>
    <p></p>
    <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
    <script>
        axios({
            url:'http://hmajax.itheima.net/api/city',
            //查询参数
            params:{
                pname:'云南省'
            }
        }).then(result => {
            console.log(result.data.list)
            document.querySelector('p').innerHTML = result.data.list.join('<br>')
        })
    </script>
</body>
</html>

渲染图如上所示,可以看到我们的确拿到了对应的数据,那么此时我们就完成了对查询参数的学习了。

axios-请求配置

常用请求方法

现在我们已经可以从服务器拿到对应的数据了,那么接下来我们来学习一下然后对服务器数据做其他的相关操作:

请求方法:对服务器资源,要执行的操作

|--------|----------|
| 请求方法 | 操作 |
| GET | 获取数据 |
| POST | 提交数据 |
| PUT | 修改数据(全部) |
| DELETE | 删除数据 |
| PATCH | 修改数据(部分) |


那么接下来我们就看看axios请求配置该如何操作吧:

  • url:请求的URL网址

  • method :请求的方法,GET可以省略(不区分大小写)

  • data:提交数据

    axios({
    url: '目标资源地址',
    method: '请求方法',
    data: {
    参数名:值
    }
    }).then((result)=>{
    // 对服务器返回的数据做后续处理
    })

接下来我们使用POST完成一个案例吧:

复制代码
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>常用请求方法和数据提交</title>
</head>

<body>
  <button class="btn">注册用户</button>
  <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
  <script>
    /*
      注册用户:http://hmajax.itheima.net/api/register
      请求方法:POST
      参数名:
        username:用户名(中英文和数字组成,最少8位)
        password:密码  (最少6位)

      目标:点击按钮,通过axios提交用户和密码,完成注册
    */
    document.querySelector('.btn').addEventListener('click', () => {
      axios({
        url: 'http://hmajax.itheima.net/api/register',
        method: 'POST',
        data: {
          username: 'qingwan666',
          password: 'qingwan666'
        }
      }).then(result => {
        console.log(result);
      })
    })
  </script>
</body>

</html>

打开网页-点击按钮后效果图->

再次点击按钮效果图->

axios-错误处理

通过刚才的案例,我们发现第二次点击按钮之后会输出错误信息,因为我们第二次点击注册按钮,原本的账号已经被我们注册过了,在服务器中已经不是唯一的了,所以给我们返回了一个报错信息,提示账号被占用,那么我们如何处理这种情况呢?

语法 :在then 方法的后面,通过点语法调用catch 方法,传入回调函数 并定义形参

复制代码
axios({
    //请求选项
}).then(result=>{
    //处理数据
}).catch(error=>{
    //处理错误
})

接下来我们继续使用刚刚的案例来完成错误处理的代码,需求:重复注册时通过弹窗提示用户错误原因

html 复制代码
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>axios错误处理</title>
</head>

<body>
  <button class="btn">注册用户</button>
  <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
  <script>
    /*
      注册用户: http://hmajax.itheima.net/api/register
      请求方法: POST
      参数名:
        username: 用户名 (中英文和数字组成, 最少8位)
        password: 密码 (最少6位)

      目标: 点击按钮, 通过axios提交用户和密码, 完成注册
      需求: 使用axios错误处理语法, 拿到报错信息, 弹框反馈给用户
    */
   document.querySelector('.btn').addEventListener('click', () => {
    axios({
      url: 'http://hmajax.itheima.net/api/register',
      method: 'post',
      data: {
        username: 'qingwan666',
        password: 'qingwan666'
      }
    }).then(result => {
      // 成功
      console.log(result)
    }).catch(error => {
      // 失败
      // 处理错误信息
      console.log(error)
      console.log(error.response.data.message)
      alert(error.response.data.message)
    })
   })
  </script>
</body>

</html>

点击按钮重复注册后->

这里就可以看到,重复注册时,浏览器已经能够通过弹窗提示用户了。


HTTP协议

那么关于Post和Get......,我们要如何在浏览器中看到对应的信息呢?它们的原理是什么呢?接下来我们要开始针对HTTP进行学习。

HTTP协议:规定了浏览器发送及服务器返回内容的格式

HTTP协议-请求报文

请求报文:浏览器按照HTTP协议要求的格式 ,发送给服务器的内容

请求报文的组成:

  1. 请求行:请求方法,URL,协议
  2. 请求头:以键值对的格式携带的附加信息,比如:Content-Type
  3. 空行:分割请求头,空行之后的是发送给服务器的资源
  4. 请求体:发送的资源

接下来我们继续用前面的案例,来看看HTTP的请求报文:

网络->左侧选择名称->标头->原始:这里就可看到请求行与请求头了

再点击负载:即可查看请求体

HTTP协议-响应报文

响应报文:服务器按照HTTP协议要求的格式 ,返回给浏览器的内容

响应报文的组成:

  1. 响应行(状态行):协议、HTTP响应状态码、状态信息
  2. 响应头:以键值对的格式携带的附加信息,比如:Content-Type
  3. 空行:分割响应头,空行之后的是发送给服务器的资源
  4. 响应体:返回的资源

HTTP响应状态码:用来表明请求是否成功完成

以下是各个状态码说明。例如:404(服务器找不到资源)

|-----|-------|
| 状态码 | 说明 |
| 1xx | 信息 |
| 2xx | 成功 |
| 3xx | 重定向消息 |
| 4xx | 客户端错误 |
| 5xx | 服务端错误 |

查看响应报文的方式与请求报文大致相同,只需要将请求标头换为响应标头,负载换为响应即可

接口文档

接口文档:描述接口的文章(后端工程师提供)

接口:使用AJAX和服务器通讯时,使用的URL,请求方法,以及参数

举例:下图为登录接口文档

这里给了我们请求方法、URL,以及需要的参数,那么我们用代码来写一下登入功能:

html 复制代码
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>接口文档</title>
</head>

<body>
  <button class="btn">用户登录</button>
  <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
  <script>
    document.querySelector('.btn').addEventListener('click', () => {
      // 用户登录
      axios({
        url: 'http://hmajax.itheima.net/api/login',
        method: 'post',
        data: {
          username: 'qingwan666',
          password: 'qingwan666'
        }
      })
    })
  </script>
</body>

</html>

点击登录按钮->

以上就是接口文档的使用教程啦,接下来我们进入下一步的学习。

AJAX原理

AJAX原理 - XMLHttpRequest

定义:XMLHttpRequest(XHR)对象用于与服务器交互,通过XMLHttpRequest可以在不刷新页面的情况下请求特定URL,获取数据。这允许网页在不影响用户操作的情况下,更新页面的局部内容。XMLHttpRequest在AJAX变成中被大量使用。

关系:axios内部采用XMLHttpRequest与服务器交互

步骤:

  1. 创建XMLHttpRequest对象

  2. 配置请求方法 和请求url地址

  3. 监听loadend事件,接收响应结果

  4. 发起请求

    const xhr = new XMLHttpRequest()
    xhr.open('请求方法', '请求url网址')
    xhr.addEventListener('loadend', () => {
    //响应结果
    console.log(xhr.response)
    })
    //发送请求
    xhr.send()

我们继续用前面获取地区的案例来演示一下原生XML如何使用:

html 复制代码
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>XMLHttpRequest_基础使用</title>
</head>

<body>
  <p class="my-p"></p>
  <script>
    /**
     * 目标:使用XMLHttpRequest对象与服务器通信
     *  1. 创建 XMLHttpRequest 对象
     *  2. 配置请求方法和请求 url 地址
     *  3. 监听 loadend 事件,接收响应结果
     *  4. 发起请求
    */
    // 1. 创建 XMLHttpRequest 对象
    const xhr = new XMLHttpRequest()

    // 2. 配置请求方法和请求 url 地址
    xhr.open('GET', 'http://hmajax.itheima.net/api/province')

    // 3. 监听 loadend 事件,接收响应结果
    xhr.addEventListener('loadend', () => {
      console.log(xhr.response)
      const data = JSON.parse(xhr.response)
      console.log(data.list.join('<br>'))
      document.querySelector('.my-p').innerHTML = data.list.join('<br>')
    })

    // 4. 发起请求
    xhr.send()
  </script>
</body>

</html>

网页效果图->

接下来我们看看原生XML查询参数的使用案例:

html 复制代码
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>XMLHttpRequest_查询参数</title>
</head>

<body>
  <p class="city-p"></p>
  <script>
    /**
     * 目标:使用XHR携带查询参数,展示某个省下属的城市列表
    */
    const xhr = new XMLHttpRequest()
    xhr.open('GET', 'http://hmajax.itheima.net/api/city?pname=辽宁省')
    xhr.addEventListener('loadend', () => {
      console.log(xhr.response)
      const data = JSON.parse(xhr.response)
      console.log(data)
      document.querySelector('.city-p').innerHTML = data.list.join('<br>')
    })
    xhr.send()
  </script>
</body>

</html>

再看看如何提交数据:

html 复制代码
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>XMLHttpRequest_数据提交</title>
</head>

<body>
  <button class="reg-btn">注册用户</button>
  <script>
    /**
     * 目标:使用xhr进行数据提交-完成注册功能
    */
    document.querySelector('.reg-btn').addEventListener('click', () => {
      const xhr = new XMLHttpRequest()
      xhr.open('POST', 'http://hmajax.itheima.net/api/register')
      xhr.addEventListener('loadend', () => {
        console.log(xhr.response)
      })

      // 设置请求头-告诉服务器内容类型(JSON字符串)
      xhr.setRequestHeader('Content-Type', 'application/json')
      // 准备提交的数据
      const userObj = {
        username: 'itheima007',
        password: '7654321'
      }
      const userStr = JSON.stringify(userObj)
      // 设置请求体,发起请求
      xhr.send(userStr)
    })
  </script>
</body>

</html>

学习原生XML只是为了了解axios的原理,帮助我们更加方便的使用它,继续开始下一步的学习


Promise

由于AJAX是异步请求,对于有些场景,如果请求失败,那么需要如何操作呢?

这时候我们就需要Promise来帮助我们管理

定义:Promise对象用于表示一个异步操作的最终完成(或失败)及其结果值。

我们再了解下学习Promise有什么好处:

  • 逻辑更清晰
  • 了解axios函数内部运作机制
  • 能解决回调函数地狱问题
html 复制代码
// 1.创建Promise对象
const p = new Promise((resolve, reject) => {
    //2.执行异步任务-并传递结果
    //成功调用:resolve(值) 触发then()执行
    //失败调用:reject(值) 触发catch()执行
})
// 3.接收结果
p.then(result => {
    //成功
}).catch(error => {
    //失败
})

让我们用代码看看Promise吧:

html 复制代码
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>认识Promise</title>
</head>

<body>
  <script>
    /**
     * 目标:使用Promise管理异步任务
    */
    // 1. 创建Promise对象
    const p = new Promise((resolve, reject) => {
      // 2. 执行异步代码
      setTimeout(() => {
        // resolve('模拟AJAX请求-成功结果')
        reject(new Error('模拟AJAX请求-失败结果'))
      }, 2000)
    })

    // 3. 获取结果
    p.then(result => {
      console.log(result)
    }).catch(error => {
      console.log(error)
    })
  </script>
</body>

</html>

Promise - 三种状态

作用:了解Promise对象如何关联处理函数,以及代码执行顺序

  1. new Promise() 此时的Promise状态:待定
  2. resolve() 此时的Promise状态:已兑现(.then(回调函数))
  3. reject() 此时的Promise状态:已拒绝(.catch(回调函数))

待定(pending):初始状态,既没有被兑现,也没有被拒绝

已兑现(fulfilled):意味着,操作成功完成

已拒绝(rejected):意味着,操作失败

注意:Promise对象一旦被兑现/拒绝,就无法改变状态了


AJAX封装

现在我们已经学习了XML和Promise,也就是axios内部最核心的两个技术我们都已经掌握,那么现在我们来像axios函数一样进行一个调用。

需求:基于Promise + XHR封装myAxios函数,获取省份列表展示

步骤:

  1. 定义myAxios函数,接收配置兑现 ,返回Promise对象
  2. 发起XHR请求,默认请求方法为GET
  3. 调用成功/失败的处理程序
  4. 使用myAxios函数,获取省份列表展示

代码实现:

html 复制代码
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>封装_简易axios函数_获取省份列表</title>
</head>

<body>
  <p class="my-p"></p>
  <script>
    /**
     * 目标:封装_简易axios函数_获取省份列表
     *  1. 定义myAxios函数,接收配置对象,返回Promise对象
     *  2. 发起XHR请求,默认请求方法为GET
     *  3. 调用成功/失败的处理程序
     *  4. 使用myAxios函数,获取省份列表展示
    */
    // 1. 定义myAxios函数,接收配置对象,返回Promise对象
    function myAxios(config) {
      return new Promise((resolve, reject) => {
        // 2. 发起XHR请求,默认请求方法为GET
        const xhr = new XMLHttpRequest()
        xhr.open(config.method || 'GET', config.url)
        xhr.addEventListener('loadend', () => {
          // 3. 调用成功/失败的处理程序
          if (xhr.status >= 200 && xhr.status < 300) {
            resolve(JSON.parse(xhr.response))
          } else {
            reject(new Error(xhr.response))
          }
        })
        xhr.send()
      })
    }

    // 4. 使用myAxios函数,获取省份列表展示
    myAxios({
      url: 'http://hmajax.itheima.net/api/province'
    }).then(result => {
      console.log(result)
      document.querySelector('.my-p').innerHTML = result.list.join('<br>')
    }).catch(error => {
      console.log(error)
      document.querySelector('.my-p').innerHTML = error.message
    })
  </script>
</body>

</html>

网页部分效果图->

再看看axios查询参数的封装案例吧:

html 复制代码
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>封装_简易axios函数_获取地区列表</title>
</head>

<body>
  <p class="my-p"></p>
  <script>
    /**
     * 目标:封装_简易axios函数_获取地区列表
     *  1. 判断有params选项,携带查询参数
     *  2. 使用URLSearchParams转换,并携带到url上
     *  3. 使用myAxios函数,获取地区列表
    */
    function myAxios(config) {
      return new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest()
        // 1. 判断有params选项,携带查询参数
        if (config.params) {
          // 2. 使用URLSearchParams转换,并携带到url上
          const paramsObj = new URLSearchParams(config.params)
          const queryString = paramsObj.toString()
          // 把查询参数字符串,拼接在url?后面
          config.url += `?${queryString}`
        }

        xhr.open(config.method || 'GET', config.url)
        xhr.addEventListener('loadend', () => {
          if (xhr.status >= 200 && xhr.status < 300) {
            resolve(JSON.parse(xhr.response))
          } else {
            reject(new Error(xhr.response))
          }
        })
        xhr.send()
      })
    }

    // 3. 使用myAxios函数,获取地区列表
    myAxios({
      url: 'http://hmajax.itheima.net/api/area',
      params: {
        pname: '辽宁省',
        cname: '大连市'
      }
    }).then(result => {
      console.log(result)
      document.querySelector('.my-p').innerHTML = result.list.join('<br>')
    })

  </script>
</body>

</html>

最后看看提交数据的封装,我们也就完成对AJAX的学习了:

html 复制代码
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>封装_简易axios函数_注册用户</title>
</head>

<body>
  <button class="reg-btn">注册用户</button>
  <script>
    /**
     * 目标:封装_简易axios函数_注册用户
     *  1. 判断有data选项,携带请求体
     *  2. 转换数据类型,在send中发送
     *  3. 使用myAxios函数,完成注册用户
    */
    function myAxios(config) {
      return new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest()

        if (config.params) {
          const paramsObj = new URLSearchParams(config.params)
          const queryString = paramsObj.toString()
          config.url += `?${queryString}`
        }
        xhr.open(config.method || 'GET', config.url)

        xhr.addEventListener('loadend', () => {
          if (xhr.status >= 200 && xhr.status < 300) {
            resolve(JSON.parse(xhr.response))
          } else {
            reject(new Error(xhr.response))
          }
        })
        // 1. 判断有data选项,携带请求体
        if (config.data) {
          // 2. 转换数据类型,在send中发送
          const jsonStr = JSON.stringify(config.data)
          xhr.setRequestHeader('Content-Type', 'application/json')
          xhr.send(jsonStr)
        } else {
          // 如果没有请求体数据,正常的发起请求
          xhr.send()
        }
      })
    }
  
    document.querySelector('.reg-btn').addEventListener('click', () => {
      // 3. 使用myAxios函数,完成注册用户
      myAxios({
        url: 'http://hmajax.itheima.net/api/register',
        method: 'POST',
        data: {
          username: 'itheima999',
          password: '666666'
        }
      }).then(result => {
        console.log(result)
      }).catch(error => {
        console.dir(error)
      })
    })
  </script>
</body>

</html>
相关推荐
恋猫de小郭3 小时前
Flutter Zero 是什么?它的出现有什么意义?为什么你需要了解下?
android·前端·flutter
崔庆才丨静觅10 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby606111 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了11 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅11 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅11 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅12 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment12 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅12 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊12 小时前
jwt介绍
前端