Buffer

概念

Buffer是一个类似于数组的对象,用于表示固定长度的字节序列

Buffer本质是一段内存空间,专门用来处理二进制数据

特点

  1. Buffer大小固定且无法调整
  2. Buffer性能较好,可以直接对计算机内存进行操作
  3. 每个元素的大小为1字节(byte)

使用

创建Buffer

1
2
3
4
5
6
7
8
9
10
11
//创建了一个长度为 10 字节的 Buffer,相当于申请了 10 字节的内存空间,每个字节的值为 0
let buf = Buffer.alloc(10); // 结果为 <Buffer 00 00 00 00 00 00 00 00 00 00>

//创建了一个长度为 10 字节的 Buffer,buffer 中可能存在旧的数据, 可能会影响执行结果,所以叫 unsafe
let buf1 = Buffer.allocUnsafe(10000);

//通过字符串创建 Buffer
let buf2 = Buffer.from('hello');

//通过数组创建 Buffer
let buf3 = Buffer.from([105, 168, 1231, 1, 516]);

Buffer与字符串的转化

可以借助toString方法将Buffer转为字符串

1
2
let buf_4 = Buffer.from([105, 108, 111, 118, 101, 121, 111, 117]);
console.log(buf_4.toString()) // iloveyou

Buffer的读写

Buffer可以直接通过[]的方式对数据进行处理。

1
2
3
4
5
6
7
// 读取
console.log(buf1[1]);
console.log(buf1[1].toString(2)); // 注意,这个toString的方法是用来将数据转为二进制(因为传参2)进行显示
// 修改
buf1[1] = 97;
//查看字符串结果
console.log(buf1.toString());

注意:如果修改的数值超过255,则超过8位数据会被舍弃

一个uff-8的字符一般占3个字节

fs模块

fs全称为file system,称之为文件系统,是Node.js中的内置模块,可以对计算机中的磁盘进行操作。

文件写入

文件写入就是将数据保存到文件当中。

方法 说明
writeFile 异步写入
writeFileSync 同步写入
appendFile / appendFileSync 追加写入
createWriteStream 流式写入

writeFile异步写入

语法:

1
fs.writeFile(file, data[, options], callback)

参数说明

  1. file 文件名
  2. data 待写入的数据
  3. options 选项设置(可选)
  4. callback 写入回调

返回值:undefined

代码示例:

1
2
3
4
5
6
7
8
9
10
11
// require 是 Node.js 环境中的'全局'变量,用来导入模块
const fs = require('fs');
//将 『三人行,必有我师焉。』 写入到当前文件夹下的『座右铭.txt』文件中
fs.writeFile('./座右铭.txt', '三人行,必有我师焉。', err => {
//如果写入失败,则回调函数调用时,会传入错误对象,如写入成功,会传入 null
if(err){
console.log(err);
return;
}
console.log('写入成功');
});

writeFileSync同步写入

语法:

1
fs.writeFileSync(file, data[, options])

参数与fs.writeFile大体一致,只是没有callback参数

返回值:undefined

代码示例:

1
2
3
4
5
try{
fs.writeFileSync('./座右铭.txt', '三人行,必有我师焉。');
} catch(e){
console.log(e);
}

Node.js中的磁盘操作是由其他线程完成的,结果的处理有两种模式

  1. 同步处理 javascript主线程会等待其他线程的执行结果,然后再继续执行主线程的代码
  2. 异步处理 javascript主线程不会等待其他线程的执行结果,直接执行后续的主线程代码

appendFile / appendFileSync追加写入

appendFile作用是在文件尾部追加内容,appendFile语法于writeFile语法完全相同

语法:

1
2
fs.appendFile(file, data[, options], callback)
fs.appendFileSync(file, data[, options])

返回值:二者都会undefined

示例代码:

1
2
3
4
5
fs.appendFile('./座右铭.txt','择其善者而从之,其不善者而改之。', err => {
if(err) throw err;
console.log('追加成功')
});
fs.appendFileSync('./座右铭.txt','\r\n温故而知新, 可以为师矣');

createWriteStream流式写入

语法:

1
fs.createWriteStream(path[, options])

参数说明

  1. path 文件路径
  2. options 选项配置(可选)

返回值:Object

代码示例:

1
2
3
4
5
6
let ws = fs.createWriteStream('./观书有感.txt');
ws.write('半亩方塘一鉴开\r\n');
ws.write('天光云影共徘徊\r\n');
ws.write('问渠那得清如许\r\n');
ws.write('为有源头活水来\r\n');
ws.end();

程序打开一个文件是需要消耗资源的,流式写入可以减少打开关闭文件的次数。

流式写入方式适用于大文件写入或者频繁写入的场景,writeFile适合于写入频率较低的场景。

写入文件的场景

文件写入在计算机中是一个非常常见的操作,下面的场景都用到了文件写入

  1. 下载文件
  2. 安装软件
  3. 保存程序日志,入Git
  4. 编辑器保存文件
  5. 视频录制

当需要持久化保存数据的时候,应该想到文件写入

文件读取

文件读取顾名思义,就是通过程序从文件中取出其中的数据,可以使用如下几种方式:

方法 说明
readFile 异步读取
readFileSync 同步读取
createReadStream 流式读取

readFile异步读取

语法:

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

参数说明:

  1. path 文件路径
  2. options 选项配置
  3. callback 回调函数

返回值:undefined

示例代码:

1
2
3
4
5
6
7
8
9
10
11
//导入 fs 模块
const fs = require('fs');
fs.readFile('./座右铭.txt', (err, data) => {
if(err) throw err;
console.log(data);
});

fs.readFile('./座右铭.txt', 'utf-8',(err, data) => {
if(err) throw err;
console.log(data);
});

readFileSync同步读取

语法:

1
fs.readFileSync(path[, options])

参数说明:

  1. path 文件路径
  2. options 选项配置

返回值:string | Buffer

代码示例:

1
2
let data = fs.readFileSync('./座右铭.txt');
let data2 = fs.readFileSync('./座右铭.txt', 'utf-8');

createReadStream流式读取

语法:

1
fs.createReadStream(path[, options])

参数说明:

  1. path 文件路径
  2. options 选项配置(可选)

代码示例:

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

读取文件应用场景

  1. 电脑开机
  2. 程序运行
  3. 编辑器打开文件
  4. 查看图片
  5. 播放视频
  6. 播放音乐
  7. Git查看日志
  8. 上传文件
  9. 查看聊天记录

文件移动与重命名

在Node.js中,我们可以使用rename或renameSync来移动或重命名文件或文件夹

语法:

1
2
fs.rename(oldPath, newPath, callback)
fs.renameSync(oldPath, newPath)

参数说明:

  1. oldPath 文件当前的路径
  2. newPath 文件新的路径
  3. callback 操作后的回调

代码示例:

1
2
3
4
5
fs.rename('./观书有感.txt', './论语/观书有感.txt', (err) =>{
if(err) throw err;
console.log('移动完成')
});
fs.renameSync('./座右铭.txt', './论语/我的座右铭.txt');

文件删除

在Node.js中,我们可以使用unlink、unlinkSync、rm、rmSync来删除文件

语法:

1
2
3
4
fs.unlink(path, callback)
fs.unlinkSync(path)
fs.rm(path, callback)
fs.rmSync(path)

参数说明:

  1. path 文件路径
  2. callback 操作后的回调

代码示例:

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

fs.rm('./test.txt', err => {
if(err) throw err;
console.log('删除成功');
});
fs.rmSync('./test2.txt');

文件夹操作

借助Node.js的能力,可以对文件夹进行创建、读取、删除等操作

方法 说明
mkdir / mkdirSync 创建文件夹
readdir / readdirSync 读取文件夹
rmdir / rmdirSync 删除文件夹

创建文件夹

语法:

1
2
fs.mkdir(path[, options], callback)
fs.mkdirSync(path[, options])

参数说明:

  1. path 文件夹路径
  2. options 选项配置(可选)
  3. calback 操作后的回调

示例代码:

1
2
3
4
5
6
7
8
9
10
11
fs.mkdir('./page', err => {
if(err) throw err;
console.log('创建成功');
});
//递归异步创建,如果不传recursive这个参数,那么就会报错
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. path 文件夹路径
  2. options 选项配置(可选)
  3. callback 操作后的回调

示例代码:

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);

删除文件夹

语法:

1
2
fs.rmdir(path[, options], callback)
fs.rmdirSync(path[, options])

参数说明:

  1. path 文件夹路径
  2. options 选项配置(可选)
  3. callback 操作后的回调

示例代码:

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})

查看资源状态

在Node.js中,可以使用stat或statSync来查看资源的详细信息

语法:

1
2
fs.stat(path[, options], callback)
fs.statSync(path[, options])

参数说明:

  1. path 文件夹路径
  2. options 选项配置(可选)
  3. callback 操作后的回调

示例代码:

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. size 文件体积
  2. birthtime 创建时间
  3. mitime 最后修改时间
  4. isFile 检测是否为文件
  5. isDirectory 检测是否为文件夹

__dirname

__dirname与require类似,都是Node.js环境中的’全局变量’

__dirname保存着当前文件所在目录的绝对路径,可以使用__dirname与文件名拼接成绝对路径

代码示例:

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

path模块

path模块提供了操作路径的功能,如下为几个常用的API:

API 说明
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(hypertext transport protocol)协议;中文叫超文本传输协议

是一种基于TCP/IP的应用层通信协议

这个协议详细规定了浏览器和万维网服务器之间互相通信的规则

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

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

报文:可以简单理解为就是一堆字符串

二、请求报文的组成

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

三、HTTP的请求行

  1. 请求方法(get、post、put、delete等)
  2. 请求URL(统一资源定位器)
    1. 例如:http://www.baidu.com:80/index.html?a=100&b=200#logo
      1. http:协议(https、ftp、ssh等)
      2. <www.baidu.com> 域名
      3. 80 端口号
      4. /index.html 路径
      5. a=100&b=200 查询字符串
      6. #logo 哈希(锚点链接)
  3. HTTP协议版本号

四、HTTP请求头

格式:【头名:头值】

常见的请求头有:

请求头 解释
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

五、HTTP请求体

请求体内容的格式是非常灵活的

(可以是空)==> GET请求

(也可以是字符串,还可以是JSON) ===> POST请求

六、响应报文的组成

  1. 响应行
    1. HTTP/1.1 200 OK
      1. HTTP/1.1:HTTP协议版本号
      2. 200:响应状态码 404 Not Found 500 Internal Server Error
      3. OK:响应状态描述
  2. 响应头
    1. Cache-Control:缓存控制 private 私有的,只允许客户端缓存数据
    2. Connection 链接设置
    3. Content-Type:text/html;charset=utf-8 设置响应体的数据类型以及字符集,响应体为html,字符集utf-8
    4. Content-Length:响应体的长度,单位为字节
  3. 空行
  4. 响应体

响应体内容的类型是非常灵活的,常见的类型有HTML、CSS、JS、图片、JSON

七、创建HTTP服务

使用Node.js创建HTTP服务

操作步骤

1
2
3
4
5
6
7
8
9
10
11
12
//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. 监听端口, 启动服务
server.listen(9000, () => {
console.log('服务已经启动, 端口 9000 监听中...');
});

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

测试

直接使用浏览器请求对应的端口

1
http://127.0.0.1:9000

注意事项

响应内容中文乱码的解决方法(在createServer的回调函数中调用)

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

八、获取HTTP请求报文

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

含义 语法 重点掌握
请求方法 request.method *
请求版本 request.httpVersion
请求路径 request.url *
URL路径 request(‘url’).parse(request.url).pathname *
URL查询字符串 request(‘url’).parse(request,url,true).query *
请求头 request.headers *
请求体 request.on(‘data’,function(chunk){})
request.on(‘end’,function(){})

注意事项:

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

九、设置HTTP响应报文

作用 语法
设置响应状态码 response.statusCode
设置响应状态描述 response,statusMessage
设置响应头信息 response.setHeader(‘头名’, ‘头值’)
设置响应体 response.write(‘xx’)
response.end(‘xx’)
1
2
3
4
5
6
7
8
write 和 end 的两种使用情况:
//1. write 和 end 的结合使用 响应体相对分散
response.write('xx');
response.write('xx');
response.write('xx');
response.end(); //每一个请求,在处理的时候必须要执行 end 方法的
//2. 单独使用 end 方法 响应体相对集中
response.end('xxx');

Node.js模块化

一、介绍

什么是模块化与模块

将一个复杂的程序文件依据一定规则拆分成多个文件的过程称之为模块化

其中拆分的每个文件就是一个模块,模块的内部数据是私有的,不过模块可以暴露内部数据以便其他模块使用

什么是模块化项目

编码时是按照模块一个一个编码的,整个项目就是一个模块化的项目

模块化的好处

  1. 防止命名冲突
  2. 高复用性
  3. 高维护性

二、模块暴露数据

模块初体验

1.创建me.js

1
2
3
4
5
6
7
// 声明函数
function tiemo(){
console.log('xxx');
}

// 暴露数据
module.exports = tiemo;

2.创建index.js

1
2
3
4
// 导入模块
const tiemo = require('./me.js');
// 调用函数
tiemo();

暴露数据

模块暴露数据的方式有两种

  1. module.exports = value;
  2. exports.name = value;

使用时有几点注意:

  1. module.exports可以暴露任意数据
  2. 不能使用exports = value的形式暴露数据,模块内部module与exports的隐式关系exports = module.exports = {},require返回的是目标模块中module.exports的值

三、导入(引入)模块

在模块中使用require传入文件路径即可引入文件

1
const test = require('./me.js');

require使用的一些注意事项:

  1. 对于自己创建的模块,导入时路径建议写相对路径,且不能省略./和../
  2. js和json文件导入时可以不用写后缀,c/c++编写的node扩展文件也可以不写后缀,但是一般用不到
  3. 如果导入其他类型的文件,会以js文件进行处理
  4. 如果导入的路径是个文件夹,则会首先检测该文件下下package.json文件中main属性对应的文件,如果存在则导入,反之则会报错。如果main属性不存在,或者pachage.json不存在,则会尝试导入文件夹下的index.js和index.json,如果还是没找到,就会报错
  5. 导入node.js内置模块时,直接require模块的名字即可,无需加./和../

四、导入模块的基本流程

  1. 将相对路径转为绝对路径,定位目标文件
  2. 缓存检测
  3. 读取目标文件代码
  4. 包裹为一个函数并执行,通过anguments.callee.toString()查看自执行函数
  5. 缓存模块的值
  6. 返回module.exports的值

包管理工具

一、概念介绍

包是什么

【包】英文单词是package,代表了一组特定功能的源码的集合

包管理工具

管理【包】的应用软件,可以对【包】进行下载安装、更新、删除、上传等操作

借助包管理工具,可以快速开发项目,提升开发效率

包管理工具是一个通用的概念,很多变成语言都有包管理工具,所以掌握好包管理工具很重要

常用的包管理工具

  1. npm
  2. yarn
  3. cnpm

二、npm

npm全称是Node Package Manager,即Node的包管理工具

npm是node.js官方内置的包管理工具

npm的安装

node.js在安装时会自动安装npm。

可以通过npm -v查看版本号,如果显示版本号则说明安装成功

npm的基本使用

初始化

创建一个空目录,然后以此目录作为工作目录启动命令行工具,执行npm init

npm init命令的作用是将文件夹初始化为一个【包】,交互式创建package.json文件

package.json是包的配置文件,每个包都必须要有package.json

package.json内容示例

1
2
3
4
5
6
7
8
9
10
11
{
"name": "test",
"version": "1.0.0",
"description": "",
"main": "me.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC"
}

初始化的过程中还有一些注意事项:

  1. package name不能使用中文、大写,默认值是文件夹的名称,所以文件夹名称也不能使用中文和大写
  2. version要求x.x.x的形式定义,x必须是数字,默认值是1.0.0
  3. ISC证书与MIT证书功能上是相同的。
  4. package.json可以手动创建和修改
  5. 使用npm init -y或者npm init –yes极速创建package.json(前提是文件夹不能是中文及大写)

搜索包

搜索包的方式有两种

  1. 命令行【npm s/search 关键字】
  2. 网站搜索,http://www.npmjs.com/

下载安装包

通过npm install或npm i命令安装包

运行后文件夹下会增加两个资源

  1. node_module文件夹存放下载的包
  2. package-lock.json包的锁文件,用来锁定包的版本

require导入npm包基本流程

  1. 在当前文件夹下node_modules中寻找同名的文件夹
  2. 在上级目录中下的node_module中寻找同名的文件夹,直至找到磁盘根目录

生产环境与开发环境

开发环境是程序员专门用来写代码的环境,一般是指程序员的电脑,开发环境的项目一般只能程序员自己访问。

生产环境是项目代码正式运行的环境,一般是指正式的服务器电脑,生产环境的项目一般每个客户都可以访问。

生产依赖与开发依赖

我们可以在安装时设置选项来区分依赖的类型,目前分为两类:

类型 命令 补充
生产依赖 npm i -S uniq
npm i –save uniq
-S等效于 –save,-S是默认选项
包信息保存在package.json中dependencies属性
开发依赖 npm i -D less
npm i –save-dev less
-D等效于 –save-dev
包信息保存在package.json中devDependencies属性

简单说就是,开发依赖只有在开发阶段才会使用到的依赖包,而生产依赖则是开发阶段和最终上线运行阶段都用的到的依赖包

全局安装

可以执行安装选项**-g**进行全局安装

全局安装完成之后就可以在命令行的任何位置运行nodemon命令

该命令的作用是自动重启node应用程序

说明:

  1. 全局安装的命令不受工作目录位置影响
  2. 可以通过npm root -g查看全局安装包的位置
  3. 不是所有的包都适合全局安装,只有全局类的工具才适合,可以通过查看包的官方文档来确定安装方式

修改windows的执行策略

windows默认不允许nom全局命令执行脚本文件,所以需要修改执行策略

  1. 以管理员身份打开powershell命令行
  2. 键入关键命令set-ExecutionPolicy remoteSigned
  3. 键入A然后敲回车
  4. 如果不生效,可以尝试重启vscode

环境变量Path

Path是操作系统的一个环境变量,可以设置一些文件夹的路径,在当前工作目录下找不到可执行文件时,就毁在环境变量Path的目录中挨个的查找,如果找到则执行,如果找不到就会报错

补充说明:

  1. 如果希望某个程序在任何工作目录下都能正常运行,就应该将该程序的所在目录配置到环境变量Path中
  2. windows下查找命令的所在位置
    1. cmd命令行中执行where nodemon
    2. powershell命令行执行get-command nodemon

安装包依赖

在项目协作中有一个常用的命令就是npm i,通过该命令可以依据package.json和package-lock.json的依赖声明安装项目依赖

1
2
npm i
npm install

安装指定版本的包

项目中可能会遇到版本不匹配的情况,有时就需要安装指定版本的包,可以使用下面的命令

1
2
3
4
## 格式
npm i <包名@版本号>
## 示例
npm i jquery@1.11.2

删除依赖

项目中可能需要删除某些不需要的包,可以使用下面的命令

1
2
3
4
5
## 局部删除
npm remove uniq
npm r uniq
## 全局删除
npm remove -g nodemon

配置命令别名

通过配置命令别名可以更简单的执行命令

配置package.json中的scripts属性

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"name": "test",
"version": "1.0.0",
"description": "",
"main": "me.js",
"scripts": {
"server": "node server.js",
"start": "node index.js",
},
"author": "",
"license": "ISC"
}

配置完成后,可以使用别名执行命令

1
2
npm run server
npm run start

不过start别名比较特别,使用时可以省略run

1
npm start

三、cnpm

介绍

cnpm是一个淘宝构建的npmjs.com的完整镜像,也称为淘宝镜像。

cnpm服务部署在国内阿里云服务器上,可以提高包的下载速度。

官方也提供了一个全局工具包cnpm,操作命令与npm大体相同。

安装

可以通过npm来安装cnpm工具

1
npm install -g cnpm --registry=https://registry.npmmirror.com

操作命令

功能 命令
初始化 cnpm init / cnpm init
安装包 cnpm i uniq
cnpm i -S uniq
cnpm i -D uniq
cnpm i -g nodemon
安装项目依赖 cnpm i
删除 cnpm r uniq

npm配置淘宝镜像

用npm也可以使用淘宝镜像,配置的方式有两种

  1. 直接配置
  2. 工具配置

直接配置

执行下方的命令:

1
npm config set registry https://registry.npmmirror.com/

工具配置

使用nrm配置npm的镜像地址npm registry manager

  1. 安装nrm: npm i -g nrm
  2. 修改镜像:nrm use taobao
  3. 检查是否配置成功:npm config list

检查registry地址是否为https://registry.npmmirror.com/,如果是则表明成功

四、yarn

yarn介绍

yarn是有Facebook在2016年推出的新的Javascript包管理工具,官方网址:https://yarnpkg.com

yarn特点

yarn官方宣称的一些特点

  1. 速度超快:yarn缓存了每个下载过的包,所以再次使用时无需重复下载。同时利用并行下载以最大化资源利用率,因此安装速度更快
  2. 超级安全:在执行代码前,yarn会通过算法校验每个安装包的完整性
  3. 超级可靠:使用详细、简洁的锁文件格式和明确的安装算法,yarn能够保证在不同系统上无差异的工作

yarn安装

可以使用npm安装yarn

1
npm i -g yarn

yarn常用命令

功能 命令
初始化 yarn init / yarn init -y
安装包 yarn add uniq 生产依赖
yarn add less –dev 开发依赖
yarn global add nodemon 全局安装
删除包 yarn remove uniq 删除项目依赖包
yarn global remove nodemon 删除全局包
安装项目依赖 yarn
运行命令别名 yarn <别名> # 不需要添加run

yarn全局安装包的位置可以通过yarn global bin来查看

yarn配置淘宝镜像

1
yarn config set registry https://registry.npmmirror.com/

可以通过yarn config list查看yarn的配置项

npm和yarn的选择

可以根据不同的项目场景进行选择

  1. 个人项目
    1. 如果是个人项目,哪个工具都可以,可以根据自己的喜好来选择
  2. 公司项目
    1. 如果是公司要根据项目代码来选择,可以通过锁文件判断项目的包管理工具
      1. npm的锁文件未package-lock.json
      2. yarn的锁文件为yarn.lock

包管理工具不要混着用

五、管理发布包

创建与发布

可以将自己开发的工具包发布到npm服务器上,方便自己和其他开发者使用

  1. 创建文件夹,并创建文件index.js,在文件中声明函数,使用module.exports暴露
  2. npm初始化工具包,package.json填写包的信息
  3. 注册账号:https://www.npmjs.com/signup
  4. 激活账号(一定要激活账号)
  5. 修改为官方的官方镜像(命令行中运行nrm use npm)
  6. 命令行下npm login填写相关用户信息
  7. 命令行下npm publish提交包

更新包

后续可以对自己发布的包进行更新,操作步骤如下

  1. 更新包中的代码
  2. 测试代码是否可用
  3. 修改package.json中的版本号
  4. 发布更新
1
npm publish

删除包

执行如下命令删除包

1
npm unpublish --force

删除包需要满足一定的条件

  1. 你是包的作者
  2. 发布小于24小时
  3. 大于24小时后,没有其他包依赖,并且每周小于300下载量,并且只有一个维护者

六、扩展内容

在很多语言中都有包管理工具,比如:

语言 包管理工具
PHP composer
Python pip
Java maven
Go go mod
JavaScript npm/yarn/cnpm/other
Ruby rubyGems

除了编程语言领域有包管理工具外,操作系统层面也存在包管理工具,不过这个包指的是【软件包】

操作系统 包管理工具 网址
Centos yum https://packages.debian.org/stable/
Ubuntu apt https://packages.ubuntu.com/
MacOS homebrew https://brew.sh/
Windows chocolatey https://chocolatey.org/

nvm

一、介绍

nvm全称Node Version Manager,顾名思义,是一个用来管理node版本的工具,方便切换到不同版本的Node.js

二、使用

nvm的使用非常简单,跟npm的使用方法类似

下载安装

下载地址:https://github.com.coreybutler/nvm-windows/releases

选择nvm-setup.exe即可下载

常用命令

命令 说明
nvm list available 显示所有可以下载的Node.js版本
nvm list 显示已安装的版本
nvm install 18.12.1 安装18.12.1版本的Node.js
nvm install latest 安装最新版本的Node.js
nvm uninstall 18.12.1 删除某个版本的Node.js
nvm use 18.12.1 切换到18.12.1的Node.js

Express.js

一、express介绍

express是一个基于Node.js平台的极简、灵活的WEB应用开发框架,官方网址:https://www.expressjs.com.cn/

简单来说,express是一个封装好的工具包,封装了很多的功能,便于开发WEB应用(HTTP服务)

二、express使用

express下载

express本身是一个npm包,所以可以通过npm安装

1
2
npm init
npm i express

express初体验

1.创建JS文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 1.导入express
const express = require('express');

// 2.创建应用对象
const app = express();

// 3.创建路由规则
app.get('/home', (req, resp) => {
res.end('hello express server');
})

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

2.命令行下执行该脚本

1
2
3
node <文件名>
# 或者
nodemon <文件名>

3.然后在浏览器就可以访问https://127.0.0.1:3000/home

三、express路由

什么是路由

官方定义:路由确定了应用程序如何响应客户端对特定端点的请求

路由的使用

一个路由的组成有请求方法,路径和回调函数组成

express中提供了一系列的方法,可以很方便的使用路由,使用格式如下:

1
app.<method>(path, callback)

代码示例:

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
// 引入express
const express = require('express');

// 创建应用对象
const app = express();

// 创建get路由
app.get('/home', (req, resp) => {
resp.send('这个是home页面');
})

// 首页路由
app.get('/', (req, resp) => {
resp.send('我是首页');
})

// 创建post路由
app.post('/login', (req, resp) => {
resp.send('登录成功');
})

// 匹配所有请求方法
app.all('/search', (req, resp) => {
resp.send('1秒钟为您找到相关结果约100,000,000个');
})

// 自定义404路由
app.all('*splat', (req, resp) => {
resp.send('<h1>404 Not Found</h1>');
})

app.listen(9000, () => {
console.log('服务器成功运行,监听在9000端口');
})

获取请求参数

express框架封装了一些API来方便获取请求报文中的数据,并且兼容原生HTTP模块的获取方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const express = require('express');

const app = express();

app.get('/home', (req, resp) => {
// 原生操作
console.log(req.method);
console.log(req.url);
console.log(req.httpVersion);
console.log(req.headers);

// express操作
console.log(req.path);
console.log(req.query);
console.log(req.ip);
console.log(req.get('host'));

resp.end();
})

app.listen(9000, () => {
console.log('服务已经启动');
})

获取路由参数

路由参数指的是URL路径中的参数(数据)

1
2
3
app.get('/:id/html',(req, resp)=>{
resp.send('商品详情,商品id为:'+ req.params.id);
})

四、express响应设置

express框架封装了一些API来方便给客户端响应数据,并且兼容原生HTTP模块的获取方式

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
const express = require('express');

const app = express();

app.get('/response', (req, resp) => {
// 原生操作
resp.statusCode = 404;
resp.statusMessage = `xxx`;
resp.setHeader('content-type', 'text/html;charset=utf-8');
resp.write('aka');
resp.end();

// express响应
resp.status(500);
resp.set('aaa', 'bbb');
resp.send('as');

// 链式调用
resp.status(500).set('aaa', 'bbb').send('ad');

// 其他响应
// 重定向
resp.redirect('https://www.baidu.com');
// 下载响应
resp.download(__dirname + '/package.json');
// JSON响应
resp.json({
name: 'xh',
slogan: 'ajka'
})
// 文件响应
resp.sendFile(__dirname + '/form.html');

})

app.listen(9000, () => {

})

五、express中间件

什么是中间件

中间件(Middleware)本质是一个回调函数

中间件函数可以像路由回调一样访问请求对象(request)响应对象(response)

中间件的作用

中间件的作用就是使用函数封装公共操作,简化代码

中间件的类型

  1. 全局中间件
  2. 路由中间件

定义全局中间件

每一个请求到达服务端之后都会执行全局中间件函数

声明中间件函数

1
2
3
4
5
6
7
// 声明中间件函数
function recordMiddleWare(req, resp, next) {
// 实现功能代码
// ....
// 执行next函数,如果希望执行完中间件函数之后仍然继续执行路由中的回调函数,必须调用next()
next();
}

应用中间件

1
app.use(recordMiddleWare);

多个全局中间件

express允许使用app.use()定义多个全局中间件

1
2
3
4
5
6
app.use(function(req, resp, next)=>{
nxet();
})
app.use(function(req, resp, next)=>{
nxet();
})

定义路由中间件

如果只需要对某一些路由进行功能封装,则需要路由中间件

1
2
3
4
5
6
7
app.get('/路径', `中间件函数名`, (req, resp)=>{

})

app.get('/路径', `中间件函数名`, (req, resp)=>{

})

静态资源中间件

express内置处理静态资源的中间件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 引入express框架
const express = require('express');
// 创建服务对象
const app = express();
// 静态资源中间件的设置,将当前文件夹下的public目录作为网站的根目录
app.use(express.static(__dirname + '/public'));
// 如果访问的内容经常变化,则还是需要设置路由
// 如果Public目录下有index.html文件,单独也有index.html的路由,则谁写在前,谁生效
app.get('/index.html', (req, resp)=>{
resp.send('首页');
})
// 监听端口
app.listen(9000, ()=>{
console.log('服务器在9000端口启动');
})

注意事项:

  1. index.html文件为默认打开的资源
  2. 如果静态资源与路由规则同时匹配,谁先匹配谁就响应
  3. 路由响应动态资源,静态资源中间件响应静态资源

获取请求体数据body-parser

使用express内置的body来获取请求体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const express = require('express');

const app = express();

app.use(express.json());
app.use(express.urlencoded({ extended: true }));

app.get('/login', (req, resp) => {
resp.sendFile(__dirname + '/08_form.html');
})

app.post('/login', (req, resp) => {
console.log(req.body);
resp.send('获取用户数据');
})

app.listen(9000, () => {
console.log('服务器启动成功');
})

六、Router

什么是Router

express中的Router是一个完整的中间件和路由系统,可以看做是一个小型的app对象

Router作用

对路由进行模块化,将路由单独提取出来一个文件,更好的管理路由

Router的使用

创建独立的JS文件(homeRouter.js)

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

// 2.创建路由器对象
const router = express.Router();

// 3.在router对象身上添加路由
router.get('/',(req, resp)=>{
resp.send('首页');
})

router.get('/cart',(req, resp)=>{
resp.send('购物车');
})

// 4.暴露
module.exports = router;

主文件

1
2
3
4
5
6
7
8
9
10
11
12
13
const express = require('express');

const app = express();

// 5.引入子路由文件
const homeRouter = require('./routes/homeRouter');

// 6.设置和使用中间件
app.use(homeRouter);

app.listen(9000, ()=>{
console.log('服务器在9000端口启动');
})

七、EJS模版引擎

什么是模版引擎

末班引擎是分离用户界面和业务数据的一种技术

什么是EJS

EJS是一个高效的JavaScript的模板引擎

EJS初体验

下载安装EJS

1
npm i ejs --save

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
// 1.引入ejs
const ejs = require('ejs');

// 2.定义数据
let person = ['张三', '李四', '王五'];

// 3.ejs解析模版返回结构
// <%= %>是ejs解析内容的标记,作用是输出当前表达式的执行结构
let html = ejs.render('<%= person.join("." %)>', {person:person});

// 4.输出结果
console.log(html);

EJS常用语法

执行JS代码

1
<% code %>

输出转义的数据到模版上

1
<%= code %>

输出非转移的数据到模版上

1
<%- code %>

八、Express应用程序生成器

通过应用生成器工具express-generator可以快速创建一个应用的骨架。

可以通过npx(包含在Node.js 8.2.0及更高的版本中)命令来运行Express应用程序生成器。

1
npx express-generator

对于较老的Node版本,可以通过npm将Express应用程序生成器安装到全局环境中并使用。

1
2
npm install -g express-generator
express

-h参数可以列出所有可用的命令行参数

九、处理文件上传

首先,html文件的表单需要设置上传类型,即enctype属性。

1
2
3
4
<form action="http://127.0.0.1:3000/portrait" method="post" enctype="multipart/form-data">
<input type="file" name="portrait">;
<input type="submit" value="上传图片">
</form>

安装formidable,因为需要使用require导入,所以不能使用最新版本的,需要指定版本

1
npm i formidable@2.1.2

在处理上传请求的地方

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
router.post('/portrait', (req, resp) => {
// 创建form表单对象
const form = formidable({
multiples: true,
// 设置上传文件的保存目录
uploadDir: path.resolve(__dirname, "../public/images"),
// 保持文件后缀
keepExtensions: true
});

// 解析请求报文
form.parse(req, (err, fields, files) => {
if (err) {
next(err);
return;
}
console.log(fields); // 存放的是表单的其他请求数据如username等
console.log(files); // 存放的是上传的文件数据

// 服务器保存该图片的访问URL(portrait是因为表单中上传的文件name是这个值)
let url = path.resolve(__dirname, "../public/images") + "/" + files.portrait.newFilename; // 将来将此数据保存到数据库当中

// 展示用户上传的图片
resp.sendFile(url);
})
})

十、lowdb

lowdb是一个json格式的数据库文件,可以简单替代数据库进行持久化操作。

安装lowdb包:

1
npm i lowdb@1.0.0

代码示例:

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
// 导入lowdb
const low = require('lowdb')
const FileSync = require('lowdb/adapters/FileSync')

const adapter = new FileSync('db.json')
// 获取db对象
const db = low(adapter)

// 初始化数据
db.defaults({ posts: [], user: {} })
.write()

// 写入数据
db.get('posts').push({ id: 1, title: 'hello' }).write();
// 将数据写入到最前方
db.get('posts').unshift({ id: 2, title: 'hello' }).write();

// 获取数据
console.log(db.get('posts').value());

// 删除数据
db.get('posts').remove({ id: 2 }).write();
console.log(db.get('posts').value());

// 更新数据
db.get('posts').find({ id: 1 }).assign({ title: '你好' }).write();
console.log(db.get('posts').value());

但是这样的记录有一个缺点就是没有id(主键)

所以需要安装另外一个包:shortid

1
npm i shortid

代码示例:

1
2
3
4
5
6
7
8
router.post('/account/create', (req, resp) => {
// 生成id(主键)
let id = shortid.generate();
// 使用lowdb持久化数据
db.get('accounts').unshift({ id: id, ...req.body }).write();

resp.send('添加成功');
})

数据展示:

1
2
3
4
5
6
7
8
9
10
11
12
{
"accounts": [
{
"id": "16Nc-PAWA",
"event": "坐地铁",
"time": "2026-7-30",
"type": "交通费用",
"money": "6",
"tips": "无"
}
]
}

MongoDB

一、简介

MongoDB是什么

MongoDB是一个基于分布式文件存储的数据库,官方网址:https://www.mongodb.com/

数据库是什么

数据库(database)是按照数据结构来组织、存储和管理数据的应用程序

数据库的作用

数据库的主要作用就是管理数据,对数据进行增(c)删(d)改(u)查(r)

数据库管理数据的特点

相比于纯文件管理数据,数据库管理数据有如下特点:

  1. 速度更快
  2. 扩展性更强
  3. 安全性更强

为什么选择MongoDB

操作语法与JavaScript类似,容易上手,学习成本低

二、核心概念

MongoDB中有三个重要概念需要掌握

  1. 数据库(database)数据库是一个数据仓库,数据库服务下可以创建很多数据库,数据库中可以存放很多集合
  2. 集合(collection)集合类似于JS中的数组,在集合中可以存放很多文档
  3. 文档(document)文档是数据库中的最小单位,类似于JS中的对象

JSON文件示例:

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
{
"accounts": [
{
"id": "3-YLju5f3",
"title": "买电脑",
"time": "2023-02-08",
"type": "-1",
"account": "5500",
"remarks": "为了上网课"
},
{
"id": "3-YLju5f4",
"title": "请女朋友吃饭",
"time": "2023-02-08",
"type": "-1",
"account": "214",
"remarks": "情人节聚餐"
},
{
"id": "mRQiD4s3K",
"title": "发工资",
"time": "2023-02-19",
"type": "1",
"account": "4396",
"remarks": "终于发工资啦!~~"
],
"users":[
{
"id": 1,
"name": "zhangsan",
"age": 18
},
{
"id": 2,
"name": "lisi",
"age": 20
},
{
"id": 3,
"name": "wangwu",
"age": 22
}
]
}

可以通过JSON文件来理解MongoDB中的概念

  1. 一个JSON文件好比是一个数据库,一个MongoDB服务下可以有N个数据库
  2. JSON文件中的一级属性的数组值好比是集合
  3. 数组中的对象好比是文档
  4. 对象中的属性有事也称为字段

一般情况下

  1. 一个项目使用一个数据库
  2. 一个集合会存储同一种类型的数据

三、下载安装与启动

下载地址:https://www.mongodb.com/try/download/community

建议选择zip类型,通用性更强

配置步骤如下:

  1. 将压缩包移动到C:\Program Files下,然后解压
  2. 创建C:\data\db目录,mongdb会将数据默认保存在这个文件夹
  3. 以mongodb中bin目录作为工作目录,启动命令行
  4. 运行命令mongod
  5. 看到控制台最后的waiting for connections则表明服务已经启动成功
  6. 然后可以使用mongo命令连接本机的mongodb服务

注意:

  1. 为了方便后续方便使用mongod命令,可以将bin目录配置到环境变量Path中
  2. 千万不要选中服务端窗口的内容,选中会停止服务,可以敲回车取消选中

四、命令行交互

命令行交互一般是学习数据库的第一步,不过这些命令在后续用到较少

数据库命令

1.显示所有的数据库

1
show dbs

2.切换到指定的数据库,如果数据库不存在会自动创建数据库

1
use 数据库名

3.显示当前所在的数据库

1
db

4.删除当前的数据库

1
2
use 库名
db.dropDatabase()

集合命令

1.创建集合

1
db.createCollection('集合名称')

2.显示当前数据库中的所有集合

1
show collections

3.删除某个集合

1
db.集合名.drop()

4.重命名集合

1
db.集合名.renameCollection('新名称')

文档命令

1.插入文档

1
db.集合名.insert(文档对象)

2.查询文档

1
db.集合名.find(查询条件)

_id是mongodb自动生成的唯一编号,用来标识文档

3.更新文档

1
2
db.集合名.update(查询条件,新的文档)
db.集合名.update({name:'张三'},{$Set:{age:19}})

4.删除文档

1
db.集合名.remove(查询条件)

五、Mongoose

介绍

Mongoose是一个对象文档模型库,官网:https://www.mongoosejs.net/

作用

方便使用代码操作mongodb数据库

使用流程

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
const mongoose = require('mongoose');

mongoose.connect('mongodb://127.0.0.1:27017/bilibili');

mongoose.connection.once('open', () => {
console.log('连接成功');
// 创建文档的结构对象
// 设置集合中文档的属性以及属性值的类型
let BookSchema = new mongoose.Schema({
name: String,
author: String,
price: Number
})

// 创建模型对象,对文档操作的封装对象
let BookModel = mongoose.model('books', BookSchema);

// 新增
BookModel.create({
name: "西游记",
author: "吴承恩",
price: "19.9"
})
.then(data => {
console.log(data);
// 关闭数据库连接(项目运行过程中一般不会这么操作)
mongoose.disconnect();
})
.catch(err => {
console.log(err);
})
})

mongoose.connection.on('error', () => {
console.log('连接失败');
})

mongoose.connection.on('close', () => {
console.log('连接断开');
})

字段类型

文档结构可选的常用字段类型列表

类型 描述
String 字符串
Number 数字
Boolean 布尔值
Array 数组,也可以用[]来表示
Date 日期
Buffer Buffer对象
Mixed 任意类型,需要使用mongoose.Schema.Types.Mixed指定
ObjectId 对象id,需要使用mongoose.Schema.Types.ObjectId指定
Decimal128 高精度数字,需要使用mongoose.Schema.Types.Decimal128指定

字段值验证

Mongoose有一些内建验证器,可以对字段值进行验证

必填项

1
2
3
4
title: {
type: String,
required: true //设置必填项
}

默认值

1
2
3
4
author: {
type: String,
default: '匿名' //默认值
}

枚举值

1
2
3
4
gender: {
type: String,
enum: ['男', '女'] //设置的值必须是数组中的
}

唯一值

1
2
3
4
username: {
type: String,
unique: true
}

unique需要重建集合才能有效果

永远不要相信用户的输入

CURD

增加

插入一条

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
mongoose.connection.once('open', () => {
console.log('连接成功');
// 创建文档的结构对象
// 设置集合中文档的属性以及属性值的类型
let BookSchema = new mongoose.Schema({
name: String,
author: String,
price: Number
})

// 创建模型对象,对文档操作的封装对象
let BookModel = mongoose.model('books', BookSchema);

// 新增
BookModel.create({
name: "西游记",
author: "吴承恩",
price: "19.9"
})
.then(data => {
console.log(data);
// 关闭数据库连接(项目运行过程中一般不会这么操作)
mongoose.disconnect();
})
.catch(err => {
console.log(err);
})
})

批量插入

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
mongoose.connection.once('open', () => {
console.log('连接成功');
// 创建文档的结构对象
// 设置集合中文档的属性以及属性值的类型
let BookSchema = new mongoose.Schema({
name: String,
author: String,
price: Number
})

// 创建模型对象,对文档操作的封装对象
let BookModel = mongoose.model('books', BookSchema);

// 新增
BookModel.insertMany(
{
name: "西游记",
author: "吴承恩",
price: "19.9"
},
{
name: "西游记",
author: "吴承恩",
price: "19.9"
}
)
.then(data => {
console.log(data);
// 关闭数据库连接(项目运行过程中一般不会这么操作)
mongoose.disconnect();
})
.catch(err => {
console.log(err);
})
})

删除

删除一条数据

1
2
3
4
5
6
7
BookModel.deleteOne({ _id: '6a6b3e799b87b9fa0af1e66a' })
.then(data => {
console.log('删除成功')
})
.catch(err => {
console.log('删除失败');
})

批量删除

1
2
3
4
5
6
7
BookModel.deleteMany({ author: '小红' })
.then(data => {
console.log('数据批量删除成功', data);
})
.catch(err => {
console.log('删除操作失败', err);
})

更新

更新一条数据

1
2
3
4
5
6
7
BookModel.updateOne({ name: '红楼梦' }, { price: 20 })
.then(data => {
console.log('更新数据成功');
})
.catch(err => {
console.log('更新数据出错', err);
})

批量更新数据

1
2
3
4
5
6
7
BookModel.updateMany({ name: '红楼梦' }, { price: 20 })
.then(data => {
console.log('更新数据成功', data);
})
.catch(err => {
console.log('更新数据失败', err);
})

查询

查询一条数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
BookModel.findOne({ name: 'CallOfDuty' })
.then(data => {
console.log('查询数据成功', data);
})
.catch(err => {
console.log('查询数据失败', err);
})
// 根据ID获取文档
BookModel.findById('6a6b86316df7343c63850079')
.then(data => {
console.log('查询数据成功', data);
})
.catch(err => {
console.log('查询数据失败', err);
})

批量查询数据,find方法中不传参数则表示查询所有文档

1
2
3
4
5
6
7
BookModel.find({ author: '余华' })
.then(data => {
console.log('批量查询数据成功', data)
})
.catch(err => {
console.log('批量查询失败', err);
})

条件控制

运算符

在mongodb中不能使用> < >= <= !==等运算符,需要使用替代符号

  1. > 使用 $gt
  2. < 使用$lt
  3. >=使用$gte
  4. <=使用$lte
  5. !==使用$ne
1
2
// 查找id号比3大的所有记录
db.students.find({id: {$gt:3}});

逻辑运算

$or逻辑或的情况

1
db.students.find({$or:[{age:18},{age:24}]});

$and逻辑与的情况

1
db.students.find({$and:[{age:{$lt:20}},{age:{$gt:15}}]});

正则匹配

挑拣中可以直接使用JS的正则语法,通过正则可以进行模糊查询

1
db.students.find({name:/imissyou/});

个性化读取

字段筛选

1
2
3
4
5
6
7
8
9
10
11
12
// 0:不要的字段
// 1:要的字段
BookModel
.find()
.select({_id:0, title:1})
.exec()
.then(data=>{

})
.catch(err=>{

})

数据排序

1
2
3
4
5
6
7
8
9
10
11
12
13
// sort排序
// 1:升序
// -1:降序
BookModel
.find()
.sort({got:1})
.exec()
.then(data=>{

})
.catch(err=>{

})

数据截取

1
2
3
4
5
6
7
8
9
10
11
12
13
// skip跳过 limit限定
// 可以作为分页处理,skip(跳过前10条),limit(取10条),可以理解为如果每页有10条数据,那么当前取的就是第二页的数据,完成了分页的功能
BookModel
.find()
.skip(10)
.limit(10)
.exec()
.then(data=>{

})
.catch(err=>{

})

数据库模块化提取

目录结构:

其中config文件夹中配置连接数据库的配置

1
2
3
4
5
module.exports = {
DBHost: '127.0.0.1',
DBPort: '27017',
DBName: 'bilibili'
}

db文件夹下的db.js负责连接数据库等操作

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
module.exports = function (success, error) {
const mongoose = require('mongoose');

const { DBHost, DBPort, DBName } = require('../config/config');

mongoose.connect(`mongodb://${DBHost}:${DBPort}/${DBName}`);

if (error !== 'function') {
error = () => {
console.log('连接失败');
}
}

mongoose.connection.once('open', () => {
success();
})

mongoose.connection.on('error', () => {
error();
})

mongoose.connection.on('close', () => {
console.log('连接断开');
})
}

models文件夹下负责定义文档对象

1
2
3
4
5
6
7
8
9
10
11
const mongoose = require('mongoose');

let BookSchema = new mongoose.Schema({
name: String,
author: String,
price: Number
});

let BookModel = mongoose.model('book', BookSchema);

module.exports = BookModel;

index.js中为入口脚本

1
2
3
4
5
6
7
8
9
10
11
const db = require('./db/db');

const BookModel = require('./models/BookModel');

db(() => {
console.log('连接成功');

BookModel.create() // 进行相应的操作
}, () => {
console.log('连接失败');
})

接口

一、简介

接口是什么

接口是前后端通信的桥梁

简单理解:一个接口就是服务中的一个路由规则,根据请求响应结果

接口的英文单词是API(Application Program Interface),所以有时也称为API接口

这里的接口指的是数据接口,与编程语言的interface不同

接口的作用

实现前后端通信

接口的开发与调用

大多数接口都是由后端工程师开发的,开发语言不限

一般情况下接口都是由前端工程师调用的,但有时后端工程师也会调用接口,比如短信接口、支付接口等

接口的组成

一个接口一般由如下几个部分组成

  1. 请求方法
  2. 接口地址(URL)
  3. 请求参数
  4. 响应结果

二、RESTful API

RESTful API是一种特殊风格的接口们主要特点有如下几个:

  1. URL中的路径表示资源,路径中不能有动词,例如createdeleteupdate
  2. 操作资源要与HTTP请求方法对应
  3. 操作结果要与HTTP响应状态码对应

规则示例:

操作 请求类型 URL 返回
新增歌曲 POST /song 返回新生成的歌曲信息
删除歌曲 DELETE /song/10 返回一个空文档
修改歌曲 PUT /song/10 返回更新后的歌曲信息
修改歌曲 PATCH /song/10 返回更新后的歌曲信息
获取所有歌曲 GET /song 返回歌曲列表数组
获取单个歌曲 GET /song/10 返回单个歌曲信息

三、json-server

json-server是一个JS编写的工具包,可以快速搭建RESTful API服务

官方网址:http://github.com/typicode/json-server

操作步骤:

1.全局安装json-server

1
npm i -g json-server

2.创建JSON文件(json.db),编写基本结构

1
2
3
4
5
6
7
{
"song": [
{"id": 1, "name": "干杯", "singer": "五月天"},
{"id": 2, "name": "当", "singer": "动力火车"},
{"id": 3, "name": "不能说的秘密", "singer": "周杰伦"}
]
}

3.以JSON文件所在文件夹作为工作目录,执行如下命令

1
json-server --watch db.json

默认监听端口为3000

四、接口测试工具

Apifox

使用教程

会话控制

一、介绍

所谓会话控制就是对会话进行控制

HTTP是一种无状态的协议,它没有办法区分多次的请求是否来自于同一个客户端,无法区分用户,而产品中又有大量存在的这样的需求,所以需要通过会话控制来解决该问题

常见的会话控制技术有三种

  1. cookie
  2. session
  3. token

二、cookie

cookie是什么

cookie是HTTP服务器发送到用户浏览器并保存在本地的一小块数据

cookie是保存在浏览器端的一小块数据

cookie是按照域名划分保存的

简单示例:

域名 cookie
www.baidu.com a=100;b=200
www.bilibili.com xid=1020;hm=1124
jd.com x=100;ocw=124

cookie的特点

浏览器向服务器发送请求时,会自动将当前域名下可用的cookie设置在请求头中,然后传递给服务器

这个请求头的名字也叫cookie,所以将cookie理解为一个HTTP的请求头也是可以的

cookie的运行流程

填写账号和密码校验身份,校验通过后下发cookie

有了cookie之后,后续向服务器发送请求时,就会自动携带cookie

代码操作cookie

express中可以使用cookie-parse进行处理

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
const express = require('express');
const cookieParser = require('cookie-parser');
const app = express();
app.use(cookieParser());

// 设置cookie
app.get('/set-cookie', (req, res) => {
// 会在浏览器关闭的时候自动销毁
res.cookie('name', 'zhangsan');
// 可以设置过期时间,过期之后自动销毁,单位是毫秒
res.cookie('name', 'lisi', {maxAge: 60 * 1000})
res.send('home');
})

// 删除cookie(比如用户退出登录的时候)
app.get('/remove-cookie', (req, res) => {
res.clearCookie('name');
res.send('删除成功');
})

// 获取cookie
app.get('/get-cookie', (req, res) => {
console.log(req.cookies);
res.send('获取cookie');
})

// 启动服务
app.listen(3000, () => {
console.log('Server started on port 3000');
})

不同浏览器中的cookie是相互独立的,不共享

三、session

session是什么

session是保存在服务器端的一块数据,保存当前访问用户的相关信息

session的作用

实现会话控制,可以识别用户的身份,快速获取当前用户的相关信息

session的运行流程

填写账号和密码校验身份,校验通过后创建session信息,然后将session_id的值通过响应头返回给浏览器

有了cookie,下次发送请求时会自动携带cookie,服务器通过cookie中的session_id的值确定用户的身份

session的代码操作

exprss中可以使用express-session对session进行操作

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
const express = require('express');
const session = require('express-session');
const MongoStore = require('connect-mongo').default;

const app = express();

// 设置session中间件
app.use(session({
name: 'sid', // 设置cookie的name,默认值是:connect.sid
secret: 'secret', // 参与加密的字符串,又称为签名
saveUninitialized: false, // 是否为每一次请求都设置一个cookie来保存session的id
resave: true, // 是否在每次请求时重新保存session
store: MongoStore.create({
mongoUrl: 'mongodb://127.0.0.1:27017/bilibili', // 数据库的链接配置
}),
cookie: {
httpOnly: true, // 开启后,前端无法通过JS操作
maxAge: 1000 * 6 * 5 //控制SessionID的过期时间
}
}))

app.get('/', (req, res) => {
res.render('index', {title: 'Home'});
})

// session的设置
app.get('/login', (req, res) => {
if (req.query.username === 'admin' && req.query.password === 'admin') {
// 设置session信息
req.session.username = req.query.username;
res.send('登录成功');
} else {
res.send('登录失败');
}
})

// session的读取
app.get('/cart', (req, res) => {
// 中间件通过请求头中的sid去数据库查询是否存在session,如果存在则返回,同时表明用户处于登录的状态
if (req.session.username) {
res.send('欢迎');
} else {
res.send('您还没有登录');
}
})

// session的销毁
app.get('/logout', (req, res) => {
req.session.destroy(() => {
res.send('退出成功');
});
})

app.listen(3000, () => {
console.log('Server started on port 3000');
})

四、session和cokkie的区别

cookie和session的区别主要有如下几点

  1. 存在的位置
    1. cookie:浏览器端
    2. session:服务端
  2. 安全性
    1. cookie是以明文的方式存放在客户端的,安全性相对较低
    2. session存放于服务器中,所以安全性相对较好
  3. 网络传输量
    1. cookie设置内容过多会增大报文体积,影响传输效率
    2. session数据存储在服务器,只是通过cookie传递id,所以不影响效率
  4. 存储限制
    1. 浏览器限制单个cookie保存的数据不能超过4k,且单个域名下的存储数量也有限制
    2. session数据存储在服务器中,所以没有这些限制

五、token

token是什么

token是服务端生成并返回给HTTP客户端的一串加密字符串,token中保存着用户信息

token的作用

实现会话控制,可以识别用户的身份,主要用于移动端APP

token的工作流程

填写账号和密码校验身份,校验通过后响应token,token一般是在响应体中返回给客户端的

后续发送请求时,需要手动将token添加在请求报文中,一般是放在请求头中

token的特点

  1. 服务端压力更小
    1. 数据存储在客户端
  2. 相对更安全
    1. 数据加密
    2. 可以避免CSRF(跨站请求伪造)
  3. 扩展性更强
    1. 服务间可以共享
    2. 增加服务节点更简单

JWT

JWT(JSON Web Token)是目前最流行的跨域认证解决方案,可用于基于token的身份验证

JWT使token的生成与校验更规范

可以使用jsonwebtoken包来操作token

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

// 创建token
// jwt.sign(数据, 加密字符串, 配置对象)
let token = jwt.sign({
username: 'zhangsan'
}, 'aasdasd', {
expiresIn: 60 // 单位是秒
})

// 解析token
jwt.verify(token, 'aasdasd', (err, data) =>{
if(err){
console.log('校验失败');
return;
}
console.log(data);
})

六、附录

本地域名

所谓本地域名就是只能在本机使用的域名,一般在开发阶段使用

操作流程

编辑文件C:\Windows\System32\drivers\etc\hosts

1
127.0.0.1 www.baidu.com

如果修改失败,可以修改该文件的权限

原理

在地址栏输入域名之后,浏览器会先进行DNS(Domain Name System)查询,获取该域名对应的IP地址

请求会发送到DNS服务器,可以根据域名返回IP地址

可以通过ipconfig/all查看本机的DNS服务器

hosts文件也可以设置域名与IP的映射关系,在发送请求前,可以通过该文件获取域名的IP地址