node.js学习记录总结

Node.js

模块化

什么是模块化?

模块化是指解决一个复杂问题时,自顶向下把系统划分成若干模块的过程。对于整个系统来说,模块是可组合、分解和更换的单元。

模块化的好处

  1. 提高代码的复用性
  2. 提高代码的可维护性
  3. 可以实现按需加载

node.js中模块的分类

  1. 内置模块(由node.js官方提供的模块,如fs、path、http等)
  2. 自定义模块(用户创建的每个.js文件)
  3. 第三方模块(由第三方开发出来的模块,使用需要下载)

加载模块

使用require()方法来加载所需模块

注意:

  • 使用require()方法加载其他模块时,会执行被加载模块中的代码
  • 可以省略.js后缀名

模块作用域

  1. 和函数作用域相似,在自定义模块中定义的变量、方法等成员,只能在当前模块内被访问
  2. 好处:
    • 防止全局变量污染问题

向外共享模块作用域中的成员

  1. module 对象
  2. module.exports 对象
    • 在自定义模块中,可以使用module.exports对象将模块内的成员共享出去
    • 外界用require方法导入自定义模块时,得到的就是module.exports所指向的对象
  3. 共享成员时的注意点:使用require()导入模块时,得到的结果永远以module.exports指向的对象为准
  4. exports和module.exports指向同一个对象

模块的加载机制

优先从缓存中加载

模块第一次加载后会被缓存,多次调用require()不会导致模块的代码被执行多次,从而提高模块的加载效率

内置模块的加载优先级最高

若遇到同名的模块,内置模块优先级较高

自定义模块加载机制(省略扩展名情况)
  1. 按照确切文件名进行加载
  2. 补全.js扩展名进行加载
  3. 补全.json扩展名进行加载
  4. 补全.node扩展名进行加载
  5. 加载失败,终端报错
第三方模块加载机制

从当前模块的父目录开始,若找不到,则移动到上一层父目录,进行加载,直到文件系统的根目录

以目录为模块传递给require
  1. 先寻找目录下package.json文件,查找main属性,作为require()加载的入口
  2. 若没有package.json文件或main属性入口不存在或无法解析,则node.js会加载目录下的index.js
  3. 若都失败了,则在终端打印错误信息,报告模块的确实:Error:Con not find module ‘xxx’

fs文件系统模块

  1. 利用require导入fs模块 const fs = require('fs');
  2. fs.readFile()读取文件
    • 参数1:读取文件的存放路径(必选)
    • 参数2:读取文件的编码格式,一般默认为utf8(可选)
    • 参数3:回调函数,失败和成功的结果(function(){失败,成功})(必选)
  3. fs.writeFile()写入内容
    • 参数1:读取文件的存放路径(必选)
    • 参数2:要写入的内容
    • 参数3:读取文件的编码格式,一般默认为utf8(可选)
    • 参数4:回调函数,失败和成功的结果(function(){失败,成功})(必选)
  4. 使用terminal出现路径拼接错误的问题,是因为使用了./ 或 …/开头的相对路径,使用绝对路径可以解决此问题
  5. __dirname表示当前文件所处的目录
  • 注:fs.writeFile()写入同一文件时会覆盖

path路径模块

  1. path.join()将多个路径片段拼接
    • 各参数用’,'隔开
  2. path.basename()从路径字符串解析出文件名
    • 参数1:路径(必须)
    • 参数2:后缀名(可选),选择后会移除
  3. path.extname()获取扩展名

http模块

  1. IP地址:互联网上每台计算机的一个唯一标识
    • IP地址格式通常为“点分十进制”表示成(a,b,c,d)的形式,都是为0-255之间的十进制整数
    • 互联网中每台web服务器都有属于自己的IP地址
  2. 域名和域名服务器
    • IP地址和域名是一一对应的关系
  3. 端口号:
    • 每个端口号不能同时被多个web服务占用
    • 在实际应用中,url的80端口可以省略
  4. 创建基本的web服务器步骤:
    • 导入http模块
    const http = require('http');
    
    • 创建web服务器实例
    const server = http.createServer();
    
    • 为server绑定request事件,监听服务器请求
    server.on('request', function(req, res){})
    
    • 启动服务器
    server.listen(端口号, function(){})
    
  5. 解决中文乱码的问题:
    • 设置内容的编码格式
    res.setHeader('Content-Type', 'text/html; charset=utf-8');
    
    • 向服务端发送内容
    res.end('发送的内容')
    

npm与包

创建自己的npm包
  1. 新建文件夹
  2. 设置package.json文件,index.js文件和README.md文档
    • index.js放主要信息
    • package.json放配置信息
      • 例子:
      {
         "name": "", // 所起的名字
         "version": "1.0.1", // 版本号
         "main": "index.js", // 主要文件 即前面设置的index.js
         "description": "", // npm包的描述
         "keywords": [], // 关键词
         "license": "ISC" // 开源协议
      }
      
  3. 可以将所创建的js文件放在新建的src文件夹下,每个js文件分别要module.exports
  4. 在index.js中module.exports={}所有src里的js文件
  5. 使用时通过require根文件夹引入
发布包
  1. 在cmd中cd至发布文件的根文件夹
  2. 运行命令:npm publish
删除已发布的包
  1. cmd中运行:npm unpublish 包名 --force
  2. 注意:
    • npm unpublish 命令只能删除72小时内发布的包
    • 删除的包在24小时内不允许重复发布

Express

了解Express

  1. 本质为npm上的第三方包,提供了快速创建Web服务器的便捷方法
  2. 可以方便快速创建Web网站服务器或API接口的服务器

使用Express创建服务器

基本使用
const express = require('express');
const app = express();
app.listen(80, () => {
    console.log('server running at http://127.0.0.1');
})
监听get请求
app.get('请求url',function(req,res){
   res.send(); //向客户端响应的内容
})
req.query

req.query默认是一个空对象,客户端使用?name=a&age=30这种查询字符串的形式发送到服务器的参数,可以用req.query获取
例:在地址栏输入127.0.0.1/?username=test&age=20

app.get('/',function(req,res)=>{
   res.send(req.query); //{"username":"test","age":"20"}
})
req.params

URL地址中,可以通过:参数名的形式匹配动态参数值,req.params默认是一个空对象,里面存放着通过:动态匹配到的参数值(参数名不固定)
例:在地址栏输入127.0.0.1/user/1

app.get('/user/:id', (req, res) => {
    res.send(req.params); //{"id":"1"}
})
监听post请求
app.post('请求url',function(req,res){
   res.send();//向客户端响应的内容
})
托管静态资源
  1. app.use(express.static())
    注意:Express在指定静态目录中查找文件,并对外提供资源的访问路径,因此,存放静态文件的目录名不会在url中
    若要托管多个目录,则多次调用express.static()函数

app.use('/public', express.static('./public'))

挂载路径前缀

Express路由

  1. 路由指的是客户端的请求与服务器处理函数之间的映射关系
  2. Express中的路由分3部分,分别是请求的类型、请求的url地址、处理函数
    app.METHOD(PATH, HANDLER)
最简单的挂载方法
app.get('url', function(req, res)=>{})
app.post('url', function(req, res)=>{})
模块化路由
  1. 创建路由模块对应的js文件
  2. 调用express.Router()创建路由对象
  3. 向路由对象上挂载具体的路由
  4. 使用module.exports向外共享路由对象
  5. 在路由文件下使用require()引入路由模块
  6. 使用app.use()函数注册路由模块
    • 在use中可以给路由模块添加统一的访问前缀

Express中间件

什么是中间件

中间件特指业务流程中的中间处理环节

中间件的格式

本质上是一个function处理函数格式如下:(有包含next则为中间件处理函数,没有则为路由处理函数)

const express = require('express');
const app = express();
app.get('/', function(req, res, next){
   next();
});
app.listen(80);
全局中间件
  1. 使用app.use()定义的中间件函数,为全局中间件,next()函数可以共享给后面的路由
  2. 若定义了多个全局中间件就依次写下去
局部生效中间件
  1. 使用app.use()定义的中间件,为局部生效中间件
  2. 使用方式:
const mw = (req, res, next)=>{
   next();
}
app.get('/', mw, (req, res)=>{
   res.send()
})
  1. 定义多个局部中间件
//以下两种写法完全等价
app.get('/', mw1, mw2, (req, res)=>{res.send()});
app.get('/', [mw1, mw2], (req, res)=>{res.send()});
  1. 监听req的data事件
//监听req的data事件,获取客户端发送到服务器的数据,若数据量比较大,客户端会把数据切割后分批发送到服务器,所以data事件有可能会触发多次,需要对接收到的数据进行拼接
let str = '';
req.on('data', (data)=>{
   str += data;
});
  1. 监听req的end事件
//监听req的end事件(请求体发送完毕后自动触发),可用于获取完整请求体数据
req.on('end',()=>{
   console.log(str);
});
中间件的分类
  1. 应用级别的中间件
  2. 路由级别的中间件
  3. 错误级别的中间件
  4. Express内置的中间件
  5. 第三方的中间件
应用级别的中间件

通过app.use()或app.get()或app.post绑定到app实例上的中间件

路由级别的中间件

绑定到express.Router()实例上的中间件,用法和应用级别的没有区别

错误级别的中间件
  1. 作用:专门用来捕获整个项目中发生的异常错误,从而防止项目异常崩溃的问题
  2. 格式:错误级别的中间件有4个形参,(err, req, res, next)
  3. 注意:错误级别的中间件必须注册在所有路由之后
  4. 例:
const express = require('express');
const app = express();

app.get('/', (req, res) => {
    throw new Error('服务器内部发生了错误!');
    res.send('Home page');
})

app.use((err, req, res, next) => {
    console.log('wrong!');
    res.send('Error' + err.message);
})

app.listen(80, () => {
    console.log('http://127.0.0.1');
})
Express内置的中间件
  1. express.static 快速托管静态资源的内置中间件,如:html文件、图片、css样式等((无兼容性)
  2. express.json解析JSON格式的请求体数据(有兼容性,在4.16.0+版本可用)
app.use(express.json())

//在服务器可以使用req.body来接收客户端发送的请求体数据,若没有配置解析的中间件,req.body默认为{}
app.post('/user', (req, res) => {
    console.log(req.body);
    res.send('ok');
})
  1. express.urlencoded解析URL-encoded格式的请求体数据(有兼容性,在4.16.0+版本可用)
app.use(express.urlencoded({
   extended: false
}))

//在服务器可以使用req.body来接收客户端发送的请求体数据,若没有配置解析的中间件,req.body默认为{}
app.post('/book', (req, res) => {
    console.log(req.body);
    res.send('ok');
})
第三方中间件
  1. 运行npm install xxx
  2. 使用require导入
  3. 调用app.use()注册并使用
  4. 例:(express.urlencoded和express.json基于body-parser封装而来)
const express = require('express');
const app = express();

const parser = require('body-parser');
app.use(parser.urlencoded({
    extended: false
}))

app.post('/user', (req, res) => {
    console.log(req.body); //req.body是请求体,包含了发送请求的内容
    res.send('ok');
})

app.listen(80, () => {
    console.log('http://127.0.0.1');
})

使用Express写接口

什么是CORS

CORS(Cross-Origin Resource Sharing,跨域资源共享)由一系列HTTP响应头组成,这些http响应头觉得浏览器是否阻止前端js代码跨域获取资源

浏览器的同源安全策略默认会阻止网页跨域获取资源,如果有接口服务器配置了CORS相关的HTTP响应头,就可以解除浏览器端的跨域访问限制

CORS的注意事项
  1. CORS主要在服务器端进行配置,客户端无须做额外配置
  2. CORS在浏览器中有兼容性,只有支持XMLHttpRequest Level2的浏览器,才能正常访问(如:IE10+、Chrome4+、FireFox3.5+)
CORS跨域资源共享
  1. npm install cors 安装中间件
  2. require('cors')导入中间件
  3. app.use(cors())配置中间件
CORS响应头部
  1. 默认情况下,CORS仅支持客户端向服务器发送以下9个请求头:
    Accept、Accept-Language、Content-Language、DPR、DownLink、Sava-Data、ViewPort-Width、Width、Content-Type(值仅限于text/plain、multipart/form-data、application/x-www-form-urlencoded三者之一)
  2. 默认情况下,CORS仅支持客户端发起GET、POST、HEAD请求
Access-Control_Allow-Origin

origin参数的值指定了允许访问该资源的外域URL

res.setHeader('Access-Control_Allow-Origin','<origin>')

*表示允许来自任何域的请求

res.setHeader('Access-Control_Allow-Origin','*')
Access-Control_Allow-Headers

若客户端需要向服务器发送额外的请求头信息,需要在服务器端通过 Access-Control_Allow-Headers对额外的请求进行声明,否则请求会失败

res.setHeader('Access-Control_Allow-Header','Content-type, X-Custom-Header')
Access-Control_Allow-Methods

如果客户端希望通过PUT、DELETE等其他方式请求服务器资源,需要通过Access-Control_Allow-Methods指明所允许使用的HTTP方法

res.setHeader('Access-Control_Allow-Methods','POST, GET, PUT, DELETE')

//允许所有的HTTP方法
res.setHeader('Access-Control_Allow-Methods','*')
JSONP
  1. 若要配置jsonp接口,必须在配置cors前配置jsonp,否则jsonp接口会被处理成开启了cors的接口
  2. 例:(服务器端)
app.get('/api/jsonp', (req, res) => {
    //获取客户端发送的回调函数的名字
    const funcName = req.query.callback;
    //得到要通过JSONP发送给客户端的数据
    const data = {
        name: 'a',
        age: 20
    };
    //根据前面的数据拼接函数调用的字符串
    const str = `${funcName}(${JSON.stringify(data)})`;
    //将字符串响应给客户端的<script>标签进行解析执行
    res.send(str);
})

数据库使用

SQL基本使用

select语句

select关键字对大小写不敏感

select * from 表名称
select 列名称 from 表名称
insert into语句

insert into 语句用于向数据表中插入新的数据行

insert into table_name(1,列2...) values(1,值2...)
update语句

update用于修改表中的数据

update 表名称 set 列名称=新值 where 列名称=
delete语句

delete用于删除表中的数据

delete from 表名称 where 列名称=
count(*)函数

用于查询结果的总数据条数

select count(*) as total from 表名称

在项目中操作MySQL

  1. 安装第三方模块mysql
  2. 通过mysql模块连接到数据集
  3. 通过mysql模块执行sql语句
  4. 基本使用:
建立连接
const mysql = require('mysql');

//建立连接关系
const db = mysql.createPool({
    host: '127.0.0.1', //数据库的IP地址
    user: 'root', //登录数据库的账号
    password: '', //密码
    database: '' //要操作的数据库
})

//测试mysql模块能否正常工作
db.query('select 1', (err, results) => {
    //查询失败
    if (err) {
        return console.log(err.message);
    }
    //查询成功
    //如果执行的是select查询语句,执行的结果是数组
    console.log(results);
})
插入数据
//向users表中插入数据,可以通过affectedRows属性来判断是否插入成功
const user = {
    username: 'newUser',
    password: 'newPsd',
    status: '0'
}
//插入数据的简易写法
//const sqlStr = 'insert into users set ?';
const sqlStr = 'insert into users (username, password, status) values (?,?,?)';
//db.query(sqlStr, user, (err, results)=>{})
db.query(sqlStr, [user.username, user.password, user.status], (err, results) => {
    if (err) {
        return console.log(err.message);
    }
    //执行后结果是一个对象,可以通过affectedRows判断是否成功
    if (results.affectedRows === 1) {
        console.log('插入成功!');
    }
})
更新数据
//更新用户信息
const user = {
    id: 3,
    username: 'sb',
    password: '54sb',
}
//更新数据简便写法
//const sqlStr = 'update users set ? where id=?';
const sqlStr = 'update users set username=?, password=? where id=?';
//db.query(sqlStr, [user, user.id], (err, results) => {})
db.query(sqlStr, [user.username, user.password, user.id], (err, results) => {
    if (err) {
        return console.log(err.message);
    }
    if (results.affectedRows === 1) {
        console.log('更新成功!');
    }
})
标记删除

先将欲删除的行进行标记,避免因误删而导致数据丢失的悲惨后果

const sqlStr = 'update users set status=1 where id=?';
db.query(sqlStr, 4, (err, results) => {
    if (err) {
        return console.log(err.message);
    }
    if (results.affectedRows === 1) {
        console.log('标记删除成功!');
    }
})

Web开发模式

服务器渲染的Web开发模式

  1. 概念:服务器发送给客户端的html页面,是在服务器通过字符串的拼接动态生成的,客户端不需要使用ajax这样的技术额外请求页面的数据
  2. 优点:
    • 前端耗时少
    • 有利于SEO。服务器的响应是完整的html页面内容,爬虫更容易爬取获得信息,更有利于SEO
  3. 缺点:
    • 占用服务器端资源
    • 不利于前后端分离,开发效率低

前后端分离的Web开发模式

  1. 概念:依赖ajax技术,后端只提供api接口,前端使用ajax调用接口的开发模式
  2. 优点:
    • 开发体验好
    • 用户体验好
    • 减轻服务器端的渲染压力
  3. 缺点:
    • 不利于SEO(可以利用Vue、React等前端框架的SSR(Server side render)技术解决)

如何选择Web开发模式

  1. 如果网站的主要功能是展示而没有复杂的交互,并且需要良好的SEO,可以使用服务器渲染的开发模式
  2. 如果网站的交互性比较强,不需要考虑SEO,可以使用前后端分离
  3. 若为了兼顾首页的渲染速度和前后端分离的开发效率,也可以采用首屏服务器渲染和其他页面前后端分离的开发模式

身份认证

不同开发模式的身份认证

  1. 服务器端渲染推荐使用Session认证机制
  2. 前后端分离推荐使用JWT认证机制

Session认证机制

Http协议的无状态性

客户端的每次http请求都是独立的,连续多个请求之间没有直接的关系,服务器不会主动保留每次http请求的状态

Cookie
  1. Cookie是存储在用户浏览器的一段不超过4kb的字符串,有一个名称(Name)、一个值(Value)和其他几个用于控制Cookie有效期、安全性、使用范围的可选属性组成
  2. Cookie的几大特性:
    • 自动发送
    • 域名独立
    • 过期时限
    • 4kb限制
  3. 过程:
    • 客户端第一次请求服务器时,服务器通过响应头的形式,向客户端发送一个身份认证的Cookie,客户端将Cookie保存在浏览器中,当客户端浏览器每次请求服务器的时候,浏览器会自动将身份认证相关的Cookie通过请求头的形式发送给服务器,服务器即可验证客户端的身份
  4. Cookie不具备安全性,不要使用Cookie存储重要且隐私的数据
Express中使用Session认证
配置session中间件
const session = require('express-session');
app.use(session({
   secret: 'xxx',
   resave: false,
   saveUninitialized: true
}))
向session中存放数据

express-session中间件配置后,可以通过req.session来访问和使用session对象,从而存储用户的相关信息

req.session.user = req.body; //存储用户信息
req.session.isLogin = true; //存储登录状态
清除session中的数据
req.session.destroy();
session认证的局限性

Session认证需要配合Cookie才能实现。Cookie默认不支持跨域访问,当涉及前端跨域请求后端接口的时候,需要做很多额外的配置,才能实现跨域Session认证

JWT认证机制

认识JWT
  1. JWT(JSON Web Token)是目前最流行的跨域认证解决方案
  2. 工作原理:用户的信息通过Token字符串的形式,保存在客户端浏览器中,服务器通过还原Token字符串的形式来认证用户身份
  3. JWT组成部分:Header(头部)、Payload(有效荷载)、Signature(签名),三者之间用英文的“.”分割,格式:Header.Payload.Signature
    • Payload部分存放真正的用户信息,是用户信息经过加密之后生成的字符串
    • Header和Signature是安全性相关的部分,为了保护Token的安全

Signature
客户端收到服务器返回的JWT后,通常会存储在localStorage或sessionStorage中,客户端之后与服务器的通信中,可以将JWT放在HTTP请求头的Authorization字段中,来进行身份验证

Authorization:Bearer <token>
Express中使用JWT认证
安装jsonwebtoken和express-jwt包
  1. jsonwebtoken用于生成JWT字符串
  2. express-jwt用于将JWT字符串解析还原成JSON对象
定义secret密钥
  1. 保证JWT字符串的安全性,防止JWT字符串在网络传输过程中被别人破解
  2. 当生成JWT字符串时,需要使用secret密钥对用户信息进行加密,最终得到加密好的字符串
  3. 把JWT字符串解析还原成JSON对象时,需要使用secret密钥进行解密
生成JWT字符串
  1. 调用jsonwebtoken包提供的sign()方法,将用户信息加密成JWT字符串,响应给客户端
  2. jwt.sign()方法包含3个参数:
    • 参数1:用户的信息对象
    • 参数2:加密的密钥
    • 参数3:配置对象,配置当前token的有效期(expiresIn:‘3s’ && expiresIn:‘3h’)
const tokenStr = jwt.sign({
        username: userInfo.username
    }, secretKey, {
        expiresIn: '30s'
    });

res.send({
      //为了方便客户端使用token,在服务器端直接拼上前缀
         token: 'Bearer ' + tokenStr
    })
将JWT字符串解析为JSON对象

注意:

  1. 只要配置成功了express-jwt这个中间件,就可以把解析出来的用户信息挂载到req.user属性
  2. 不要把密码加密到token中
// expressJWT({secret: secretKey}) 用来解析token的中间件
// .unless用来指定哪些接口不需要访问权限
app.use(expressJWT({
    secret: secretKey,
    algorithms: ['HS256']
}).unless({
    path: [/^\/api\//]
}))
捕获解析JWT失败后产生的错误
app.use((err, req, res, next)=>{
   // token解析失败导致的错误
   if(err.name === 'UnauthorizedError'){
      return res.send({
         status: 401,
         message: '无效token'
      })
   }
   //其他原因导致的错误
   res.send({
      status: 500,
      message: '未知错误'
   })
})

对密码进行加密处理

  1. 可以使用bcryptjs对要存放进数据库的密码进行加密

  2. 优点:

    • 加密之后的密码无法被逆向破解
    • 同一密码多次加密,得到的加密结果不相同
  3. 步骤:

    • 安装bcryptjs包
    • 导入bcryptjs
    • 在处理函数中调用bcrypt.hashSync(明文密码,随机的长度)方法进行加密处理,示例:
    const bcrypt = require('bcryptjs');
    password = bcrypt.hashSync(password, 8);
    
    • 调用bcrypt.compareSync(用户提交的密码,数据库中的密码)方法比较密码是否一致,返回值是布尔值,示例:
    const compareResult = bcrypt.compareSync(xx,xx);
    if(!compareResult){
       return res.send({
          status: 1,
          message: '密码错误!'
       })
    }
    

表单验证

  1. 前端验证为辅,后端验证为主
  2. 适当使用第三方的包来辅助验证(如:joi包、@escook/express-joi包)
  3. 例:
const joi = require('joi')

/**
 * string() 值必须是字符串
 * alphanum() 值只能是包含 a-zA-Z0-9 的字符串
 * min(length) 最小长度
 * max(length) 最大长度
 * required() 值是必填项,不能为 undefined
 * pattern(正则表达式) 值必须符合正则表达式的规则
 */

// 用户名的验证规则
const username = joi.string().alphanum().min(1).max(10).required()
// 密码的验证规则
const password = joi
  .string()
  .pattern(/^[\S]{6,12}$/)
  .required()

解析表单数据

  1. 注:使用express.urlencoded()无法解析multipart/form-data格式的请求体数据

  2. 可以使用multer来解析multipart/form-data格式的表单数据

  3. 步骤:

    • 安装multer包
    • 导入并配置multer,创建multer的实例对象,通过dest属性指定文件的存放路径
    const multer = require('multer');
    const path = require('path');
    const upload = multer({
       dest: path.join(__dirname, 'xxx')
    })
    
    // upload.single() 是一个局部生效的中间件,用来解析 FormData 格式的表单数据
    // 将文件类型的数据,解析并挂载到 req.file 属性中
    // 将文本类型的数据,解析并挂载到 req.body 属性中
    router.post('/add', upload.single('cover_img'), article_handler.addArticle)
    
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值