【简单学NODEJS+Express】

NODEJS介绍

NODEJS是前端工程化的基础
NODEJS无法调用DOM,BOM
NODE代码中建议使用绝对路径
常用框架express,electron,restify
运行node文件(项目目录下) node 文件名,其它需要添加路径支持

安装NODEJS

NODE版本管理工具NVM

1.下载安装NVM
安装NVM前一定要先卸载已安装的NODEJS,否则会发生冲突。
请使用管理员身份安装,避免出现乱码

NVM下载地址:https://github.com/coreybutler/nvm-windows/releases

2.配置NVM
在安装路径默认c:\用户\登录用户\AppData\Roaming\nvm目录打开setting.txt,添加如下代码:(使用国内镜像)

node_mirror:https://npm.taobao.org/mirrors/node/
npm_mirror:https://npm.taobao.org/mirrors/npm

3.使用NVM管理NODEJS
在控制台或CMD,终端下命令:

nvm on/off使用NVM管理node
nvm -vnvm的版本,查看nvm是否安装成功
nvm install 安装nodejs的版本号
nvm uninstall卸载node版本
nvm ls(list)显示已安装的nodejs版本号
nvm list availablewidnow显示远程可安装版本
nvm ls-remotelinux显示远程可安装版本
nvm use使用nodejs版本号
nvm install stable安装最稳定版本
nvm use node安装最新版

实时监控文件变化运行NODE文件

1.安装第三方包nodemon
npm i -g nodemon
2.使用nodemon
默认监控当前工作目录
nodemon app.js localhost 3697
其它nodemon使用方法请移步度娘搜索nodemon

创建工作环境

1.初始化项目(工作目录)
npm init -y

模块化

NODEJS中每个文件都是一个独立的模块,模块间使用特定语法导出,导入使用
默认使用commonJS标准,变更ECMA标准在package.json指定
{“type”:“module”}

使用require()方法加载其它模块时,会执行被加载模块中的代码

module.exports和exports

默认exports和module.exports 指向同一个对象
使用误区:
require模块时,指向的是module.exports指向的对象
建议不要在同一个模块中同时使用exports和module.exports

CommonJS标准

默认导出module.exports={}
按需导出exports.变量名
导入require('模块名‘)
自定义模块需要添加路径

ECMAScript标准

默认导出 export defalut{}
按需导出 export
导入import 变量名 from ‘模块名’

包的概念

将模块,代码,其他资料整合在一个文件夹下
包分类:
项目包:编写项目和业务逻辑
软件包:封装工具和方法进行使用
package.json文件记录软件包的名字,作者,入口文件等信息。
导入一个包文件夹,是根据package.json文件。
第三方包目录在node_modules
本地软件包:作用当前项目
全局软件包:作用所有项目

package.json文件

package-lock.json 锁定开发依赖清单
软件包版本:X,Y,Z:X大版本,Y功能版本,Z修复版本
~1.2.2,安装的是1.2.x不低于1.2.2版本,但不安装1.3.x
^1.2.2安装1.x.x的最新版本,不低于1.2.2,不装2.x.x
大版本X为0,^和~是表示相同的。
latest最新版

npm shrinkwrap 当前目录下生成npm-shrinkwrap.json,npm i 优先使用这个安装(package-lock.json)

dependencies 记录安装了哪些包
devDependencies 开发环境使用的包

开发自己的软件包

1.包的顶级目录存在package.json
2.包必须是单独的目录
3.package.json包含:name,version,main,description

发布包的方法:
1.官网注册账户,发布时切换到官网npm源
2.在终端登录npm login
3.发布 npm publish
4.删除已发布 npm unpublish 包名 --force

只能删除72小时内的包,删除后在24小时内不许重复发布,不要随意发布垃圾包

软件包管理器NPM

安装NODEJS内置了NPM包管理器
允许用户从NPM服务器下载第三方包到本地使用
允许用户从NPM服务器下载安装别人的命令程序到本地使用
允许用户上传到NPM给别人使用

npm 使用

npm i根据package.json补全项目文件清单
npm i -g全局安装软件包
npm uni卸载软件包
npm i -D安装开发环境使用的软件包,生产环境不用

软件包的安装方式:通过 https://www.npmjs.com/查看具体的建议安装方式

使用NRM管理NPM源,快速切换npm源

npm install -g nrm安装NRM
nrm ls查看源列表 , *代表当前使用源
nrm current当前使用的源
nrm use 切换源,源名称
nrm add 添加源,源名称,地址
nrm del删除源
nrm test测试源

如果遇到禁止运行脚本提示,在power shell管理员运行如下命令:
set-ExecutionPolicy RemoteSigned

NODEJS内置模块–文件读写FS

FS模块封装了与本机文件系统交互的方法,属性
语法:
1.加载fs模块对象
2.写入文件内容
3.读取文件内容

const fs = require('fs')
fs.readFile(path[,options],callback)
fs.writeFile(path,data,[,options],callback)

判断是否读取成功
读取成功,err,为null,读取失败results为undefined

const fs = require('fs')
fs.readFile('11.txt','utf-8',(err,results)=>{
   console.log( err ? err.message : '读取成功')
    console.log(results? results : undefined)
    //未完成,读取成功,文件为空的情况
})

判断写入成功
写入文件默认是utf8模式覆盖写入,写入成功err为null,

const fs = require('fs')
const str = '和您一起学习nodejs'
fs.writeFile('11.txt', str, 'utf-8', (err,results)=>{
   console.log( err ? err.message : '写入成功')
})

NODEJS内置模块–路径path

语法:
1.引入path模块
2.使用path.join方法拼路径

const path = require('path')
path.join(__dirname,filename)

路径动态拼接

NODE代码中,相对路径是根据终端所在路径来查找的,可能无法找到文件,建议在NODE代码中使用绝对路径

__dirname(不包含文件)node内置获取当前模块目录绝对路径。
__filename(包含文件)node内置获取当前模块目录绝对路径。

path.basename(path,[options] 获取路径中的文件名,
options会省略文件扩展名

path.resolve()返回以程序为根目录,作为起点,根据参数解析出一个绝对路径。注意“ / ”的使用,代表根目录

path.join(…paths<string>) 多个路径片段拼接完整路径字符串
path.resolve(‘a’, ‘/b’) 返回路径是根目录下的b目录(c:\b)

path.extname(path)获取路径中的文件扩展名

const path = require(‘path’);
const pathstr = path.join([‘/a’,’/b’c’,’../’,’./d’,’e’])
console.log(pathstr)  // 输出\a\b\d\\e
这里 ../ 拼接时抵消了它前面的字符串,抵消了 C

NODEJS内置模块–WEB服务http

语法:
1.引入http模块对象
2.实例化web服务器
3.绑定事件,监听request请求事件,设置响应
4.监听,启动web服务

const http = require('http')
const fs = require('fs')
const path = require('path')
const server = http.createServer();
server.on('request',(req,res)=>{
    const url = req.url;
    const filePath = path.join(__dirname, url);
    fs.readFile(filePath,(err,results)=>{
    	if(err) return res.send('404 not found')
    	res.end(results)
    })
})
server.listen(80,()=>console.log( 'server running at http://127.0.0.1'))

处理查询字符串

将查询字符串处理解析成对象格式
语法:
1.引入模块对象
2.使用parse()函数
const qs = require(‘querystring’)
const obj = qs.parse(str)

示例 压缩HTML

步骤
1.读取源html文件内容
2.正则替换字符串
3.写入到新的html文件中

Express基础

基于NODE内置的http模块封装,创建WEB服务器
1.安装

npm init -y
npm i express@4.17.2

2.使用

const express = require('express')
const app = express()
const port = 3000

app.get('/', (req, res) => {
  res.send('Hello World!')
})

app.listen(port, () => {
  console.log(`Example app listening on port ${port}`)
})

3.运行
nodemon app.js

Express路由

客户端请求与服务器处理函数之间的映射关系
路由3部分组成:
请求类型,请求的URL,处理函数
app.method(path,handler)
示例:app.get(‘/’,(req,res)=>res.send(‘hello world’)

app实例,get请求方法,path服务上的文件路径,handler路由匹配时执行的数据。

当请求到达服务器之后,需要先经过路由匹配成功,才调用对应的处理函数,路径匹配按路由定义的顺序进行匹配,当请求类型和请求URL同时匹配成功,则express会将这次请求转交给对应的函数进行处理

路由方法

get,post,put,delete,all

路由路径

Express 使用路径到正则表达式来匹配路由路径;
‘/ab?cd’ =‘acd abcd
’/ab+cd’=abcd abbcd abbbcd
‘ab*cd’=abcd abxcd abRANDOMcd ab123cd
‘/ab(cd)?e’= /abe/abcde
/a/=a
/.*fly$/=butterflydragonflybutterflymandragonflyman

路由参数

1.获取URL的动态参数
app.get(‘/user/:id’,(req,res)=>res.send(req.params)

path: /flights/:from-:to
URL: http://localhost:3000/flights/LAX-SFO

path: /plantae/:genus.:species
URL: http://localhost:3000/plantae/Prunus.persica

path: /user/:userId(\d+)
URL: http://localhost:3000/user/42

2.获取URL地址参数
path=/user?name=abc&age=18’,
app.get(‘/user’,(req,res)=>res.send(req.query)

3.获取表单数据
使用内置中间件app.use(express.urlencoded({extended:false})
req.body()

路由处理函数

app.get(‘/example/c’, [cb0, cb1, cb2])
app.get(‘/example/d’, [cb0, cb1], function (req, res, next) {})

路由处理结果函数

res.download()提示下载文件。
res.end()结束响应过程。
res.json()发送 JSON 响应
res.jsonp()发送支持 JSONP 的 JSON 响应。
res.redirect()重定向请求。
res.render()呈现视图样板
res.send()发送各种类型的响应。
res.sendFile()将文件作为八位字节流发送。
res.sendStatus()设置响应状态代码并将其字符串表示形式作为响应正文发送。

模块化路由

不建议将路由直接挂载到APP实例上,推荐将路由抽离为单独的模块。
1.创建单独的js文件
2.调用express.Router()创建路由对象
3.向路由对象挂载具体的路由
4.向外共享路由对象
5.使用app.use()在app实例注册路由模块

//router.js
const express = require('express')
const router = express.Router()
rout.get('/', (req,res)=>res.send('welcome'))
module.exports = router
//app.js
const express = require('express')
const app=express()
const router = require('./router')
app.use(router)
app.listen(80,()=>console.log('app running ')

路由前缀

app.use(‘/api’,userRouter)

中间件middleware

对当次请求进行预处理,中间件必须包next参数,路由只有req,res

next函数的作用

实现多个中间件连续调用的关键,表示把流转关系交给下一个中间件或路由

全局中间件

const mw = (req,res,next)=>{
   console.log('xxx')
   next();
}
//通过app.use(中间件函数)注册中间件

可以连续定义多个全局中间件,会按照定义的先后顺序依次进行调用。

局部中间件

不使用app.use定义的中间件

const express = require('express');
const app = express();
//局部中间件
const mw1 = (req, res, next) => {
  console.log('调用了局部中间件');
  next();
};
const mw2 = (req, res, next) => {
  console.log('调用了局部中间件');
  next();
};
app.get('/', mw1,mw2, (req, res) => {
  res.send('Home Page');
});
app.listen(80, () => console.log('running'));

中间件作用

多个中间件共享同一份req,res,上下游中间件之间可以给req对象添加自定义属性、方法给下游中间件或路由使用

const express = require('express');
const app = express();
const mw = (req, res, next) => {
  console.log('mw');
  const time = Date.now();
  req.startTime = time;
  next();
};
//注册为全局
app.use(mw);
//路由处理函数
app.get('/', (req, res) => res.send('abc' + req.startTime));
app.listen(80, () => console.log('app1 running'));

中间件使用注意事项

1.必须放在路由前面,否则无效
2.可以连续调用多个中间件
3.不要忘记调用next
4.next后不要写代码,业务已经结束
5.连续调用多个中间件,中间件之间共享req,res对象

中间件分类

1.应用级别
app.use,app.get,app.post等绑定到app实例的中间件
2.路由级别
绑定到express.Router实例上的
router.use((req,res,next)=>next())
3.错误级别
捕获项目错误,放在所有路由的最后

const express = require('express');
const router = require('./router');
const app = express();
app.get('/', (req, res) => {
 throw new Error('服务器内部发生了错误');
 res.send('Home Page');
});
app.use((err, req, res, next) => {
 console.log('发生了错误:' + err.message);
 res.send('Error!' + err.message);
});
app.listen(80, () => console.log('app running at 127.0.0.1'));

内置中间件

1.express.static快速托管静态资源
2.express.json解析JSON格式的请求数据,有兼容性在4.16+可用
3.express.urlencoded解析url-encode格式的请求体数据 有兼容性在4.116+可用
app.use(express.json())
app.use(express.urlencoded({extended:false}))

第三方中间件

1.npm i
2.引入
3.挂载app.use

自定义中间件

1.定义中间件文件
2.监听req的data事件
3.监听req的end事件
4.使用querystring模块解析请求体数据
5.将解析的数据对象挂载为req.body
6.将自定义中间件封装为模块

客户端如果数据量比较大,无法一次发送完毕,会把数据切割后分批发送到服务器,所以data事件会触发多次,每次触发获取到数据的一部分,需要手动对接收的数据进行拼接。

拼接后使用node内置模块querystring对字符串转换

const express = require('express');
const customBodyParse = require('./middleware.js');
const app = express();
app.use(customBodyParse);
app.post('/', (req, res) => {
  console.log(req.body);
  res.send(req.body);
});
app.listen(80, () => console.log('app running at 127.0.0.1'));
//模块化自定义中间件
const qs = require('querystring');

const bodyParse = (req, res, next) => {
  //1.监听req的data,完整数据
  let str = '';
  req.on('data', chunk => (str += chunk));
  //监听req end
  req.on('end', () => {
    // console.log('str: ' + str);
    const body = qs.parse(str);
    req.body = body;
    next();
  });
};
module.exports = bodyParse;

托管静态文件

定义:express.static(path,[options])
发布:app.use(express.static(‘public’)

访问方法:http://localhost/

express在静态目录中查找文件,因此存放静态文件的目录不会出现在URL中

API 接口编程

1.创建api服务器
2.创建api路由模块
3.注册api路由模块
4.编写接口

const express = require('express');
const app = express();
app.listen(80, () => console.log('app running at 127.0.0.1'));
const express = require('express');
const router = express.Router();
//路由定义
module.exports = router;
const express = require('express');
const app = express();
const router = require('./apiRouter');
app.use('/api', router);
app.listen(80, () => console.log('app running at 127.0.0.1'));
router.get('/get', (req, res) => {
  //通过req.query获取客户端数据
  const query = req.query;
  res.send({
    status: 0,
    msg: 'GET 请求成功!',
    data: query,
  });
});
const express = require('express');
const app = express();
const router = require('./apiRouter');
app.use(express.urlencoded({ extended: false }));
app.use('/api', router);
app.listen(80, () => console.log('app running at 127.0.0.1'));
router.post('/post', (req, res) => {
  //通过req.body获取客户端url-encoded数据
  const query = req.body;
  console.log(query);
  res.send({
    status: 0,
    msg: 'POST 请求成功!',
    data: query,
  });
});

跨域问题

方案
cors
jsonp只支持GET请求

使用cors中间件
1.安装npm i cors
2.导入 const cors = require(‘cors’)
3.配置app.use(cors())必须放置在路由之前

什么是cors

cross-origin resource sharing
浏览器的同源安全策略默认会阻止网页跨域,如果接口服务器配置了cors相关的http响应头就可以解除浏览器的跨域访问限制

服务器要允许访问
只有支持XMLHttpRequest LEVEL2的浏览器才能正常访问(ie10+)

cors响应头

Access-Control-Allow-Origin:|允许所有的url
res.setHeader(‘Access-Control-Allow-Origin’, ’
’)

Access-Control-Allow-Headers:额外的请求信息
res.setHeader(‘Access-Control-Allow-Headers’, ‘content-type, X-Custom-Header’)

Access-Control-Allow-Methods 默认get,post,head
如果要使用put,delete,需要在服务端设置

res.setHeader(‘Access-Control-Allow-Methods’, ‘*’)
res.setHeader(‘Access-Control-Allow-Methods’, ‘put,delete’)

cors请求的分类
根据请求方式,请求头不同:
1.简单请求
请求方式:get,post,head
http头部不超过以下几种字段:
accept,accept-language,content-language,DPR,Downlink,save-data,viewport-width,width,content-type(application/x-www-form-urlencoded,multipart/form-data,text/plain)
2.预检请求
上面的请求之外的请求,请求头包含了自定义头部字段
向服务器发送了application/json格式数据

浏览器与服务器正式通信之前,浏览发送option请求进行预检,以获知服务器是否实际支持,成功响应后,才会发送真正的请求,并携带真实数据

简单请求只发生一次请求

预检请求发生二次请求

JSONP接口

浏览器通过《script》标签的src属性,请求服务器数据,同时服务器返回一个函数的调用。
JSONP不属于真正Ajax,没有使用XMLHttpRequest
仅支持Get

JSONP请求防止冲突,必须配置在cors之前
//jsonp
app.get(‘/api/jsonp’,(req,res)=>{

})

实现jsonp
1.获取client发送过来的回调函数名字
2.得到要通过JSONP形式发送给客户端的数据
3.拼一个函数调用的字符串
4.响应给client

$('#jsonp').on('click', () => {
                $.ajax({
                    type: 'GET',
                    url: 'http://127.0.0.1/api/jsonp',
                    dataType: 'jsonp',
                    success: (res) => console.log(res)
                })
            })

//jsonp
app.get('/api/jsonp', (req, res) => {
  const funname = req.query.callback;
  const data = { name: 'zs', age: 20 };
  const str = `${funname}(${JSON.stringify(data)})`;
  res.send(str);
});

MySql数据库

数据库结构:库,表,行,字段
工具:mysql server, mysql workbench
1.安装mysql第三方模块
2.通过mysql模块连接MySQL数据库
3.通过MySQL模块执行sql语句

mysql8连接认证错误:修改连接认证协议为
ALTER USER ‘root’@‘localhost’ IDENTIFIED WITH mysql_native_password BY ‘123456’;
flush privileges;

npm i mysql

配置mysql模块

const mysql = require('mysql');
const db = mysql.createConnection({
  host: '127.0.0.1',
  user: 'root',
  password: 'nethand88',
  database: 'my_db_01',
});

执行slq语句

查询

db.connect();
const sqlstr = 'select * from users';

db.query(sqlstr, (err, results) => {
  if (err) return console.log(err.message);
  console.log(results);
});
db.end();

插入

db.connect();
const user = { username: 'splider2', password: 'pc1234' };
const sqlstr = 'insert into users(username,password)values(?,?)';
db.query(sqlstr, [user.username, user.password], (err, results) => {
  if (err) return console.log(err.message);
  console.log(results);
  if (results.affectedRows === 1) return console.log('成功');
});
db.end();

快速插入
如果新增数据时,数据对象的每个属性和数据表的字段一一对应,则可以通过下面的方式快速插入

db.connect();
const user = { username: 'splider3', password: 'pc1234' };
const sqlstr = 'insert into users set ?';
db.query(sqlstr, user, (err, results) => {
  if (err) return console.log(err.message);
  console.log(results);
  if (results.affectedRows === 1) return console.log('成功');
});
db.end();

更新数据

db.connect();
const user = { username: 'splider5', password: 'pc12345',id:10 };
const sqlstr = 'update users set username=?,password=? where id=?';
db.query(sqlstr, [user.username,user.password,user.id], (err, results) => {
  if (err) return console.log(err.message);
  console.log(results);
  if (results.affectedRows === 1) return console.log('成功');
});
db.end();

快速更新

db.connect();
const user = { username: 'splider5', password: '0000',id:10 };
const sqlstr = 'update users set ? where id=?';
db.query(sqlstr, [user,user.id], (err, results) => {
  if (err) return console.log(err.message);
  console.log(results);
  if (results.affectedRows === 1) return console.log('成功');
});
db.end();

删除数据
根据id删除

db.connect();
const sqlstr = 'delete from users where id=?';
db.query(sqlstr, 10, (err, results) => {
  if (err) return console.log(err.message);
  console.log(results);
  if (results.affectedRows === 1) return console.log('成功');
});
db.end();

标记删除(软删除)

const sqlstr = 'update users set status=? where id=?';
db.query(sqlstr, [1,6], (err, results) => {
  if (err) return console.log(err.message);
  console.log(results);
  if (results.affectedRows === 1) return console.log('成功');
});
db.end();

身份认证

session

express-session中间件
npm i express-session

const session = require('express-session');
app.use(
  session({
    secret: 'itheima',
    resave: false,
    saveUninitialized: true,
  })
);

req.session来访问和使用session对象

router.post('/login', (req, res) => {
  if (req.body.username !== 'admin' || req.body.password !== '000000') {
    return res.send({ status: 1, msg: '登录失败' });
  }
  req.session.user = req.body;
  req.session.islogin = true;
  res.send({ status: 0, msg: '登录成功' });
});

router.get('/username', (req, res) => {
  if (!req.session.islogin) {
    return res.send({ status: 1, msg: 'fail' });
  }
  res.send({
    status: 0,
    msg: 'success',
    username: req.session.user.username,
  });
});

router.get('/logout', (req, res) => {
  req.session.destroy();
  res.send({ status: 0, msg: '退出登录成功' });
});

JWT

npm i jsonwebtoken express-jwt
jsonwebtoken用于生成JWT
express-jwt用于解析jwt还原

// 导入 express 模块
const express = require('express');
// 创建 express 的服务器实例
const app = express();

// TODO_01:安装并导入 JWT 相关的两个包,分别是 jsonwebtoken 和 express-jwt
const jwt = require('jsonwebtoken');

// 允许跨域资源共享
const cors = require('cors');
app.use(cors());

// 解析 post 表单数据的中间件
const bodyParser = require('body-parser');
const { expressjwt } = require('express-jwt');
app.use(bodyParser.urlencoded({ extended: false }));

// TODO_02:定义 secret 密钥,建议将密钥命名为 secretKey
const secretKey = 'abcbit&&';

// TODO_04:注册将 JWT 字符串解析还原成 JSON 对象的中间件
app.use(expressjwt({ secret: secretKey, algorithms: ['HS256'] }).unless({ path: [/^\/api\//] }));

// 登录接口
app.post('/api/login', function (req, res) {
  // 将 req.body 请求体中的数据,转存为 userinfo 常量
  const userinfo = req.body;
  // 登录失败
  if (userinfo.username !== 'admin' || userinfo.password !== '000000') {
    return res.send({
      status: 400,
      message: '登录失败!',
    });
  }
  // 登录成功
  // TODO_03:在登录成功之后,调用 jwt.sign() 方法生成 JWT 字符串。并通过 token 属性发送给客户端
  const token = jwt.sign({ username: userinfo.username }, secretKey, {
    expiresIn: '300s',
  });
  res.send({
    status: 200,
    message: '登录成功!',
    token: token, // 要发送给客户端的 token 字符串
  });
});

// 这是一个有权限的 API 接口
app.get('/admin/getinfo', function (req, res) {
  // TODO_05:使用 req.user 获取用户信息,并使用 data 属性将用户信息发送给客户端
  console.log(req.auth);
  res.send({
    status: 200,
    message: '获取用户信息成功!',
    data: req.auth, // 要发送给客户端的用户信息
  });
});
// TODO_06:使用全局错误处理中间件,捕获解析 JWT 失败后产生的错误
app.use((err, req, res, next) => {
  if (err.name === 'UnauthorizedError') return res.send({ status: 401, msg: '无效的token' });
  res.send({ status: 500, msg: '未知的错误' });
  next();
});

// 调用 app.listen 方法,指定端口号并启动web服务器
app.listen(8888, function () {
  console.log('Express server running at http://127.0.0.1:8888');
});
登录成功后获取用户信息,express-jwt@9,获取用户的方式req.auth
exports.getUserInfo = (req, res) => {
  const sql = 'select id,username,nickname,email,user_pic from ev_users where id=?';
  db.query(sql, req.auth.id, (err, results) => {
    if (err) return res.cc(err);
    if (results.length !== 1) return res.cc('获取用户信息 失败!');
    res.send({
      status: 0,
      message: '获取用户基本信息 成功!',
      data: results[0],
    });
  });
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值