axios(尚硅谷)

一、 HTTP相关

HTTP请求基本过程

在这里插入图片描述
1,浏览器端向服务器发送HTTP请求(请求报文)
2.后台服务器接收到请求后,处理请求,向浏览器端返回HTTP响应(响应报文
3.浏览器端接收到响应,解析显示响应体或调用回调函数

HTTP请求报文

1、请求行
格式:method url
例如:GET/product_detail?id=2 或 POST/login

2、请求头(一般有多个请求头)
Host:www.baidu.com
Cookie:BAIDUID=AD3BOFA706E;BIDUPSID=AD3BOFA706;
Content-Type:application/x-www-form-urlencoded 或者 application/json

3、请求体(GET请求是没有的)
username=tom&pwd=123
{“username”:“tom”,“pwd”:123)

HTTP响应报文

1、响应行
格式:status statusText
例如:200OK或404 Not Found

2、响应头(一般有多个)
Content-Type:text/html;charset=utf-8
Set-Cookie:BD_CK_SAM=1;path=/

3、响应体
html/json/js/css/图片…

常见的响应状态码

200 ok 请求成功 一般用于GET与POST请求
201 Created 已创建 成功请求并创建了新的资源
401 Unauthorized 未授权/请求要求用户的身份认证:
404 Not Found 服务器无法根据客户端的请求找到资源(前端的锅
500 Internal Server Error 服务器内部错误,无法完成请求

二、请求方式与请求参数的总结

请求方式

1.GET(索取):从服务器端读取数据 – 查®
2.POST(交差):向服务器端添加新数据 – 增©
3.PUT:更新服务器端已存在的数据 – 改(U)
4.DELETE:删除服务器端数据 – 删(D)

请求参数

1、query参数(查询字符串参数)
1.参数包含在请求地址中,格式为:/xxx?name=tom&age=18
2.敏感数据不要用query参数,因为参数是地址的一部分,比较危险。
3.备注:query参数又称查询字符串参数,编码方式为urlencoded

2、params参数
1.参数包含在请求地址中,格式如下
http://localhost:3000/add_person/tom/18
2.敏感数据不要使用params参数,因为参数是地址的一部分,比较危险

3、请求体参数
1.参数包含在请求体中,可通过浏览器开发工具查看
2.常用的两种格式:
格式一:urlencoded格式
例如:name=tom&age=18-
对应请求头:Content-Type:application/x-www-form-urlencoded
格式二:json格式
例如:{'name":“tom”,"age:12}
对应请求头:Content–Type:application/json

特别注意

1.GET请求不能携带请求体参数,因为GET请求没有请求体。
2.理论上一次请求可以随意使用上述3种类型参数中的任何一种,甚至一次请求的3个参数可以用3种形式携带,但一般不这样做。
3.一般来说我们有一些“约定俗成”的规矩:↓
(I)例如form表单发送post请求时:自动使用请求体参数,用urlencoded编码。
(2)例如jQuery发送ajax-post请求时:自动使用请求体参数,用urlencoded编码。
4.开发中请求到底发给谁?用什么请求方式?携带什么参数?— 要参考项目的API接口文档 必须符合要求来进行传递参数操作

API相关

API分类

1、REST API (restful风格的API) (用的多)
①发送请求进行CRUD哪个操作由请求方式来决定
②同一个请求路径可以进行多个操作
③请求方式会用到GET/POST/PUT/DELETE
在这里插入图片描述
2、非REST API (restless风格的API)
④ 请求方式不决定请求的CRUD操作
⑤ 一个请求路径只对应一个操作
⑥ 一般只有GET/POST

使用json-server搭建REST API

1、json-server是什么?

  • 用来快速搭建REST API工具包
  • 前端人员必备技能,前端人员没有服务器的情况下,快速开启一台服务器

2、使用json-server
1.在线文档:在线文档
2.下载

npm install -g json-server

在练习的时候使用json-server db.json 命令创建的时候,报错:
json-server db.json json-server : 无法将“json-server”项识别为 cmdlet、函数 解决方案是 严格使用上述的安装命令 npm install -g json-server 即可

3.目标根目录下创建数据库json文件:db.json
使用命令 json-server db.json 生成最开始的json文件
在自己想创建的目录下 使用json-server进行创建即可

json-server db.json
{
  "posts": [
    {
      "id": 1,
      "title": "json-server",
      "author": "typicode"
    }
  ],
  "comments": [
    {
      "id": 1,
      "body": "some comment",
      "postId": 1
    }
  ],
  "profile": {{
  "students": [
    {
      "id": 1,
      "name": "小张",
      "age": "18"
    }, 
    {
      "id": 2,
      "name": "小李",
      "age": "19"
    }, 
    {
      "id": 3,
      "name": "小王",
      "age": "20"
    }
  ],
  "teachers": [
    {
      "id": 1,
      "name": "老刘",
      "subject": "前端"
    }, 
    {
      "id": 2,
      "name": "强哥",
      "subject": "后端"
    }
  ],
  "school": {
    "name": "atguigu"
  }
}
    "name": "typicode"
  }
}

4.修改自己创建的db.json文件,创建自己想要的数据库,比如说创建一个学生、老师,学校数据库

{
  "students": [
    {
      "id": 1,
      "name": "小张",
      "age": "18"
    }, 
    {
      "id": 2,
      "name": "小李",
      "age": "19"
    }, 
    {
      "id": 3,
      "name": "小王",
      "age": "20"
    }
  ],
  "teachers": [
    {
      "id": 1,
      "name": "老刘",
      "subject": "前端"
    }, 
    {
      "id": 2,
      "name": "强哥",
      "subject": "后端"
    }
  ],
  "school": {
    "name": "atguigu"
  }
}

然后重启该db.json文件即可 - 且上述文件中,json文件中最后一个数据,不应该有逗号出现

postman测试接口的工具使用

  • 前端人员主要用postman来测试接口
    在这里插入图片描述
  • 使用postman还可以进行添加数据的操作
    在这里插入图片描述
  • 而且在json文件中,内容会进行相应的修改操作,加上了我们所配置的内容,非常关键,而且进行添加配置的,使用postman工具对数据进行修改操作,原文件也会的代码会对应进行修改

PUT方式是对文件进行相应的修改操作,这一点还是很关键的 但是id只能通过params进行相应的设置

在这里插入图片描述

一般的http请求与ajax请求

1.ajax请求是一种特别的http请求
2.对服务器端来说,没有任何区别,区别在浏览器端
3.浏览器端发请求:只有XHR或fetch发出的才是ajax请求,其它所有的
都是非ajax请求(一般http请求)(jquery也是对xhr进行封装)
4.浏览器端接收到响应
(1)一般请求:浏览器一般会直接显示响应体数据,也就是我们常说的
自动刷新/跳转页面
(2)ajax请求:浏览器不会对界面进行任何更新操作(页面无刷新获取数据),只是调用监视的回调函数并传入响应相关数据

Ajax

Ajax是什么?

  1. 概念; 异步的js和xml;
  2. 作用; 1、数据交换: 通过ajax可以给服务器发送请求,并获取服务器响应的数据; 2、异步交互: 可以在不加载整个页面的情况下,与服务器交换数据并更新部分网页的技术; 比如: 搜索联想、用户名是否可用的校验等等;

原生的Ajax请求

  1. 准备数据地址
  2. 创建XMLHttpRequest对象: 用于和服务器交换数据
  3. 向服务器发送请求
  4. 获取服务器响应数据

axios

axios是什么?

  1. 前端最流行的ajax请求
  2. react/vue 官方都推荐使用axios发ajax请求
  3. 文档:文档

axios特点

1.基本promise的异步ajax请求库(基于promise的异步ajax请求库)
2.浏览器端/node端都可以使用 (浏览器端/node端都可以使用)
3.支持请求/响应拦截器
4.支持请求取消
5.请求/响应数据转换
6.批量发送多个请求 (批量发送多个请求)

axios发送一个简单的GET请求

资料文件

点击资料文件下载解压以后,放置到server文件夹中,在终端打开,使用yarn 安装依赖,接着使用node server.js 启动文件即可

yarn
yarn install v1.22.18
warning package-lock.json found. Your project contains lock files generated by tools other than Yarn. It is advised not to mix package managers in order to avoid resolution inconsistencies caused by unsynchronized lock files. To clear this warning, remove package-lock.json.
[1/4] Resolving packages...
[2/4] Fetching packages...
[3/4] Linking dependencies...
[4/4] Building fresh packages...
node server.js
服务器启动成功,接口文档地址:http://localhost:5000/apidoc/index.html

打开上述服务器,观察使用api-doc生成的接口文档
在这里插入图片描述
使用postman来测试接口
在这里插入图片描述

01_axios的基本使用

如果不下载axios.js到本地,可以使用在线js供使用

 <script src="https://cdn.bootcdn.net/ajax/libs/axios/0.27.2/axios.min.js"></script>
<!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>Document</title>
    <script src="https://cdn.bootcdn.net/ajax/libs/axios/0.27.2/axios.min.js"></script>
</head>

<body>
    <button id="btn1">点我获取所有人的信息</button>
    <!-- 
        1、axios调用的返回值是Promise实例
        2、成功的值叫response 失败的值叫error
        3、axios成功的值是一个axios封装的response对象,服务器返回的真正数据在response.data中
            - 配置中的想要的url是小写,严格规定的
     -->
    <script>
        // 获取按钮
        const btn1 = document.getElementById('btn1')

        // 发送GET请求 -- 不携带参数
        btn1.onclick = () => {
            const result = axios({
                url: 'http://localhost:5000/persons',  // 请求地址
                method: 'GET', // 请求方式
            })
            result.then(
                response => {console.log('请求成功了', response.data)}, 
                error => {console.log('请求失败了', error)}
            )
        }
    </script>
</body>

</html>

注意点

1、axios调用的返回值是Promise实例
2、成功的值叫response 失败的值叫error
3、axios成功的值是一个axios封装的response对象,服务器返回的真正数据在response.data中
- 配置中的想要的url是小写,严格规定的
4、携带query参数时,编写的配置项叫params
5、携带params参数时,需要自己手动拼在url中

1、获取所有人的信息 – 发送GET请求 – 不携带参数

// 完整版 写法
btn1.onclick = () => {
   // 完整版
   axios({
       url: 'http://localhost:5000/persons',  // 请求地址
       method: 'GET', // 请求方式
   }).then(
       response => { console.log('请求成功了', response.data) },
       error => { console.log('请求失败了', error) }
   )
}
// 精简版写法
btn1.onclick = () => {
	 axios.get('http://localhost:5000/persons').then(
         response => {console.log('请求成功了', response.data)}, 
         error => {console.log('请求失败了', error)}
     )
}

2、获取某个人的信息 — 发送GET请求 — 携带query参数

// 完整版
btn2.onclick = () => {
    // 完整版
    axios({
        // 注意观察自己到底是想要访问哪一个地址操作
        // 'http://localhost:5000/person/1' params参数的书写过程
        url: 'http://localhost:5000/person',
        method: 'GET',
        params: { id: personId.value }  // 携带的是query参数,想要携带params参数,只能往上面追加
    }).then(
        response => { console.log('成功了', response.data) },
        error => { console.log('失败了', error) }
    )
}
// 精简版
btn2.onclick = () => {
    // 精简版  params参数也要带上, 
    axios.get('http://localhost:5000/person', { params: { id: personId.value } }).then(
        response => { console.log('成功了', response.data) },
        error => { console.log('失败了', error) }
    )
}

3、添加一个人 — 发送POST请求 — 携带json编码参数 或 urlencoded编码

// 完整版
btn3.onclick = () => {
   axios({
       url: 'http://localhost:5000/person',
       method: 'POST',
       // data: { name: personName.value, age: personAge.value } // 携带请求体参数(JSON编码)
       data: `name=${personName.value}&age=${personAge.value}` // 携带请求体参数(urlencoded编码)去请求标头中查看
   }).then(
       response => { console.log('成功了', response.data) },
       error => { console.log('失败了'), error }
   )
}
// 精简版
btn3.onclick = () => {
    axios.post('http://localhost:5000/person', { name: personName.value, age: personAge.value }).then(
        response => { console.log('成功了', response.data) },
        error => { console.log('失败了', error) }
    )
}

4、更新一个人 – 发送PUT请求 – 携带json编码参数 或 urlencoded编码

// 完整版
btn4.onclick = () => {
    axios({
        url: 'http://localhost:5000/person',
        method: 'PUT',
        data: {
            id: personUpdateId.value,
            name: personUpdateName.value,
            age: personUpdateAge.value
        }
    }).then(
        response => { console.log('成功了', response.data) },
        error => { console.log('失败了', error) }
    )
}
// 精简版
btn4.onclick = () => {
    axios.put('http://localhost:5000/person', {
        id: personUpdateId.value,
        name: personUpdateName.value,
        age: personUpdateAge.value
    }).then(
        response => { console.log('成功了', response.data) },
        error => { console.log('失败了', error) }
    )
}

5、删除一个人 – 发送DELETE请求 – 携带params参数

btn5.onclick = () => {
    axios({
        // 带params参数的话,直接使用模板字符串进行即可
        url: `http://localhost:5000/person/${personDeleteId.value}`,
        method: 'DELETE',
    }).then(
        response => { console.log('成功了', response.data) },
        error => { console.log('失败了'), error }
    )
}

02_axios常用配置项

1、axios配置项,有30多种,我们只需要知道常用的即可
2、使用axios发送请求的时候,如果多个请求发送的内容差不多,可以使用axios.defaults.xxx 进行配置相应的属性

使用defaults进行配置

<body>
    <button id="btn1">点我获取所有人</button>
    <button id="btn2">点我获取测试数据</button>
    <script>
        const btn1 = document.getElementById('btn1')
        const btn2 = document.getElementById('btn2')

        // 给axios配置默认属性,主要是axios发出的,都带有这些默认属性
        axios.defaults.timeout = 2000
        // axios.defaults.headers = {school:'atguigu'}
        axios.defaults.headers = {token:'128anffhaojhgnfb'} // 令牌,京东管理员的身份
        axios.defaults.baseURL = 'http://localhost:5000'

        // 既然要验证配置项,所以我们要写完整版才行
        btn1.onclick = () => {
            axios({
                // 配置项写了很多,但是不见得都要用到
                url: '/persons',  // 请求地址
                method: 'GET', // 请求方式 其中get请求是没有请求体的
                // params:{a:1, b:2}, // 配置query参数
                // params: { delay: 3000 },
                // 配置query参数
                // data:{c:3, d:3}, // 配置请求体参数(json编码)
                // data:'e=5&f=6', // 配置请求体参数(urlencoded编码)
                // timeout: 2000, // 配置超时时间
                // headers:{demo:123}, // 配置请求头
                // responseType: 'json' // 配置响应数据的格式(默认值) 前后端商量好用json就需要配置这个
            }).then(
                response => { console.log('成功了', response.data) },
                error => { console.log('失败了', error) }
            )
        }

        btn2.onclick = () => {
            axios({
                // 配置项写了很多,但是不见得都要用到
                url: '/test1',  // 请求地址
                method: 'GET', // 请求方式 其中get请求是没有请求体的
                // timeout: 2000,
            }).then(
                response => { console.log('成功了', response.data) },
                error => { console.log('失败了', error) }
            )
        }

    </script>
</body>

03 axios_create方法

const axios2 = axios.create({
    timeout : 3000, 
    // headers :{name:'tom'}, 有的服务器可以使用,有的不能带上和headers这个 
    baseURL:'https://api.apiopen.top'
})

// 给axios配置默认属性,主要是axios发出的,都带有这些默认属性
// 默认的配置为了保险起见最好是写在最前方比较保险
axios.defaults.timeout = 2000
// axios.defaults.headers = {school:'atguigu'}
axios.defaults.headers = {token:'128anffhaojhgnfb'} // 令牌,京东管理员的身份
axios.defaults.baseURL = 'http://localhost:5000'

axios创建一个新项的时候,需要在创建默认属性值之前创建

复习

1、async修饰的函数

  • 函数的返回值为promise对象
  • Promise实例的结果由async函数执行的返回值决定

2、await表达式
await右侧的表达式一般为Promise实例对象,但也可以是其他的值

  • 如果表达式是Promise实例对象,await后的返回值是promise成功的值
  • 如果表达式是其他值,直接此值作为await的返回值

若我们使用async配合await这种写法
1、表面上不会出现任何的回调函数
2、但实际上底层把我们写的代码进行了加工,把回调函数“还原”回来了
3、最终运行的代码是依然有回调的,只是程序员没有看见而已

axios中的拦截器(interceptors)

axios请求拦截器

1、是什么?

  • 在真正发请求之前执行的一个回调函数
    2、作用
  • 对所有的请求做统一的处理:追加请求头、追加参数、界面loading提示等等
    (对于请求拦截器后指定的先执行)
// 请求拦截器
axios.interceptors.request.use((config) => {
    console.log('请求拦截器执行了')
    // config.a = 1 毫无意义的操作
    // 如果当前时间戳是偶数那么添加一定的响应头即可 
    // 一般在开发中我们带过去的是 token 就是相应的一些指令操作
    if(Date.now() % 2 === 0) {
        config.headers.school = 'atguigu'
    }
    // console.log('@', config)
    return config
})

axios响应拦截器

1、是什么?

  • 得到响应之后执行的一个回调函数
    2、作用
  • 若请求成功,对成功的数据进行处理,
  • 若请求失败,对失败进行进一步操作
// 响应拦截器
axios.interceptors.response.use(
    response => {
        console.log('响应拦截器成功的回调执行了', response)
        /* 这样程序员就不用每一次都.data来获取到数据进行操作,下面进行response直接进行返回response即可 */
        // return response.data
        // 也可以像之前一样进行相应的操作 在时间戳为偶数的情况下进行返回数据操作
        if(Date.now() % 2 === 0) {
            return response.data
        }else {
            return '时间戳不是偶数,不能给你数据'
        }
    }, 
    error => {
        // console.log('响应拦截器失败的回调执行了', error)
        console.log('响应拦截器失败的回调执行了')
        alert(error)
        // 实实在在写一个失败的回调才行
        // return Promise.reject(error)
        return new Promise(() => {
            
        })
    }
)

<body>
    <button id="btn1">点我获取所有人信息</button>

    <!-- 
        axios请求拦截器
            1、是什么 ?
                在真正发请求之前执行的一个回调函数
            2、作用
                对所有的请求做统一的处理:追加请求头、追加参数、界面loading提示等等
                
            - 后指定的先执行
            
        axios响应拦截器
            1、是什么? 
                得到响应之后执行的一个回调函数
            2、作用
                若请求成功,对成功的数据进行处理,
                若请求失败,对失败进行进一步操作

     -->
    <script>
        const btn = document.getElementById('btn1')

        // 请求拦截器
        axios.interceptors.request.use((config) => {
            console.log('请求拦截器执行了')
            // config.a = 1 毫无意义的操作
            // 如果当前时间戳是偶数那么添加一定的响应头即可 
            // 一般在开发中我们带过去的是 token 就是相应的一些指令操作
            if(Date.now() % 2 === 0) {
                config.headers.school = 'atguigu'
            }
            // console.log('@', config)
            return config
        })

        // 响应拦截器
        axios.interceptors.response.use(
            response => {
                console.log('响应拦截器成功的回调执行了', response)
                /* 这样程序员就不用每一次都.data来获取到数据进行操作,下面进行response直接进行返回response即可 */
                // return response.data
                // 也可以像之前一样进行相应的操作 在时间戳为偶数的情况下进行返回数据操作
                if(Date.now() % 2 === 0) {
                    return response.data
                }else {
                    return '时间戳不是偶数,不能给你数据'
                }
            }, 
            error => {
                // console.log('响应拦截器失败的回调执行了', error)
                console.log('响应拦截器失败的回调执行了')
                alert(error)
                // 实实在在写一个失败的回调才行
                // return Promise.reject(error)
                return new Promise(() => {
                    
                })
            }
        )

        /* btn.onclick = () => {
            axios.get('http://localhost:5000/persons2').then(
                response => { console.log('成功了', response.data) },
                error => { console.log('失败了', error) }
            )
        } */
        btn.onclick = async() => {
            const result = await axios.get('http://localhost:5000/persons2')
            // 因为在响应拦截器中可以具体指定错误信息,
            console.log(result)
        }
    </script>
</body>

axios取消请求

使用CancelToken取消请求操作

<body>
    <button id="btn1">点我获取所有人信息</button>
    <button id="btn2">取消请求</button>

    <script>
        const btn1 = document.getElementById('btn1')
        const btn2 = document.getElementById('btn2')

        const { CancelToken } = axios // CancelToken能为一次请求“打标识”
        let cancel

        btn1.onclick = async () => {
            axios({
                url: 'http://localhost:5000/test1?delay=3000',
                method: 'get',
                // 注意:此处的cancelToken为c为小写
                cancelToken: new CancelToken((c) => { // c是一个函数,调用c就可以关闭本次请求
                    // console.log(c)
                    // c()
                    cancel = c
                    // c() 
                })
            }).then(
                response => { console.log('成功了', response.data) },
                error => { console.log('失败了', error) }
            )
        }

        btn2.onclick = () => {
            cancel() 
        }
    </script>
</body>

注意: 配置项中的cancelToken 第一个字母为小写,后面new出来的为大写操作

使用isCancel() 来使取消请求中的错误划分变得更加详细


<body>
    <button id="btn1">点我获取所有人信息</button>
    <button id="btn2">取消请求</button>

    <script>
        const btn1 = document.getElementById('btn1')
        const btn2 = document.getElementById('btn2')

        const { CancelToken, isCancel } = axios // CancelToken能为一次请求“打标识”
        let cancel

        btn1.onclick = async () => {
            // 每次进来的时候判断是否有值, 有值的话,就进行取消操作
            if(cancel) cancel() 
            axios({
                url: 'http://localhost:5000/test1?delay=3000',
                method: 'get',
                // 注意:此处的cancelToken为c为小写
                cancelToken: new CancelToken((c) => { // c是一个函数,调用c就可以关闭本次请求
                    // console.log(c)
                    // c()
                    cancel = c
                    // c() 
                })
            }).then(
                response => { console.log('成功了', response.data) },
                error => {
                    if (isCancel(error)) {
                        // 如果进入判断,证明是用户取消了请求
                        // isCancel可以将错误类型划分的更加详细
                        console.log('用户取消了请求,原因是:', error.message)
                    } else {
                        console.log('失败了', error)
                    }
                }
            )
        }

        btn2.onclick = () => {
            cancel('任性,不要了')
        }
    </script>
</body>

axios中取消请求配合拦截器使用


<body>
    <button id="btn1">点我获取所有人信息</button>
    <button id="btn2">取消请求</button>

    <script>
        const btn1 = document.getElementById('btn1')
        const btn2 = document.getElementById('btn2')

        const { CancelToken, isCancel } = axios // CancelToken能为一次请求“打标识”
        let cancel

        axios.interceptors.request.use((config) => {
            if (cancel) cancel()
            config.cancelToken = new CancelToken((c) => cancel = c)
            return config
        })

        // 但是没有使用到响应拦截器来处理错误信息
        axios.interceptors.response.use(
            response => { return response.data },
            error => {
                if (isCancel(error)) {
                    // 如果进入判断 证明:是用户取消了请求
                    console.log('用户取消了请求,原因是', error.message)
                } else {
                    console.log('失败了', error)
                }
                return new Promise(() => { }) //  中断
            }
        )

        btn1.onclick = async () => {
            const result = await axios.get('http://localhost:5000/test1?delay=3000')
        }


        btn2.onclick = () => {
            cancel('任性,不要了')
        }
    </script>
</body>

axios批量发送请求

<body>
    <button id="btn1">点我获取所有人信息</button>
    <script>
        const btn = document.getElementById('btn1')

        btn.onclick = async () => {
            axios.all([
                axios.get('http://localhost:5000/test1'),
                axios.get('http://localhost:5000/test2?delay=3000'),
                axios.get('http://localhost:5000/test3'),
            ]).then(
                response => {console.log(response)}, 
                error => {console.log(error)}
            )
        }

    </script>
</body>

就算有延迟,返回的顺序依然会帮我们维护好
在这里插入图片描述

  • 6
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值