初识Node
定义
node.js 是一个基于ChromeV8引擎的 JavaScript运行环境,在浏览器中运行js为前端,在node中运行JS代码为后端。(V8引擎解析效率最高)
node的运行环境核心部分
- V8引擎
- 内置API 注:node中无法调用Dom和Bom等浏览器API
Node.js 可以做什么
- 基于express框架,快速web构建web应用
- 基于Electron框架 ,可以构建跨平台的桌面应用
- 基于restify框架,可以快速构建API接口项目
- 文件读写和操作数据库、创建实用的命令行工具辅助前端开发。
学习路径
JS基础语法 + Node.js内置模块 + 第三方API模块(express、 mysql)
读写文件(require('fs'))
- 读取指定文件中的内容 fs.readFile(path[, options], callback) 参数,被中括号包裹的为 可选参数项。
path 表示文件的路径;
options 表示以什么编码格式来读取文件,默认为utf-8;
callback 文件读取完成后通过回调函数拿到读取的结果;
导入读写模块
const fs = require('fs');
fs.readFile('../../node_base/千峰.txt', 'utf8',(err, dataStr) =>{
console.log(err); // 输出null,没有文件输出错误对象
console.log('--------');
console.log(dataStr); // 输出文件内容,没文件输出undefined
} )
- 向指定的文件中写入内容 fs.writeFile(path,data[, options], callback)
path:为想要写入的文件的地址。
(1)若没有这个文件则先创建此文件;
(2)如果有这个文件,先把原来的内容清空,再把想要写入的内容写入;
(3)只能创建文件不能创建路径(文件的上一层)
data : 想要写入的内容
fs.writeFile('./write.txt', '44', (err) =>{
console.log(err); // 写入成功为null,否则输出错误对象
})
注: 以相对路径为路径时,容易出现路径的动态拼接错误问题。 原因: 代码在运行时,会以执行node命令时所处的目录,动态拼接处被操作文件的完整路径。
解决:
- (1)写绝对路径从盘符开始,斜线需要双斜线,否则代表转义。(移植性、维护性较差)
- (2) _ _dirname: 表示当前文件所处的目录(以 + 拼接)。
path模块(const path = require('path');)
定义:path模块是Node官方提供的用来处理路径的模块,它提供了一系列方法和属性,用来满足用户对路径的处理请求。
- path.join([...path]): 可以将多个路径片段拼接为完整的路径,返回路径的字符串。
const pathStr = path.join(__dirname, 'fs_read_write.js')
console.log(pathStr); //C:\Users\a\Desktop\前端之路\node.js\node\node_base\fs_read_write.js
const pathStr1 = path.join('/a', '/b/c', '../', '/c'); // "../"会抵消前面的路径
console.log(pathStr1); // 输出\a\b\c
- path.basename(path[,ext]): 可以获取路径中的最后一部分,可以用这个方法获取文件名
path: 要截取的路径; ext:可以去掉获取的文件的扩展名
const fPath = path.basename('C:/Users/a/Desktop/node/node_base/fs_read_write.js');
console.log(fPath); // fs_read_write.js
- path.extname(path): 获取文件的扩展名,path: 路径。
const pathExt = path.extname('C:/Users/a/Desktop/node/node_base/fs_read_write.js');
console.log(pathExt); // .js
☆http
定义:
客户端: 在网络节点中,负责消费资源的叫客户端; 服务器: 负责对外提供网络资源的叫服务。 http模块是Node官方提供的一个用来创建web服务器的模块,通过http提供的
http.createServer()方法,就方便把一台普通的电脑变成服务器,从而向外部提供web网络资源,不需要第三方服务器软件。 ip地址: 身份证号; 域名: 姓名; 域名服务器(DNS): 将域名转换为IP使能够访问 ;
端口号:一个电脑可以有上百个web服务器,但是每个端口号都对应唯一一个服务器(门牌号),在实际应用中,URL中的80端口可以被省略;
创建HTTP服务器
创建的基本步骤
// 1. 导入http模块
const http = require('http');
// 2.创建web服务器实例
const server = http.createServer();
// 3. 为服务器实例绑定request方法, 即可监听客户端发过来的网络请求
server.on('request', (req, res) => {
// 只要有客户端来请求我们的服务器,就会触发request方法,从而触发回调
console.log("表哥,我出来了哦");
})
// 4.启动web 服务器
server.listen(80, () => {
console.log('启动server');
})
req请求对象
req是客户端的请求对象,存放了客户端的相关数据或属性,例如:
req.url是客户端请求的url地址;req.method是客户端的method请求类型
server.on('request', (req, res) => {
// 只要有客户端来请求我们的服务器,就会触发request方法,从而触发回调
console.log("someone visit me");
console.log('客户端地址:' + req.url ); // 客户端地址:/
console.log("请求方式:" + req.method); //请求方式:GET
})
res响应对象
在request中可以访问server的相关数据或属性,例如:
res.end(): 想客户端发送指定的内容,并结束这次请求的处理过程;res.setHeader():设置请求头, = 两边不能留空格;
server.on('request', (req, res) => {
// 为了防止中文显示乱码的问题,需要设置响应头,
res.setHeader('Content-Type', 'text/html; charset=utf-8')
res.end('返回的数据')
})
根据不同的url相应不同的html页面
注意不能给content赋值为常量
server.on('request', (req, res) => {
const url = req.url;
let content = 'Not found 404!!!';
if(url === '/' || url === '/index.html') content = '<h1>首页</h1>';
if(url === '/about.html') content = '<h1>关于</h1>';
res.setHeader('Content-Type', 'text/html; charset=utf-8')
res.end(content)
})
案例:
- 把每个资源实际的存放路径,作为每个资源的请求url地址
link标签导入的文件会自动去拼接客户端请求文件的地址
server.on('request', (req, res) => {
let url = req.url;
// 帮用户补全地址
if(url === '/') url = '/index.html'
//把请求的路径映射为本地路径
const fPath = path.join(__dirname, url);
//根据地址读取文件
fs.readFile(fPath,(err,dataStr) =>{
//响应并返回客户端
if(err) return res.end('<h1>Not Found 404</h1>');
res.end(dataStr);
})
res.setHeader('Content-Type', 'text/html; charset=utf-8')
res.end(content)
})
模块化
Node中的模块化
- require加载模块
1.加载内置模块
const http = require('http');
2.加载自定义模块(需要路径)
const http = require('./index.js');
3.加载第三方模块
const http = require('express');
注: 当使用require加载模块时,会执行被加载模块中的代码。 2. 模块作用域
虽然js可以被引入,但是不能在引入的模块被使用,因为变量具有作用域; 优势:
- 防止变量的全局污染
- 向外共享模块作用域中的成员
- module对象存储了当前模块的信息
- module中的export属性可以向外暴露作用域中的变量,被引入模块使用; 注:外界使用require引入自定义模块时,得到的就是module.export中的内容。
- module.export的使用 导出的输出为:{ name: '赵本山', school: 'F4' };
module.exports.name = '赵本山';
module.exports.school = 'F4';
导出的输出为:{ F1: '刘能', F2: '赵四', F3: '宋小宝', F4: '小沈阳' };
module.exports.name = '赵本山';
module.exports.school = 'F4';
module.exports = {
F1: '刘能',
F2: '赵四',
F3: '宋小宝',
F4: '小沈阳'
}
注: 导出的结果永远以module.exports指向的对象为准。 5. export对象 定义: 由于module.exports单词写起来比较复杂,所以Node提供了exports对象,通常来说exports和module.exports指向同一个对象,最终共享结果还是以module.exports为准。
Node的模块化规范
定义:Node遵循了commonJs的模块化规范,commonJs规定了模块的特性和各模块之间的相互依赖。
CommonJs规定:
- module: 每个模块内部,module变量代表当前模块。
- module.exports: module的exports属性为对外的接口。
- require(): 加载依赖模块的module.exports 属性。
包(第三方模块)与npm
npm 的使用
- npm i 包名: 装包。
- npm uninstall 包名:卸载包且自动把包从
dependencies节点删除。 - npm install: 安装的包匹配
dependencies节点中的包。 - npm i 包名 -D(npm i 包名 --save-dev):把包记录在
devDependencies节点中。 - npm config get registry: 查看当前包的镜像源。
- npm config set 镜像源: 修改镜像源。 nrm: 方便切换镜像源的工具
- nrm is: 查看所有可用镜像源。
- nrm use 镜像源:将镜像源切换为。
package.json文件
- npm init -y : 初始化package.json文件,只能在英文的目录下运行且不能有空格。
dependencies节点: 记录npm install安装了哪些包。devDependencies节点: 包只在开发的时候用,在上线之后就不在用了,则把包记录在此节点中。
包的分类
项目包:安装到node_modules中的包都可以叫项目包。
- 开发依赖包: 被记录到
devDependencies节点中。 - 核心依赖包:被记录到
dependencies节点中。 全局包: 默认安装到C:\Users\a\AppData\Roaming\npm\node_modules路径下 - npm i 包名 -g: 全局安装指定的包。
- npm uninstall 包名 -g: 卸载全局安装的包。 注:只有工具性的包才有全局安装的必要。
包的结构
写包:
- 必须以单独的目录存在。
- 必须有package.json配置文件。
- package.json配置文件中必须包含
name、version、main三个属性,分别代表包的名字、版本号、入口。 登录npm: - 注册 npm 账号。,
- 在终端登录npm:在终端输入
npm login命令,依次输入用户名、密码、邮箱后即可登录成功。(镜像源必须是Npm官方的镜像) 发布包: - 在终端切换到包的根目录上,运行
npm publish即可发布。 删除包: npm unpublish 包名 --force即可删除发布的包- 只能删除 72 小时的包,超过不可再删除。
- 24小时内不可重复发布相同名字的包。
模块的加载机制
- 模块优先从缓存中加载。
- 内置模块的加载优先级最高。
- 在加载自定义模块时,require没有用路径符
'/'Node会把其当做内置或第三方模块加载,找不到则加载失败。 - 在Node加载自定义模块时,如果省略了文件的扩展名,则Node会以以下的顺序进行补全。
1. 按照确切的文件名进行加载。
2. 补全 JS 进行加载。
3. 补全 JSON 进行加载。
4. 补全node扩展名进行加载。
5. 加载失败,终端报错。
- Node加载第三方模块的加载顺序,先从当前文件的父目录开始,尝试从
node_modules中加载文件,如果找不到,则去上一层目录的node_modules中寻找,直到电脑的根目录。 - 如果把目录(文件夹)作为模块表示符,传递给require加载的时候有三种加载方式:
1. 在被加载的目录下查找package.json文件中的main属性,作为加载的入口。
2. 如果目录不存在package.json 文件,或main属性不存在或无法解析,则Node会加载目录下的index.js文件。
3. 如果上两步都失败了,则报错。
express框架
定义: 本质是 npm 的一个第三方包,是专门用来创建web服务器(网站服务器或接口服务器)的。
创建基本的服务器
// 导入expres
const express = require('express')
// 创建服务器
const app = express();
// 启动服务器
app.listen('8090', () =>{
console.log('不写localhost就默认127.0.0.1');
})
监听GET 和 POST 请求并处理参数
app.use(): 注册全局中间件
监听请求并返回数据
- 监听GET或POST请求:
app.get('请求地址', (req, res) => {// 回调函数}。post请求把get换成post就可以。 - 向客户端发送数据:
res.send()。
// 监听客户端的get和post请求
app.get('/user', (req, res) =>{
// 调用express的res.send方法向客户端响应一个json对象
res.send({name:'赵本山', school: 'F4'})
})
app.post('/user', (req, res) =>{
// 普通文本
res.send('I am F4')
})
处理参数
- 获取URL中携带的参数(以?续参):
req.query对象中包含以字符串传过来的数据,默认为空。
请求格式:http://127.0.0.1:8090/user?name=赵本山&school=F4
app.get('/user', (req, res) =>{
// 获取通过URL传过来的参数。
console.log(req.query);
})
- 获取动态匹配:
req.params对象来匹配到通过':'或 params形式传的参。
请求格式:http://127.0.0.1:8090/user/001
app.get('/user/:id', (req, res) =>{
res.send(req.params); // req.params是动态匹配到的url参数
})
输出: {id: '001'}
注: / 不是分隔,:才是分隔,有无 / 都可,但是必须得有:多个变量的话用 / 做分隔。
静态资源处理
- express.statics(): 通过它我们可以创建一个静态资源服务器,使静态资源对外开放。
将public目录下的文件对外开放:
app.use(express.static('./public'))
访问:
http://127.0.0.1:8090/index.css
注:
访问的时候省略开放的文件夹,不用写。
托管多个静态资源文件时,多次写app.use(express.static('./public'))托管语句就行,资源的查找顺序,按托管的先后。
2. 挂载路径前缀:上面的方法请求资源时省略了路径前缀,当引进多个相同名字的静态资源文件夹时也做不到精准匹配。
app.use('/public', express.static('/public'));
访问:
http://127.0.0.1:8090/public/index.css
前面的public也可以起别名。
nodemon工具:自动重启服务器
使用:由原本的 node app.js 启动服务器改为 nodemon app.js。
路由
概念
定义: 指的是客户端请求和服务端处理函数之间的映射关系。
路由组成:请求的类型、 请求的URL地址、 处理函数
格式: app.METHOND(PATH, CALLBACK)
执行匹配: 按组成的顺序进行匹配,请求的类型和请求的URL必须同时满足才能去调用回调。
使用
- 最简单的用法:
app.get('请求地址', (req, res) => {// 回调函数})
路由模块化
- 创建路由模块对应的JS文件。
- 调用
express.Router()函数创建路由对象。 - 向路由对象上挂载具体的路由。
- 使用
module.exports向外共享路由对象。 - 使用
app.use()函数注册路由模块。 路由页面
const express = require('express');
const router = express.Router();
router.get('/user', (req, res) =>{
res.send('getUser')
})
router.post('/user', (req, res) =>{
res.send('postUser')
})
module.exports = router;
服务器页面
const express = require('express');
const router = require('./router/user');
const app = express();
app.use(router);
app.listen('8090', () =>{
console.log('不写localhost就默认127.0.0.1');
})
注: 注册路由模前添加前缀,可用来区分不同的模块,不用再在模块中的每个api都加上自己的模块名做区分
app.use( '/api', router);
请求命令:
http://127.0.0.1:8090/api/user
中间件
定义:对请求进行预处理,express中间件本质上是function函数, req和res是共享的(包括api回调中的那个)。
注: 中间件的函数中必须包含next迭代器。
const mw = function(req, res, next){
console.log("最简单的中间件");
next()
}
全局生效的中间件
定义: 客户端发起的请求到达服务器后,都会触发的中间件叫做全局中间件。 注册: 需要在注册路由之前,否则res.send结束请求,不能执行中间件
app.use(mw)
作用:在上游的中间件中为 res 和 req 中添加方法或属性,方便在api中使用。
局部中间件
定义: 只在局部起作用,不一定非要执行。 使用:不要用app.use注册
只在该接口调用的时候才执行mw中间件
app.get('/user', 'mw', (req, res) =>{
res.send('getUser')
})
注册多个局部中间件
方法1:
app.get('/user', 'mw', 'mw1', (req, res) =>{
res.send('getUser')
})
方法2:
app.get('/user', ['mw', 'mw1'], (req, res) =>{
res.send('getUser')
})
注意
- 一定要在路由之前注册中间件。
- 客户端发送过来的请求,可以调用连续的中间件做处理。
- 自定义中间件中一定调用next。
- 为防止逻辑混乱,在next之后就不要写代码了。
- res 和 req 是全部的中间件共享的,包括路由。
☆中间件的分类
- 应用级别的中间件 定义: 通过app.use、app.get、app.post等,绑定到app实例上的中间件,叫做应用级别的中间件。
- 路由级别的中间件 定义: 绑定到router实例上的中间件。
const express = require('express');
const router = require('./router/user');
const app = express();
const mw = function(req, res, next){
console.log("最简单的中间件");
next()
}
router.use(mw);
app.use(router);
- 错误级别的中间件 定义: 专门用来捕获整个项目中发生的异常错误,从而防止项目异常崩溃的问题。
格式: function(err, req, res, next){}
app.use((err, req, res, next) => {
res.send(err)
})
注: 要放在所有的中间件后面,做承接作用。
- express的内置中间件(必须全局注册)
- express.static:托管静态资源
- express.json:解析json 格式的请求体数据,仅在4.16.0版本后可用。
app.use(express.json())
- express.urlencoded:解析 URL-encoded 格式的请求体数据,仅在4.16.0版本后可用。
app.use(express.urlencoded({extend: false}))
注: 如果不配置解析表单数据的中间件,req.body对象默认为undefined。
- 第三方中间件 安装, 引入, 注册中间件
自定义中间件
步骤:
- 定义中间件
- 监听req的data事件(必须是post等携带数据的请求类型,否则不触发req的事件)。
- 监听req的end事件。
- 使用querystring模块解析请求体数据。
- 将解析出来的数据对象挂载到 req.body 上。
- 将自定义中间件封装为模块。
const mw = function(req, res, next){
let str = ''
req.on('data', (chunk) =>{
// 当数据过多时会分批传送,不断地触发data事件。
str += chunk;
})
req.on('end', () => {
// 将字符串解析为对象
req.body = qs.parse(str)
next()
})
}
app.use(mw);
注:
- 请求的方式为post等携带body参的。
- next的结束位置要在req的end事件中。
编写接口
GET接口
路由:
const express = require('express');
const router = express.Router();
router.get('/get', (req, res) =>{
res.send({
status: 0,
msg: 'GET 请求成功',
data: req.query
})
})
module.exports = router;
服务器:
const express = require('express');
const goodRouter = require('./router/good');
const app = express();
app.use('/api', goodRouter);
app.listen('8060', () =>{
})
post接口
路由接口:
router.post('/post', (req, res) =>{
res.send({
status: 200,
msg: 'post 请求成功',
data: req.body
})
})
中间件将数据格式转换:调用内置中间件
app.use(express.urlencoded({extended: false}))
注: 必须全局注册,否则不生效。
跨域
jQuery写ajax做例子
<button id="get">
GET
</button>
<button id="post">
POST
</button>
<script>
$('#get').on('click', function () {
$.ajax({
type: 'GET',
url: 'http://127.0.0.1:8060/api/get',
data: {
name: "赵本山",
school: 'F4'
},
success: function(res){
console.log(res);
}
})
})
$('#post').on('click', function () {
$.ajax({
type: 'POST',
url: 'http://127.0.0.1:8060/api/post',
data: {
name: "赵本山",
school: 'F4'
},
success: function(res){
console.log(res);
}
})
})
</script>
CORS解决跨域问题
定义: CORS是第三方插件,可以解决第三方跨域问题
使用:
- npm install cors 安装中间件。
- const cors = require('cors') 导入
- app.use(cors()) 注册配置 注:要在路由之前
原理:
cors由一系列HTTP响应头组成,这些http响应头决定了浏览器是否阻止前端js代码进行跨域获取资源。
浏览器的同源安全策略会默认阻止网页跨域获取资源。但如果接口服务器配置CORS相关的htp响应头,就可以解除浏览器端的跨域限制
注意:
CORS只有在服务端才可配置,客户端无需做任何事情。 CORS具有兼容性,只有支持 XMLHttpRequest level2 的浏览器才兼容
☆CORS响应头
- Access-Control-Allow-Origin: 允许哪些网页来访问服务器
只允许百度网页请求
res.setHeader('Access-Control-Allow-Origin', 'https://www.baidu.com/')
- Access-Control-Allow-Headers:声明请求头
使用:
res.setHeader('Access-Control-Allow-Headers', 'Content-Type')
- Access-Control-Allow-Headers:声明请求方式(get、post、HEAD除外)
使用:
res.setHeader('Access-Control-Allow-Methods', 'PUT')
CORS 请求的分类
定义:根据请求方式和请求头可以将请求分为简单请求和预检请求。
- 简单请求:
2. 预检请求
3. 简单请求和预检请求的区别
在项目中操作数据库
安装配置mysql模块
在根目录下建立config文件夹,建立index.js文件进行配置
npm i mysql: 安装模块;
// 导入mysql模块
const mysql = require('mysql');
通过mysql模块连接到mysql数据库
// 建立与MySQL数据库的联系
const db = mysql.createPool({
host: '127.0.0.1', // 数据库的ip
user: 'root', // 登录数据库的用户名
password: '123', // 登录数据库的密码
database: 'bipowernode' // 指定要操作的数据库名字
})
通过mysql模块执行sql语句
- 查询
// 查询dept表的所有数据
db.query('select * from dept',(err, result) =>{
if(err) console.log(err);
console.log(result);
})
注: select查询语句输出的结果为数组。 2.插入
- 普通插入
// 要插入的数据
const deptDate = {DEPTNO: 99, DNAME: '摸鱼部', LOC: '济南'};
// 插入的语句, ? 为占位符
// sql不区分大小写,包括表中的字段名,但是sql尽量大写,字段名根据真实的字段名来写
const insert = 'INSERT INTO dept(DEPTNO, DNAME, LOC)values(?, ?, ?)'
// 使用数组的形式依次为 ? 占位符指定具体的值。
db.query(insert, [deptDate.DEPTNO, deptDate.DNAME, deptDate.LOC],(err, result) =>{
if(err) console.log(err);
if(result.affectedRows === 1) console.log("插入成功"); // 当result.affectedRows === 1表示插入成功
})
- 插入数据对象和字段为一一对应的关系时
// 要插入的数据
const deptDate = {DEPTNO: 99, DNAME: '摸鱼部', LOC: '济南'};
//
const insert = 'INSERT INTO dept SET ?'
// 直接将数据对象顶替占位符。
db.query(insert, deptDate,(err, result) =>{
if(err) console.log(err);
if(result.affectedRows === 1) console.log('成功');
})
注: result 结果为对象 3. 更新
- 普通方式
// 要修改的数据对象
const deptDate = {DEPTNO: 99, DNAME: '小沈阳', LOC: '沈阳'};
// 用问号来占位,和限制条件
const upDept = 'UPDATE dept SET DNAME = ?, LOC = ? WHERE DEPTNO = ?'
// 用数组来顶替占位符。要按占位符顺序赋值。
db.query(upDept, [deptDate.DNAME, deptDate.LOC, deptDate.DEPTNO],(err, result) =>{
if(err) console.log(err.message);
if(result.affectedRows === 1) console.log('更新成功');
})
- 简便写法
const deptDate = {DEPTNO: 99, DNAME: '小沈阳', LOC: '沈阳'};
const upDept = 'UPDATE dept SET ? WHERE DEPTNO = ?'
// 条件要单独出来
db.query(upDept, [deptDate, deptDate.DEPTNO],(err, result) =>{
if(err) console.log(err.message);
if(result.affectedRows === 1) console.log('更新成功');
})
- 删除(要用标记删除)
- 普通删除
const upDept = 'DELETE FROM dept WHERE DEPTNO = ?'
// 当where条件为一个时,可以省略数组
db.query(upDept, [99],(err, result) =>{
if(err) console.log(err.message);
if(result.affectedRows === 1) console.log('删除成功');
})
- 标记删除: 设置一个status字段,如果为1,则是使用状态, 为0,则标记为删除
const upDept = 'UPDATE dept SET status = ? WHERE DEPTNO = ?'
// 条件要单独出来
db.query(upDept, [0, 99],(err, result) =>{
if(err) console.log(err.message);
if(result.affectedRows === 1) console.log('标记删除成功');
})
web端开发模式
服务器端渲染开发(不用ajax)
常用: 企业级网站展示,没有复杂的交互,并且需要良好的SEO;
优点:
前端耗时少:服务器负责生成HTML,浏览器只需要渲染页面就可以(不用ajax去请求页面了),尤其是移动端,更省电。 有利于SEO: 服务器端响应的是完整的HTML页面,更利于爬虫。 缺点: 占用服务器端的资源:页面都是在服务器端拼接,如果请求较多,会对服务器造成压力。
不利于前后端分离,开发效率低。
前后端分离的开发模式(用ajax)
常用:类似于后台管理,逻辑较多,交互性强。
优点:
开发体验好:前端专注于UI的开发,后端专注于接口的开发 用户体验好:ajax的广泛使用,实现了局部的刷新 减轻了服务端的渲染压力: 最终页面在浏览器直接生成 缺点: 不利于SEO:html 在客户端拼接完成,不利于爬虫。(利用vue,react的SSR技术可以解决)
身份认证(cookie)
定义: 因为HTTP协议具有无状态性,所以服务器不知道请求的人的信息,需要身份认证进行识别
服务器端渲染模式: session身份认证;
前后端分离渲染: jwt身份认证
- cookie
概念:
身份认证中的作用:
不安全性:
提高身份认证的安全性:
服务器分配cookie,客户端请求时与存储在服务器的信息进行对比
session认证机制
原理:在客户端存储用户登录成功的cookie,在服务器端设置变量存储用户的信息。服务器根据客户端传过来的cookie查看是否有对应的信息。
使用:
安装: npm install express-session
引入配置:
const session = require('express-session')
app.use(
session({
secret: 'session_cookie', //任意字符串
resave: false,
saveUninitialized: true,
})
)
向session中存用户数据:
// 登录的 API 接口
app.post('/api/login', (req, res) => {
// 判断用户提交的登录信息是否正确
if (req.body.username !== 'admin' || req.body.password !== '000000') {
return res.send({ status: 1, msg: '登录失败' })
}
// TODO_02:请将登录成功后的用户信息,保存到 Session 中
// 注意:只有成功配置了 express-session 这个中间件之后,才能够通过 req 点出来 session 这个属性
req.session.user = req.body // 用户的信息
req.session.islogin = true // 用户的登录状态
res.send({ status: 0, msg: '登录成功' })
})
向session中取数据:
// 获取用户姓名的接口
app.get('/api/username', (req, res) => {
// TODO_03:请从 Session 中获取用户的名称,响应给客户端
if (!req.session.islogin) {
return res.send({ status: 1, msg: 'fail' })
}
res.send({
status: 0,
msg: 'success',
username: req.session.user.username,
})
})
清空session:
// 退出登录的接口
app.post('/api/logout', (req, res) => {
// TODO_04:清空 Session 信息
req.session.destroy()
res.send({
status: 0,
msg: '退出登录成功',
})
})
局限性: session认证机制需要配合cookie才能实现,由于cookie默认不支持跨域访问,所以当涉及到前端跨域请求后端接口时,需要做很多额外的配置,才能实现跨域session认证
JWT认证(json web token)
原理:客户端提交账户与密码,服务器生成一个token,把用户的信息保存到token中,再把token发送给客户端,客户端收到后把token存储到storage中,放在每次请求的http请求头Authorization字段中。
组成:
使用:
安装:npm install jsonwebtoken express-jwt
jsonwebtoken: 生成jwt(token)字符串
express-jwt: 将JWT字符串解析还原成JSON对象
const jwt = require('jsonwebtoken')
const expressJWT = require('express-jwt')
定义secret秘钥
// TODO_02:定义 secret 密钥,建议将密钥命名为 secretKey
const secretKey = 'linyunre No1 ^_^'
生成
// 登录接口
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 属性发送给客户端
// 参数1:用户的信息对象
// 参数2:加密的秘钥
// 参数3:配置对象,可以配置当前 token 的有效期
// 记住:千万不要把密码加密到 token 字符中
const tokenStr = jwt.sign({ username: userinfo.username }, secretKey, { expiresIn: '30s' })
res.send({
status: 200,
message: '登录成功!',
token: tokenStr, // 要发送给客户端的 token 字符串
})
})
将JWT还原成JSON字符串,挂载到req.user上
// TODO_04:注册将 JWT 字符串解析还原成 JSON 对象的中间件
// 注意:只要配置成功了 express-jwt 这个中间件,就可以把解析出来的用户信息,挂载到 req.user 属性上
app.use(expressJWT({ secret: secretKey }).unless({ path: [/^\/api\//] }))
捕获解析JWT失败的错误(过期,token错误)注意中间件的位置
// TODO_06:使用全局错误处理中间件,捕获解析 JWT 失败后产生的错误
app.use((err, req, res, next) => {
// 这次错误是由 token 解析失败导致的
if (err.name === 'UnauthorizedError') {
return res.send({
status: 401,
message: '无效的token',
})
}
res.send({
status: 500,
message: '未知的错误',
})
})
项目过程(www.escook.cn:8088/#/)
初始化
- npm init -y : 初始化项目;
- npm i express: 安装express;
- 创建app.js, 创建web服务器;
- npm i cors: 配置跨域;
- 配置解析表单数据的中间件;
- 创建文件,配置路由模块;
- 抽离路由处理函数。
数据库
- 建库建表
- npm i mysql: 安装mysql;
- 连接数据库,暴露;
api处理函数
- 检查提交的数据表单是否合法
- 处理数据(对密码进行加密becrypt)
- 判断成功或失败(封装res.cc中间件),然后res.send