Node.js 简介

Node.js是一个基于ChromeV8引擎的 JavaScript 运行环境

20220825135418

Node.js 可以做什么

Node.js作为一个JavaScript的运行环境,仅仅提供了基础的功能和APl。
基于Node.js提供的这些基础能,很多强大的框架和工具如雨后春笋,层出不穷:

  1. 基于Express框架(http://www.expressjs.com.cn/),可以快速构建Web应用
  2. 基于Electron框架(https://electronjs.org/),可以构建跨平台的桌面应用
  3. 基于restify框架(http://restify.com/),可以快速构建API接口项目
  4. 读写和操作数据库、创建实用的命令行工具辅助前端开发、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)

20230316140436

追加写入

应用场景:写入日志

1
fs.appendFile(path, data[, options], callback)
1
2
3
4
5
6
import { appendFile } from 'node:fs';

appendFile('message.txt', 'data to append', (err) => {
if (err) throw err;
console.log('The "data to append" was appended to file!');
});

补充:

  • 如果需要在写入的文件中插入换行可以使用 \r\n
  • fs.writeFile 也可以实现追加写入:在第三个配置项添加:{flag: 'a'}

流式写入

程序打开一个文件是需要消耗资源的 ,流式写入可以减少打开关闭文件的次数。
应用场景:流式写入方式适用于大文件写入或者频繁写入的场景, writeFile 适合于 写入频率较低的场景

1
fs.createWriteStream(path[, options])

20230316143652

文件读取

读取文件应用场景:

  • 电脑开机
  • 程序运行
  • 编辑器打开文件
  • 查看图片
  • 播放视频
  • 播放音乐
  • Git查看日志
  • 上传文件
  • 查看聊天记录

读取文件(一次性读取)

1
fs.readFile(path[, options], callback)

20230316144510

流式读取(效率高占用空间小)

1
fs.createReadStream(path[, options])
1
2
3
4
5
6
7
8
9
10
11
12
//创建读取流对象
let rs = fs.createReadStream('./观书有感.txt');
//每次取出 64k 数据后执行一次 data 回调
rs.on('data', data => {
console.log(data);
console.log(data.length);
});
//读取完毕后, 执行 end 回调
rs.on('end', () => {
console.log('读取完成')
})

文件移动与重命名

可以使用 renamerenameSync 来移动或重命名 文件或文件夹

1
2
fs.rename(oldPath, newPath, callback)
fs.renameSync(oldPath, newPath)
1
2
3
4
5
6
fs.rename('./观书有感.txt', './论语/观书有感.txt', (err) =>{
if(err) throw err;
console.log('移动完成')
});

fs.renameSync('./座右铭.txt', './论语/我的座右铭.txt');

文件删除

可以使用 unlink 或 unlinkSync 来删除文件

1
2
fs.unlink(path, callback)
fs.unlinkSync(path)
1
2
3
4
5
6
const fs = require('fs');
fs.unlink('./test.txt', err => {
if(err) throw err;
console.log('删除成功');
});
fs.unlinkSync('./test2.txt');

补充:fs.rm 方法也可以实现删除的操作

文件夹操作

可以对文件夹进行创建、读取、删除等操作

创建文件夹

1
2
fs.mkdir(path[, options], callback)
fs.mkdirSync(path[, options])
1
2
3
4
5
6
7
8
9
10
11
12
//异步创建文件夹
fs.mkdir('./page', err => {
if(err) throw err;
console.log('创建成功');
});
//递归异步创建
fs.mkdir('./1/2/3', {recursive: true}, err => {
if(err) throw err;
console.log('递归创建成功');
});
//递归同步创建文件夹
fs.mkdirSync('./x/y/z', {recursive: true});

读取文件夹

1
2
fs.readdir(path[, options], callback)
fs.readdirSync(path[, options])
1
2
3
4
5
6
7
8
//异步读取
fs.readdir('./论语', (err, data) => {
if(err) throw err;
console.log(data);
});
//同步读取
let data = fs.readdirSync('./论语');
console.log(data);

删除文件夹

更推荐使用 fs.rm 方法来删除文件/文件夹

1
2
fs.rmdir(path[, options], callback)
fs.rmdirSync(path[, options])
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//异步删除文件夹
fs.rmdir('./page', err => {
if(err) throw err;
console.log('删除成功');
});
//异步递归删除文件夹
fs.rmdir('./1', {recursive: true}, err => {
if(err) {
console.log(err);
}
console.log('递归删除')
});
//同步递归删除文件夹
fs.rmdirSync('./x', {recursive: true})

查看资源状态

可以使用 statstatSync 来查看资源的详细信息

1
2
fs.stat(path[, options], callback)
fs.statSync(path[, options])
1
2
3
4
5
6
7
//异步获取状态
fs.stat('./data.txt', (err, data) => {
if(err) throw err;
console.log(data);
});
//同步获取状态
let data = fs.statSync('./data.txt');

结果值对象结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Stats {
dev: 742588151,
mode: 33206,
nlink: 1,
uid: 0,
gid: 0,
rdev: 0,
blksize: 4096,
ino: 1125899906844979,
size: 20,
blocks: 0,
atimeMs: 1678952805634.295,
mtimeMs: 1661441206451.9998,
ctimeMs: 1661441206451.6675,
birthtimeMs: 1661441170990.5757,
atime: 2023-03-16T07:46:45.634Z,
mtime: 2022-08-25T15:26:46.452Z,
ctime: 2022-08-25T15:26:46.452Z,
birthtime: 2022-08-25T15:26:10.991Z
}

路径

fs 模块对资源进行操作时,路径的写法有两种:

  • 相对路径
    • ./座右铭.txt 当前目录下的座右铭.txt
    • 座右铭.txt 等效于上面的写法
    • ../座右铭.txt 当前目录的上一级目录中的座右铭.txt
  • 绝对路径
    • D:/Program Files windows 系统下的绝对路径
    • /usr/bin Linux 系统下的绝对路径

相对路径中所谓的 当前目录 ,指的是 命令行的工作目录 ,而并非是文件的所在目录
所以当命令行的工作目录与文件所在目录不一致时,会出现一些 BUG

__dirname

dirname 与 require 类似,都是 Node.js 环境中的’全局’变量 dirname 保存着 当前文件所在目录的绝对路径 ,可以使用 __dirname 与文件名拼接成绝对路径

1
2
let data = fs.readFileSync(__dirname + '/data.txt');
console.log(data);

使用 fs 模块的时候,尽量使用 __dirname 将路径转化为绝对路径,这样可以避免相对路径产生的Bug

path 路径模块

API Description
path.resolve 拼接规范的绝对路径【常用】
path.sep 获取操作系统的路径分隔符
path.parse 解析路径并返回对象
path.basename 获取路径的基础名称
path.dirname 获取路径的目录名
path.extname 获得路径的扩展名

代码演示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const path = require('path');
//获取路径分隔符
console.log(path.sep);
//拼接绝对路径
console.log(path.resolve(__dirname, 'test'));
//解析路径
let pathname = 'D:/program file/nodejs/node.exe';
console.log(path.parse(pathname));
//获取路径基础名称
console.log(path.basename(pathname))
//获取路径的目录名
console.log(path.dirname(pathname));
//获取路径的扩展名
console.log(path.extname(pathname));

http 模块

HTTP 协议

HTTP(hypertext transport protocol)协议;中文叫超文本传输协议,是一种基于TCP/IP的应用层通信协议,这个协议详细规定了 浏览器 和万维网 服务器 之间互相通信的规则。

协议中主要规定了两个方面的内容

  • 客户端:用来向服务器发送数据,可以被称之为请求报文
  • 服务端:向客户端返回数据,可以被称之为响应报文

请求报文的组成

  1. 请求行
  2. 请求头
  3. 空行
  4. 请求体

20230316212141

请求行

请求行包含:

  1. 请求方法
  2. URL
  3. HTTP版本号

20230316212629

常见请求方法:

方法 Description
GET 主要用于获取数据
POST 主要用于新增数据
PUT/PATCH 主要用于更新数据
DELETE 主要用于删除数据
HEAD/OPTIONS/CONNECT/TRACE 使用相对较少

URL 的构成:

  1. 协议名
  2. 主机名
  3. 端口号
  4. 路径
  5. 查询字符串

20230316213122

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}

响应报文的组成

  1. 响应行
  2. 响应头
  3. 空行
  4. 响应体

20230316214638

响应行

响应行包含:

  1. HTTP版本号
  2. 响应状态码
  3. 响应状态的描述

20230316214839

常见状态码:

状态码 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

响应头

常见响应头的结构:

  1. Cache-Control:缓存控制 private 私有的,只允许客户端缓存数据
  2. Connection 链接设置
  3. Content-Type:text/html;charset=utf-8 设置响应体的数据类型以及字符集,响应体为html,字符集utf-8
  4. Content-Length:响应体的长度,单位为字节

响应体

常见响应体的构成:

  1. HTML
  2. CSS
  3. JavaScript
  4. 图片
  5. 视频
  6. JSON

创建基本服务

  1. 导入http模块
  2. 创建web服务器实例
  3. 为服务器实例绑定request事件,监听客户端的请求
  4. 启动服务器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 1. 导入 http 模块
const http = require('http')
// 2. 创建服务对象 create 创建 server 服务
// request 意为请求. 是对请求报文的封装对象, 通过 request 对象可以获得请求报文的数据
// response 意为响应. 是对响应报文的封装对象, 通过 response 对象可以设置响应报文
const server = http.createServer((request, response) => {
response.end('Hello HTTP server');
});
// 3. 为服务器实例绑定 request 事件,监听客户端的请求
server.on('request', function (req, res) {
console.log('Someone visit our web server.')
})
// 4. 监听端口,启动服务器
server.listen(8080, function () {
console.log('server running at http://127.0.0.1:8080')
})

http.createServer 里的回调函数的执行时机: 当接收到 HTTP 请求的时候,就会执行

request 对象

createServer 方法的回调函数的第一个参数是一个request对象,拥有以下属性:

  1. url : 发出请求的网址。
  2. method : HTTP请求的方法。
  3. headers : HTTP请求的所有HTTP头信息。

response 对象

createServer 方法的回调函数的第二个参数是一个response对象,常用方法:

  1. response.end() 方法用来写入HTTP回应的具体内容,以及回应完成后关闭本次对话

补充:

当调用 response.end() 方法,向客户端发送中文内容的时候,会出现乱码问题,此时,需要手动设置内容的编码格式:

1
response.setHeader('Content-Type','text/html;charset=utf-8')

注意事项

  1. 命令行 ctrl + c 停止服务
  2. 当服务启动后,更新代码必须重启服务才能生效
  3. 端口号被占用
    1. 关闭当前正在运行监听端口的服务 ( 使用较多 )
    2. 修改其他端口号
  4. HTTP 协议默认端口是 80 。HTTPS 协议的默认端口是 443, HTTP 服务开发常用端口有 3000,
    8080,8090,9000 等

如果端口被其他程序占用,可以使用资源监视器找到占用端口的程序的PID,然后使用任务管理器关闭对应的PID程序

20230317161208

浏览器查看 HTTP 报文

查看请求行与请求头

20230317164258

查看请求体

20230317164337

查看 URL 查询字符串

20230317164841

查看响应行与响应头

20230317164916

查看响应体

20230317164944

获取 HTTP 请求报文

想要获取请求的数据,需要通过 request 对象

20230317170712

注意事项:

  1. request.url 只能获取路径以及查询字符串,无法获取 URL 中的域名以及协议的内容
  2. request.headers 将请求信息转化成一个对象,并将属性名都转化成了『小写』
  3. 关于路径:如果访问网站的时候,只填写了 IP 地址或者是域名信息,此时请求的路径为『 / 』
  4. 关于 favicon.ico:这个请求是属于浏览器自动发送的请求

提取HTTP报文

代码演示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//1. 导入 http 模块
const http = require('http');

//2. 创建服务对象
const server = http.createServer((request, response) => {
//获取请求的方法
// console.log(request.method);
//获取请求的 url
// console.log(request.url);// 只包含 url 中的路径与查询字符串
//获取 HTTP 协议的版本号
// console.log(request.httpVersion);
//获取 HTTP 的请求头
// console.log(request.headers.host);
response.end('http'); //设置响应体
});

//3. 监听端口, 启动服务
server.listen(9000, () => {
console.log('服务已经启动....')
});

提取HTTP报文的请求体

代码演示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//1. 导入 http 模块
const http = require('http');

//2. 创建服务对象
const server = http.createServer((request, response) => {
//1. 声明一个变量
let body = '';
//2. 绑定 data 事件
request.on('data', chunk => {
body += chunk;
})
//3. 绑定 end 事件
request.on('end', () => {
console.log(body);
//响应
response.end('Hello HTTP');
});
});

//3. 监听端口, 启动服务
server.listen(9000, () => {
console.log('服务已经启动....')
});

提取HTTP报文中URL的路径与查询字符串

方式一

代码演示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//导入 http 模块
const http = require('http');
//1. 导入 url 模块
const url = require('url');

//创建服务对象
const server = http.createServer((request, response) => {
//2. 解析 request.url
// console.log(request.url);
let res = url.parse(request.url, true);
//路径
let pathname = res.pathname;
//查询字符串
let keyword = res.query.keyword;

response.end('url');
});

//监听端口, 启动服务
server.listen(9000, () => {
console.log('服务已经启动....')
});

方式二(推荐)

代码演示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//导入 http 模块
const http = require('http');

//创建服务对象
const server = http.createServer((request, response) => {
//实例化 URL 的对象
// let url = new URL('/search?a=100&b=200', 'http://127.0.0.1:9000');
let url = new URL(request.url, 'http://127.0.0.1');
//输出路径
console.log(url.pathname);
//输出 keyword 查询字符串【注意此处使用 get 方法获取】
console.log(url.searchParams.get('keyword'));
response.end('url new');
});

//监听端口, 启动服务
server.listen(9000, () => {
console.log('服务已经启动....')
});

设置 HTTP 响应报文

20230317174357

代码演示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//导入 http 模块
const http = require('http');

//创建服务对象
const server = http.createServer((request, response) => {
//1. 设置响应状态码
// response.statusCode = 203;
// response.statusCode = 404;
//2. 响应状态的描述【用的少】
// response.statusMessage = 'iloveyou';
//3. 响应头
// response.setHeader('content-type', 'text/html;charset=utf-8');
// response.setHeader('Server', 'Node.js');
// response.setHeader('myHeader', 'test test test');
// response.setHeader('test', ['a','b','c']);
//4. 响应体的设置【write 和 end 方法都可以用于设置响应体】
//4.1 write 和 end 的结合使用 响应体相对分散
// response.write('love');
// response.write('love');
// response.write('love');
// response.end(); //每一个请求,在处理的时候必须要执行 end 方法的
//4.2 单独使用 end 方法 响应体相对集中
response.end('xxx');
});

//监听端口, 启动服务
server.listen(9000, () => {
console.log('服务已经启动....')
});

网页资源的基本加载过程

网页资源的加载都是循序渐进的,首先获取 HTML 的内容, 然后解析 HTML 在发送其他资源的请求,如 CSS,Javascript,图片等。

静态资源服务

静态资源是指 内容长时间不发生改变的资源 ,例如图片,视频,CSS 文件,JS文件,HTML文件,字体文件等。
动态资源是指 内容经常更新的资源 ,例如百度首页,网易首页,京东搜索列表页面等。

搭建静态资源服务小案例

1
2
3
4
5
6
7
需求:

创建一个 HTTP 服务,端口为 9000,满足如下需求:

- GET /index.html 响应 page/index.html 的文件内容
- GET /css/app.css 响应 page/css/app.css 的文件内容
- GET /images/logo.png 响应 page/images/logo.png 的文件内容

代码目录结构:

1
2
3
4
5
6
7
8
9
10
├── page
│ ├── css
│ │ └── app.css
│ ├── images
│ │ └── logo.png
│ ├── index.html
│ └── js
│ ├── abc.js
│ └── app.js
└── server.js

server.js 核心代码:

20230317215717

网站根目录或静态资源目录

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

网页中使用 URL 的场景小结:(包括但不限于如下场景)

  • a 标签 href
  • link 标签 href
  • script 标签 src
  • img 标签 src
  • video audio 标签 src
  • form 中的 action
  • AJAX 请求中的 URL

设置资源类型(mime类型)

媒体类型(通常称为 Multipurpose Internet Mail Extensions 或 MIME 类型 )是一种标准,用来表示文档、
文件或字节流的性质和格式。

1
2
mime 类型结构: [type]/[subType]
例如: text/html text/css image/jpeg image/png application/json

HTTP 服务可以设置响应头 Content-Type 来表明响应体的 MIME 类型,浏览器会根据该类型决定如何处理
资源

下面是常见文件对应的 mime 类型:

1
2
3
4
5
6
7
8
9
html: 'text/html',
css: 'text/css',
js: 'text/javascript',
png: 'image/png',
jpg: 'image/jpeg',
gif: 'image/gif',
mp4: 'video/mp4',
mp3: 'audio/mpeg',
json: 'application/json'

对于未知的资源类型,可以选择 application/octet-stream 类型,浏览器在遇到该类型的响应时,会对响应体内容进行独立存储,也就是我们常见的 下载 效果

响应资源中文乱码问题:

解决方案:在响应头中拼接上 charset=utf-8 即可,不过一般不会对此进行处理,响应回来的资源文件会根据 html 文件中设置的字符集来进行适配

1
response.setHeader('content-type',type +';charset=utf-8');

响应头中设置的字符集权重要大于 html文件中设置的字符集

错误处理

官方文档:https://nodejs.cn/api/errors.html

代码演示案例:(使用switch语句来捕获错误)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
/**
* 创建一个 HTTP 服务,端口为 9000,满足如下需求
* GET /index.html 响应 page/index.html 的文件内容
* GET /css/app.css 响应 page/css/app.css 的文件内容
* GET /images/logo.png 响应 page/images/logo.png 的文件内容
*/
//导入 http 模块
const http = require('http');
const fs = require('fs');
const path = require('path');
//声明一个变量
let mimes = {
html: 'text/html',
css: 'text/css',
js: 'text/javascript',
png: 'image/png',
jpg: 'image/jpeg',
gif: 'image/gif',
mp4: 'video/mp4',
mp3: 'audio/mpeg',
json: 'application/json'
}

//创建服务对象
const server = http.createServer((request, response) => {
if(request.method !== 'GET'){
response.statusCode = 405;
response.end('<h1>405 Method Not Allowed</h1>');
return;
}
//获取请求url的路径
let {pathname} = new URL(request.url, 'http://127.0.0.1');
//声明一个变量
let root = __dirname + '/page';
// let root = __dirname + '/../';
//拼接文件路径
let filePath = root + pathname;
//读取文件 fs 异步 API
fs.readFile(filePath, (err, data) => {
if(err){
console.log(err);
//设置字符集
response.setHeader('content-type','text/html;charset=utf-8');
//判断错误的代号
switch(err.code){
case 'ENOENT':
response.statusCode = 404;
response.end('<h1>404 Not Found</h1>');
case 'EPERM':
response.statusCode = 403;
response.end('<h1>403 Forbidden</h1>');
default:
response.statusCode = 500;
response.end('<h1>Internal Server Error</h1>');
}

return;
}
//获取文件的后缀名
let ext = path.extname(filePath).slice(1);
//获取对应的类型
let type = mimes[ext];
if(type){
//匹配到了 text/html;charset=utf-8
if(ext === 'html'){
response.setHeader('content-type', type + ';charset=utf-8');
}else{
response.setHeader('content-type', type);
}
}else{
//没有匹配到
response.setHeader('content-type', 'application/octet-stream');
}
//响应文件内容
response.end(data);
})

});

//监听端口, 启动服务
server.listen(9000, () => {
console.log('服务已经启动....')
});

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 请求则没有大小限制

模块化

概念:模块化是指解决一个复杂问题时,自顶向下逐层把系统划分成若干模块的过程。对于整个系统来说,模块是可组合、分解和更换的单元

编程领域中的模块化,就是遵守固定的规则,把一个大文件拆成独立并互相依赖的多个小模块

  1. 提高了代码的复用性
  2. 提高了代码的可维护性
  3. 可以实现按需加载

模块化规范的好处:大家都遵守同样的模块化规范写代码,降低了沟通的成本,极大方便了各个模块之间的相互调用,利人利己。

Node.js 中的模块

Node.js遵循了CommonJS模块化规范,CommonJS规定了模块的特性和各模块之间如何相互依赖。
CommonJS规定:

  1. 每个模块内部,module变量代表当前模块。
  2. module变量是一个对象,它的exports属性(即module.exports)是对外的接口。
  3. 加载某个模块,其实是加载该模块的module.exports属性。require()方法用于加载模块。

Node.js中根据模块来源的不同,将模块分为了3大类,分别是:

  1. 内置模块
    内置模块是由Node.js官方提供的。(例如: fs、path、http等)
  2. 自定义模块
    用户创建的每个.js文件,都是自定义模块
  3. 第三方模块
    由第三方开发出来的模块,并非官方提供的内置模块,也不是用户创建的自定义模块,使用前需要先下载(npm)

加载模块

使用require()方法,可以加载需要的内置模块、用户自定义模块、第三方模块进行使用。
require()方法加载其它模块时,会执行被加载模块中的代码。

模块的加载机制

优先从缓存中加载

模块在第一次加载后会被缓存,这也意味着多次调用 require() 不会导致模块的代码被执行多次。
不论是内置模块、用户自定义模块、还是第三方模块,它们都会优先从缓存中加载,从而提高模块的加载效率。

内置模块的加载机制

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

自定义模块的加载机制

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

使用require()导入自定义模块时,如果省略了文件的扩展名,则Node.js会按顺序分别尝试加载以下的文件:

  1. 按照确切的文件名进行加载
  2. 补全.js扩展名进行加载
  3. 补全.json扩展名进行加载
  4. 补全.node扩展名进行加载
  5. 加载失败,终端报错

第三方模块的加载机制

如果传递给require()的模块标识符不是一个内置模块,也没有以./../开头,则Node.js 会从当前模块的父目录开始,尝试从/node_modules文件夹中加载第三方模块。
如果没有找到对应的第三方模块,则移动到再上一层父目录中,进行加载,直到文件系统的根目录

目录作为模块

当把目录作为模块标识符,传递给require()进行加载的时候,有三种加载方式:

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

模块作用域

在自定义模块中定义的变量、方法等成员,只能在当前模块内被访问,这种模块级别的访问限制,叫做模块作用域。作用:防止了全局变量污染的问题

共享模块

module

每个.js自定义模块中都有一个module对象,它里面存储了和当前模块有关的信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Module {
id: '.',
path: 'E:\\FrontEnd\\05-Node\\00-Temp',
// 默认是一个空对象
exports: {},
parent: null,
filename: 'E:\\FrontEnd\\05-Node\\00-Temp\\01-module.js',
loaded: false,
children: [],
paths: [
'E:\\FrontEnd\\05-Node\\00-Temp\\node_modules',
'E:\\FrontEnd\\05-Node\\node_modules',
'E:\\FrontEnd\\node_modules',
'E:\\node_modules'
]
}

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

补充:

  1. 安装指定版本的包:可以在包名之后,通过 @符号 指定具体的版本。(默认自动安装最新版本的包)
  2. 包的语义化版本规范
    1. 包的版本号是以“点分十进制”形式进行定义的,总共有三位数字,例如2.24.0
    2. 第1位数字:大版本;第2位数字:功能版本;第3位数字:Bug修复版本
    3. 版本号提升的规则则:只要前面的版本号增长了,则后面的版本号归零

初次装包完成后,在项目文件夹下多一个node_modules的文件夹和package-lock.json的配置文件。
不要手动修改 node_modules或package-lock.json文件中的任何代码,npm包管理工具会自动维护它们

NPM 镜像服务器

淘宝在国内搭建了一个服务器,专门把国外官方服务器上的包同步到国内的服务器,然后在国内提供下包的服务。从而极大的提高了下包的速度。

1
2
3
4
5
# 查看当前的下包镜像源
npm config get registry

# 将下包的镜像源切换为淘宝镜像源
npm config set registry=https://registry.npm.taobao.org/

nrm

nrm 工具,可以更方便的切换下包的镜像源。利用nrm提供的终端命令,可以快速查看和切换下包的镜像源。

1
2
3
4
5
6
7
8
# 通过npm包管理器,将nrm安装为全局可用的工具
npm i nrm -g

# 查看所有可用的镜像源
nrm ls

#将下包的镜像源切换为指定镜像
nrm use 镜像名

i5ting_toc

i5ting_toc 是一个可以把 md文档 转为 html页面 的小工具

包的分类

  1. 项目包
    安装到项目 node_modules 目录中的包,都是项目包
  2. 全局包
    执行npm install命令时,添加 -g 参数,会把包安装为全局包
    全局包会被安装到:C:\Users\用户名\AppData\Roaming\npm\node_modules 目录
    只有工具性质的包,才有全局安装的必要性。因为它们提供了好用的终端命令
1
2
3
npm i 包名 -g # 全局安装指定的包

npm uninstall 包名 -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 的包管理配置文件。用来记录与项目有关的一些配置信息。例如:

  1. 项目的名称、版本号、描述等
  2. 项目中都用到了哪些包
  3. 哪些包只在开发期间会用到
  4. 那些包在开发和部署时都需要用到

快速创建 package.json

1
npm init -y

注意:

  1. npm init -y命令只能在英文的目录下成功运行!不要使用中文,不能出现空格。
  2. 运行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
2
3
4
5
# 安装指定的包,并记录到 devDependencies 节点中
npm install 包名 --save-dev

# 上面命令的简写:
npm i 包名 -D

规范的包结构

一个规范的包,它的组成结构,必须符合以下3点要求:

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

npm 包开发

包的基本结构

  1. 创建文件夹作为包的根目录
  2. 在文件夹中,新建如下三个文件:
    1. package.json(包管理配置文件)
    2. index.js (包的入口文件)
    3. README.md(包的说明文档)
  3. 初始化 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"
    }

发布包

  1. 访问 https://www.npmjs.com/ 网站注册账号
  2. 登录账号
    1. npm 账号注册完成后,可以在终端中执行 npm login 命令,依次输入用户名、密码、邮箱后,即可登录成功
    2. 注意:在运行 npm login 命令之前,必须先把下包的服务器地址切换为 npm的官方服务器。否则会导致发布包失败!
  3. 将终端切换到包的根目录,运行 npm publish 命令,即可将包发布到 npm上(注意:包名不能雷同)。

删除包

运行 npm unpublish 包名--force 命令,即可从 npm 删除已发布的包。

注意:

  1. npm unpublish 命令只能删除72小时以内发布的包
  2. 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
2
3
4
5
6
7
8
9
10
11
const express = require('express')
const app = express()
const port = 3000

app.get('/', (req, res) => {
res.send('Hello World!')
})

app.listen(port, () => {
console.log(`Example app listening on port ${port}`)
})

监听 GET 请求

通过 app.get() 方法,可以监听客户端的GET请求

1
2
3
4
5
6
7
8
9
/* 
参数1:客户端请求的URL地址
参数2:请求对应的处理函数
req:请求对象(包含了与请求相关的属性与方法)
res:响应对象(包含了与响应相关的属性与方法)
*/
app.get('请求URL', function(request, response) {
// 处理函数
})

监听POST请求

通过 app.post() 方法,可以监听客户端的POST请求

1
2
3
4
5
6
7
8
9
/* 
参数1:客户端请求的URL地址
参数2:请求对应的处理函数
req:请求对象(包含了与请求相关的属性与方法)
res:响应对象(包含了与响应相关的属性与方法)
*/
app.post('请求URL', function(req, res) {
// 处理函数
})

响应客户端

通过 response.send() 方法,可以把处理好的内容,发送给客户端

获取 URL 中携带的查询参数

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

1
2
3
4
app.get('请求URL', function(request, response) {
// request.query 默认是一个空对象
request.query
})

获取 URL 中的动态参数

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

1
2
3
4
app.get('请求URL:动态参数', function(request, response) {
// request.params 默认是一个空对象
request.params
})

express.static()

express.static() 可以非常方便地创建一个静态资源服务器

Express 在指定的静态目录中查找文件,并对外提供资源的访问路径。存放静态文件的目录名不会出现在URL中

1
2
// 将 dir 目录下的文件对外开放访问
app.use(express.static('dir'));

挂载路径前缀:

如果希望在托管的静态资源访问路径之前,挂载路径前缀,则可以使用如下的方式:

1
2
// 访问方式: http://xxxx/public/dir/..
app.use('/public',express.static('dir'))

nodemoon

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

安装

1
npm install -g nodemon

使用

node 命令替换为 nodemon 命令,使用 nodemon xxx.js 来启动项目

Express 路由

官方文档

Express中,路由指的是客户端的请求服务器处理函数之间的映射关系。

基本路由

Express中的路由分3部分组成,分别是请求的类型请求的URL地址处理函数

1
2
3
4
5
6
7
8
/* 
app 是 express 的实例。
METHOD 是 HTTP 请求方法。
PATH 是服务器上的路径。
HANDLER 是在路由匹配时执行的函数。
*/

app.METHOD(PATH, HANDLER)

路由匹配

请求到达服务器之后,需要先经过路由的匹配,匹配时,会按照路由的先后顺序进行匹配,如果请求类型请求的URL同时匹配成功,才会调用对应的处理函数。

路由匹配

基本使用

1
2
3
4
5
6
7
8
9
// GET method route
app.get('/', function (req, res) {
res.send('GET request to the homepage');
});

// POST method route
app.post('/', function (req, res) {
res.send('POST request to the homepage');
});

模块化路由

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

抽离步骤:

  1. 创建路由模块对应的.js文件
  2. 调用 express.Router() 函数创建路由对象
  3. 向路由对象上挂载具体的路由
  4. 使用 module.exports 向外共享路由对象
  5. 使用 app.use() 函数注册路由模块
    1. app.use() 函数作用:用来注册全局中间件

定义路由:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// router.js

var express = require('express');
// 创建路由对象
var router = express.Router();

// 向路由对象上挂载具体的路由
router.use(function timeLog(req, res, next) {
console.log('Time: ', Date.now());
next();
});
// 定义 the home page route
router.get('/', function(req, res) {
res.send('Birds home page');
});
// 定义 the about route
router.get('/about', function(req, res) {
res.send('About birds');
});

// 向外共享路由对象
module.exports = router;

使用路由:

1
2
3
4
5
6
7
// server.js

// 1. 导入路由模块
const userRouter = require('./router.js')

// 2. 使用app.use()注册路由模块。并添加统一的访问前缀/api【可选】
app.use('/api',userRouter);

Express 中间件

官方文档

中间件(Middleware) 特指业务流程的中间处理环节。

概念

当一个请求到达Express的服务器之后,可以连续调用多个中间件,从而对这次请求进行预处理

20220826170212

Express 的中间件,本质上就是一个 function 处理函数中间件函数的形参列表中,必须包含 next 参数

20220826170744

next 函数

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

中间件函数

1
2
3
4
5
var mw = function (req, res, next) {
console.log('这是一个最简单的中间件函数');
// next() 方法将流转关系转交给下一个中间件或路由
next();
};

中间件的作用

多个中间件之间,共享同一份 req 和 res 。
基于这样的特性,我们可以在上游的中间件中,统一为 req 或 res 对象添加自定义的属性或方法,供下游的中间件或路由进行使用。

全局生效的中间件

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

1
2
// 全局生效的中间件
app.use(中间件函数)

补充:可以使用 app.use() 连续定义多个全局中间件。客户端请求到达服务器之后,会按照中间件定义的先后顺序依次进行调用。

局部生效的中间件

定义在路由参数中的中间是局部中间件

1
2
3
4
5
6
7
8
9
10
var mw = function (req, res, next) {
console.log('局部中间件演示');
// next() 方法将流转关系转交给下一个中间件或路由
next();
};

// 路由
app.get('/',mw,(req,res)=>{
res.send('HOME')
})

补充:可以在路由中,通过如下两种等价的方式,使用多个局部中间件:

1
2
3
4
5
6
7
8
9
10
// 写法一
app.get('/',mw1,nw2,(req,res)=>{
res.send('HOME')
})

// 写法二
app.get('/',[mw1,nw2],(req,res)=>{
res.send('HOME')
})

中间件注意事项

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

中间件分类

Express 官方把常见的中间件用法,分成 5大类

  1. 应用级别的中间件
  2. 路由级别的中间件
  3. 错误级别的中间件
  4. Express 内置的中间件
  5. 第三方的中间件

应用级别的中间件

通过 app.use()app.get()app.post() 绑定到app实例上的中间件,叫做应用级别的中间

路由级别的中间件

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

错误级别的中间件

错误级别中间件的作用:专门用来捕获整个项目中发生的异常错误,从而防止项目异常崩溃的问题

格式:错误级别中间件的 function 处理函数中,必须有4个形参,形参顺序从前到后,分别是(err,req,res,next

注意:错误级别的中间件,必须注册在所有路由之后!

Express 内置的中间件

Express 内置了3个常用的中间件,极大的提高了Express项目的开发效率和体验:

  1. express.static 快速托管 静态资源 的内置中间件
  2. express.json 解析 JSON 格式的请求体数据
  3. express.urlencoded 解析 URL-encoded 格式的请求体数据

第三方的中间件

非 Express 官方内置的,而是由第三方开发出来的中间件,叫做第三方中间件。
可以按需下载并配置第三方中间件,从而提高项目的开发效率

使用第三方中间件:

  1. 运行 npm install 中间件名称 安装中间件
  2. 使用 require 导入中间件
  3. 调用 app.use() 注册并使用中间件

Express 写接口

解决跨域

解决接口跨域问题的方案主要有两种:

  1. CORS(推荐)
  2. JSONP(只支持GET请习)

CORS 响应头

Access-Control-Allow-Origin
1
2
3
4
5
/* 
origin 参数的值指定了允许访问该资源的外域URL
通配符 * 表示允许来自任何域的请求
*/
Access-Control-Allow-Origin: <origin> | *
Access-Control-Allow-Headers

默认情况下,CORS仅支持客户端向服务器发送如下的9个请求头:

  1. Accept
  2. Accept-Language
  3. Content-Language
  4. DPR
  5. Downlink
  6. Save-Data
  7. Viewport-Width
  8. Width
  9. Content-Type

如果客户端向服务器发送了额外的请求头信息则需要在服务器端,通过 Access-Control-Allow-Headers 对额外的请求头进行声明,否则这次请求会失败!

Access-Control-Allow-Methods

默认情况下,CORS仅支持客户端发起 GET、POST、HEAD 请求。

如果客户端希望通过 PUTDELETE 等方式请求服务器的资源,则需要在服务器端,通过 Access-Control-Alow-Methods 来指明实际请求所允许使用的HTTP方法。

1
2
3
4
5
// 只允许 POST、GET、DELETE、HEAD请求方法
res.setHeader("Access-Control-Allow-Methods","POST,GET,DELETE,HEAD");

//允许所有的HTTP请求方法
res.setHeader("Access-Control-Allow-Methods","*");

CORS 请求分类

根据请求方式和请求头的不同,可以将CORS的请求分为两大类,分别是:简单请求和预检请求

简单请求和预检请求的区别:

  • 简单请求的特点:客户端与服务器之间只会发生一次请求。
  • 预检请求的特点:客户端与服务器之间会发生两次请求,OPTION预检请求成功之后,才会发起真正的请求。
简单请求
  1. 请求方式:GET、POST、HEAD三者之一
  2. HTTP 头部信息不超过以下几种字段:无自定义头部字段、Accept、Accept-Language、Content-Language、DPR、Downlink、Save-Data、Viewport-Width、Width、Content-Type
预检请求
  1. 请求方式为GET、POST、HEAD之外的请求 Method 类型
  2. 请求头中包含自定义头部字段
  3. 向服务器发送了 application/json 格式的数据

在浏览器与服务器正式通信之前,浏览器会先发送OPTION请求进行预检,以获知服务器是否允许该实际请求,所以这一次的OPTION请求称为“预检请求”。
服务器成功响应预检请求后,才会发送真正的请求,并且携带真实数据。

cors中间件解决跨域

浏览器的同源安全策略默认会阻止网页跨域获取资源。但如果接口服务器配置了CORS相关的HTTP响应头,就可以解除浏览器端的跨域访问限制。(CORS主要在服务器端进行配置。客户端浏览器无须做任何额外的配置。)

cors 是 Express 的一个第三方中间件。通过安装和配置cors中间件,可以很方便地解决跨域问题。

使用步骤:

  1. 运行 npm install cors 安装中间件
  2. 使用 const cors = require('cors') 导入中间件
  3. 在路由之前调用 app.use(cors()) 配置中间件

JSONP

JSONP 概念:

浏览器端通过<script>标签的src属性,请求服务器上的数据,同时,服务器返回一个函数的调用。这种请求数据的方式叫做JSONP。

特点:

  1. JSONP 不属于真正的Ajax请求,因为它没有使用XMLHttpRequest这个对象。
  2. JSONP 仅支持GET请求,不支持 POST、PUT、DELETE 等请求。

注意事项:

如果项目中已经配置了CORS跨域资源共享,为了防止冲突,必须在配置CORS中间件之前声明JSONP的接口

数据库与身份验证

数据库的基本概念

数据库的数据组织结构

在传统型数据库中,数据的组织结构分为:

  1. 数据库(database)
  2. 数据表(table)
  3. 数据行(row)
  4. 字段(field)

安装并配置 MySQL

MySQL 相关软件

  1. MySQL Server: 专门用来提供数据存储和服务的软件
  2. MySQL Workbench: 可视化的MySQL管理工具

MySQL的基本使用

创建数据表

DataType 数据类型:

  1. int 整数
  2. varchar(len)字符串
  3. tinyint(1)布尔值

字段的特殊标识:

  1. PK(Primary Key) 主键、唯一标识
  2. NN(Not Null) 值不允许为空
  3. UQ(Unique) 值唯一
  4. AI(Auto Increment) 值自动增长

SQL 介绍

SQL(英文全称:Structured Query Language)是结构化查询语言,专门用来访问和处理数据库的编程语言。能够让我们以编程的形式,操作数据库里面的数据。

  1. SQL是一门数据库编程语言(SQL语句中的关键字对大小写不敏感。)
  2. 使用SQL语言编写出来的代码,叫做SQL语句
  3. SQL语言只能在关系型数据库中使用(例如MySQL、Oracle、SQL Server)。非关系型数据库(例如 Mongodb)不支持SQL语言。

SQL 语句

  1. 查询数据(select)
  2. 插入数据(insert into)
  3. 更新数据(update)
  4. 删除数据(delete)

where条件、and和or运算符、orderby排序、count(*)函数

SELECT

SELECT 语句用于从表中查询数据。执行的结果被存储在一个结果表中(称为结果集)

语法:

1
2
3
4
5
-- 从 FROM 指定的【表中】,查询出【所有的】数据。*表示【所有列】
SELECT * FROM 表名称

-- 从 FROM 指定的【表中】,查询出指定列名称(字段)的数据。
SELECT 列名称 FROM 表名称

INSERT INTO

INSERT INTO 语句用于向数据表中插入新的数据行

1
2
-- 列和值要一一对应,多个列和多个值之间,使用英文的逗号分隔
INSERT INTO table_name (列1,列2,...) VALUES (值1,值2,...)

UPDATE

UPDATE 语句用于修改表中的数据

1
2
-- 用 UPDATE 指定要更新哪个表中的数据 用 SET 指定列对应的新值 用 WHERE 指定更新的条件
UPDATE 表名称 SET 列名称 = 新值 WHERE 列名称 = 某值

DELETE

DELETE 语句用于删除表中的行

1
2
-- 从指定的表中,根据 WHERE 条件,删除对应的数据行
DELETE FROM 表名称 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
2
3
4
SELECT COUNT(*) FROM 表名称

-- 如果希望给查询出来的列名称设置别名,可以使用AS关键字
SELECT COUNT(*) As Total FROM 表名称

Express 中操作 MySQL

安装与配置mysql模块

安装

1
npm install mysql

配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var mysql = require('mysql');
var connection = mysql.createConnection({
host : 'localhost',
user : 'me',
password : 'secret',
database : 'my_db'
});

connection.connect();

connection.query('SELECT 1 + 1 AS solution', function (error, results, fields) {
if (error) throw error;
console.log('The solution is: ', results[0].solution);
});

connection.end();

前后端的身份认证

Web 开发模式

服务端渲染

服务端渲染的概念:服务器发送给客户端的HTML页面,是在服务器通过字符串的拼接,动态生成的

优点:

  1. 前端耗时少。因为服务器端负责动态生成HTML内容,浏览器只需要直接渲染页面即可。尤其是移动端,更省电。
  2. 有利于SEO。因为服务器端响应的是完整的HTML页面内容,所以爬虫更容易爬取获得信息,更有利于SEO。

缺点:

  1. 占用服务器端资源。即服务器端完成HTML页面内容的拼接,如果请求较多,会对服务器造成一定的访问压力。
  2. 不利于前后端分离,开发效率低。使用服务器端渲染,则无法进行分工合作,尤其对于前端复杂度高的项目,不利于项目高效开发。

前后端分离

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

优点:

  1. 开发体验好。前端专注于UI页面的开发,后端专注于api的开发,且前端有更多的选择性。
  2. 用户体验好。Ajax技术的广泛应用,极大的提高了用户的体验,可以轻松实现页面的局部刷新。
  3. 减轻了服务器端的渲染压力。因为页面最终是在每个用户的浏览器中生成的。

缺点:

  1. 不利于SEO。因为完整的HTML页面需要在客户端动态拼接完成,所以爬虫对无法爬取页面的有效信息。(解决方案:利用Vue、React等前端框架的 SSR (server side render)技术能够很好的解决SEO问题!)

身份认证

不同开发模式下的身份认证:

对于服务端渲染前后端分离这两种开发模式来说,分别有着不同的身份认证方案:

  1. 服务端渲染推荐使用 Session 认证机制
  2. 前后端分离推荐使用 JWT 认证机制

Session 认证机制

HTTP协议的无状态性

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

解决 HTTP 无状态的限制

客户端第一次请求服务器的时候,服务器通过响应头的形式,向客户端发送一个身份认证的Cookie,客户端会自动将Cookie保存在浏览器中。随后,当客户端浏览器每次请求服务器的时候,浏览器会自动将身份认证相关的Cookie,通过请求头的形式发送给服务器,服务器即可验明客户端的身份。

Cookie 是存储在用户浏览器中的一段不超过4KB的字符串。它由一个名称(Name)、一个(Value)和其它几个用于控制 Cookie 有效期、安全性、使用范围 的可选属性组成。
不同域名下的 Cookie 各自独立,每当客户端发起请求时,会自动把当前域名下所有未过期的 Cookie 一同发送到服务器。

  1. 自动发送
  2. 域名独立
  3. 过期时限
  4. 4KB限制

由于Cookie是存储在浏览器中的,而且浏览器也提供了读写Cookie的API,因此Cookie很容易被伪造,不具有安全性。因此不建议服务器将重要的隐私数据,通过Cookie的形式发送给浏览器。
注意:千万不要使用Cookie存储重要且隐私的数据!比如用户的身份信息、密码等。

Session 的工作原理

Session

Session 认证的局限性

Session认证机制需要配合Cookie才能实现。由于Cookie默认不支持跨域访问,所以,当涉及到前端跨域请求后端接口的时候,需要做很多额外的配置,才能实现跨域Session认证。

注意:

  1. 当前端请求后端接口不存在跨域问题的时候,推荐使用Session身份认证机制。
  2. 当前端需要跨域请求后端接口的时候,不推荐使用Session身份认证机制,推荐使用JWT认证机制。
Express 中使用 Session 认证

express-session:https://www.npmjs.com/package/express-session

JWT 认证机制

JWT (英文全称:JSON Web Token) 是目前最流行的跨域认证解决方案。

JWT 工作原理

20220827155819

原理:用户的信息通过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对象

Todo