node.js学习总结:node.js的内置模块,模块化,npm与包 express,前后端身份认证 JWT认证机制

什么是node.js

node.js是基于Chrome V8引擎的javaScrit运行环境
node.js中的javaScript运行环境
    注意:
        1.浏览器是javaScript的前端运行环境
        2.Node.js是javaScript的后端运行环境
        3.node.js中无法调取DOM和BOM等浏览器内置API
node.js可以做什么?
    1.基于express框架,可以快速构建Web应用
    2.基于Electron框架,可以构建跨平台的桌面应用
    3.基于restify框架,可以快速构建API接口项目
    4.读写和操作数据库,创建实用的命令行工具辅助前端开发,etc
nodejs的学习路径:
    javaScript基础语法+node.js内置模块(fs,path,http等)+第三方API模块(express,mysql等)

node.js的内置模块

fs系统模块

fs是node.js官方提供的,用来操作文件的模块。它提供了一系列的方法和属性用来满足用户对文件的操作需求
    1.fs.readFile()方法,用来读取指定文件的内容
    2.fs.writeFile()方法,用来向指定的文件中写入内容
 在js代码中使用fs模块(nodejs内置函数)
    1.导入
        const fs = require('fs')
1.读取指定文件内容
    使用fs.readFile()读取指定文件的内容:
        语法:
            fs.readFile(path,option,callback)
     参数解读:
        1.path 必选参数,字符串,表示文件的路径
        2.option 可选参数,表示使用什么编码格式来读取文件
        3.callback 必选参数,文件读取后,通过回调函数拿到读取结果
    示例:
    
        const fs = require('fs')  //导入fs模块
        fs.readFile('./a.txt', 'utf8',function(err,dataStr){
            console.log(err)
            console.log(dataStr)
        })
2.向指定模块中写入内容
    fs.writeFile()的语法格式
        使用fs.writeFile()方法写入
            1.fs.writeFile(path,data,option,function)
     参数解读:
        参数1:path 必选参数,文件的路径
        参数2:data 必选参数,表示要写入的内容
        参数3:编码格式
        参数4:回调函数 必选参数
    实例:
        const fs = require('fs')
        fs.writeFile('./file/a.txt',"hello",function(err){
            console.log(err)
        })
*注意:
    1.fs.writeFile() 方法只能用来创建文件,不能用来创建路径
    2.重复调用fs.writeFile()写入同一个文件,新写入的文件内容会覆盖之前的旧内容
3.fs模块–路径动态拼接的问题
    在使用fs 模块操作文件时,如果提供的操作路径是以/或../开头的相对路径时,很容易出现路径动态拼接错误的问题。
    原因:代码在运行的时候,会以执行node 命令时所处的目录,动态拼接出被操作文件的完整路径。
    解决这个问题:
        1.提供一个完整的文件路径
        2.不使用./ ../   使用_dirname 表示当前文件所在的目录

        //解决文件路径拼接问题
        const fs = require('fs')
        // __dirname 表示当前文件所处的目录
        console.log(__dirname);
        fs.readFile(__dirname+"/file/a.txt",'utf8',function(err, data) {
            if (err){
            return  console.error(err)
            } 
            console.log(data)
        })

path路径模块

path模块是Node.js官方提供的,用来处理路径的模块,它提供了一系列的方法和属性,用来满足用户对路径的处理和需求
例如:
    path.join()方法,用来将路径片段拼接成一个完整的路径字符串
    path.basename()方法,用来从路径中将文件名解析出来
在js代码中使用fs模块(nodejs内置函数)
    1.导入
        const path = require('path') 
路径拼接path.join()
    path.join()方法。可以将多个路径片段拼接为完整的路径字符串
    const pathStr = path.join('/a','/b/c','../','./d','e')
    console.log(pathStr) // ../会抵消前面的一个路径   \a\b\d\e

    const pathStr1 = path.join(__dirname,'/file/1.txt')
    console.log(pathStr1) // 输出 当前文件所在目录 \file\1.txt

    *涉及到路径一般使用path.join()进行路径的拼接
path.basename()的语法格式
    使用path.basename()方法,可以获取路径中的最后一部分,经常通过这个方法获取路径中的文件名
        path.basename(pth,ext)
    path<String> 必选参数,表示一个路径的字符串
    ext<String> 可选参数,表示文件的扩展名
    返回<String> 表示路径中最后的一部分
获取路径中的扩展名
    使用path.extname()方法,可以获取路径中扩展名部分,语法格式如下
        path.extname(path)
    path<String> 必选参数,表示一个路径的字符串
    返回<String> 返回得到的扩展名字符串

http模块

1.什么是http模块
    http模块是Node.js 官方提供的、用来创建web服务器的模块。通过 http模块提供的 http=createServer()方法
    就能方便的把一台普通的电脑,变成一台Web 服务器,从而对外提供 Web资源服务。
2.如何使用http模块  
    1.首先导入 const http = require('http');
3.http模块的作用
    在Node,js中,我们不需要使用lIS、Apache等这些第三方web服务器软件。因为我们可以基于Node.js 提供的http模块,通过几行简单的代码,就能轻松的手写一个服务器软件,从而对外提供web服务。
    服务器相关的概念
        1.IP地址
            Ip地址就是互联网上每台计算机的唯一地址,因此IP地址具有唯一性。如果把"个人电脑"比作"一台电话",那么"IP地址"就相当于"电话号码"只有在知道对方IP地址的前提下,才能与对应的电脑之间进行数据通信。
        2.域名和域名服务器
            尽管IРP地址能够唯一地标记网络上的计算机,但IP地址是一长串数字,不直观,而且不便于记忆,于是人们又发明了另一套字符型的地址方案,即所谓的域名(Domain Name)地址
            IP地址和域名是一—对应的关系,这份对应关系存放在一种叫做域名服务器(DNS,Domain name server)的电脑中。使用者只需通过好记的域名访问对应的服务器即可,对应的转换工作由域名服务器实现。因此,域名服务器就是提供IР地址和域名之间的转换服务的服务器。
        3.端口号
            计算机中的端口号,就好像是现实生活中的门牌号一样。通过门牌号,外卖小哥可以在整栋大楼众多的房间中,准确把外卖送到你的手中。
            同样的道理,在一台电脑中,可以运行成百上千个web服务。每个web服务都对应一个唯一的端口号。客户端发送过来的网络请求,通过端口号,可以被准确地交给对应的web 服务进行处理。
            每个端口号只能被一个web服务使用
    创建最基本的web服务器
        1.创建web服务的基本步骤
            1.导入http模块
                const http = require('http');
            2.创建web服务实例  
                通过http.creatServer()方法快速创建web服务实例
                const server = http.createServer();
            3.为服务器实例绑定request事件,建通客户端的请求
                // 使用服务器实例 .on()方法,为服务器绑定一个request事件
                server.on('request',(req,res)=>{
                    // 只要客户端来请求我们自己的服务器 就会触发 request事件 从而调用这个事件处理函数
                    console.log("Someone visit our web server")
                })
            4.启动服务
                调用服务器实例的.listen()方法,即可启动当前的web服务实例:
                    //调用server。listen(端口号,cb回调方法) 即可启动web服务器
                server.listen(80,()=>{
                    console.log('请访问 http://127.0.0.1:80')
                })
        2.req请求对象
            只要服务器接收到客户端的请i去,就会调用server.on()为服务器绑定的request事件处理函数
            如果想在事件处理函数中,访问与客户端相关的数据或属性,可以使用如下方式:
                server.on('request',(req, res) => {
                    // req.url 是客户端请求的url地址
                    const url = req.url;
                    // req.method 是客户端请求的method类型
                    const method = req.method;

                    const str = `你请求的url:${url},请求方式是:${method}`;
                    console.log(str);
                })
        3.res响应对象
            在服务器的request事件处理函数中,如果想访问服务器相关的数据或属性,可以使用如下的方式
            server.on('request',(req, res) => {
                // req.url 是客户端请求的url地址
                const url = req.url;
                // req.method 是客户端请求的method类型
                const method = req.method;
            
                const str = `你请求的url:${url},请求方式是:${method}`;
                //为了防止中文显示乱码的问题,需要设置响应头Content-Type的值为 text/html; charset=utf-8
                res.setHeader( 'Content-Type' ,'text/html; charset=utf-8 ')
                //调用res.send向客户端响应一些内容
                res.end(str)
            })
            *如果不设置相应头 res.end()返回中文会乱码
            设置响应头:res.setHeader( 'Content-Type' ,'text/html; charset=utf-8 ')
    根据不同的url地址响应不同的html内容
        核心步骤:
            1.获取请求的url地址
            2.设置默认的响应内容  404 NOT Fount
            3.判断用户请的是否为/或/index.html 首页
            4.判断用户请求的是否为/about.html 页面
            5.设置content-Type响应头,防止中文乱码
            6.使用res.end把内容响应给客户端

模块化

编程领域中的模块化,就是遵守固定的规则,把一个大的文件查分成独立并且相互依赖的多个小模块
把代码进行模块化才分的好处:
    1.提高代码的复用性
    2.提高代码的可维护性
    3.可以实现按需加载
模块化规范
    模块化规范就是对模块化进行才分组合时需要遵守的规则
        使用什么样的语法格式来引用模块?
        在模块中使用什么样的语法格式向外暴露成员
    模块化规范的好处:
        大家遵守同样的模块化规范写代码,降低了沟通的成本,极大的方便了各模块之间的相互调用。
1.node.js中模块的分类
    node.js根据模块的来源不同 ,将模块计划分为3大类,分别是:
        1.内置模块(内置模块由nodejs官方提供的,例如:fs,path,http等)
        2.自定义模块(用户创建的.js文件,都是自定义模块)
        3.第三方模块(由第三方开发出来的模块,使用前需要下载)
2.加载模块
    使用require()方法,可以加载内置模块,用户自定义模块,第三方模块进行使用
    //1.加载内置模块
    const fs = require('fs');
    //2.加载自定义模块
    const custom = require('./custom.js');
    //3.加载第三方模块
    const moment = require('moment');
*注意:
    使用require方法加载其他模块时候,会执行被加载模块中的代码
    在加载require加载用户模块期间,可以省略后缀名
3.node.js中模块作用域
    什么式模块作用域
        和作用域函数类似,在自定义模块中定义的变量,方法等成员,只能在当前模块内被访问,这种模块级别的访问限制叫做模块作用域
    模块作用域的好处:
        防止全局变量污染问题
4.向外共享模块作用域中的成员
    1.module对象
        在每个.js自定义模块中都有一个module对象,他里面存储了和当前模块有关的信息
        打印module
            Module {
                id: '.',
                path: 'E:\\student\\NODEJS\\04.模块化',
                exports: {},
                parent: null,
                filename: 'E:\\student\\NODEJS\\04.模块化\\05.演示module对象.js',
                loaded: false,
                children: [],
                paths: [
                    'E:\\student\\NODEJS\\04.模块化\\node_modules',
                    'E:\\student\\NODEJS\\node_modules',
                    'E:\\student\\node_modules',
                    'E:\\node_modules'
                ]
            }
    2.module.exports对象   默认为空对象
        在自定义模块中,可以使用module.exports对象,将模块内的成员共享出去,供外界使用
        外界用require()方法导入自定义模块时,得到的就是module.exports所指的对象
    3.共享成员的注意点
        使用require()方法导入模块时,导入的结果  永远以module.exports指向的对象为准
    4.exports对象
        由于module.expres写起来比较复杂,为了简化代码,提供了exports对象。默认情况下
        exports和module.exports指向同一个对象,最终结果还是以module.exports指向的为准
        *注意:为了防止混乱,建议不要再同一个模块中同时使用exports和module.expres
5.node.js中的模块化规范
    node.js遵循了conmmonJS模块化规范,commonJS规定了模块的特性和各模块之间如何相互依赖
    commonJS
         1.每个模块内部,module变量代表当前模块
         2.module变量是一个对象,它的exports属性(即module.exports)是对外接口
         3.加载模块时候,其实加载该模块的module.exports属性。require()方法用于加载模块

npm与包

1.什么是包?
    nodejs中的第三方模块又叫做包
2.包的来源
    不同于node.js中内置模块和自定义模块,包是由第三方个人或团队开发出来的,免费供所有人使用
    注意:Node.js中的包都是免费且开源的,不需要付费即可免费下载使用
3.为什么需要包?
    由于node.js的内置模块仅仅提供了一些底层API,导致在基于模块进行项目开发时候,效率很低
    包是基于内置模块封装出来的,提供了高级,更方便的API,极大的提高了开发效率
4.从哪里下载包
    从https://www.npmjs.com/  网站搜索自己所需要的包
5.如何下载包
    npm 包管理器   安装node的时候自动安装了
6.在项目中装包的命令
    npm install 包的完整名称
    简写  npm i 包的完整名称
7.初次装包,文件里面多了哪些文件?
    初次装包完成后,在项目文件夹下多了一个叫做 node_modules的文件夹和package-lock.json的配置文件
    node modules文件夹用来存放所有已安装到项目中的包。require()导入第三方包时,就是从这个目录中查找并加载包。package-lockjson配置文件用来记录node_modules目录下的每一个包的下载信息,例如包的名字、版本号、下载地址等。
*注意:程序员不要手动修改node modules或package-lockjson文件中的任何代码,npm包管理工具会自动维护它们。
8.安装指定版本的包
    默认情况下,使用npm install命令安装包的时候,会自动安装最新版本的包。如果需要安装指定版本的包,可以在包名之后,通过@符号指定具体的版本,例如:npm i [email protected]

9.包的语义化版本规范
    包的版本号是以“点分十进制”形式进行定义的,总共有三位数字,例如2.24.0其中每一位数字所代表的的含义如下:
    第1位数字:大版本
    第2位数字:功能版本第3位数字:Bug修复版本
    版本号提升的规则:只要前面的版本号增长了,则后面的版本号归零。
10.3.快速创建package.json
    npm包管理工具提供了一个快捷命令,可以在执行命令时所处的目录中,快速创建package.json这个包管理配置文件:
        //作用:在执行命令所处的目录中,快速新建package.json文件
         npm init -y
    注意:
    上述命令只能在英文的目录下成功运行!所以,项目文件夹的名称一定要使用英文命名.不要使用中文,不能出现空格。
    运行npm install 命令安装包的时候,npm包管理工具会自动把包的名称和版本号,记录到package.json中。
11.一次性安装所有的包
    可以运行npm install命令(或npm i)一次性安装所有的依赖包:
    1//执行npm install命令时,npm包管理工具会先读取package.json 中的 dependencies 节点
    2//读取到记录的所有依赖包名称和版本号之后,npm包管理工具会把这些包一次性下载到项目中
    3 npm install
12.卸载包
    npm uninstall 包名
13.devDependencies节点
    如果某些包只在项目开发阶段会用到,在项目上线之后不会用到,则建议把这些包记录到devDependencies节点中。
    与之对应的,如果某些包在开发和项目上线之后都需要用到,则建议把这些包记录到dependencies 节点中。
    您可以使用如下的命令,将包记录到devDependencies节点中:
        1//安装指定的包,并记录到devDependencies节点中
        2 npm i 包名-D
        3//注意:上述命令是简写形式,等价于下面完整的写法:4 npm install 包名 --save-dev
14.解决下包速度慢的问题
    1.为什么下包速度慢
        在使用npm下包的时候,默认从国外的https://registry.npmjs.org/服务器进行下载,此时,网络数据的传输需要经过漫长的海底光缆,因此下包速度会很慢。
    2.切换npm的下包镜像源
        下包的镜像源,指的就是下包的服务器地址。
        1#查看当前的下包镜像源
        2 npm config get registry
        3# 将下包的镜像源切换为淘宝镜像源
        4 npm config set registry=https://registry.npm.taobao.org/
        5 #检查镜像源是否下载成功
        6 npm config get registry
    3.nrm
        为了更方便的切换下包的镜像源,我们可以安装nrm这个小工具,利用nrm 提供的终端命令,可以快速查看和切换下包的镜像源。
            1 #通过npm包管理器,将 nrm安装为全局可用的工具2 npm i nrm -g
            3#查看所有可用的镜像源
            4 nrm ls
            5#将下包的镜像源切换为taobao镜像6 nrm use taobao

express

Express简介
    1.什么是Express
        官方给出的概念:Express是基于Node.js 平台,快速、开放、极简的Web开发框架。
        通俗的理解:Express的作用和Node.js内置的http模块类似,是专门用来创建Web服务器的。Express的本质:就是一个npm上的第三方包,提供了快速创建Web服务器的便捷方法。
        Express的中文官网: http://www.expressis.com.cn/
Express 能做什么
    对于前端程序员来说,最常见的两种服务器,分别是:
    Web 网站服务器:专门对外提供Web 网页资源的服务器。API接口服务器:专门对外提供API接口的服务器。
    使用Express,我们可以方便、快速的创建Web 网站的服务器或 API接口的服务器。
1.安装
    在项目所处的目录中,运行如下的终端命令,即可将express安装到项目中使用:
    1 npm i [email protected]
2.创建基本的Web服务器
    1 // 1.导入express
    2 const express = require( ' express ' )
    3 // 2.创建web服务器
    4 const app = express()
    6 // 3.调用app.listen(端口号,启动成功后的回调函数),启动服务器
    7 app.listen(80,() =>{
        console.log( ' express server running at http: //127.0.0.1')})
3.监听get请求
    通过app.get( )方法,可以监听客户端的GET请求,语法如下:
        1.参数1∶客户端请求的URL 地址
        2.参数2:请求对应的处理函数
        3.req:请求对象((包含了与请求相关的属性与方法)
        4.res:响应对象(包含了与响应相关的属性与方法)
        5 app.get('请求URL',function(req,res) {/*处理函数*/ })
4.监听post请求
    通过app.post( )方法,可以监听客户端的GET请求,语法如下:
        1.参数1∶客户端请求的URL 地址
        2.参数2:请求对应的处理函数
        3.req:请求对象((包含了与请求相关的属性与方法)
        4.res:响应对象(包含了与响应相关的属性与方法)
        5 app.post('请求URL',function(req,res) {/*处理函数*/ })
5.把内容响应给客户端
    通过res.send)方法,可以把处理好的内容,发送给客户端:
         app.get( ' /user ' , (req, res) =>{
            向客户端发送JSON 对象
           res.send({ name: 'zs ',age: 20,gender: '男'})
         })

         app.post( " /user'. (req,res) =>{
           向客户端发送文本内容
           res.send( '请求成功')
        })
6.获取URL中携带的查询参数
    通过req.query对象,可以访问到客户端通过查询字符串的形式,发送到服务器的参数:
    app.get( '/', (req,res) => {
        //req.query 默认是一个空对象
        //客户端使用?name=zs&age=20这种查询字符串形式,发送到服务器的参数,
        //可以通过req.query对象访问到,例如:
        // req.query.name  req.query.age
    console.log(req. query)
7.获取URL中的动态参数
    通过req.params对象,可以访问到URL中,通过:匹配到的动态参数:
        //URL地址中,可以通过:参数名的形式,匹配动态参数值
        app.get( '/user/:id',(req,res) => {
            //req.params 默认是一个空对象
            //里面存放着通过:动态匹配到的参数值
            console.log(req. params)
        })
8.托管静态资源
    1.express.static()
        express提供了一个非常好用的函数,叫做express.static(),通过它,我们可以非常方便地创建一个静态资源服务器,例如,通过如下代码就可以将public目录下的图片、CSS文件JavaScript 文件对外开放访问了:
            app.use(express.static('public'))
        *如过不生效
            const path = require('path');
            app.use(express.static(path.join(__dirname, 'public')))
        挂载路径前缀
            如果希望在托管的静态资源访问路径之前,挂载路径前缀,则可以使用如下的方式:
                app.use( '/public' , express.static( 'public' ))
            现在,你就可以通过带有/public 前缀地址来访问public目录中的文件了:

express路由

1.Express中的路由
    在Express 中,路由指的是客户端的请求与服务器处理函数之间的映射关系。
    Express 中的路由分3部分组成,分别是请求的类型、请求的URL地址、处理函数,格式刻
        app.METHOD(PATH,HANDLER)
2.Express中的路由的例子
    匹配GET请求,且请求URL为/
        app.get( '/',function (req,res) {res.send( 'Hello world! ')})
    匹配POST请求,且请求URL为/
        app.post('/', function (req,res) {res.send( ' Got a PoST request ')})
3.路由的匹配过程
    1. 每当一个请求到达服务器之后,需要先经过路由的匹配,只有匹配成功之后,才会调用对应的处理函数。
    2. 在匹配时,会按照路由的顺序进行匹配,如果请求类型和请求的URL同时匹配成功,则 Express 会将这次请求,转交给对应的function 函数进行处理。
    3. 路由匹配的注意点:
        按照定义的先后顺序进行匹配
        请求类型和请求的URL同时匹配成功,才会调用对应的处理函数  
4.路由的使用  
    在Express 中使用路由最简单的方式,就是把路由挂载到app 上,示例代码如下:
        const express = require('express')
        const app = express()

        //挂载路由
        app.get('/',(req, res) =>{
            res.send("hello word")
        })

        app.post('/',(req, res) =>{
            res.send("post request")
        })


        app.listen(3000,() => {
            console.log('http://localhost:3000');
        })
5.模块化路由
    为了方便对路由进行模块化的管理,Express 不建议将路由直接挂载到app上,而是推荐将路由抽离为单独的模块。
    将路由抽离为单独模块的步骤如下:创建路由模块对应的.js文件
        1.创建路由模块对应的.js文件
        2.调用express.Router()函数创建路由对象
        3.向路由对象上挂载具体的路由
        4.使用module.exports向外共享路由对象
            //这是路由模块

            // 1.导入express
            const express = require('express')
            //2.创建路由对象
            const router = express.Router()

            //3. 挂载具体的路由

            router.get('/user/list',(req, res) => {
                res.send("get user list")
            })

            router.post('/user/add',(req, res) => {
                res.send('post user add')
            })

            //4.向外导出路由
            module.exports = router
        5.使用app.use()函数注册路由模块
            const express = require('express');
            const app = express();
            //1.导入路由模块
            const routes = require('./05.router');
            //2.注册路由模块   app.use这个函数的作用,就是用来注册全局的中间件
            app.use(routes)
            app.listen(3000,()=>{
                console.log('listening on 3000')
            });
    给路由添加统一的访问前缀
        app.use('/api',routes)
6.EXpress中间件
    1.什么是中间件
        中间件:特指业务流程的中间处理环节
    2.express中间件的调度流程
        当请求到达Express的服务之后,可以连续调用多个中间件,从而对这次请求进行预处理
    3.Express中间件的格式
        Express的中间件,本质上就是一个function处理函数,Express中间件的格式如下:
            app.get( '/',(req,res,next) => {
                next()
            })
            app.listen(3000);
        *注意:中间件函数的形参列表中,必须包含next参数,而路由器处理函数只包含req和res
    4.next()形参的作用
        next函数是实现多个中间件连续调用的关键,它表示把流转关系转交给下一个中间件或路由
7.Express中间件初体验
    1.定义中间件
        可以通过如下方式定义,定义一个最简单的中间件
            //常量 mw所指向的,就是一个中间件函数
            const mw = function(req,res,next){
                console.log("这是一个最简单的中间件")
                // 注意:在当前中间件的业务处理完毕后,必须调用next()函数
                // 表示把流转关系交给下一个中间件或路由
                next()
            }
    2.全局生效的中间件
        客户端发起任何请求,到达服务器之后,都会触发的中间件叫做全局生效中间件
        通过调用app.use()中间件函数,即可定义一个全局中间件,示例代码如下:
            const express = require('express')
            const app = express()
            const port = 3000

            // 定义一个最简单的中间件函数
            const mw = function (req, res,next) {
                console.log("这是最简单的中间件函数");
                //把流转关系,转交给下一个中间件
                next()
            }

            // 将mw注册成为全局的中间件
            app.use(mw)


            app.get('/', (req, res) =>{
                console.log("调用了/这个路由");
                res.send('Hello World!')
            })

            app.get('/user', (req, res) => {
                console.log(`调用了${req.url}`);
                res.send('my is user')
            })


            app.listen(port, () => console.log(`Example app listening on port ${port}!`))
    3.定义全局中间件的简化形式
        app.use((req, res, next) => {
            console.log("这是简化的全局中间件");
            next();
        })
    4.中间件的作用
        多个中间件之间 共享一份req和res基于这个特性,我们可以在上有的中间件中,统一为req或res对象添加自定义的属性和方法,供下游的中间件或路由使用

    5.定义多个全局中间件
        可以使用app.use()连续定义多个全局中间件。客户端请求到达服务器之后,会按照中间件的先后顺序,依次进行调用,实例代码:
            
            // 定义第一个全局中间件
            app.use((req, res, next) =>{
                console.log("调用了第一个全局中间件");
                next()
            })

            //定义第二个全局中间件
            app.use((req, res, next) =>{
                console.log("调用了第二个全局中间件");
                next()
            })
            //定义一个路由
            app.get('/', (req, res, next) =>{
                res.send(req.url)
            })
    6.局部生效的中间件
        不使用app.use()定义的中间件,叫做局部生效的中间件 示例代码如下
            const mw = (req, res, next) => {
                console.log("调用局部生效中间件");
                next()
            }
            app.get('/',mw, (req, res) => {
                res.send("/")

            })
            app.get('/user',(req, res) => {
                res.send("/user")
            })
    7.定义多个局部中间件
        可以在路由中通过如下两种等价的方式,使用多个局部中间件
            //创建局部生效的中间件
            const mw1 = (req, res, next) =>{
                console.log('调用了第一个局部生效的中间件');
                next()
            }
            const mw2 = (req, res, next) =>{
                console.log("调用了第二个局部生效的中间件");
                next()
            }

            // 以下两种写法均为完全等价,可以使用任意一种
            app.get('/', mw1,mw2,(req, res) =>{
                res.send('/')
            })
            app.get('/user',[mw1,mw2],(req, res) =>{
                res.send('/user')
            })
    8.了解中间件的5个注意事项
        1.一定要在路由注册之前注册中间件
        2.客户端发送过来的请求,可以连续调用多个中间件进行处理
        3.执行完中间件的业务代码之后,不要忘记调用next()函数
        4.为了防止逻辑混乱,盗用next()函数后不要频再写额外的代码
        5.连续调用中间键的时候多个中间件之间,共享req和res对象
8.中间件的分类
    1.应用级别的中间件  app
        通过app.use()或app.get()或app.post()绑定到app实例上的中间件,叫做应用级别的中间件
    2.路由级别的中间件  router
        绑定到express.Routerl)实例上的中间件,叫做路由级别的中间件。它的用法和应用级别中间件没有任何区别。只不过,应用级别中间件是绑定到 app实例上,路由级别中间件绑定到router实例上 
    3.错误级别的中间件
        错误级别中间件的作用:专门用来捕获整个项目中发生的异常错误,从而防止项目异常崩溃的问题。
        格式:错误级别中间件的function处理函数中,必须有4个形参,形参顺序从前到后,分别是(err, req, res, next)。
            app.get( ' /', function (req,res) {   // 路由
                //抛出一个自定义的错误
                throw new Error( '服务器内部发生了错误!')
                res.send( 'Home Page.')
            })
            app.use( function (err,req,res,next) { 
                //错误级别的中间件
                console.log( '发生了错误:' +err.message)
                // 在服务器打印错误消息
                res.send( 'Error! ' +err.message) // 2.2向客户端响应错误相关的内容
            })
        ** 错误级别中间件,必须注册再所有路由之后
    4.Express内置的中间件
        自Express 4.16.0版本开始,Express 内置了3个常用的中间件,极大的提高了Express 项目的开发效率和体验
        1.express.static快速托管静态资源的内置中间件,例如:HTML文件、图片、CSS样式等(无兼容性)
        2.express.json解析JSON格式的请求体数据(有兼容性,仅在4.16.0+版本中可用)
        3.express.urlencoded解析URL-encoded格式的请求体数据(有兼容性,仅在4.16.0+版本中可用)

        //配置解析application/json格式数据的内置中间件
            app.use(express.json())
        //配置解析 application/x-ww-form-urlencoded格式数据的内置中间件
            app.use(express.urlencoded( { extended: false }))


        app.use(express.json())
        app.use(express.urlencoded({extended:false}))
        app.post('/book', function (req, res) {
            //在客户端可以使用req.body这个属性,来接收客户端发送过来的请求数据
            // 默认情况下,如果不配置解析表单的中间件,则req.body 默认等于 undefined
            console.log(req.body);
            res.send('POST request to the homepage')
        })
        app.get('/', (req, res) => {
            res.send('GET request to the homepage')
        })
        app.post('/', function (req, res) {
            // 在服务器端,可以通过req.body 来获取JSON格式和url-encoded格式的数据
            console.log(req.body);
            res.send('POST request to the homepage')
        })
    5.第三方的中间件
        非Express 官方内置的,而是由第三方开发出来的中间件,叫做第三方中间件。在项目中,大家可以按需下载并配置第三方中间件,从而提高项目的开发效率。
        例如:在[email protected]之前的版本中,经常使用body-parser这个第三方中间件,来解析请求体数据。使用步骤如下:
        运行npm install body-parser安装中间件使用require 导入中间件
    6.解决跨越问题中间件
        cors
        cors 是 Express的一个第三方中间件。通过安装和配置cors 中间件,可以很方便地解决跨域问题。
            1.运行 npm install cors 安装中间件
            2.使用const cors = require('cors') 导入中间件
            3.在路由之前调用app.use(cors) 配置中间件
        什么是cors
            CORS (Cross-Origin Resource Sharing,跨域资源共享)由一系列HTTP响应头组成,这些HTTP响应头决定浏览器是否阻止前端JS代码跨域获取资源。
            浏览器的同源安全策略默认会阻止网页“跨域”获取资源。但如果接口服务器配置了CORS相关的HTTP响应头,就可以解除浏览器端的跨域访问限制。
        CORS 的注意事项
            CORS主要在服务器端进行配置。客户端浏览器无须做任何额外的配置,即可请求开启了CORS的接口。CORS在浏览器中有兼容性。只有支持XMLHttpRequest Level2的浏览器,才能正常访问开启了CORS的服务端接口(例如:IE10+、Chrome4+、FireFox3.5+)。

        CORS 响应头部-Access-Control-Allow-Origin
            响应头部中可以携带一个 Access-Control-Allow-Origin字段,其语法如下:
                Access-Control-Allow-origin: <origin>|*
            其中,origin 参数的值指定了允许访问该资源的外域URL。
            例如,下面的字段值将只允许来自http://itcast.cn的请求:
                res.setHeader ( 'Access-Control-Allow-Origin', 'http:/litcast.cn')
            如果指定了Access-Control-Allow-Origin字段的值为通配符*,表示允许来自任何域的请求,示例代码如下:
                res.setHeader( 'Access-Control-Allow-Origin', '*')
        CORS 响应头部 Access-Control-Allow-Headers
            默认情况下,CORS仅支持客户端向服务器发送如下的9个请求头:
                Accept、Accept-Language、Content-Language、DPR、Downlink、Save-Data、Viewport-Width、Width ,Content-Type (值仅限于text/plain、multipart/form-data、application/x-www-form-urlencoded三者之一)
            如果客户端向服务器发送了额外的请求头信息,则需要在服务器端,通过Access-Control-Allow-Headers 对额外的请求头进行声明,否则这次请求会失败!
            允许客户端额外向服务器发送Content-Type请求头和X-Custom-Header 请求头
            注意:多个请求头之间使用英文的逗号进行分割
                res.setHeader(' Access-Control-Allow-Headers','Content-Type,X-Custom-Header ' )
        CORS响应头部– Access-Control-Allow-Methods
            默认情况下,CORS仅支持客户端发起GET、POST、HEAD请求。
            如果客户端希望通过PUT、DELETE等方式请求服务器的资源,则需要在服务器端,通过Access-Control-Alow-Methods来指明实际请求所允许使用的HTTP方法。
                只允许POST、GET、DELETE、HEAD请求方法
                    res.setHeader( 'Access-Control-Allow-Methods ','POST,GET,DELETE,HEAD')
                允许所有的 HTTP请求方法
                    res.setHeader( ' Access-Control-Allow-Methods ', '*')
        简单请求:
            同时满足一下两个大条件的请求,就属于简单请求
                1.请求方式GET,POST,HEAD 三者之一
                2.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)
        预检请求
            只要符合以下任何一个条件的请求,都需要进行预检请求:
            1.请求方式为GET、POST、HEAD之外的请求Method类型
            2.请求头中包含自定义头部字段
            3.向服务器发送了application/json格式的数据

            在浏览器与服务器正式通信之前,浏览器会先发送OPTION请求进行预检,以获知服务器是否允许该实际请求,所以这-次的OPTION请求称为“预检请求”。服务器成功响应预检请求后,才会发送真正的请求,并且携带真实数据。
        简单请求和预检请求的区别
            简单请求的特点:客户端与服务器之间只会发生一次请求。
            预检请求的特点:客户端与服务器之间会发生两次请求,OPTION预检请求成功之后,才会发起真正的请求。

express+mysql

安装mysql模块
    npm install mysql
配置mysql模块
    // 1.导入mysql模块
        const mysql = require( ' mysql' )
    //2.建立与 MySQL数据库的连接
        const db = mysql.createPool({
            host: '127.0.0.1', //数据库的IP地址
            user : 'root',//登录数据库的账号
            password: 'admin123',//登录数据库的密码
            database: 'my_db_01' //指定要操作哪个数据库
        })
测试mysql模块是否正常工作
    // 测试mysql 是否正常工作
        db.query('SELECT 1', (err, result) => {
            if (err) {
                return console.log("错误");
            }
            //只要能打印出{'1':1}的结果,就证明数据库连接成功
            console.log(result);
        })
查询数据
    // 查询list表中的所有数据
        const sql = "SELECT * FROM list";

        db.query(sql, (err, result) => {
            // 查询失败
            if (err) { return console.log("查询数据失败"+err.message); }
            // 查询失败   得到一个数组
            console.log(result);
        })
插入数据
    // 向user表中插入一条数据
        const user = {username:'root',password:'123456'}
        const userSql = 'INSERT INTO user (username,password) VALUES (?, ?)'

        db.query(userSql,[user.username,user.password],(err, result)=>{
            // 插入失败
            if (err) { return console.log(err.message); }
            // 插入成功
            if (result.affectedRows === 1){ console.log("插入成功")}
        })
    简便的方式:
        const user = {username:'root',password:'123456'}
        const userSql = 'INSERT INTO user set ?'

        db.query(userSql,user,(err, result)=>{
            // 插入失败
            if (err) { return console.log(err.message); }
            // 插入成功
            if (result.affectedRows === 1){ console.log("插入成功")}
        })
更新数据
    // 1.要更新的数据对象
        const user = {id:1,username:'lisi',password:'789456'}

        const upSql = 'UPDATE user SET username=?,password = ? WHERE id=?'

        db.query(upSql,[user.username,user.password,user.id],(err, result)=>{
            if (err) { return console.log(err.message); }
            if (result.affectedRows === 1){ console.log("更新数据成功")}
        })
删除数据:
    
    // 删除user表中的一条数据
        const sql = 'DELETE FROM user WHERE id= ?'
        db .query(sql,[1], (err, result)=>{
            if (err){
                return console.log(err.message);
            }
            if (result.affectedRows === 1){ console.log("删除成功")}
        })
标记删除
    使用DELETE语句,会把真正的把数据从表中删除掉。为了保险起见,推荐使用标记删除的形式,来模拟删除的动作。
    所谓的标记删除,就是在表中设置类似于status这样的状态字段,来标记当前这条数据是否被删除。
    当用户执行了删除的动作时,我们并没有执行DELETE语句把数据删除掉,而是执行了UPDATE语句,将这条数据对应的status字段标记为删除即可。

前后端身份认证

web开发模式
    目前主流的Web开发模式有两种,分别是:
        1基于服务端渲染的传统 Web开发模式
        2基于前后端分离的新型Web开发模式
    1.服务端渲染的Web开发模式
        服务端渲染的概念:服务器发送给客户端的HTML页面,是在服务器通过字符串的拼接,动态生成的。因此,客户端不需要使用Ajax这样的技术额外请求页面的数据.

    2.服务端渲染的优缺点
        优点:
            前端耗时少。因为服务器端负责动态生成HTML内容,浏览器只需要直接渲染页面即可。尤其是移动端,更省电。
            有利于SEO。因为服务器端响应的是完整的HTML页面内容,所以爬虫更容易爬取获得信息,更有利于SEO。
        缺点:
            占用服务器端资源。即服务器端完成HTML页面内容的拼接,如果请求较多,会对服务器造成一定的访问压力。
            不利于前后端分离,开发效率低。使用服务器端渲染,则无法进行分工合作,尤其对于前端复杂度高的项目,不利于项目高效开发。
    3.前后端分离的 Web开发模式
        前后端分离的概念:前后端分离的开发模式,依赖于Ajax技术的广泛应用。简而言之,前后端分离的Web开发模式,就是后端只负责提供API接口,前端使用Ajax调用接口的开发模式。
    4.前后端分离的优缺点
        优点:
            开发体验好。前端专注于UI页面的开发,后端专注于api的开发,且前端有更多的选择性。
            用户体验好。Ajax技术的广泛应用,极大的提高了用户的体验,可以轻松实现页面的局部刷新。
            减轻了服务器端的渲染压力。因为页面最终是在每个用户的浏览器中生成的。
        缺点:
            不利于 SEO。因为完整的HTML页面需要在客户端动态拼接完成,所以爬虫对无法爬取页面的有效信息。
            (解决方案:利用Vue、React等前端框架的SSR (server side render)技术能够很好的解决SEO问题! )
1.什么是身份认证?
    身份认证(Authentication)又称“身份验证”、“鉴权”,是指通过一定的手段,完成对用户身份的确认。
2.为什么需要身份认证
    身份认证的目的,是为了确认当前所声称为某种身份的用户,确实是所声称的用户。
3.不同开发模式下的身份认证
    对于服务端渲染和前后端分离这两种开发模式来说,分别有着不同的身份认证方案:
        1.服务端渲染推荐使用Session 认证机制
        2.前后端分离推荐使用JWT认证机制

Session认证机制
    1.HTTP协议的无状态性
        了解HTTP协议的无状态性是进一步学习Session认证机制的必要前提。
        HTTP协议的无状态性,指的是客户端的每次HTTP请求都是独立的,连续多个请求之间没有直接的关系,服务器不会主动保留每次HTTP请求的状态。
    2.如何突破HTTP无状态的限制
        通过Cookie
    3.什么是Cookie
        Cookie是存储在用户浏览器中的一段不超过4KB的字符串。它由一个名称(Name)、一个值(Value)和其它几个用于控制Cookie有效期、安全性、使用范围的可选属性组成。
        不同域名下的Cookie 各自独立,每当客户端发起请求时,会自动把当前域名下所有未过期的Cookie一同发送到服务器。
        Cookie的几大特性:
            1.自动发送
            2.域名独立
            3.过期限制
            4.4KB限制
    4.Cookie在身份认证中的作用
        客户端第一次请求服务器的时候,服务器通过响应头的形式,向客户端发送一个身份认证的Cookie,客户端会自动将Cookie保存在浏览器中。
        随后,当客户端浏览器每次请求服务器的时候,浏览器会自动将身份认证相关的Cookie,通过请求头的形式发送给服务器,服务器即可验明客户端的身份。    
    5.Cookie 不具有安全性
        由于Cookie是存储在浏览器中的,而且浏览器也提供了读写Cookie的API,因此Cookie很容易被伪造,不具有安全性。因此不建议服务器将重要的隐私数据,通过Cookie的形式发送给浏览器。
        注意:千万不要使用Cookie存储重要且隐私的数据!比如用户的身份信息、密码等。
    6.提高身份认证的安全性
        为了防止客户伪造会员卡,收银员在拿到客户出示的会员卡之后,可以在收银机上进行刷卡认证。只有收银机确认存在的会员卡,才能被正常使用。
        这种“会员卡+刷卡认证”的设计理念,就是Session认证机制的精髓。
在express中使用session认证
    1.安装express-session中间件
        npm i express-session
    2.配置session中间件
        const session = require('express-session')
        app.use(
            session({
                secret: 'xinghuo',
                resave: false,
                saveUninitialized: true,
            })
        )
    3.向session中存数据
        当express-session中间件配置成功后,即可通过 req.session来访问和使用session对象,从而存储用户的关键信息:
            app.post('/api/login',(req, res) => {
                if (req.body.username != 'admin' || req.body.password != '123456'){
                    return res.send(
                        {
                            statis:1,
                            msg: '登录失败'
                        }
                    )
                }
                
                req.session.user = req.body  //存储用户信息
                req.session.idlogin = true   //存储登录信息

            })
    4.在session中取数据
        直接在req.session对象上获取之前存储的数据
            app.post('/', function (req, res) {
                if(!req.session.islogin){
                    return res.send({
                        
                        status:1,
                        msg: 'fail'
                    })
                }
                res.send({
                    status:0,
                    msg: 'success',
                    username : req.session.user.username
                })
            })
    5.清空session
        调用req.session.destroy()函数,即可清空服务器保存的session 信息
    6.了解 Session 认证的局限性
            Session认证机制需要配合Cookie才能实现。由于Cookie 默认不支持跨域访问,所以当涉及到前端跨域请求后端接口的时候,需要做很多额外的配置,才能实现跨域Session认证。 
        注意:
            当前端请求后端接口不存在跨域问题的时候,推荐使用Session身份认证机制。
            当前端需要跨域请求后端接口的时候,不推荐使用Session身份认证机制,推荐使用JWT认证机制。

JWT认证机制

1.JWT的组成部分
    JWT通常由三部分组成,分别是 Header(头部)、Payload(有效荷载)、Signature(签名)。
    三者之间使用英文的“.”分隔,格式如下:
        Header.Payload.Signature
2.JWT的三个部分各自代表的含义
    JWT的三个组成部分,从前到后分别是 Header、Payload、Signature。
        其中:
            Payload部分才是真正的用户信息,它是用户信息经过加密之后生成的字符串。
            Header和Signature是安全性相关的部分,只是为了保证Token的安全性。
3.JWT的使用方式
    客户端收到服务器返回的WT之后,通常会将它储存在localStorage或sessionStorage 中。
    此后,客户端每次与服务器通信,都要带上这个WT的字符串,从而进行身份认证。推荐的做法是把JWT放在HTTP请求头的Authorization字段中,格式如下:
        Authorization: Bearer <token>
4.expresss中使用JWT
    1.安装相关的包
            npm install jsonwebtoken express-jwt
        jsonwebtoken用于生成JWT字符串
        express-jwt用于将JWT字符串解析还原成JSON 对象
    2.导入JWT相关的包
        使用require()函数,分别导入JWT相关的两个包:
            // 安装并导入JWT相关的包  分别是 jsionwebtoken  和 express-jwt
            const jwt = require('express-jwt')
            const jsont = require('jsonwebtoken')
    3.定义secret密钥
        为了保证JWT字符串的安全性,防止JWT字符串在网络传输过程中被别人破解,我们需要专门定义一个用于加密和解密的secret密钥:
        当生成JWT字符串的时候,需要使用secret密钥对用户的信息进行加密,最终得到加密好的WT字符串当把JWT字符串解析还原成JSON对象的时候,需要使用secret密钥进行解密
    4.在登录成功后生成JWT字符串
        调用jsonwebtoken包提供sign()方法,将用户的信息加密成JWT字符串,响应给客户端
            // 定义一个secret密钥,建议将密钥命名为 secretKey
            const secretKey = 'itbnsadklsdbabsdalhnlk ^_^'

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

            res.send('POST request to the homepage')
            })
    5.将JWT字符串还原为JSON对象
        客户端每次东访问那些有权限接口的时候,都需要主动通过请求头中的Authorization字段,将Token字符串发送到服务器进行身份认证。
        此时,服务器可以通过express-jwt这个中间件,自动将客户端发送过来的Token解析还原成JSON对象:
            // 安装并导入JWT相关的包  分别是 jsionwebtoken  和 express-jwt

            const expressjwt = require('express-jwt')
            const jsont = require('jsonwebtoken')


            // 定义一个secret密钥,建议将密钥命名为 secretKey
            const secretKey = 'itbnsadklsdbabsdalhnlk ^_^'


            // 初测将JWT字符串解析还原成JSON对象的中间件
            app.use(expressjwt({secret: secretKey,algorithms:['HS256']}).unless({path:[/^\/api\//]}))
    6.捕获解析JWT失败后产生的错误
        当使用express-jwt解析Token字符串时,如果客户端发送过来的Token字符串过期或不合法,会产生一个解析失败的错误,影响项目的正常运行。我们可以通过 Express的错误中间件,捕获这个错误并进行相关的处理,示例代码如下:
            app.use((err, req, res,next) => {
                if(err.name === 'UnauthorizedError'){
                    return res.send({
                        status:401,
                        msg:'无效的token'
                    })
                }
                res.send({
                    status:500,
                    message:'未知的错误'
                })
            })

猜你喜欢

转载自blog.csdn.net/weixin_45822938/article/details/123257958