web学习-Node.js入门学习
- 1.回顾与思考
- 2. 初识Node.js
- 2.1 Node.js的简介
- 2.2Node.js的环境安装
- 2.3. fs文件系统模块
- 2.3.1 fs.readFile()
- 2.3.2 fs.writeFile()
- 2.3.3 练习-整理考试成绩
- 2.3.4 fs模块-路径动态拼接的问题
- 2.4 path路径模块
- 2.5 http模块
- 2.5.1 服务器相关的概念
- 2.5.2 创建最基本的web服务器
- 2.5.3 根据不同的url响应不同的html内容
- 3. 模块化
- 3.1 模块化的基本概念
- 3.2 Node.js中模块的分类
- 3.2.1 加载模块
- 3.2.2 模块作用域
- 3.2.3 向外共享模块作用域中的成员
- 3.3 npm与包
- 3.3.1 npm和包的概述
- 3.3.2 npm初体验
- 3.3.3 包管理配置文件
- 3.3.4 包的分类
- 3.3.5 开发属于自己的包
- 3.3.6 发布自己开发的包
- 3.4 模块的加载机制
- 4.Express
- 4.1 Express的基本使用
- 4.2 express路由
- 4.2.1 路由的介绍
- 4.2.2 路由的使用
- 4.3 express中间件
- 4.3.1 中间件的概念
- 4.3.2 Express 中间件的初体验
- 4.3.3 中间件的分类
- 4.3.4 自定义中间件
- 4.4使用Express写接口
- 4.4.1 接口实现
- 4.4.2 CORS跨域资源共享
- 4.4.3 JSONP接口
- 5. MySQL数据库
- 6 Web开发模式
- 6.1 身份认证
- 6.2 session认证机制
- 6.3 JWT认证机制
1.回顾与思考
浏览器中的JavaScript的组成部分:
为什么JavaScript可以在浏览器中执行:
为什么JavaScript可以操作DOM和BOM:
浏览器中的JavaScript运行环境(运行环境指的是代码正常运行所需的必要环境):
2. 初识Node.js
2.1 Node.js的简介
Node.js
是一个基于Chrome V8
引擎JavaScript运行环境
,Node.js官网
Node.js中的JavaScript运行环境:
- Node.js可以做什么:Node.js作为一个JavaScript的运行环境,仅仅提供了基础的功能和API,然而,基于Node.js提供的这些基础功能,很多强大的工具和框架如雨后春笋,层出不穷,所以学习Node.js,可以让前端程序员胜任更多的工作和岗位:
- 基于Express框架 ,可以快速构建Web应用
- 基于Electron框架,可以构建跨平台的桌面应用
- 基于restify框架,可以快速构建API接口项目
- 读写和操作数据库、创建实用的命令行工具辅助前端开发、…
2.2Node.js的环境安装
如果希望通过Node.js来运行JavaScript代码,则必须在计算机上安装Node.js
环境才能运行。
安装包可以去Node.js官网下载
LTS
版本和Current
版本的区别:- LTS为长期稳定版,对于
追求稳定性
和企业级项目
来说,推荐安装LTS版本的Node.js - Current为新特性尝鲜版,对热衷于尝试新特性的用户来说,推荐安装Current版本的Node.js,但是Current版本中可能存在隐藏的Bug或安全性漏洞,因此不推荐企业级项目中使用Current版本的Node.js
- LTS为长期稳定版,对于
如何确定Node.js已经安装成功:终端输入命令node -v
可查看安装的版本,能看到安装的版本则表示安装成功, 否则没有安装成功
如何在Node.js环境中执行JavaScript的代码: 在终端进入到js文件目录下, 输入终端指令:node 要执行文件的路径
注意: 使用tab键, 能够快读不全路径
在终端中使用tab键快速不全路径, mac电脑不知道到支不支持
2.3. fs文件系统模块
fs模块
是Node.js官方提供的、用来提供操作文件的模块。它提供了一系列的方法和属性,用来满足用户对文件的操作需求。
fs.readFile()
方法,用来读取指定文件的内容fs.writeFile()
方法,用来向指定的文件中写入内容
但是如果想要在JavaScript中,使用fs
模块操作文件,则需要使用如下方式导入它:const fs = require(''fs)
2.3.1 fs.readFile()
fs.readFile(path, [options], callback)
参数解读:
- 参数1:
必选
参数,字符串,表示文件路径 - 参数2:可选参数,表示以什么
编码格式
来读取文件 - 参数3:
必选
参数,文件读取完成后,通过回调函数拿到读取信息的结果
上面方法是正确读取文件的内容, 下面是 读取文件错误的错误信息打印
2.3.2 fs.writeFile()
fs.writeFile(path,data, [options], callback)
- 参数解读:
- 参数1:
必选参数
,需要指定一个文件的路径 - 参数2:
必须参数
,表示写入的内容 - 参数3:
可选参数
,表示以什么格式写入文件内容,默认值是utf-8
- 参数4:
必选参数
,文件写入完成后的回调函数 - 注意:只能用来创建文件,不能用来创建路径;重复调用write方法,新写入的内容会覆盖旧的内容
- 参数1:
文件写入成功,则error对象是null, 否则是一个错误对象。
2.3.3 练习-整理考试成绩
2.3.4 fs模块-路径动态拼接的问题
在使用fs模块操作文件时,如果提供的操作路径../
或./
开头的相对路径
时,很容易出现路劲动态拼接错误的问题。
原因: 代码在运行的时候,会以执行node命令是所处的目录
,动态拼接出被操作文件的完整路径。
解决方案: 在使用fs
模块操作文件时,直接提供完整路径
,不要提供./
或../
开头的相对路径,从而防止路径动态拼接的问题。
__dirname
是一个固定路径,表示当前node文件执行文件所处在的目录
2.4 path路径模块
path模块
:是Node.js官方提供的, 用来处理路径
的模块, 它提供了一系列的方法和属性,用来满足用户对路径的处理需求path.join()
: 用来将多个路径片段拼接成一个完整的路径字符串
path.basename()
:用来从路径字符串中,将文件名解析出来注意:如果要在JavaScript
代码中,使用path模块处理路径,则需要使用如下的方式先导入它:const path = require('path')
path.join([…paths])
- paths路径片段的序列
- 返回值:
path.basename()的示例代码
path.extname(): 获取路径中的扩展名部分,语法格式如下
path。extname(path)
- path 必选参数,表示一个路径的字符串
返回: 返回得到的扩展名字符串
2.5 http模块
http模块是Node.js官方提供的, 用来创建Web服务器
的模块,通过http模块提供的http.createServer()
方法,就能方便的把一台普通的电脑,变成一台Web服务器,从而对外提供Web资源服务
当然要使用http模块创建Web服务器
,则需要先导入该模块:const http = require('http')
2.5.1 服务器相关的概念
服务器和普通电脑的区别在于,服务器上安装了web服务器软件, 例如:IIS、Apache等。通过安装这些服务软件就能把一台普通的电脑变成一台web服务器
在Node.js中,我们不需要使用这些第三方的web服务器。因为我们可以基于Node.js提供的http模块,通过几行简单的代码,就能轻松的手写一个服务器软件,从而对外听过web服务。
IP地址:表示互联网上每台计算机的唯一地址,因此IP地址具有唯一性。如果把个人电脑比作一台电话,那么IP地址就相当于电话号码,只有在知道对方IP地址的前提下,才能与对应的电脑之间进行数据通信。
IP地址的格式:通常使用点分十进制
表示(a.b.c.d
)的形式,其中a,b,c,d都是0~255之间的十进制整数。例如:用点份十进制的IP地址(192.168.1.1)
注意:
- 互联网中每台Web服务器,都有自己的IP地址,例如:大家可以在Windows的终端中运行
ping www.baidu.com
命令,即可查看到百度服务器的IP地址- 在开发期间,自己的电脑既是一台服务器,也是一个客户端,为了方便测试,可以在自己的浏览器中输入127.0.0.1这个IP地址,就能发自己的电脑当做一台服务器进行访问了
**端口号:**计算机的端口号,就好想是现实生活中的门牌号一样。通过门牌号,外卖小哥可以在整栋大楼众多的房间中,准确的把外卖送到你手中。
同样的道理,在一台电脑中,可以运行成百上千个web服务器。每个web服务器都对应一个唯一的端口号。客户端发送过来的网络请求,铜鼓端口号,可以被准备地交给对应的web服务器
进行处理
2.5.2 创建最基本的web服务器
- 导入http模块
- 创建web服务器实例
- 为服务器实例绑定
request
事件,监听客户端的请求 - 启动服务器
然后再浏览器输入http://127.0.0.1访问本机, 输出控制台会输出如下信息。
req
请求对象: 只要服务器接受到客户端的请求,就会调用server.on()
为服务器绑定的request事假处理函数
。如果想要在事件处理函数,访问与客户端相关的数据或属性
可以使用如下方式:
res
响应对象: 在服务器request事件处理函数中,如果访问与服务器相关的数据或属性,可以使用如下方式:
2.5.3 根据不同的url响应不同的html内容
- 核心实现步骤:
- 获取
请求的url地址
- 设置默认的响应内容为 404 Not found
- 判断用户请求的是否为
/
或/index.html
首页 - 判断用户请求是否为
/about.html
关于页面 - 设置
Content-Type响应头
,防止中文乱码 - 使用
res.end()
把内容响应给客户端
- 获取
3. 模块化
3.1 模块化的基本概念
模块化:指解决一个复杂问题时,自顶向下逐层把系统划分成若干模块的过程
。对于整个系统来说,模块是可组合、分解合更换的单元。
编程领域中的模块化,就是遵守固定的规则,把一个大文件拆分成独立并互相依赖的多个小模块。
把代码进行模块化 拆分的好处:
- 提高了代码的
复用性
- 提高了代码的
可维护性
- 可以实现
技术加载
模块化规范
就是对代码进行模块化的拆分与组合时,需要遵守的那些规则。
例如:使用什么样的语法格式来引用模块
、在模块中使用什么样的语法格式向外暴露成员
模块化规范的好处
:大家都遵守同样的模块化规范写代码,降低了沟通成本,极大的方便了各个模块之间的相互调试。利人利己。
3.2 Node.js中模块的分类
Node.js中根据模块来源的不同,将模块化分为了3大类,分别是:
内置模块
:内置模块是有Node.js官方提供的, 例如fs、path、http等自定义模块
:用户创建的每个.js文件,都是自定义模块第三方模块
:由第三方开发出来的模块,并非官方提供的内置模块,也不是用户创建的自定义模块,使用前需要先下载
3.2.1 加载模块
使用强大的require()
方法,可以加载需要的内置模块、用户 自定义那模块、第三方模块进行使用:
注意:使用require()方法加载其他模块时,会执行被加载模块中的代码。在使用require()加载用户自定义模块期间,可以省略.js的后缀名
3.2.2 模块作用域
和函数作用域类似,在自定义模块中定义的变量、方法等成员,只能在当前模块内被访问
,这种模块级别的访问限制
,叫做模块作用域
模块作用域的好处: 防止了全局变量污染的问题
3.2.3 向外共享模块作用域中的成员
module对象
:在每个.js自定义模块中都有一个module对象,它里面存储了和当前模块有关的信息
,打印如下:
module.exports
对象: 在自定义模块中,可以使用module.exports对象,将模块内的成员共享出去,供外界使用。外界用require()
方法导入自定义模块时,得到的就是module.exports所指向的对象。
注意:使用require()方法导入模块时,导入的结果永远以module.exports指向的对象为准。
exports对象
:由于module.exports单词写起来比较复杂,为了简化向外共享成员的代码,Node提供了exports对象,默认情况下,exports和module.exports指向同一个对象。
最终共享的结果,还是以module.exports
指向的对象为准
module.exports
和exports
的使用误区: 时刻谨记,require()模块时,得到的永远是module.exports
指向的对象:
- Node.js中模块化规范:Node.js遵循了CommonJS模块化规范,CommonJS规定了
模块化特性
和各模块之间如何相互依赖
。CommonJS规定:- 每个模块内部, Module变量代表当前的模块
- module变量是一个对象, 它的exports属性,即
module.exports是对外的接口
- 加载某个模块, 其实是加载该模块的module.exports属性。
require()方法用于加载模块
3.3 npm与包
3.3.1 npm和包的概述
不同于Node.js中的内置模块与自定义模块, 包是由第三方个人或团队开发出来的,免费供所有人适用。
注意: Node.js中的包都是免费且开源的,不需要付费即可免费下载使用。
由于Node.js的内置模块仅仅提供了一些底层的API,导致在基于内置模块进行开发项目时,效率很低。
包是基于内置模块封装出来的
,提供了更加高级的功能,更方便的API,极大提高了开发效率。
包和内置模块
之间的关系,类似JQuery和浏览器内置API之间的关系。
- 搜索包的地址:https://www.npmjs.com
- 下载包的地址:https://registry.npmjs.org
npm,Inc
公司除了提供上述地址之外,还提供了一个包管理工具Node Package Manager(简称npm包管理工具)
,这个工具随着Node.js的安装一起安装到电脑上了,大家可以在终端上执行npm -v
命令,来查看电脑上所安装npm包管理工具的版本。
3.3.2 npm初体验
- 格式化时间的传统做法:
- 格式化时间的高级做法:
使用npm包管理工具,在具体的项目中安装格式化时间的包 monment
- 使用require()导入格式化时间的包
- 参考monment的官方API 文档对时间进行格式化
在项目中安装包的命令:npm install 包的完整名称
, 上述命令还可以简写为npm i 完整包名称
那么初次安装包之后,多了哪些文件?
初次安装包完成后,在项目文件夹下多了一个叫做node_modules
的文件夹和package-lock.json
的配置文件。
node_modules文件夹
用来存放所有已安装大项目中的包。require()导入第三方包时,就是从这个目录中查找并加载包的。
package-lock.json配置文件
用来记录node_modules目录下的每一个包的下载信息
,例如包的名字、版本号、下载地址等
注意: 程序员不要手动修改node_modules或package-lock.json文件中的任何代码,npm包管理工具会自动维护他们
默认情况下,使用npm install
命令安装包的时候,会自动安装最新版本的包
。如果需要指定安装指定版本的包,可以在包名之后,通过@符号
指定具体的版本.例如:
包的版本号是以点分十进制
形式进行定义的,总共有三位数字,例如2.24.0
,其中每一位数字代表的含义如下:
- 第一位数字: 大版本
- 第二位数字:功能版本
- 第三位数字:Bug修复版本
版本号的提升规则: 只要前面的版本号增长,则后面的版本号归零。
3.3.3 包管理配置文件
npm 规定,在项目根目录中, 必须提供一个叫做package,.json
的包管理配置文件。用来记录与项目有关的一些配置信息。例如:
- 项目的名称、版本号、描述等
- 项目中都用到了那些包
- 哪些包只在
开发期间
会用到 - 那些包在
开发
和部署
时都需要用到
如果第三方的体积过大,不方便团队之间共享项目源代码。解决方案:共享时剔除node_modules
如何在记录项目中安装了哪些包?
在项目根目录中
,创建了一个叫做package.json
的配置文件,即可用来记录项目中安装了哪些包。从而方便剔除node_modules目录之后,在团队成员之间共享项目的源代码。
注意:在今后的项目开发中,一定要把node_modules文件夹,添加到.gitignore忽略文件中。
-
快速创建package.json : npm包管理工具提供了一个
快捷命令
,可以在执行命令时所处的目录中
快速创建package.json这个包管理配置文件:
-
如果包含中文和空格,会报名称不合法的错误
-
dependencies结点
:package.json文件中,有一个dependencies
结点,专门用来记录您使用npm install
命令安装了哪些包
当我们拿到一个剔除了node_modules
的项目之后,需要先把所有的包下载到项目中,才能将项目运行起来。否则会报类似于下面的错误:
可以运行npm install 命令
或者npm i
一次性安装所有的依赖包:
关于卸载包,可以运行命令npm uninstall
命令来卸载指定的包,例如:npm uninstall moment
, 这里需要注意的是 npm uninstall 命令执行成功后, 会把卸载的包,自动从package.json的dependencies种移除
如果某些包只在项目开发阶段
会用到,在项目上线后不会用到
,则建议把这些包记录到devDependencies几点中。与之对应的,如果在某些包在开发
和项目上线之后
都需要用到,则建议把这些包记录到dependenies节点中。
可以使用如下命令,将包记录到devDenpendencies节点中:
有时候下载包的速断比较慢?这是因为要国外的网站,所以有时候会比较慢。
可以切换npm
的下包镜像源:
为了更方便的切换下包的镜像源,我们可以安装nrm
这个小工具,利用nrm
提供的终端命令,可以快速产看和切换下包的镜像源。
3.3.4 包的分类
项目包
:那些被安装到项目的node_modules
目录中的包都是项目包, 项目包又分两类:开发依赖包
:被记录到devDenpendencies
节点中的包,只在开发期间会用到核心依赖包
:被记录到denpendencies
节点中的包,在开发期间和项目上线之后都会用到
在执行npm install命令时, 如果提供了 -g
参数,则会把包安装为全局包。
全局包会被安装到/usr/local/lib/node_modules
目录下 npm i 包名 -g (全局安装指定的包)
;npm uninstall 包名 -g(卸载全局安装的包)
注意:1.只有工具性质的包
,才有全局安装的必要性,因为他们提供了好用的终端命令。2、判断某个包是否需要全局才能使用,可以参考官方提供的使用说明
即可
-
i5ting_toc
:是一个可以把md文档转换成html页面的小工具,使用步骤如下:
-
规范的包结构:一个规范的包,它的组成结构,必须符合以下3点要求:
- 包必须以
单独的目录
而存在 - 包的顶级目录下面必须包含
package.json
这个包管理配置文件 - package.json中必须包含
name(包的名字)、version(包的版本)、main(包的入口)
这三个属性。 - 注意:以上三点要求是一个规范的包结构必须遵守的格式,关于更多的约束,需要自行参考资料
- 包必须以
3.3.5 开发属于自己的包
-
初始化包的基本结构
- 新建date-tools文件夹,作为
包的根目录
- 在date-tools文件中,新建如下三个文件:
- package.json: 包的管理配置文件
- index.js : 包的入口文件
- README.md: 包的说明文档
- 配置package.json文件:
- 新建date-tools文件夹,作为
- 实现index.js中的格式化时间功能:
注意: 我们没有直接导入确定的.js文件,node会去该文件下面去查找是否有package.json文件,然后再看.json文件中是否有指定main属性(入口) 如果 没有则会报错 找不到文件
如果我们里面后面还有很多其他的功能方法, index.js文件中的代码会越来越多,会变得越来越难维护,为了解决这种情况,所以我们把功能相关的代码抽离出来作为一个独立的文件。
注意:...
是展开对象中的每一个属性
- 编写包的说明文档:包根目录中
README.md
文件,是包的使用说明文档
。通过它,我们可以事先把包的使用说明,以markdown的格式写出来,方便用户参考。 内容包含: 安装方式、导入方式、功能使用介绍、开源协议
3.3.6 发布自己开发的包
访问https://www.npmjs.com/,然后注册一个账号。然后我们需要再终端
执行命令npm login
,依次输入用户名、密码、邮箱后,即可以登录成功。这里有一个注意点在运行npm login 命令之前,必须先把下包的服务器
地址切换为npm 的官方服务器
,否则会导致发布包失败
- 发布包:将终端切换到包的根目录之后,执行
npm publish
命令,即可将包发布到npm上(注意:包名不能雷同
) - 删除已发布包: 执行
npm unpublish 包名 --force
命令,即可从npm删除已发布的包。- npm unpublish 命令只能删除
72小时以内发布的包
- npm unpublish 删除的包,在
24小时内
不允许重复发布 - 发布包的时候要慎重,
尽量不要在王npm上发布没有意义的包
- npm unpublish 命令只能删除
3.4 模块的加载机制
模块在第一次加载后会被缓存
,这也意味着多次调用require()
不会导致模块的代码被执行多次。注意:不论是内置模块、用户自定义模块、还是第三方模块,他们都会优先从缓存中加载,从而提高模块的加载效率
-
内置模块是有Node.js官方提供的模块,
内置模块的加载优先级最高
。例如,require(‘fs’)始终返回内置的fs模块,即使在node_modules目录下有名字相同的包也叫做fs -
使用require()加载自定义模块时,必须指定以
./
或../
开头的路径标识符
,在加载自定义模块时,如果没有指定./
或则../
这样的路劲标识符,则node会把它当做内置模块
或第三方模块
进行加载。
同时,在使用require()导入自定义模块时,如果省略了文件的扩展名,则Node.js会按顺序
分别尝试加载以下的文件:
- 按照
确切的文件名
进行加载 - 补全
.js
扩展名进行加载 - 补全
.json
扩展名进行加载 - 补全
.node
扩展名进行加载 - 加载失败,终端报错
- 第三方模块加载机制: 如果传递给require()的模块标识符不是一个内置模块,也没有以
./
或../
开头,则Node.js会从当前模块的父目录开始,尝试从/node_modules文件夹中加载第三方模块。如果没有找到对应的第三方模块,则移动到再上一层父目录中,进行加载,直到文件系统的根目录。
目录
作为模块: 当把目录作为模块标识符,传递给require()进行加载的时候,又三种加载方式:- 在被加载的目录下查找一个叫做package.json的文件,并寻找main属性,作为require()加载的入口
- 如果目录里没有package.json文件,或则main入口不存在或无法解析,则Node.js将会视图加载目录下的
index.js文件。
- 如果以上两步都失败了,则Node.js会在终端打印错误信息,报告模块的缺失:Error Cannot find module ‘xxx’
4.Express
什么是Express?
官方给出的概念:Express是基于Node.js平台
,快速、开放、极策的Web开发框架。通俗理解Express的作用和Node.js内置的http模块类似,是专门用来创建Web服务器的
,其本质就是一个npm上的第三方包,提供了快速创建Web服务器的便捷方法
Express中文官网 ,这里安装的是4.17.1版本。
Express能做什么?
对于前端程序员来说,最常见两种服务器,分别是:
Web网站服务器
:专门对外提供Web网页资源的服务器API接口服务器
:专门对外提供API接口的服务器
使用Express,我们可以方便,快速的创建Web网站的服务器或者API接口服务器
4.1 Express的基本使用
在项目所处的目录中, 运行如下的终端命令,即可将express安装到项目中使用:npm i express@4.17.1
- 使用express创建基本的web服务器:
- 监听
GET
请求, 我们通过app.get()
方法,可以监听客户端的GET请求,具体的语法格式如下:
- 监听
POST
请求,通过app.post()
方法,可以监听客户端的POST请求。具体的语法格式如下:
- 服务器把内容响应给客户端,通过
res.send()
方法,可以把处理好的内容,发送给客户端:
- 获取URL所带的查询参数:通过
req.query
对象,可以访问到客户端通过查询字符串
的形式,发送到服务器的参数:
- 获取URL中的动态参数: 通过
req.params
对象,可以访问到URL中,通过:
匹配到的动态参数:
- 托管静态资源:express提供了一个非常好用的函数,叫做
express.static()
,通过它,我们可以非常方便地创建
一个静态资源服务器
,例如,通过下面代码就可以将public
目录下的图片、CSS文件、javaScript文件对外开放了app.user(express.static('public'))
- 托管多个静态资源目录:如果要托管多个静态资源目录,请多次调用
express.static()
函数:
注意:访问静态资源文件时,express.stack()
函数会提供目录的添加顺序查找所需要的文件
- 挂载
静态资源前缀
:如果希望在托管的静态资源访问路径
之前,挂在路径前缀
,则可以使用如下的方式:
- nodemon: 在编写调试Node.js项目的时候, 如果修改了项目的代码,则需要频繁手动
close
掉,然后再重新启动,非常繁琐。现在,我们可以使用nodemon
这个工具,它能够监听项目文件的变动
,当代码修改后,nodemon会自动帮我们重启项目
,极大方便了开发和调试
在终端执行命令npm install -g nodemon
,即可将nodemon
安装为全局可用的工具
当基于Node.js编写一个网站应用的时候,传统方式,是运行node app.js
,来启动项目,这样做的坏处是,当代码被修改之后,需要手动重启项目。
现在,我们可以将node
命令替换为nodemon
,使用nodemon app.js
来启动项目,这样做的好处:代码被修改之后,会被nodemon监听到,从而实现自动重启动项目的效果。
4.2 express路由
4.2.1 路由的介绍
在Express中, 路由指的是客户端的请求
与服务器处理函数
之间的映射关系。
Express中路由分3不分组成, 分别是请求的类型、请求的URL地址、处理函数
格式如下:app.METHOD(PATH, HANDLER)
- 路由的匹配过程: 每当一个请求到达服务器之后,
需要先经过路由的匹配
,只有匹配成功之后, 才会调用对应的处理函数。在匹配时,会按照路由的顺序进行匹配,如果请求类型
和请求的URL
同时匹配成功, 则Express 会将这次请求, 转交给对应的function
函数进行处理。
注意:按照定义的先后顺序
进行匹配,请求类型
和请求的URL
同时匹配成功,才会调用对应的处理函数
4.2.2 路由的使用
在Express 中使用路由最简单的方式, 就是把路由挂载到app上,实力代码如下
模块化路由:为了方便路由进行模块化的管理
,Express 不建议
将路由直接挂载到app上, 而是推荐将路由抽离为单独的模块。
将路由抽离为单独模块步骤如下:
- 创建路由模块对应的js文件
- 调用
express.Route()
函数创建路由对象 - 向路由对象上挂载具体的路由
- 使用
module.express
向外共享路由对象 - 使用
app.user()
函数注册路由模块
我们还可以为路由模块添加前缀
, 类似于托管静态资源时, 为静态资源统一挂载访问访问前缀一样,路由模块添加前缀的方式也非常简单:
4.3 express中间件
4.3.1 中间件的概念
Express中间件的调用流程
, 当一个请求到达Express的服务器之后,可以连续调用多个中间件,从而对这次请求进行预处理
。
Express中间件的格式: Express的中间件,本质上就是一个function处理函数
,Express中间件的格式如下:
注意: 中间件函数的形参列表中, 必须包含next参数
,而路由处理函数中只包含req和res
上面多次提到next()
函数,那么next()
函数的作用是什么了?
next()
函数是实现多个中间件连续调用
的关键,它表示把流转关系转交
给下一个中间件
或路由
。
4.3.2 Express 中间件的初体验
那么我们如何定义一个中间件函数,可以通过如下方式:
全局生效
的中间件: 客户端发起的任何请求
,到达服务器之后,都会触发的中间件
,叫做全局生效的中间件。通过调用app.use(中间件函数),即可定义一个
全局生效的中间件
,示例代码如下:
定义中间件的简化形式:
中间件的作用
:多个中间件之间,共享同一份req和res
,基于这样的特性,我们可以在上游的中间件,统一
为req或res对象添加自定义的属性或方法, 供下游
的中间件或路由进行使用。
定义多个
全局中间件:可以使用app.use()连续定义多个中间件,客户端请求到达服务器之后
, 会按照中间件定义的先后顺序
依次进行调用,示例代码如下:
除了全局中间件之外,我们还可以使用局部中间件,所谓局部生效的中间件, 就是不适用app.use()
定义的中间件, 叫做局部
生效中间件
注意:不使用app.use()注册的中间件都是局部中间件
跟全局中间件一样,我们一样也可以同时定义多个中间件。我们可以通过如下两种等价的方式,使用多个局部中间件
:
- 中间件的5个使用注意事项:
- 一定要在
路由之前
注册中间件 - 客户端发送过来的请求,
可以连续调用多个
中间件进行处理 - 执行完中间件的业务代码之后,
不要忘记调用next()函数
- 为了
防止代码逻辑混乱
,调用next()函数后不要在写额外的代码 - 连续调用多个中间件时,多个中间件之间,
共享
req和res对象
- 一定要在
4.3.3 中间件的分类
为了方便大家理解和记忆中间件的使用, Express官方把常见的中间件用法
,分成了5大类
,分别是
应用级别
的中间件路由级别
的中间件错误级别
的中间件Express内置
的中间件第三方
的中间件
通过app.use()
或app.get()
或app.post()
,绑定到APP实例上的中间件, 叫做应用级别的中间件。代码示例如下:
绑定到express.Router()
实例上的中间件,叫做路由级别的中间件, 它的想法和应用级别中间件没有任何的区别, 不过应用级别中间件是绑定到app实力上, 路由级别中间件绑定到router实例上:
错误级别的中间件的作用:专门用来捕获整个项目中发生的异常错误, 从而防止项目异常崩溃的问题。
格式
:错误级别的中间件function处理函数中,必须有4个参数
,形参顺序从前到后,分别是(err,req,res,next)。
注意:错误级别的中间件,必须注册在所有路由之后!
Express内置
的中间件:自从Express 4.16.0版本开始Express 内置了3个常用的中间件,极大的提高了Express项目的开发效率和体验:express.static
:快速托管静态资源的内置中间件,例如:HTML文件、图片、CSS样式等(无兼容性
)express.json
: 解析JSON格式的请求体数据(有兼容性,仅在4.16.0+版本中可用
)express.urlencoded
: 解析URL-encoded格式的请求数据(有兼容性,仅在4.16.0+版本中可用
)
第三方
的中间件指的是非Express官方内置的,而是由第三方开发出来的中间件,叫做第三方中间件。在项目开发中,大家可以按需下载并配置第三方中间件,从而提高项目的开发效率。例如在express@4.16.0之前的版本中经常使用body-parser这个第三方中间件,来解析请求数据,使用步骤如下:
- 运行
npm install body-parser
安装中间件 - 使用
require
导入中间件 - 调用
app.use()
注册并使用中间件
注意:Express内置的express.urlencoded中间件,就是基于body-parser这个第三方中间件进一步封装出来的
4.3.4 自定义中间件
学习上述中间件的知识 ,我们可以尝试写一个自己的中间件。接下来我们手动模拟一个类似于express.unlencoded这样的中间件, 来解析POST提交服务器的表单数据
。实现步骤如下:
- 定义中间件
- 监听req的data事件(在中间件中,需要监听req对象的data事件,来获取客户端发送到服务器的数据。如果数据量比较大,无法一次性发送完毕,则客户端
会把数据切割后,分批发送到服务器。
所以data事件可能会触发多次,每一次触发data事件时,获取到数据只是完整数据的一部分
,需要手动对接收到的数据进行拼接。) - 监听req的end事件(当请求数据
接受完毕
之后,会自动触发req的end事件。因此,我们可以在req的end事件中,拿到并处理完整的请求数据
。) - 使用querystring模块解析请求体数据(Node.js内置一个
querystring
模块,专门用来处理查询字符串
。通过这个模块提供的parse()
函数,可以轻松把查询字符串,解析成对象的格式的数据) - 将解析出来的数据对象挂在为req.body(
上游的中间件和下游的中间件及路由之间,共享同一份req和res
。因此我们可以将解析出来的数据,挂在为req的自定义属性,命名为req.body
,供下游使用) - 将自定义中间件封装为模版
上述我们已经完成了我们自己中间件的功能实现, 但是我们发现我们的中间件的逻辑代码和主模块的代码在一起, 这样的代码结构不太好管理, 为了优化代码的结构,我们可以把自定义的中间件函数,封装为独立的模块,
4.4使用Express写接口
4.4.1 接口实现
上述html使用的jQuery是在线版本的相关地址-免费CND服务
编写接口之后,当我们进行接口测试的时候,发现浏览器报了如下错误:
4.4.2 CORS跨域资源共享
上述编写的接口,我们也发现了,存在一个很严重的问题: 不支持跨域请求
。解决接口跨域问题的方案主要有两种:
CORS
(主流的解决方案,推荐使用)JSONP
(有缺陷的解决方案:支持支GET请求)
我们可以使用cors中间件
解决跨域问题, cors是Express的一个第三方插件,通过安装和配置cors中间件,可以很方便地解决跨域问题。使用步骤如下:
- 运行
npm install cors
中间件 - 使用
const cors = require('cors')
导入中间件 - 在路由之前调用
app.use(cors())
配置中间件
我们可能会有一个疑问? 到底什么是CORS?
cors(Cross-Origin Resource Sharing,跨域资源共享)由一系列HTTP响应头
组成,这些HTTP响应头决定浏览器是否阻止前端JS代码跨域获取资源
。
浏览器的同源安全策略
默认会组织网页’跨域’获取资源,但如果接口服务器配置了CORS相关的HTTP响应头,就可以
接触浏览器的跨域访问限制。
- CORS的注意事项:
- CORS主要是在
服务器
进行配置,客户端浏览器无须做任何额外的配置
,即可请求开启了CORS的接口。 - CORS在浏览器中
有兼容性
,只支持XMLHttpRequestLevel2的浏览器,才能正常访问开启了CORS的服务端接口
,例如IE10+、chrome4、FireFox3.5+
- CORS主要是在
CORS响应头部:Access-Control-Allow-Origin,响应头部中可以携带该字段,其语法如下:
Access-Control-Allow-Origin: | *,其中
origin参数指定了
允许访问该资源的外域URL`。
例如,下面的字段值将只允许
来自http://itcast.cn的请求:
如果指定了Access-Control-Allow-Origin
的字段值为通配符 *
,表示允许来自任何域的请求,示例代码如下:res.setheader('Access-Control-Allow-Origin', '*')
CORS响应头Access-Control-Allow-Headers
默认情况下,CORS仅支持客户端像服务器
发送如下的9个请求头:Accept、Accept-Language、Content-Lanaguage、DPR、Downlink、Viewport-Width、Width、Content-Type(值仅于text/plain、mulitpart/from-data、application/x-www-form-urlencoded三者之一)
如果客户端向服务端发送了额外的请求头信息
, 则需要再服务器,通过Access-Control-Allow-Header对额外的请求头进行声明
,否则这次请求会失败
CORS响应头部Access-Control-Allow-Methods
,默认情况下,CORS仅支持客户端发起的GET、POST、HEAD
请求,如果客户端希望通过PUT、DELETE
等方式请求服务器资源,则需要再服务器端,通Access-Control-Allow-Methods
来指明实际请求所允许使用的HTTP方法
。
CORS请求的分类,客户端在请求CORS接口时,根据请求方式
和请求头
的不同,可以将CORS的请求分成两大类,分别是:简单请求
和预检请求
简单请求:就是满足请求方式是GET、POST、HEAD
三者之一, HTTP头部信息不超过上述的9种字段,无自定义表头。
预检请求: 只要符合其中一个要求,都需要进行预检请求。请求方式为GET、POST、HEAD
之外的请求Method
类型, 请求头中包含自定义头部字段
,向服务器发送了application/json格式的数据
4.4.3 JSONP接口
浏览器端通过<script>
标签的src
属性,请求服务器上的数据,同时,服务器返回一个函数的调用,这种请求数据的方式叫做JSONP
。其特点:
- JSONP不属于真正的Ajax请求,因为它没有使用
XMLHttpRequest
这个对象。 - JSONP仅支持GET请求,不支持POST、PUT、DELETE等请求
创建JSONP请求的注意事项:如果项目中已经配置了CORS跨域资源共享,为了防止冲突,必须在配置CORS中间件之前申明JSONP的接口
,否则JSONP接口会被处理成开启了CORS的接口。
使用JSONP的接口步骤:
获取
客户端发来的回调函数名字
- 得到要通过JSONP形式
发送给客户端的数据
- 根据前两部得到的数据,
拼接出一个函数调用的字符串
- 把上一步拼接得到的数据,响应给客户端
script
标签进行解析执行
5. MySQL数据库
关于MySQL数据一些操作数据库的指令可以参考文章Java学习-MySQL数据库
这里主要介绍下载Express中怎么使用数据库。首先在项目中操作数据的步骤:
- 安装操作MySQL数据库的第三方模块(
mysql
) - 通过mysql模块
链接到MySQL数据库
- 通过
mysql
模块执行SQL语句
mysql
模块是托管与npm上的第三方模块
。它提供了Node.js项目中链接
和操作
MySQL数据库的能力。想要在项目中使用它,需要将mysql安装为项目的依赖包:npm install mysql
在使用mysql模块操作MySQL数据之前,必须先对mysql模块进行必要的配置
,主要的配置步骤如下:
调用db.query
函数,指定要执行SQL语句,通过回调函数那道执行的结果:
6 Web开发模式
6.1 身份认证
目前主流的Web开发者模式有两种,分别是:
- 基于
服务端渲染
的传统Web开发模式 - 基于
前后端分离
的新型Web开发者模式
服务端渲染的概念
:服务器发送给客户端的HTML页面
,是在服务器通过字符串的拼接,动态生成的
。因此,客户端步需要使用Ajax这样的技术额外请求页面的数据。
服务端渲染的优缺点:
- 优点:
前端耗时少
,因此服务端负责动态生成HTML内容,浏览器只需要直接渲染页面即可,尤其是移动端,更省电。有利于SEO
,因为服务器响应是完成的HTML页面内容,所以爬虫更容易爬取获得信息,更有利于SEO
- 缺点:
占用服务器资源
,即服务器完成HTML页面的拼接,如果请求较多,会对服务器造成一定的 访问压力不利于前后端分离,开发效率低
,使用服务端渲染,则无法进行分工合作
,尤其对于前端复杂度高
的项目,不利于项目高效开发。
前后端分离的开发模式
:前后端分离的开发模式,依赖于Ajax技术的广泛应用
。简而言之,前后端分离的Web开发模式,就是后端只负责提供API接口,前端使用Ajax调用接口
的开发模式
前后端分离的优缺点:
- 优点:
开发体验好
,前端专注于UI页面的开发,后端专注于api的开发,切前端有更多的选择用户体验好
,Ajax的技术广泛应用,极大的提高了用户的体验,可以轻松实现页面的局部刷新。减轻了服务器端的渲染压力
。因此页面最终在每个用户的浏览器中生成的
- 缺点:
不利于SEO
。因为完整的HTML页面需要再客户端动态拼接万完成,所以爬虫无法爬取页面的有效信息。(解决方案:利用Vue、React等前端架构的SSR(server side rende)技术能够很好的解决SEO问题!)
关于如何选择Web开发模式?
不谈业务场景而盲目选择使用何种开发模式都是耍流氓.
- 比如企业级网站,主要功能展示没有复杂的交互,并且需要良好的SEO,则这时我们就需要使用服务端渲染
- 而类似后台管理项目,交互性比较强,不需要考虑SEO,那么久可以使用前后端分离的开发模式。
另外,具体使用何种开发模式并不是绝对的,为了同时兼顾了首页的渲染速度和前后端分离的开发效率,
一些网站采用了首屏服务端渲染+其它页面点后端分离的开发者模式。
什么是身份认证?
身份认证(Authentication)又称’身份验证‘、鉴权,是指,通过一定的手段,完成对用户身份的确认
。 在Web开发中,涉及到用户身份认证,例如:各大网站的手机验证码登录、邮箱密码登录、二维码登录
不同开发模式下的身份认证, 对于服务端渲染
和前后端分离
这两种开发模式来说,分别有着不同的身份认证方案:
服务端渲染
推荐使用Session
认真机制前后端分离
推荐使用JWT认证机制
6.2 session认证机制
- HTTP协议的
无状态性
: 了解HTTP协议的无状态是进一步学习Session认证机制的必要前提。HTTP协议的无状态性,指的是客户端的每次HTTP请求都是独立的,
连续多个请求之间没有直接关系,服务器不会主动保留每次HTTP
请求的状态。
如何突破
HTTP无状态的限制:对于超市来说,为了方便收银员在进行结算时给VIP用户打折,超市可以为每个VIP用户发放会员卡。
注意: 显示生活中会员卡身份认证机制
,在Web开发中专业术语
叫做Cookie
。
Cookie
:指存储在用户浏览器中的一段不超过4KB的字符串
。它由一个名称(Name)、一个值(value)和其他几个用于Cookie有效期、安全性、使用范围
的可选属性组成, 不同域名下的Cookie个字独立,每当客户端发起请求时,会自动
把当前域名下
所有未过期的Cookie
一同发送到服务器。- Cookie的几大特性:自动发送、域名独立、过期时限、4KB限制。
Cookie在身份认证中的作用: 客户端在第一次请求服务器的时候,服务器通过
通过响应头的形式
,向客户端发送一个身份认证的Cookie,客户端会自动将Cookie保存在浏览器中。
随后,当客户端浏览器每次请求服务器的时候, 浏览器会自动将自动
将身份认证相关的Cookie,通过请求头的形式
发送给服务器,服务器即可验明客户端的身份。
由于Cookie是存储在浏览器中的,而且浏览器也提供了读写Cookie的API
,因此Cookie很容易被伪造
, 不具备安全性,因此不建议服务器将重要的隐私数据,通过Cookie的形式发送给服务器。
注意: 千万不要使用Cookie存储重要且隐私的数据!比如用户的省份信息、密码等
为了防止客户伪造会员卡,收银员在拿到客户的会员卡之后,可以在收银机上进行刷卡认证
,只有收银机确认存在的会员卡,才能被正常使用。
Session认证机制的工作原理:
在Express中使用Session认证, 首先我们需要安装express-session
中间件,既可以在项目中使用
当express-session中间件配置成功后, 即可通过req-session
来访问和使用session对象,从而存储用户的关键信息:
6.3 JWT认证机制
Session认证机制需要配合Cooke才能实现
,由此Cookie默认不支持跨域访问,所以,当涉及到前端跨域请求后端接口
的时候,需要做很多额外的配置
,才能实现跨域Session认证。
注意:
- 当前段请求后端接口
不存在跨域问题
的时候,推荐使用Session
身份认证机制。 - 当前端需要跨域请求后端接口的时候,不推荐使用Session身份认证机制,推荐使用JWT认证机制。
JWT的工作原理
:
- 用户的信息通过Token字符串的形式,保护在客户端浏览器中,服务器通过还原Token字符串的形式来认证用户的身份
JWT通常由三部分组成,分别是Header(头部)、Payload(有效荷载)、Signature(签名)
,三者之间使用英文的.
分隔,格式如下:Header.Payload.Signature
Payload
部分才是真正的用户信息
,它是用户信息经过加密之后生成的字符串- Header和Signature是
安全性相关
的部分,只是为了保证token的安全性。
如何使用JWT?
客户端收到服务器返回的JWT之后,通常会将它存储在localStorage
或sessionStorage
中。此后,客户端每次与服务器通信,都需要带上这个字符串,从而进行身份认证。推荐的做法就是把JWT放在HTTP请求头的Authorization字段中
,格式如下:Authorization: Bearer <token>
如果想要在项目中使用JWT
相关功能,需要安装两个相关的包;npm install jsonwebtoken express-jwt
在项目中为了保证JWT字符串的安全性,
防止JWT字符串在网络传输过程中被别人破解,我们需要专门定义一个用于加密
和解密
的secrt
秘钥:
- 当生成JWT字符串的时候,需要使用secret密钥对用户信息
进行加密
,最终得到加密好的JWT字符串 - 当把JWT字符串解析还原成JSON对象的时候,需要使用secret密钥
进行解密
定义一个密钥,密钥本质是一个字符串,例如:const secretKey = "itedad NO1 @"
客户端每次在访问那些有权限的接口的时候,都需要主动在请求头中的Authorization字段
,将token字符串发送到服务器进行身份验证。此时,服务器可以通过express-jwt
这个中间件,自动将客户端发送过来的Token解析还原成JSON对象。
unless方法是设置那些接口,不需要使用访问权限。
注意:只要配置成功了express-jwt这个中间件,解析出来的信息可以通过auth
来访问
当使用express-jwt解析Token字符串时,如果客户端发送过来的Token字符串过期
或不合法
,会产生一个解析失败
的错误,影响项目的正常运行,我们可以通过Express的错误中间件
,捕获整个错误并进行相关处理,示例代码如下: