Node.js


Node.js

安装包可以从 Node.js 的官网首页直接下载,进入到 Node.js 的官网首页(https://nodejs.org/en/)
打开终端,在终端输入命令 node –v 后,按下回车键,即可查看已安装的 Node.js 的版本号

终端快捷键

使用 ↑ 键,可以快速定位到上一次执行的命令
使用 tab 键,能够快速补全路径
使用 esc 键,能够快速清空当前已输入的命令
输入 cls 命令,可以清空终端

fs文件系统模块

fs 模块是 Node.js 官方提供的、用来操作文件的模块。它提供了一系列的方法和属性,用来满足用户对文件的操作需求
fs.readFile() 方法,用来读取指定文件中的内容
fs.writeFile() 方法,用来向指定的文件中写入内容

const fs = require('fs'); // 导入文件模块
读取文件
 // url文件存放路径, 编码格式 , 回调函数 err 错误返回 dataStr读取的文字
fs.readFile(__dirname + "/files/测试.txt",'utf-8',(err,dataStr) => {  // __dirname 表示当前文件所在目录
    if(err){//读取失败不为空
        return console.log('文件读取失败' + err.message);
    }
    console.log(dataStr);//读取文件内容
})
写入文件
// url文件读取路径, 写入的文字 , 编码格式 ,回调函数 err 错误返回
fs.writeFile(__dirname + '/files/测试.txt','这是测试的文字','utf-8',(err) => {// 这里的路径是相对路径
    // __dirname 表示当前文件所在目录
    if(err){//写入失败执行
        return console.log('文件写入失败'+err.message);
    }
    console.log('文件写入成功');
})
路径问题

fs 模块操作文件时,如果提供的操作路径是以 ./ 或 …/ 开头的相对路径时,很容易出现路径动态拼接错误的问题。
原因:代码在运行的时候,会以执行 node 命令时所处的目录,动态拼接出被操作文件的完整路径。
在使用 fs 模块操作文件时,直接提供完整的路径,不要提供 ./ 或 …/ 开头的相对路径,从而防止路径动态拼接的问题
__dirname 表示当前文件所在目录

path路径模块

path 模块是 Node.js 官方提供的、用来处理路径的模块。它提供了一系列的方法和属性,用来满足用户对路径的处理
path.join() 方法,用来将多个路径片段拼接成一个完整的路径字符串
path.basename() 方法,用来从路径字符串中,将文件名解析出来

const path = require('path'); //导入路径模块
//路径拼接操作用 path.join() ,不在使用 + 进行拼接
const pathStr = path.join('/a','/b/c','../','./d','e') // ../为返回上一级目录
console.log(pathStr) // \a\b\d\e
const pathStr2 = path.join(__dirname,'./files/测试.txt')
console.log(pathStr2) // 当前文件所在目录\files\1.txt
const fpath = '/a/b/c/index.html';//路径
var fullName = path.basename(fpath); // 获取到文件名称
console.log(fullName); // index.html
var nameWithoutExt = path.basename(fpath , '.html'); // 去掉.html
console.log(nameWithoutExt); // index
const fext = path.extname(fpath); // 获取拓展名
console.log(fext) // .html

http模块

http 模块是 Node.js 官方提供的、用来创建 web 服务器的模块。
通过 http 模块提供的 http.createServer() 方法,就能方便的把一台普通的电脑,变成一台 Web 服务器,从而对外提供 Web 资源服务。

const http = require('http'); // 导入http模块

http.createServer(); // 创建服务器实例
通过.on()方法绑定request事件监听客户端请求
req 是请求对象, 包含客户端相关的数据和属性
res 是相应对象, 服务器相关的属性或数据
调用服务器.listen()方法,启动服务器

const server =  http.createServer(); // 创建服务器实例

server.on('request',(req,res) => { // 监听客户端请求
    // req 是请求对象, 包含客户端相关的数据和属性
    const url = req.url; // req.url 客户端请求地址
    const method = req.method; // req.method 客户端请求类型
    // res 是相应对象, 服务器相关的属性或数据
    res.setHeader('Content-Type','text/html; Charset=utf-8'); // 防止中文乱码
    const str = `Your request url is ${url},and request method is ${method}`;
    const str1 = `<br />你的访问的url地址是 ${url},请求方式为 ${method}`;
    res.end(str+str1); // 向客户端发送内容,结束请求
})

server.listen (8080,() => { // 启动服务器 在8080端口下运行
    console.log('http server running at http://127.0.0.1:8080');
})

Node.js中的模块化

Node.js 中根据模块来源的不同,将模块分为了 3 大类,分别是:
内置模块(内置模块是由 Node.js 官方提供的,例如 fs、path、http 等)
自定义模块(用户创建的每个 .js 文件,都是自定义模块)
第三方模块(由第三方开发出来的模块,并非官方提供的内置模块,也不是用户创建的自定义模块,使用前需要先下载)
通过require()方法,加载模块,加载时会执行一篇代码

const fs = require('fs'); // 加载内置 fs 模块
const custom = require('./custom.js'); // 加载用户自定义模块
const moment = require('moment'); // 加载第三方模块
模块作用域
// 05-test.js 自定义模块
console.log('执行了05-test.js');// 导入模块时会执行一次test.js
const username = '张三' // 当前常量只被内部访问

// 05-模块作用域测试
const test = require('./05-test.js'); // 导入得到的是module.exports 所指向的对象
console.log(test); // 执行了05-test.js {} 访问不到username
module对象

在每个 .js 自定义模块中都有一个 module 对象,它里面存储了和当前模块有关的信息
在自定义模块中,可以使用 module.exports 对象,将模块内的成员共享出去,供外界使用
使用 require() 方法导入模块时,导入的结果,永远以 module.exports 指向的对象为准
exports 和 module.exports 指向同一个对象。最终共享的结果,还是以 module.exports 指向的对象为准

// 05-test.js 自定义模块
const age = 18;
function seyHello(){
    console.log('Hello');
}
module.exports.username = '张三';
module.exports = { // 通过module.expors 对外提供成员 重新指向了一个新的对象
    age,
    seyHello
}

// 05-模块作用域测试
// 永远以 module.exports 指向的对象为准
const test = require('./05-test.js'); // 导入得到的是module.exports 所指向的对象。
console.log(test); //{ age: 18, seyHello: [Function: seyHello] }
module.exports.username = 'zs'
exports = {
    gender: '男',
    age: 22
} // 结果以 module.exports 为准 { username:'zs' }
npm与包

Node.js 中的第三方模块又叫做包,包是由第三方个人或团队开发出来的,免费供所有人使用
包的查询地址:https://www.npmjs.com/ ,它是全球最大的包共享平台
包的下载地址:https://registry.npmjs.org/ 的服务器

安装包
// 在项目中安装包的命令
npm install 包的完整名称
npm i 包的完整名称 // 简写
npm i moment@2.22.2 // 通过@符号指定具体版本
// 版本号以'点分十进制'定义总共有三位数字,例如 2.24.0 大版本.功能版本.Bug修复

初次装包完成后,在项目文件夹下多一个叫做 node_modules 的文件夹和 package-lock.json 的配置文件node_modules 文件夹用来存放所有已安装到项目中的包。require() 导入第三方包时,就是从这个目录中查找并加载包。
package-lock.json 配置文件用来记录 node_modules 目录下的每一个包的下载信息,例如包的名字、版本号、下载地址等

package.json配置文件

在项目根目录中,创建一个叫做 package.json 的配置文件,即可用来记录项目中安装了哪些包,包的名称和版本号

npm init -y // 在所处目录中,新建package.json文件

package.json 文件中,有一个 dependencies 节点,专门用来记录您使用 npm install 命令安装了哪些包

npm install // 安装所有包
npm i // 安装所有包
npm uninstall moment // 卸载指定包会 package.json 的 dependencies 中移除掉。

devDependencies 节点:
如果某些包只在项目开发阶段会用到,在项目上线之后不会用到,则建议把这些包记录到 devDependencies 节点中

如果某些包在开发和项目上线之后都需要用到,则建议把这些包记录到 dependencies 节点中

npm i 包名 -D // 安装到devDependencies节点中
npm install 包名 --save-dev
解决下包速度慢

在https://registry.npmjs.org/ 服务器进行下载,此时,网络数据的传输需要经过漫长的海底光缆
淘宝 NPM 镜像服务器,专门把国外官方服务器上的包同步到国内的服务器

npm config get registry //查看当前下包路径
npm config set registry=https://registry.npm.taobao.org/  //切换淘宝NPM路径
nrm

方便的切换下包的镜像源,我们可以安装 nrm 这个小工具

npm i nrm -g //全局可用的工具
nrm ls // 查看所有镜像源
nrm use taobao // 切换至淘宝
包的分类
项目包

安装到项目的 node_modules 目录中的包,都是项目包,项目包分两类
开发依赖包(被记录到 devDependencies 节点中的包,只在开发期间会用到)
核心依赖包(被记录到 dependencies 节点中的包,在开发期间和项目上线之后都会用到)

npm i 包名 -D # 开发依赖包
npm i 包名 # 核心依赖包
全局包

全局包会被安装到 C:\Users\用户目录\AppData\Roaming\npm\node_modules 目录下

npm i 包名 -g # 全局安装指定包
npm uninstall 包名 -g # 卸载全局安装包
发布包

包必须以单独的目录而存在
包的顶级目录下要必须包含 package.json 这个包管理配置文件
package.json 中必须包含 name,version,main 这三个属性,分别代表包的名字、版本号、包的入口。

初始化包

①新建 itheima-tools 文件夹,作为包的根目录
②在 itheima-tools 文件夹中,新建如下三个文件:

  • lpackage.json (包管理配置文件)

    { // 注意 json 不允许出现注释
        "name": "itheima-luokong", // 包名
        "version": "1.0.0", // 版本
        "main": "index.js", // 入口文件
        "description": "提供了格式化时间,HTMLEscape的功能", // 介绍
        "keywords": ["itheima","dateFormat"], // 关键词
        "license": "ISC" // 协议
    }
    
  • lindex.js (包的入口文件)

    ①将格式化时间的功能,拆分到 src -> dateFormat.js 中

    ②将处理 HTML 字符串的功能,拆分到 src -> htmlEscape.js 中

    ③在 index.js 中,导入两个模块,得到需要向外共享的方法

    ④在 index.js 中,使用 module.exports 把对应的方法共享出去

  • lREADME.md (包的说明文档)
    是包的使用说明文档。通过它,我们可以事先把包的使用说明,以 markdown 的格式写出来,方便用户参考
    安装方式、导入方式、格式化时间、转义 HTML 中的特殊字符、还原 HTML 中的特殊字符、开源协议

发布过程
登录npm

先切换为 npm 的官方服务器
可以在终端中执行 npm login 命令,依次输入用户名、密码、邮箱后,即可登录成功

发布npm

将终端切换到包的根目录之后,运行 npm publish 命令,即可将包发布到 npm 上(注意:包名不能雷同)

删除包

运行 npm unpublish 包名 --force 命令,即可从 npm 删除已发布的包,只能删除 72 小时以内发布的包, 24 小时内不允许重复发布

模块的加载机制
优先从缓存中加载

模块在第一次加载后会被缓存这也意味着多次调用 require() 不会导致模块的代码被执行多次

内置模块的加载机制

内置模块是由 Node.js 官方提供的模块,内置模块的加载优先级最高

自定义模块的加载机制

使用 require() 加载自定义模块时,必须指定以 ./ 或 …/ 开头的路径标识符。在加载自定义模块时,如果没有指定 ./ 或 …/ 这样的路径标识符,则 node 会把它当作内置模块或第三方模块进行加载。

第三方模块的加载机制

如果传递给 require() 的模块标识符不是一个内置模块,也没有以 ‘./’ 或 ‘…/’ 开头,则 Node.js 会从当前模块的父目录开始,尝试从 /node_modules 文件夹中加载第三方模块。

目录作为模块

①在被加载的目录下查找一个叫做 package.json 的文件,并寻找 main 属性,作为 require() 加载的入口
②如果目录里没有 package.json 文件,或者 main 入口不存在或无法解析,则 Node.js 将会试图加载目录下的 index.js 文件。
③如果以上两步都失败了,则 Node.js 会在终端打印错误消息,报告模块的缺失:Error: Cannot find module ‘xxx’

Express

Express 是基于 Node.js 平台,快速、开放、极简的 Web 开发框架,是专门用来创建 Web 服务器的

npm i express@4.17.1 //安装Express
创建Web服务器
const express = require("express"); // 导入express模块

const app = express(); // 创建 web 服务器

app.listen(8080,() => { // 启动服务器
    console.log('express server running at http://127.0.0.1:8080')
})
监听请求
// 客户端请求的url ,请求对应的处理函数 req:请求对象 res:相应对象
app.get('请求URL', function(req,res){ /*处理函数*/ })
app.post('请求URL', function(req,res){ /*处理函数*/ })

通过 res.send() 方法,可以把处理好的内容,发送给客户端
1.如果服务器端没有数据要返回到客户端的话,就直接用res.end() 中文会乱码结束这次响应
2.如果服务器需要有数据返回到客户端的话,就需要用res.send() 中文不会乱码,处理成JSON格式

// res.send() 将内容发送到客户端
app.get('/user',(req,res)=>{
   // 向客户端发送JSON对象
   res.send({naem:'zs',age: 20})
})

app.post('/user',(req,res)=>{
    res.send('请求成功')
})

通过 req.query 对象,可以访问到客户端通过查询字符串的形式,发送到服务器的参数

app.get('/',(req,res)=>{
    console.log(req.query); // req.query获取到客户端 ?name=zs&age=20
})

通过 req.params 对象,可以访问到 URL 中,通过 : 匹配到的动态参数

app.get('/user/:id',(req,res)=>{
    console.log(req.params); // 动态参数
}) 

express.static(‘文件夹’),创建一个静态资源服务器目录下的图片、CSS 文件、JavaScript 文件对外开放访问了

app.use(express.static('clock')) // 静态托管不包含clock
app.use('/clock',express.static('clock')) // 挂载路径前缀 /clock
nodemon

它能够监听项目文件的变动,当代码被修改后,nodemon 会自动帮我们重启项目,极大方便了开发和调试。

npm install -g nodemon // 安装 nodemon
node app.js // 正常运行js
nodemon app.js // 通过nodemon运行js
// 禁止运行失败
1.菜单栏搜索:PowerShell,右键以管理员方式运行
2.输入命令:set-ExecutionPolicy RemoteSigned 执行策略更改
3.输入命令:Y(A也行)
路由

在 Express 中,路由指的是客户端的请求与服务器处理函数之间的映射关系。
Express 中的路由分 3 部分组成,分别是请求的类型、请求的 URL 地址、处理函数,格式如下:

app.METHOD(PATH, HANDLER)
const express = require("express");
const app = express(); // 创建服务器

// 挂载路由 到app上 
// 匹配 GET 请求,且URL为 /
app.get('/', function (req,res){ res.send('Hello World!') })
// 匹配 POST 请求,且 URL为 /
app.post('/', function (req,res){ res.send('请求成功') })

app.listen(8080,()=>{ // 启动路由器
    console.log('express server running at http://127.0.0.1:8080')
})
模块化路由

方便对路由进行模块化的管理,Express 不建议将路由直接挂载到 app 上,而是推荐将路由抽离为单独的模块。

// 在新建的08-user.js中
const express = require("express");
const router = express.Router(); // 创建路由对象

// 挂载路由
router.get('/user/list',(req,res) => { res.send("GET user list.")})
router.post('/user/add',(req,res) => { res.send("Add new user.")})

// 向外导出路由
module.exports = router;
// 在原先的js中
const user = require("./08-user"); // 导入08-user
app.use('/app',user) // 通过app.use 注册路由模块 添加统一的访问前缀 '/app'
中间件

中间件(Middleware ),特指业务流程的中间处理环节,当一个请求到达 Express 的服务器之后,可以连续调用多个中间件,从而对这次请求进行预处理
多个中间件之间,共享同一份req 和res。基于这样的特性,我们可以在上游的中间件中,统一为 req 或 res 对象添加自定义的属性或方法,供下游的中间件或路由进行使用。

next函数

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

const mw = function (req,res,next){  // mw 指向这个中间件函数
    console.log('这是一个简单中间件')
    next() // 处理完后必须调用next转交下一个中间件或路由
}
全局中间件

客户端发起的任何请求,到达服务器之后,都会触发的中间件,调用 app.use(中间件函数),即可定义一个全局生效的中间件

app.use(mw) // 全局中间件
// 或者
app.use(function (req,res,next){
    console.log('这是一个简单中间件')
    next()
})

多个全局中间件

app.use(function(req,res,next){
    console.log('调用了第一个中间件');
    next();
}) // 全局中间件
app.use(function(req,res,next){
    console.log('调用了第二个中间件');
    next();
}) // 全局中间件

app.get('/user',(req,res) => { // 请求这个路由,会依次触发上面两个中间件
    console.log("get");
    res.send("get user");
})
局部中间件

不使用 app.use() 定义的中间件,叫做局部生效的中间件

const mw1 = function(req,res,next){
    console.log('这是中间件函数');
    next();
}

app.get('/api',mw1,(req,res) => { // mw1 这个中间件只在当前路由生效
    console.log("get");
    res.send("get user");
})

多个局部中间件

app.get('/', mw1, mw2,(req,res) => { res.send('Home page.') }) 
app.get('/', [mw1, mw2],(req,res) => { res.send('Home page.') }) // 两个等价
注意事项

①一定要在路由之前注册中间件
②客户端发送过来的请求,可以连续调用多个中间件进行处理
③执行完中间件的业务代码之后,不要忘记调用 next() 函数
④为了防止代码逻辑混乱,调用 next() 函数后不要再写额外的代码
⑤连续调用多个中间件时,多个中间件之间,共享 req 和 res 对象

中间件分类
应用级别的中间件

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

路由级别的中间件

绑定到 express.Router() 实例上的中间件,叫做路由级别的中间件

错误级别的中间件

错误级别中间件的作用:专门用来捕获整个项目中发生的异常错误,从而防止项目异常崩溃的问题,必须注册在所有路由之后

app.use(function(err,req,res,next){ // 错误级别的中间件,在路由之后
    console.log('发生了错误:' + err.message)
    res.send('Error! + err.message')
})
Express 内置的中间件

①express.static 快速托管静态资源的内置中间件,例如: HTML 文件、图片、CSS 样式等(无兼容性)
② express.json 解析 JSON 格式的请求体数据(有兼容性,仅在 4.16.0+ 版本中可用)
③ express.urlencoded 解析 URL-encoded 格式的请求体数据(有兼容性,仅在 4.16.0+ 版本中可用)

第三方的中间件
运行 npm install body-parser 安装中间件 // 解析请求体数据
使用 require 导入中间件
调用 app.use() 注册并使用中间件
自定义中间件
function bodyparser(req,res,next) { // 模块化
        let str = ''
        req.on('data',(dataStr)=>{ // data 事件,来获取客户端发送到服务器的数据
            str+=dataStr;
        })
        req.on('end',()=>{ // 请求体数据接收完毕之后,会自动触发 req 的 end 事件
            console.log(str);
            const body = qs.parse(str) // 解析字符串为对象
            req.body = query // 挂载req.body属性
            next();
        })
}
module.exports = bodyparser; // 向外导出
const bp = require('./10-body-parser'); // 导入模块
app.use(bp) // 注册中间件
app.post('/user',(req,res)=>{ // 监听post请求
    res.send(req.body)
})

写接口

// 创建基本服务器
const express = require("express"); 
const app = express(); // 创建服务器实例
app.listen(8080,()=>{ // 启动服务器 端口8080
    console.log('express server running at http://127.0.0.1:8080')
})
// API 路由模块
const express = require('express');
const router = express.Router();
router.get('/get',(req,res)=>{
    const query = req.query; // 获取客户端查询字符串
    res.send({ //相应数据给客户端
        status: 0, // 状态码
        msg: '请求成功', // 状态描述
        data: query // 相应数据
    })
})
router.post('/post',(req,res)=>{
    const body = req.body;  // 获取通过请求体,发送URL-encoded数据
    res.send({ //相应数据给客户端
        status: 0, // 状态码
        msg: 'POST请求成功', // 状态描述
        data: body // 相应数据
    })
})
module.exports = router;
const cors = require('cors'); // 通过cors模块 解决跨域问题
app.use(cors()); // 写在接口前
app.use(express.urlencoded({ extended: false}))// 解析表单数据中间件
const router = require('./11-apiRouter') // 导入接口
app.use('/api',router); // 注册接口
JSONP接口

浏览器端通过

app.get('/api/jsonp',(req,res) => {
    const funcName = req.query.callback // 获取回调函数的名字
    const data = { name: 'zs' , age: '22'} // JSONP 数据
    const scriptStr = `${funcName}(${JSON.stringify(data)})` // 拼接字符串
    res.send(scriptStr) // 响应script标签进行解析
}) // JSONP接口
app.use(cors()) // CORS处理所有接口
$.ajax({ // 发起jsonp请求
	method: 'GET',
    url: 'http://127.0.0.1:8080/api/jsonp',
    dataType: 'jsonp',
    success: function(res){
    	console.log(res);
    }
})

数据库Mysql

安装模块

mysql 模块是托管于 npm 上的第三方模块。它提供了在 Node.js 项目中连接和操作 MySQL 数据库的能力

npm install mysql // 安装
配置mysql
// 导入数据库模块
const mysql = require('mysql');

// 连接数据库
const db = mysql.createPool({
    host: '127.0.0.1', // 数据库的IP地址
    user: 'root', // 数据库账号
    password: '12345678', // 数据库密码
    database: 'db_py' // 操作那个数据库
})
测试数据
// 测试 mysql模块
db.query('select 1',(err,results) => {
    // 失败
    if(err) return console.log(err.message)
    // 成功 [ RowDataPacket { '1': 1 } ]
    console.log(results) 
})
查询数据
// 查询 tb_py 表中的用户数据
db.query('SELECT * FROM tb_py',(err,results)=>{
    if(err) return console.log(err.message)
    // 成功
    console.log(results);
})
插入数据
// 插入数据
// 1. 要插入表中的数据对象
const user = { name: 'luokon', sex: '男', age: 19}
// 2. 待执行的 SQL ,其中 ? 代表占位符
const sqlStr = 'insert into tb_py (name,sex,age) values (?,?,?)'
// 3. 数组形式,依次为 ? 占位符添加值
db.query(sqlStr,[user.name,user.sex,user.age],(err,results) => {
    if(err) return console.log(err.message)
    // affectedRows是受影响行数
    if(results.affectedRows === 1){
        console.log('插入数据成功')
    }
})
// 如果数据对象的每个属性和数据表的字段一一对应 快速插入
const user = { name: 'luokon', sex: '男', age: 40}
const sqlStr = 'insert into tb_py set ?'
db.query(sqlStr,user,(err,results) => {
    if(err) return console.log(err.message);
    if(results.affectedRows === 1){
        console.log('插入数据成功');
    }
})
更新数据
const user = { id: 14, name: 'luokon', sex: '男', age: 40}
const sqlStr = 'update tb_py set name=?,sex=?,age=? where id=?'
db.query(sqlStr,[user.name,user.sex,user.age,user.id],(err,results) => {
    if(err) return console.log(err.message);
    if(results.affectedRows === 1){
        console.log('更新数据成功!');
    }
})
const user = { id: 9, name: 'luokon', sex: '男', age: 40}
const sqlStr = 'update tb_py set ? where id=?'
// 使用数组为占位符指定具体的值
db.query(sqlStr,[user,user.id],(err,results) => {
        if(err) return console.log(err.message);
        if(results.affectedRows === 1){
            console.log('更新数据成功!');
        }
    })
删除数据
const sqlStr = 'delete from tb_py where id=?'
// 只有一个值可以省略数组
db.query(sqlStr, 6,(err,results) => {
    if(err) return console.log(err.message);
    if(results.affectedRows === 1){
        console.log('删除数据成功!');
    }
})
// status状态字段来标记删除 0 未删除,1 删除
// 标记删除:updata 替代 delete 更新状态,未删除
db.query('updata tb_py set status=1 where id=?',6,(err,results) => {
    if(err) return console.log(err.message);
    if(results.affectedRows === 1){
        console.log('删除数据成功!');
    }
})

前后端身份认证

开发模式
服务端渲染的传统 Web 开发模式

服务端渲染的概念:服务器发送给客户端的 HTML 页面,是在服务器通过字符串的拼接,动态生成的。因此,客户端不需要使用 Ajax 这样的技术额外请求页面的数据

app.get('/index.html',(req,res) =>{
    // 1.渲染数据
    const user = { name: 'zs', age: 20}
    const html = `<h1>姓名:${user.name},年龄:${user.age}</h1>`
    res.send(html)
})

②基于前后端分离的新型 Web 开发模式

前后端分离的概念:前后端分离的开发模式,依赖于 Ajax 技术的广泛应用。简而言之,前后端分离的 Web 开发模式,就是后端只负责提供 API 接口,前端使用 Ajax 调用接口的开发模式。

身份认证

身份认证(Authentication)又称“身份验证”、“鉴权”,是指通过一定的手段,完成对用户身份的确认

Session认证

不支持跨域

HTTP 协议的无状态性

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

Cookie 是存储在用户浏览器中的一段不超过 4 KB 的字符串。它由一个名称(Name)、一个值(Value)和其它几个用于控制 Cookie 有效期、安全性、使用范围的可选属性组成。

npm install express-session // 安装express-session中间件

配置express-session中间件

// 导入 session 中间件
var session = require('express-session')
// 配置 Session 全局中间件
app.use(session({
    secret: 'keyboard cat', // secret 属性值可为任意字符串
    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: '退出登录成功',
  })
})
JWT认证

JWT 通常由三部分组成,分别是 Header(头部)、Payload(有效荷载)、Signature(签名)三者之间使用英文的“.”分隔
Payload 部分才是真正的用户信息,它是用户信息经过加密之后生成的字符串。
Header 和 Signature 是安全性相关的部分,只是为了保证 Token 的安全性。
客户端收到服务器返回的 JWT 之后,通常会将它储存在 localStorage 或 sessionStorage 中 JWT 放在 HTTP 请求头的 Authorization 字段中

Authorization: Bearer <token>
npm install jsonwebtoken express-jwt
// jsonwebtoken 生成JWT字符串 express-jwt解析JWT字符串

导入JWT

// TODO_01:安装并导入 JWT 相关的两个包,分别是 jsonwebtoken 和 express-jwt
const jwt = require('jsonwebtoken')
const expressJWT = require('express-jwt')
// 允许跨域资源共享
const cors = require('cors')
app.use(cors())

定义secret密钥

// TODO_02:定义 secret 密钥,建议将密钥命名为 secretKey
const secretKey = 'itheima No1 ^_^'

登入成功生成JWT字符串

// TODO_03:在登录成功之后,调用 jwt.sign() 方法生成 JWT 字符串。并通过 token 属性发送给客户端
  // 参数1:用户的信息对象
  // 参数2:加密的秘钥
  // 参数3:配置对象,可以配置当前 token 的有效期
  // 记住:千万不要把密码加密到 token 字符中
 // username: userinfo.username 加密了什么就解析出什么
  const tokenStr = jwt.sign({ username: userinfo.username }, secretKey, { expiresIn: '30s' })
  res.send({
    status: 200,
    message: '登录成功!',
    token: tokenStr, // 要发送给客户端的 token 字符串
  })

JWT字符串还原JSON对象

// TODO_04:注册将 JWT 字符串解析还原成 JSON 对象的中间件
// 注意:只要配置成功了 express-jwt 这个中间件,就可以把解析出来的用户信息,挂载到 req.user 属性上
// 参数1: 解密密钥 ,参数2:指定哪些接口不需要访问权限
app.use(expressJWT({ secret: secretKey }).unless({ path: [/^\/api\//] }))

req.user获取用户信息

// 这是一个有权限的 API 接口
app.get('/admin/getinfo', function (req, res) {
  // TODO_05:使用 req.user 获取用户信息,并使用 data 属性将用户信息发送给客户端
  console.log(req.user)
  res.send({
    status: 200,
    message: '获取用户信息成功!',
    data: req.user, // 要发送给客户端的用户信息 { "username": "admin" }
  })
})

捕获JWT失败产生的错误

// 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: '未知的错误',
  })
})
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值