Node
Node.js 简介
Node.js是一个基于ChromeV8引擎的 JavaScript 运行环境
Node.js 可以做什么
Node.js作为一个JavaScript的运行环境,仅仅提供了基础的功能和APl。
基于Node.js提供的这些基础能,很多强大的框架和工具如雨后春笋,层出不穷:
- 基于Express框架(http://www.expressjs.com.cn/),可以快速构建Web应用
- 基于Electron框架(https://electronjs.org/),可以构建跨平台的桌面应用
- 基于restify框架(http://restify.com/),可以快速构建API接口项目
- 读写和操作数据库、创建实用的命令行工具辅助前端开发、etc…
Buffer
Buffer 类
创建 Buffer
Buffer.alloc(size[, fill[, encoding]])1
const buf = Buffer.alloc(5);
Buffer.allocUnsafe(size)1
const buf = Buffer.allocUnsafe(10);
Buffer.from(array)1
const buf = Buffer.from([0x62, 0x75, 0x66, 0x66, 0x65, 0x72]);
fs 文件系统
fs模块可以实现与硬盘的交互。例如文件的创建、删除、重命名、移动,还有文件内容的写入、读取,以及文件夹的相关操作
引入模式
所有文件系统操作都具有同步、回调和基于 promise 的形式
(默认是异步),并且可以使用 CommonJS 语法和 ES6 模块进行访问。
fs 类
文件写入
写入文件的场景:
- 下载文件
- 安装软件
- 保存程序日志,如 Git
- 编辑器保存文件
- 视频录制
创建并写入
1 | fs.writeFile(file, data[, options], callback) |
追加写入
应用场景:写入日志
1 | fs.appendFile(path, data[, options], callback) |
1 | import { appendFile } from 'node:fs'; |
补充:
- 如果需要在写入的文件中插入换行可以使用
\r\n
- fs.writeFile 也可以实现追加写入:在第三个配置项添加:
{flag: 'a'}
流式写入
程序打开一个文件是需要消耗资源的 ,流式写入可以减少打开关闭文件的次数。
应用场景:流式写入方式适用于大文件写入或者频繁写入
的场景, writeFile 适合于 写入频率较低的场景
1 | fs.createWriteStream(path[, options]) |
文件读取
读取文件应用场景:
- 电脑开机
- 程序运行
- 编辑器打开文件
- 查看图片
- 播放视频
- 播放音乐
- Git查看日志
- 上传文件
- 查看聊天记录
读取文件(一次性读取)
1 | fs.readFile(path[, options], callback) |
流式读取(效率高占用空间小)
1 | fs.createReadStream(path[, options]) |
1 | //创建读取流对象 |
文件移动与重命名
可以使用
rename
或renameSync
来移动或重命名 文件或文件夹
1 | fs.rename(oldPath, newPath, callback) |
1 | fs.rename('./观书有感.txt', './论语/观书有感.txt', (err) =>{ |
文件删除
可以使用 unlink 或 unlinkSync 来删除文件
1 | fs.unlink(path, callback) |
1 | const fs = require('fs'); |
补充:fs.rm
方法也可以实现删除的操作
文件夹操作
可以对文件夹进行创建、读取、删除等操作
创建文件夹
1 | fs.mkdir(path[, options], callback) |
1 | //异步创建文件夹 |
读取文件夹
1 | fs.readdir(path[, options], callback) |
1 | //异步读取 |
删除文件夹
更推荐使用
fs.rm
方法来删除文件/文件夹
1 | fs.rmdir(path[, options], callback) |
1 | //异步删除文件夹 |
查看资源状态
可以使用 stat
或 statSync
来查看资源的详细信息
1 | fs.stat(path[, options], callback) |
1 | //异步获取状态 |
结果值对象结构:
1 | Stats { |
路径
fs 模块对资源进行操作时,路径的写法有两种:
- 相对路径
- ./座右铭.txt 当前目录下的座右铭.txt
- 座右铭.txt 等效于上面的写法
- ../座右铭.txt 当前目录的上一级目录中的座右铭.txt
- 绝对路径
- D:/Program Files windows 系统下的绝对路径
- /usr/bin Linux 系统下的绝对路径
相对路径中所谓的
当前目录
,指的是命令行的工作目录
,而并非是文件的所在目录
所以当命令行的工作目录与文件所在目录不一致时,会出现一些 BUG
__dirname
dirname 与 require 类似,都是 Node.js 环境中的’全局’变量 dirname 保存着 当前文件所在目录的绝对路径 ,可以使用 __dirname 与文件名拼接成绝对路径
1 | let data = fs.readFileSync(__dirname + '/data.txt'); |
使用 fs 模块的时候,尽量使用 __dirname 将路径转化为绝对路径,这样可以避免相对路径产生的Bug
path 路径模块
API | Description |
---|---|
path.resolve | 拼接规范的绝对路径【常用】 |
path.sep | 获取操作系统的路径分隔符 |
path.parse | 解析路径并返回对象 |
path.basename | 获取路径的基础名称 |
path.dirname | 获取路径的目录名 |
path.extname | 获得路径的扩展名 |
代码演示:
1 | const path = require('path'); |
http 模块
HTTP 协议
HTTP(hypertext transport protocol)协议;中文叫超文本传输协议,是一种基于TCP/IP的应用层通信协议,这个协议详细规定了 浏览器 和万维网 服务器 之间互相通信的规则。
协议中主要规定了两个方面的内容
- 客户端:用来向服务器发送数据,可以被称之为
请求报文
- 服务端:向客户端返回数据,可以被称之为
响应报文
请求报文的组成
- 请求行
- 请求头
- 空行
- 请求体
请求行
请求行包含:
- 请求方法
- URL
- HTTP版本号
常见请求方法:
方法 | Description |
---|---|
GET | 主要用于获取数据 |
POST | 主要用于新增数据 |
PUT/PATCH | 主要用于更新数据 |
DELETE | 主要用于删除数据 |
HEAD/OPTIONS/CONNECT/TRACE | 使用相对较少 |
URL 的构成:
- 协议名
- 主机名
- 端口号
- 路径
- 查询字符串
HTTP版本号:
Syntax | Description |
---|---|
1.0 | 1996年 |
1.1 | 1999年 |
2 | 2015年 |
3 | 2018年 |
请求头
MDN 参考文档:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers
请求头 | Description |
---|---|
Host | 主机名 |
Connection | 连接的设置 keep-alive(保持连接);close(关闭连接) |
Cache-Control | 缓存控制 max-age = 0 (没有缓存) |
Upgrade-Insecure-Requests | 将网页中的http请求转化为https请求(很少用)老网站升级 |
User-Agent | 用户代理,客户端字符串标识,服务器可以通过这个标识来识别这个请求来自哪个客户端 ,一般在PC端和手机端的区分 |
Accept | 设置浏览器接收的数据类型 |
Accept-Encoding | 设置接收的压缩方式 |
Accept-Language | 设置接收的语言 q=0.7 为喜好系数,满分为1 |
Cookie |
请求体
请求体内容的格式是非常灵活的,可以是空(GET),也可以是字符串,还可以是JSON(POST)
例如:
- 字符串:keywords=手机&price=2000
- JSON:{“keywords”:”手机”,”price”:2000}
响应报文的组成
- 响应行
- 响应头
- 空行
- 响应体
响应行
响应行包含:
- HTTP版本号
- 响应状态码
- 响应状态的描述
常见状态码:
状态码 | Description |
---|---|
200 | 请求成功 |
403 | 禁止请求 |
404 | 找不到资源 |
500 | 服务器内部错误 |
状态码分类:
状态码分类 | Description |
---|---|
1xx | 信息响应 |
2xx | 成功响应 |
3xx | 重定向消息 |
4xx | 客户端错误响应 |
5xx | 服务端错误响应 |
常见响应状态的描述:
MDN 参考文档:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Status
Syntax | Description |
---|---|
200 | OK |
403 | Forbidden |
404 | Not Found |
500 | Internal Server Error |
响应头
常见响应头的结构:
- Cache-Control:缓存控制 private 私有的,只允许客户端缓存数据
- Connection 链接设置
- Content-Type:text/html;charset=utf-8 设置响应体的数据类型以及字符集,响应体为html,字符集utf-8
- Content-Length:响应体的长度,单位为字节
响应体
常见响应体的构成:
- HTML
- CSS
- JavaScript
- 图片
- 视频
- JSON
创建基本服务
- 导入http模块
- 创建web服务器实例
- 为服务器实例绑定request事件,监听客户端的请求
- 启动服务器
1 | // 1. 导入 http 模块 |
http.createServer 里的回调函数的执行时机: 当接收到 HTTP 请求的时候,就会执行
request 对象
createServer 方法的回调函数的第一个参数是一个request对象,拥有以下属性:
url
: 发出请求的网址。method
: HTTP请求的方法。headers
: HTTP请求的所有HTTP头信息。
response 对象
createServer 方法的回调函数的第二个参数是一个response对象,常用方法:
- response.end() 方法用来写入HTTP回应的具体内容,以及回应完成后关闭本次对话
补充:
当调用 response.end() 方法,向客户端发送中文内容的时候,会出现乱码问题,此时,需要手动设置内容的编码格式:
1 | response.setHeader('Content-Type','text/html;charset=utf-8') |
注意事项
- 命令行 ctrl + c 停止服务
- 当服务启动后,更新代码必须重启服务才能生效
- 端口号被占用
- 关闭当前正在运行监听端口的服务 ( 使用较多 )
- 修改其他端口号
- HTTP 协议默认端口是 80 。HTTPS 协议的默认端口是 443, HTTP 服务开发常用端口有 3000,
8080,8090,9000 等
如果端口被其他程序占用,可以使用
资源监视器
找到占用端口的程序的PID
,然后使用任务管理器
关闭对应的PID
程序
浏览器查看 HTTP 报文
查看请求行与请求头
查看请求体
查看 URL 查询字符串
查看响应行与响应头
查看响应体
获取 HTTP 请求报文
想要获取请求的数据,需要通过 request
对象
注意事项:
- request.url 只能获取路径以及查询字符串,无法获取 URL 中的域名以及协议的内容
- request.headers 将请求信息转化成一个对象,并将属性名都转化成了『小写』
- 关于路径:如果访问网站的时候,只填写了 IP 地址或者是域名信息,此时请求的路径为『 / 』
- 关于 favicon.ico:这个请求是属于浏览器自动发送的请求
提取HTTP报文
代码演示:
1 | //1. 导入 http 模块 |
提取HTTP报文的请求体
代码演示:
1 | //1. 导入 http 模块 |
提取HTTP报文中URL的路径与查询字符串
方式一
代码演示:
1 | //导入 http 模块 |
方式二(推荐)
代码演示:
1 | //导入 http 模块 |
设置 HTTP 响应报文
代码演示:
1 | //导入 http 模块 |
网页资源的基本加载过程
网页资源的加载都是循序渐进
的,首先获取 HTML 的内容, 然后解析 HTML 在发送其他资源的请求,如 CSS,Javascript,图片等。
静态资源服务
静态资源是指
内容长时间不发生改变的资源
,例如图片,视频,CSS 文件,JS文件,HTML文件,字体文件等。
动态资源是指内容经常更新的资源
,例如百度首页,网易首页,京东搜索列表页面等。
搭建静态资源服务小案例
1 | 需求: |
代码目录结构:
1 | ├── page |
server.js 核心代码:
网站根目录或静态资源目录
HTTP 服务在哪个文件夹中寻找静态资源,那个文件夹就是 静态资源目录
,也称之为 网站根目录
网页中的 URL
网页中的 URL 主要分为两大类:相对路径与绝对路径
绝对路径
绝对路径可靠性强
,而且相对容易理解,在项目中运用较多
形式 | Description |
---|---|
http://xxx.com/web | 直接向目标资源发送请求,容易理解。网站的外链 会用到此形式 |
/web | 与页面 URL 的协议、主机名、端口拼接形成完整 URL 再发送请求。中小型网站 |
//xxx/web | 与页面 URL 的协议拼接形成完整 URL 再发送请求。大型网站用的比较多 |
相对路径
相对路径在发送请求时,需要与当前页面 URL 路径进行 计算 ,得到完整 URL 后,再发送请求,主要是学习阶
段用的较多【不可靠】
例如当前网页 url 为 http://www.xxx.com/course/h5.html
形式 | Description |
---|---|
./css/app.css | http://www.xxx.com/course/css/app.css |
js/app.js | http://www.xxx.com/course/js/app.js |
../img/logo.png | http://www.xxx.com/img/logo.png |
../../mp4/show.mp4 | http://www.xxx.com/mp4/show.mp4 |
网页中使用 URL 的场景小结:(包括但不限于如下场景)
- a 标签 href
- link 标签 href
- script 标签 src
- img 标签 src
- video audio 标签 src
- form 中的 action
- AJAX 请求中的 URL
设置资源类型(mime类型)
媒体类型(通常称为 Multipurpose Internet Mail Extensions 或 MIME 类型 )是一种标准,用来表示文档、
文件或字节流的性质和格式。
1 | mime 类型结构: [type]/[subType] |
HTTP 服务可以设置响应头 Content-Type 来表明响应体的 MIME 类型,浏览器会根据该类型决定如何处理
资源
下面是常见文件对应的 mime 类型:
1 | html: 'text/html', |
对于未知的资源类型,可以选择
application/octet-stream
类型,浏览器在遇到该类型的响应时,会对响应体内容进行独立存储,也就是我们常见的下载
效果
响应资源中文乱码问题:
解决方案:在响应头中拼接上 charset=utf-8
即可,不过一般不会对此进行处理,响应回来的资源文件会根据 html 文件中设置的字符集来进行适配
1 | response.setHeader('content-type',type +';charset=utf-8'); |
响应头中设置的字符集权重要大于 html文件中设置的字符集
错误处理
代码演示案例:(使用switch语句来捕获错误)
1 | /** |
GET 和 POST 请求场景及区别
请求场景
GET 请求的情况:
- 在地址栏直接输入 url 访问
- 点击 a 链接
- link 标签引入 css
- script 标签引入 js
- img 标签引入图片
- form 标签中的 method 为 get (不区分大小写)
- ajax 中的 get 请求
POST 请求的情况:
- form 标签中的 method 为 post(不区分大小写)
- AJAX 的 post 请求
区别
- GET 主要用来获取数据,POST 主要用来提交数据
- GET 带参数请求是将参数缀到 URL 之后,在地址栏中输入 url 访问网站就是 GET 请求,POST 带参数请求是将参数放到请求体中
- POST 请求
相对
GET 安全一些,因为在浏览器中参数会暴露在地址栏 - GET 请求大小有限制,一般为 2K,而 POST 请求则没有大小限制
模块化
概念:模块化是指解决一个复杂问题时,自顶向下逐层把系统划分成若干模块的过程。对于整个系统来说,模块是可组合、分解和更换的单元。
编程领域中的模块化,就是遵守固定的规则,把一个大文件拆成独立并互相依赖的多个小模块。
- 提高了代码的复用性
- 提高了代码的可维护性
- 可以实现按需加载
模块化规范的好处:大家都遵守同样的模块化规范写代码,降低了沟通的成本,极大方便了各个模块之间的相互调用,利人利己。
Node.js 中的模块
Node.js遵循了CommonJS模块化规范,CommonJS规定了模块的特性和各模块之间如何相互依赖。
CommonJS规定:
- 每个模块内部,module变量代表当前模块。
- module变量是一个对象,它的exports属性(即module.exports)是对外的接口。
- 加载某个模块,其实是加载该模块的module.exports属性。require()方法用于加载模块。
Node.js中根据模块来源的不同,将模块分为了3大类,分别是:
- 内置模块
内置模块是由Node.js官方提供的。(例如: fs、path、http等) - 自定义模块
用户创建的每个.js
文件,都是自定义模块 - 第三方模块
由第三方开发出来的模块,并非官方提供的内置模块,也不是用户创建的自定义模块,使用前需要先下载(npm)
加载模块
使用require()
方法,可以加载需要的内置模块、用户自定义模块、第三方模块进行使用。require()
方法加载其它模块时,会执行被加载模块中的代码。
模块的加载机制
优先从缓存中加载
模块在第一次加载后会被缓存,这也意味着多次调用 require()
不会导致模块的代码被执行多次。
不论是内置模块、用户自定义模块、还是第三方模块,它们都会优先从缓存中加载,从而提高模块的加载效率。
内置模块的加载机制
内置模块是由Node.js官方提供的模块,内置模块的加载优先级最高。
自定义模块的加载机制
使用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'
模块作用域
在自定义模块中定义的变量、方法等成员,只能在当前模块内被访问,这种模块级别的访问限制,叫做模块作用域。作用:防止了全局变量污染的问题。
共享模块
module
每个.js
自定义模块中都有一个module对象,它里面存储了和当前模块有关的信息:
1 | Module { |
module.exports
自定义模块中,可以使用module.exports
对象,将模块内的成员共享出去,供外界使用。
外界用require()
方法导入自定义模块时,得到的就是module.exports所指向的对象。
注意:使用require()方法导入模块时,导入的结果永远以module.exports指向的对象为准(注意 module.exports 覆盖问题)
exports
由于module.exports单词写起来比较复杂,为了简化向外共享成员的代码,Node提供了exports对象。
默认情况下,exports和module.exports指向同一个对象。最终共享的结果,还是以module.exports指向的对象为准。
npm 包
Node.js中的第三方模块又叫做包。包是由第三方个人或团队开发出来的,免费供所有人使用。
包是基于内置模块封装出来的,提供了更高级、更方便的API,极大的提高了开发效率。
下载包
Node Package Manager(简称npm包管理工具),这个包管理工具集成在 Node.js 中
https://www.npmjs.com
https://registry.npmjs.org
下载命令:
1 | npm install/[i] pakageName |
补充:
- 安装指定版本的包:可以在包名之后,通过
@符号
指定具体的版本。(默认自动安装最新版本的包) - 包的语义化版本规范
- 包的版本号是以“点分十进制”形式进行定义的,总共有三位数字,例如2.24.0
- 第1位数字:大版本;第2位数字:功能版本;第3位数字:Bug修复版本
- 版本号提升的规则则:只要前面的版本号增长了,则后面的版本号归零
初次装包完成后,在项目文件夹下多一个node_modules
的文件夹和package-lock.json
的配置文件。
不要手动修改 node_modules或package-lock.json文件中的任何代码,npm包管理工具会自动维护它们。
NPM 镜像服务器
淘宝在国内搭建了一个服务器,专门把国外官方服务器上的包同步到国内的服务器,然后在国内提供下包的服务。从而极大的提高了下包的速度。
1 | # 查看当前的下包镜像源 |
nrm
nrm 工具,可以更方便的切换下包的镜像源。利用nrm提供的终端命令,可以快速查看和切换下包的镜像源。
1 | # 通过npm包管理器,将nrm安装为全局可用的工具 |
i5ting_toc
i5ting_toc 是一个可以把 md文档 转为 html页面 的小工具
包的分类
- 项目包
安装到项目node_modules
目录中的包,都是项目包 - 全局包
执行npm install命令时,添加-g
参数,会把包安装为全局包
全局包会被安装到:C:\Users\用户名\AppData\Roaming\npm\node_modules
目录
只有工具性质的包,才有全局安装的必要性。因为它们提供了好用的终端命令
1 | npm i 包名 -g # 全局安装指定的包 |
卸载包
运行 npm uninstall
命令,来卸载指定的包:
1 | npm uninstall pakageName |
node_modules
node_modules 文件夹用来存放所有已安装到项目中的包。require()导入第三方包时,就是从这个目录中查找并加载包。
忽略方案
第三方包的体积过大,不方便团队成员之间共享项目源代码。
解决方案:
- 共享时剔除node_modules,在项目根目录中,把 node_modules文件夹,添加到
.gitignore
忽略文件中。 - 创建一个叫做package.json的配置文件,即可用来记录项目中安装了哪些包。
package-lock.json
package-lock.json 配置文件用来记录node_modules目录下的每一个包的下载信息,例如包的名字、版本号、下载地址等。
package.json
npm规定,在项目根目录中,必须提供一个叫做 package.json
的包管理配置文件。用来记录与项目有关的一些配置信息。例如:
- 项目的名称、版本号、描述等
- 项目中都用到了哪些包
- 哪些包只在开发期间会用到
- 那些包在开发和部署时都需要用到
快速创建 package.json
1 | npm init -y |
注意:
npm init -y
命令只能在英文的目录下成功运行!不要使用中文,不能出现空格。- 运行npm install命令安装包的时候,npm包管理工具会自动把包的名称和版本号,记录到package.json中。
dependencies 节点
package.json文件中,有一个dependencies节点,专门用来记录您使用npm install命令安装了哪些包。
一次性安装所有的包
执行 npm install
命令时,npm 包管理工具会先读取 package.json 中的 dependencies 节点,读取到记录的所有依赖包名称和版本号之后,npm包管理工具会把这些包一次性下载到项目中
1 | npm install |
devDependencies 节点
包只在项目开发阶段会用到,在项目上线之后不会用到,则建议把这些包记录到 devDependencies
节点中。
与之对应的,如果某些包在开发和项目上线之后都需要用到,则建议把这些包记录到 dependencies
节点中。
1 | # 安装指定的包,并记录到 devDependencies 节点中 |
规范的包结构
一个规范的包,它的组成结构,必须符合以下3点要求:
- 包必须以单独的目录而存在
- 包的顶级目录下要必须包含
package.json
这个包管理配置文件 - package.json中必须包含
name
,version
,main
这三个属性,分别代表包的名字、版本号、包的入口。
npm 包开发
包的基本结构
- 创建文件夹作为包的根目录
- 在文件夹中,新建如下三个文件:
- package.json(包管理配置文件)
- index.js (包的入口文件)
- README.md(包的说明文档)
- 初始化 package.json
1
2
3
4
5
6
7
8
9
10
11
12{
"name": "PakageName",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": ["xxx","xxx"],
"author": "",
"license": "ISC"
}
发布包
- 访问
https://www.npmjs.com/
网站注册账号 - 登录账号
- npm 账号注册完成后,可以在终端中执行
npm login
命令,依次输入用户名、密码、邮箱后,即可登录成功 - 注意:在运行
npm login
命令之前,必须先把下包的服务器地址切换为 npm的官方服务器。否则会导致发布包失败!
- npm 账号注册完成后,可以在终端中执行
- 将终端切换到包的根目录,运行
npm publish
命令,即可将包发布到 npm上(注意:包名不能雷同)。
删除包
运行 npm unpublish 包名--force
命令,即可从 npm 删除已发布的包。
注意:
- npm unpublish 命令只能删除72小时以内发布的包
- npm unpublish 删除的包,在24小时内不允许重复发布
Express
初识 Express
简介
官方文档:https://expressjs.com/zh-cn/
Express 的作用和 Node.js内置的http模块类似,是基于内置的http模块进一步封装出来的,专门用来创建Web服务器,能够极大的提高开发效率。
Express 的本质:就是一个npm上的第三方包,提供了快速创建Web服务器的便捷方法。
安装
1 | npm install express@4.17.1 |
创建基本的Web服务器
1 | const express = require('express') |
监听 GET 请求
通过 app.get()
方法,可以监听客户端的GET请求
1 | /* |
监听POST请求
通过 app.post()
方法,可以监听客户端的POST请求
1 | /* |
响应客户端
通过 response.send()
方法,可以把处理好的内容,发送给客户端
获取 URL 中携带的查询参数
通过 request.query
对象,可以访问到客户端通过查询字符串的形式,发送到服务器的参数:
1 | app.get('请求URL', function(request, response) { |
获取 URL 中的动态参数
通过 request.params
对象,可以访问到URL中,通过 :
匹配到的动态参数:
1 | app.get('请求URL:动态参数', function(request, response) { |
express.static()
express.static()
可以非常方便地创建一个静态资源服务器
Express 在指定的静态目录中查找文件,并对外提供资源的访问路径。存放静态文件的目录名不会出现在URL中。
1 | // 将 dir 目录下的文件对外开放访问 |
挂载路径前缀:
如果希望在托管的静态资源访问路径之前,挂载路径前缀,则可以使用如下的方式:
1 | // 访问方式: http://xxxx/public/dir/.. |
nodemoon
nodemoon 能够监听项目文件的变动,当代码被修改后,nodemon会自动帮我们重启项目,极大方便了开发和调试。
安装
1 | npm install -g nodemon |
使用
将 node
命令替换为 nodemon
命令,使用 nodemon xxx.js
来启动项目
Express 路由
官方文档
Express中,路由指的是客户端的请求与服务器处理函数之间的映射关系。
基本路由
Express中的路由分3部分组成,分别是请求的类型
、请求的URL地址
、处理函数
1 | /* |
路由匹配
请求到达服务器之后,需要先经过路由的匹配,匹配时,会按照路由的先后顺序进行匹配,如果请求类型和请求的URL同时匹配成功,才会调用对应的处理函数。
基本使用
1 | // GET method route |
模块化路由
为了方便对路由进行模块化的管理,Express不建议将路由直接挂载到app
上,而是推荐将路由抽离为单独的模块。
抽离步骤:
- 创建路由模块对应的
.js
文件 - 调用
express.Router()
函数创建路由对象 - 向路由对象上挂载具体的路由
- 使用
module.exports
向外共享路由对象 - 使用
app.use()
函数注册路由模块- app.use() 函数作用:用来注册全局中间件
定义路由:
1 | // router.js |
使用路由:
1 | // server.js |
Express 中间件
官方文档
中间件(Middleware) 特指业务流程的中间处理环节。
概念
当一个请求到达Express的服务器之后,可以连续调用多个中间件,从而对这次请求进行预处理。
Express 的中间件,本质上就是一个 function 处理函数
,中间件函数的形参列表中,必须包含 next
参数
next 函数
next 函数是实现多个中间件连续调用的关键,它表示把流转关系转交给下一个中间件或路由。
中间件函数
1 | var mw = function (req, res, next) { |
中间件的作用
多个中间件之间,共享同一份 req 和 res 。
基于这样的特性,我们可以在上游的中间件中,统一为 req 或 res 对象添加自定义的属性或方法,供下游的中间件或路由进行使用。
全局生效的中间件
客户端发起的任何请求,到达服务器之后,都会触发的中间件,叫做全局生效的中间件。
通过调用 app.use(中间件函数)
,即可定义一个全局生效的中间件
1 | // 全局生效的中间件 |
补充:可以使用 app.use()
连续定义多个全局中间件。客户端请求到达服务器之后,会按照中间件定义的先后顺序依次进行调用。
局部生效的中间件
定义在路由参数中的中间是局部中间件
1 | var mw = function (req, res, next) { |
补充:可以在路由中,通过如下两种等价的方式,使用多个局部中间件:
1 | // 写法一 |
中间件注意事项
- 一定要在路由之前注册中间件
- 客户端发送过来的请求,可以连续调用多个中间件进行处理
- 执行完中间件的业务代码之后,必须调用next()函数
- 为了防止代码逻辑混乱,调用next()函数后不要再写额外的代码
- 连续调用多个中间件时,多个中间件之间,共享req和res对象
中间件分类
Express 官方把常见的中间件用法,分成 5大类
- 应用级别的中间件
- 路由级别的中间件
- 错误级别的中间件
- Express 内置的中间件
- 第三方的中间件
应用级别的中间件
通过 app.use()
或 app.get()
或 app.post()
绑定到app实例上的中间件,叫做应用级别的中间
路由级别的中间件
绑定到 express.Router()
实例上的中间件,叫做路由级别的中间件,路由级别中间件绑定到router实例上
错误级别的中间件
错误级别中间件的作用:专门用来捕获整个项目中发生的异常错误,从而防止项目异常崩溃的问题
格式:错误级别中间件的 function 处理函数中,必须有4个形参,形参顺序从前到后,分别是(err,req,res,next)
注意:错误级别的中间件,必须注册在所有路由之后!
Express 内置的中间件
Express 内置了3个常用的中间件,极大的提高了Express项目的开发效率和体验:
- express.static 快速托管
静态资源
的内置中间件 - express.json 解析
JSON
格式的请求体数据 - express.urlencoded 解析
URL-encoded
格式的请求体数据
第三方的中间件
非 Express 官方内置的,而是由第三方开发出来的中间件,叫做第三方中间件。
可以按需下载并配置第三方中间件,从而提高项目的开发效率。
使用第三方中间件:
- 运行
npm install 中间件名称
安装中间件 - 使用
require
导入中间件 - 调用
app.use()
注册并使用中间件
Express 写接口
解决跨域
解决接口跨域问题的方案主要有两种:
- CORS(推荐)
- JSONP(只支持GET请习)
CORS 响应头
Access-Control-Allow-Origin
1 | /* |
Access-Control-Allow-Headers
默认情况下,CORS仅支持客户端向服务器发送如下的9个请求头:
- Accept
- Accept-Language
- Content-Language
- DPR
- Downlink
- Save-Data
- Viewport-Width
- Width
- Content-Type
如果客户端向服务器发送了额外的请求头信息则需要在服务器端,通过 Access-Control-Allow-Headers
对额外的请求头进行声明,否则这次请求会失败!
Access-Control-Allow-Methods
默认情况下,CORS仅支持客户端发起 GET、POST、HEAD 请求。
如果客户端希望通过 PUT
、DELETE
等方式请求服务器的资源,则需要在服务器端,通过 Access-Control-Alow-Methods
来指明实际请求所允许使用的HTTP方法。
1 | // 只允许 POST、GET、DELETE、HEAD请求方法 |
CORS 请求分类
根据请求方式和请求头的不同,可以将CORS的请求分为两大类,分别是:简单请求和预检请求
简单请求和预检请求的区别:
- 简单请求的特点:客户端与服务器之间只会发生一次请求。
- 预检请求的特点:客户端与服务器之间会发生两次请求,OPTION预检请求成功之后,才会发起真正的请求。
简单请求
- 请求方式:GET、POST、HEAD三者之一
- HTTP 头部信息不超过以下几种字段:无自定义头部字段、Accept、Accept-Language、Content-Language、DPR、Downlink、Save-Data、Viewport-Width、Width、Content-Type
预检请求
- 请求方式为GET、POST、HEAD之外的请求 Method 类型
- 请求头中包含自定义头部字段
- 向服务器发送了
application/json
格式的数据
在浏览器与服务器正式通信之前,浏览器会先发送OPTION请求
进行预检,以获知服务器是否允许该实际请求,所以这一次的OPTION请求称为“预检请求”。
服务器成功响应预检请求后,才会发送真正的请求,并且携带真实数据。
cors中间件解决跨域
浏览器的同源安全策略默认会阻止网页跨域
获取资源。但如果接口服务器配置了CORS相关的HTTP响应头,就可以解除浏览器端的跨域访问限制。(CORS主要在服务器端进行配置。客户端浏览器无须做任何额外的配置。)
cors 是 Express 的一个第三方中间件。通过安装和配置cors中间件,可以很方便地解决跨域问题。
使用步骤:
- 运行
npm install cors
安装中间件 - 使用
const cors = require('cors')
导入中间件 - 在路由之前调用
app.use(cors())
配置中间件
JSONP
JSONP 概念:
浏览器端通过<script>
标签的src
属性,请求服务器上的数据,同时,服务器返回一个函数的调用。这种请求数据的方式叫做JSONP。
特点:
- JSONP 不属于真正的Ajax请求,因为它没有使用
XMLHttpRequest
这个对象。 - JSONP 仅支持GET请求,不支持 POST、PUT、DELETE 等请求。
注意事项:
如果项目中已经配置了CORS跨域资源共享,为了防止冲突,必须在配置CORS中间件之前声明JSONP的接口
数据库与身份验证
数据库的基本概念
数据库的数据组织结构
在传统型数据库中,数据的组织结构分为:
- 数据库(database)
- 数据表(table)
- 数据行(row)
- 字段(field)
安装并配置 MySQL
MySQL 相关软件
- MySQL Server: 专门用来提供数据存储和服务的软件
- MySQL Workbench: 可视化的MySQL管理工具
MySQL的基本使用
创建数据表
DataType 数据类型:
- int 整数
- varchar(len)字符串
- tinyint(1)布尔值
字段的特殊标识:
- PK(Primary Key) 主键、唯一标识
- NN(Not Null) 值不允许为空
- UQ(Unique) 值唯一
- AI(Auto Increment) 值自动增长
SQL 介绍
SQL(英文全称:Structured Query Language)是结构化查询语言,专门用来访问和处理数据库的编程语言。能够让我们以编程的形式,操作数据库里面的数据。
- SQL是一门数据库编程语言(SQL语句中的关键字对大小写不敏感。)
- 使用SQL语言编写出来的代码,叫做SQL语句
- SQL语言只能在关系型数据库中使用(例如MySQL、Oracle、SQL Server)。非关系型数据库(例如 Mongodb)不支持SQL语言。
SQL 语句
- 查询数据(select)
- 插入数据(insert into)
- 更新数据(update)
- 删除数据(delete)
where条件、and和or运算符、orderby排序、count(*)函数
SELECT
SELECT 语句用于从表中查询数据。执行的结果被存储在一个结果表中(称为结果集)
语法:
1 | -- 从 FROM 指定的【表中】,查询出【所有的】数据。*表示【所有列】 |
INSERT INTO
INSERT INTO 语句用于向数据表中插入新的数据行
1 | -- 列和值要一一对应,多个列和多个值之间,使用英文的逗号分隔 |
UPDATE
UPDATE 语句用于修改表中的数据
1 | -- 用 UPDATE 指定要更新哪个表中的数据 用 SET 指定列对应的新值 用 WHERE 指定更新的条件 |
DELETE
DELETE 语句用于删除表中的行
1 | -- 从指定的表中,根据 WHERE 条件,删除对应的数据行 |
WHERE 子句
WHERE 子句用于限定选择的标准。在 SELECT、UPDATE、DELETE 语句中,皆可使用WHERE子句来限定选择的标准。
AND 和 OR 运算符
AND 和 OR 可在 WHERE 子语句中把两个或多个条件结合起来。AND 表示必须同时满足多个条件,OR 表示只要满足任意一个条件即可。
ORDER BY
ORDER BY 语句用于根据指定的列对结果集进行排序。
- ORDER BY 语句默认按照升序(
ASC
)对记录进行排序。 - 可以使用
DESC
关键字按照降序对记录进行排序。 - 多重排序
COUNT(*) 函数
COUNT(*) 函数 用于返回查询结果的总数据条数
1 | SELECT COUNT(*) FROM 表名称 |
Express 中操作 MySQL
安装与配置mysql模块
安装
1 | npm install mysql |
配置
1 | var mysql = require('mysql'); |
前后端的身份认证
Web 开发模式
服务端渲染
服务端渲染的概念:服务器发送给客户端的HTML页面,是在服务器通过字符串的拼接,动态生成的
优点:
- 前端耗时少。因为服务器端负责动态生成HTML内容,浏览器只需要直接渲染页面即可。尤其是移动端,更省电。
- 有利于SEO。因为服务器端响应的是完整的HTML页面内容,所以爬虫更容易爬取获得信息,更有利于SEO。
缺点:
- 占用服务器端资源。即服务器端完成HTML页面内容的拼接,如果请求较多,会对服务器造成一定的访问压力。
- 不利于前后端分离,开发效率低。使用服务器端渲染,则无法进行分工合作,尤其对于前端复杂度高的项目,不利于项目高效开发。
前后端分离
前后端分离的概念:前后端分离的开发模式,依赖于Ajax技术的广泛应用。简而言之,前后端分离的Web开发模式,就是后端只负责提供API接口,前端使用Ajax调用接口的开发模式。
优点:
- 开发体验好。前端专注于UI页面的开发,后端专注于api的开发,且前端有更多的选择性。
- 用户体验好。Ajax技术的广泛应用,极大的提高了用户的体验,可以轻松实现页面的局部刷新。
- 减轻了服务器端的渲染压力。因为页面最终是在每个用户的浏览器中生成的。
缺点:
- 不利于SEO。因为完整的HTML页面需要在客户端动态拼接完成,所以爬虫对无法爬取页面的有效信息。(解决方案:利用Vue、React等前端框架的
SSR
(server side render)技术能够很好的解决SEO问题!)
身份认证
不同开发模式下的身份认证:
对于服务端渲染
和前后端分离
这两种开发模式来说,分别有着不同的身份认证方案:
- 服务端渲染推荐使用
Session 认证机制
- 前后端分离推荐使用
JWT 认证机制
Session 认证机制
HTTP协议的无状态性
HTTP协议的无状态性,指的是客户端的每次HTTP请求都是独立的,连续多个请求之间没有直接的关系,服务器不会主动保留每次HTTP请求的状态。
解决 HTTP 无状态的限制
客户端第一次请求服务器的时候,服务器通过响应头的形式,向客户端发送一个身份认证的Cookie,客户端会自动将Cookie保存在浏览器中。随后,当客户端浏览器每次请求服务器的时候,浏览器会自动将身份认证相关的Cookie,通过请求头的形式发送给服务器,服务器即可验明客户端的身份。
Cookie
Cookie 是存储在用户浏览器中的一段不超过4KB的字符串。它由一个名称(Name)、一个值(Value)和其它几个用于控制 Cookie 有效期、安全性、使用范围 的可选属性组成。
不同域名下的 Cookie 各自独立,每当客户端发起请求时,会自动把当前域名下所有未过期的 Cookie 一同发送到服务器。
Cookie 特性
- 自动发送
- 域名独立
- 过期时限
- 4KB限制
Cookie 安全性
由于Cookie是存储在浏览器中的,而且浏览器也提供了读写Cookie的API,因此Cookie很容易被伪造,不具有安全性。因此不建议服务器将重要的隐私数据,通过Cookie的形式发送给浏览器。
注意:千万不要使用Cookie存储重要且隐私的数据!比如用户的身份信息、密码等。
Session 的工作原理
Session 认证的局限性
Session认证机制需要配合Cookie才能实现。由于Cookie默认不支持跨域访问,所以,当涉及到前端跨域请求后端接口的时候,需要做很多额外的配置,才能实现跨域Session认证。
注意:
- 当前端请求后端接口不存在跨域问题的时候,推荐使用Session身份认证机制。
- 当前端需要跨域请求后端接口的时候,不推荐使用Session身份认证机制,推荐使用
JWT
认证机制。
Express 中使用 Session 认证
express-session:https://www.npmjs.com/package/express-session
JWT 认证机制
JWT (英文全称:JSON Web Token) 是目前最流行的跨域认证解决方案。
JWT 工作原理
原理:用户的信息通过Token字符串的形式,保存在客户端浏览器中。服务器通过还原Token字符串的形式来认证用户的身份。
JWT 组成部分
JWT 通常由三部分组成,分别是 Header(头部)
、Payload(有效荷载)
、Signature(签名)
。三者之间使用英文的 .
分隔
Payload 部分才是真正的用户信息,它是用户信息经过加密之后生成的字符串。
Header 和 Signature 是安全性相关的部分,只是为了保证 Token 的安全性。
JWT 使用方式
客户端收到服务器返回的 JWT 之后,通常会将它储存在 localStorage 或 sessionStorage 中。
此后,客户端每次与服务器通信,都要带上这个JWT的字符串,从而进行身份认证。推荐的做法是把 JWT 放在 HTTP请求头的 Authorization
字段中,格式如下:
Express 中使用 JWT
jsonwebtoken:https://www.npmjs.com/package/jsonwebtoken
express-jwt:https://www.npmjs.com/package/express-jwt
jsonwebtoken 用于生成JWT字符串
express-jwt 用于将JWT字符串解析还原成JSON对象