09_MongoDB

image-20230414111604062

1.简介

1.1 Mongodb 是什么

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

1.2 数据库是什么

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

没错,数据库就是一个应用程序。是不是和我所理解的应用程序差别很大,我所理解的应用程序是有界面、严重依赖鼠标、键盘只是用于输入必要信息、完全不用输入代码的程序。

像数据库这样的应用程序,其实我也做过,vue、html、react甚至是编程语言的任意第三方库,都相当于是应用程序,只不过数据库的抽象程度非常高,不容易掌握。

1.3 数据库的作用

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

1.4 数据库管理数据的特点

相比于纯文件管理数据(纯文本管理数据,最起码有IO瓶颈、IO冲突等问题,像pandas这样的数据处理库,是没有办法解决的),数据库管理数据有如下特点:

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

数据库其实是一个应用程序,原来我是不知道的。我对数据库只有模糊的印象,别人说要用,我就用,因为数据库太难学,所以一直没用。

为什么要使用数据库?因为数据库可以帮助我解决很多问题,不管是开发还是部署,都已经有了成熟的方案,照着做就行了。而且数据库里面有各种数据结构,所以响应速度非常快。我只需要知道怎么使用“数据库”这种软件就行了。

1.5 为什么选择 Mongodb

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

2.核心概念

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

什么是“数据库服务”?

MongoDB就是一个数据库服务,例如在本机的MongoDB中,可以创建多个数据库。

image-20230414112948526

JSON 文件示例:

image-20230917203657581

大家可以通过 JSON 文件来理解 Mongodb 中的概念

一般情况下

3.下载安装与启动

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

建议选择 zip 类型, 通用性更强。不过我是下载的msi,直接安装即可,下面的配置步骤都是针对zip类型安装来的。

配置步骤如下:

1> 将压缩包移动到 C:\Program Files 下,然后解压

2> 创建 C:\data\db 目录,mongodb 会将数据默认保存在这个文件夹

3> 以 mongodb 中 bin 目录作为工作目录,启动命令行

4> 运行命令 mongod

看到最后的 waiting for connections 则表明服务已经启动成功

image-20230414133856605

然后可以使用 mongo 命令连接本机的 mongodb 服务,里面可以输入mongo的命令。

image-20230414133916924

注意:

image-20230917202241174

4.命令行交互

命令行交互一般是学习数据库的第一步,不过这些命令在后续用的比较少,所以大家了解即可。

4.1 数据库命令

  1. 显示所有的数据库
  1. 切换到指定的数据库,如果数据库不存在会自动创建数据库
  1. 显示当前所在的数据库
  1. 删除当前数据库(首先要切换到要删除的数据库,并且删除的命令不需要指定数据库名称)

image-20231115163341724

4.2 集合命令

  1. 创建集合
  1. 显示当前数据库中的所有集合
  1. 删除某个集合
  1. 重命名集合

image-20231115163615505

4.3 文档命令

  1. 插入文档
  1. 查询文档。查询条件如果为空,表示查询所有数据。

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

  1. 更新文档
  1. 删除文档

image-20231115164426033

4.4 应用场景

4.4.1 新增
4.4.2 删除

有些情况下,可以做伪删除,帮助用户如果需要数据的时候,可以恢复。比如说在一个数据对象里面,添加is_deleted属性,如果删除,则设置为true,这样在返回数据结果的时候,加上这个条件即可。

4.4.3 更新
4.4.4 查询

记住使用场景对我来说非常有用,因为我的项目经验有限,什么时候需要使用MongoDB,什么时候使用关系型数据库,这些都还不知道,所以我把使用场景记下来,等遇到的时候,想起来就行了。

ORM模型,现在看来只适合于关系型数据库,不管是sequelize还是prisma,在里面我都没有看到引入MongoDB或者其它的No SQL。另外我看了一下MongoDB的文档,里面的查询语言本身和ORM模型的语言很类似,所以就没有人来编写它的ORM模型。(2023-11-15记录:这个理解是错误的,因为MongoDB最好的ORM模型就是mongoose,谁说没有的)

关系型数据库和No SQL没有谁更重要的问题,而是谁更适合某些场景,在下面这些场景下,使用No SQL是最好的选择。

MongoDB 主要应用于以下场景:

以下是一些具体的例子:

以下是MongoDB的主要应用场景以及一些示例:

  1. 内容管理系统(CMS)

    • 示例: 一个新闻网站可以使用MongoDB来存储文章、图片、视频和评论等内容,每个内容项都可以表示为一个文档。这样的CMS系统可以轻松地扩展以处理大量的新闻和媒体内容。
  2. 社交媒体应用程序

    • 示例: 社交媒体平台可以使用MongoDB存储用户资料、帖子、评论、关注关系等数据。由于MongoDB支持嵌套文档,可以将用户的活动流存储在其个人资料文档中,使实时推送和社交互动更高效。
  3. 实时分析和大数据处理

    • 示例: 一家电子商务公司可以使用MongoDB来存储销售数据、用户行为和产品信息。通过MongoDB的聚合功能,可以实时分析销售趋势、用户偏好和库存情况,以做出及时的经营决策。
  4. 物联网(IoT)和传感器数据

    • 示例: IoT设备可以生成大量数据,如温度、湿度、位置等。MongoDB可以用于存储和查询这些数据,以监控设备状态、执行预测维护和生成实时报告。
  5. 实时应用程序

    • 示例: 在线游戏和聊天应用程序需要快速响应用户的操作和实时更新。MongoDB可以用于存储用户配置、游戏状态和聊天消息,以实现实时互动。
  6. 日志和事件存储

    • 示例: 大型服务器集群生成大量的日志和事件数据。MongoDB可以用于存储和检索这些数据,以进行系统监控、性能分析和故障排除。
  7. 地理空间数据

    • 示例: 地图应用程序和位置服务可以使用MongoDB来存储地理空间数据,如地点、地区边界和路线。这些数据可以用于位置搜索、导航和地理信息分析。

5.Mongoose

5.1 介绍

Mongoose 是一个对象文档模型库,官网 http://www.mongoosejs.net/,前面是中文网,不忍直视。还是直接看英文网:https://mongoosejs.com/

5.2 作用

image-20230923151301332

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

其实一直搞不懂为什么SQL有自己的语言,在项目中使用的时候却要使用ORM模型。

原因其实很简单,我如何才能针对不同的需求简洁、快速地写出不同的SQL语句呢?我如果想要使用原生SQL语句做到,还要在模板字符串里面写SQL语句,SQL语句复杂的时候有多长你知道吗?一个简单的关联查询就会很长了,更不要说复杂的增删改查了。模板字符串不方便的,是真不方便,里面预留出的${}真的能够应付不同的需求吗?我看未必,所以就每一个数据库,都会有很多ORM模型设计出来,使用ORM真的很方便。

5.3 使用流程

5.3.1 基本使用

创建一个文件夹,使用npm init -y来初始化包,在里面安装mongoose。

看到下面的内容,可能会感觉有些奇怪,mongoose是在单个js文件里面使用的啊,为什么要初始化一个包呢?

因为要用到mongoose,需要导入才能使用。

  1. 安装 mongoose

npm i mongoose

image-20230930093856660

测试之前,一定要把MongoDB服务启动,命令行中输入mongod启动。执行这个文件,看效果:

image-20230930094423952

在上述代码最后加上这段代码,演示一下连接关闭的效果:

image-20230930094704051

注意:一般在项目中,是不需要关闭连接的,因为项目上线后就是为了不断使用的。

5.3.2 连接mongodb补充说明

在mongodb@5.x版本中,官方推荐在绑定open方法时,使用once来绑定。http://www.mongoosejs.net/docs/index.html

image-20230930095252465

为什么要改成使用once来绑定open事件呢?因为once表示open事件里面的回调函数只执行一次,而on表示open事件里面的回调函数每次触发都会执行。假如说mongodb连接掉线了,那么mongoose会尝试重新连接,重新连接上之后,使用on绑定的事件都会执行一次,而once在之前已经执行过了,此时就不会再次执行了。

那么open事件的回调函数里面会放一些只需要执行一次的代码,在后面的学习中可以看到。

5.3.3 使用mongoose向mongodb数据库插入文档

注意:

在学习的时候,由于MongoDB是5.x版本,所以相应的mongoose也是5.x版本。现在MongoDB最新版是8.x版本,所以mongoose也是8.x版本。

定义文档结构的时候,我一直都没有使用new关键字,而是直接定义,项目并没有报错:

现在看了官方文档之后,发现定义文档结构的时候,都使用了new关键字,所以以后一定要加上这个关键字,避免意想不到的bug。

下面的笔记中我看到的位置都会加上new关键字,但是难免有没有改到的地方,那么看到了就要注意。

image-20230930100835784

 

执行查看效果:

image-20230930102704582

这里的_id是mongodb为我们生成的一个唯一编号。__v是mongoose为我们生成的一个版本编号,一般用不到。

查询数据库,也可以看到数据插入成功了。

image-20230930103326350

5.4 mongoose字段类型

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

类型描述
String字符串
Number数字
Boolean布尔值
Array数组,也可以使用 [] 来表示
Date日期
BufferBuffer对象
Mixed任意类型,实际使用时需要写完整的mongoose.Schema.Types.Mixed指定
ObjectId对象ID,实际使用时需要写完整的mongoose.Schema.Types.ObjectId指定。常用作外键。
Decimal128高精度数字,实际使用时需要写完整的mongoose.Schema.Types.Decimal128指定

5.5 字段值验证

Mongoose 有一些内建验证器,可以对字段值进行验证。在创建文档的结构对象时,除了指定属性值类型外,还可以进行验证。

5.5.1 必填项
5.5.2 默认值
5.5.3 枚举值
5.5.4 唯一值

unique 需要 重建集合 才能有效果。什么叫“重建”?重建就是先删除集合再创建集合,重新创建。

永远不要相信用户的输入,字段值验证是非常必须的。

5.6 CURD

为了保证数据的完整性,可以先将bilibili数据库删除掉,执行下面的代码。

 

数据库的基本操作包括四个,增加(create),删除(delete),修改(update),查(read)。为了方便学习,先将老师提供的data.js执行一下,插入一些数据。

注意:mongoose.model()会使用集合名称的复数,在数据库中创建集合。上面的代码中,使用了"novel",实际在数据库中,就是novels

注意:因为在mongoose@7.5.x版本中,一些方法不再有第二个参数,而是使用then的链式调用,所以一般我都写的是@7.5.x版本的方法,@5.x版本的方法不再给出示例了,其实也很简单,就是把@5.x版本中的第二个参数放到then中即可。

但是then里面的参数到底是(err,data)=>{}还是(data)=>{},还是要看官方文档,也可以自己试一下。

我看了一下,应该是这样写是不行的:

这样会报错没有办法获取到throw err的err值,所以必须写一个catch。

但是写了catch还是会报错,所以一般我写的时候,可以先这样写:

如果没有反应,输出不了data,可以改成这样:

如果还是不行,就直接看文档。

 

与mongoose的版本还是有关系的,如果不知道,还是看文档。

5.6.1 增加

插入一条

create()方法。

批量插入

insertMany()方法。

5.6.2 删除

1、删除一条数据

image-20230930112120076

2、批量删除

image-20230930112522912

5.6.3 更新

1、更新一条数据

image-20230930112931819

image-20230930113031458

2、批量更新数据

image-20230930113340080

5.6.4 查询

1、查询一条数据

findOne()方法:

image-20230930113718144

findById()方法:

2、批量查询数据

不加条件查询,获取所有数据:

加条件查询:

image-20230930114430859

5.7 条件控制

注意:虽然下面的例子大部分是MongoDB原生语言的例子,但mongoose使用的时候,条件部分是一样的。我会给出部分例子,照着写就行了。

需要留意条件的写法,条件可以看成是一种值,采用对象的方式来写,条件为key,具体的范围为值。

5.7.1 运算符

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

效果:

image-20231001124144569

5.7.2 逻辑运算

注意括号的写法。

$or 逻辑或的情况

image-20231001124655168

$and 逻辑与的情况

image-20231001125150284

5.7.3 正则匹配

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

image-20231001125558348

5.8 个性化读取

MongoDB版本问题,参考:https://juejin.cn/post/7225240369524670519

5.8.1 字段筛选

只读取某些字段,这样返回的速度会快很多。

效果:

image-20231001130442780

5.8.2 数据排序

效果:

image-20231001130900028

5.8.3 数据截取

 

image-20231001131511476

6.mongoose代码模块化

1、将mongoose操作基本代码提取到一个单独的文件中。

难点在于mongoose绑定的open和error事件,里面的回调函数该怎么做?答案:使用函数参数,将这些回调函数传递过来。

在项目package.json同级创建db文件夹,创建db.js文件。

2、在index.js文件中,引入db/db.js文件,传入两个函数参数。重点在于第一个参数函数,里面可以放一些数据库操作的代码。

效果:

image-20231001133635411

3、提取结构对象和模型对象

新建一个models文件夹,里面创建BookModel.js文件(每个模型对象都可以对应一个文件)。将原本在db的第一个参数函数里面创建模型对象的过程,提取出来,放到这个文件里面,并暴露。

在index.js中导入BookModel,并使用:

效果:

image-20231001134344918

4、为db.js的第二个参数设置默认值

因为第二个参数只是显示error,大多数情况下在调用db的时候,可以省略不写,此时就可以赋默认值,默认值为一个函数。

将连接端口号改为27018,同时去掉db调用时的第二个参数,测试看效果:

image-20231001140725053

5、将连接MongoDB的一些参数,提取出来,放到配置文件中。

创建config文件夹,config.js文件。

在db/db.js中导入配置项。

其实,把mongoose的基本流程和定义模型对象的流程搞清楚,上面的流程就都搞清楚了。

疑问:如果有多个模型对象,是不是都要引入到index.js文件里面,在第一个函数参数里面进行操作?这样代码还是很复杂,该怎么模块化呢?

老师举了一个例子,就是新增MovieModel这个模型对象,但是使用的时候,新建了一个js文件,里面导入了db,在db的第一个参数函数中操作MovieModel。说明index.js不是操作数据库的唯一入口文件,而是每个Model对应一个操作文件,该执行哪个文件就执行哪个文件。具体怎么操作,看后面会不会讲到,如果没有讲到,就找别的视频来看。

从下面的案例中可以看到,express项目中,第一步做好之后,就可以在具体的router文件里面进行model的操作了,不需要统一在index.js中引入并使用,这是怎么实现的?暂时不管。

7.图形化管理工具

我们可以使用图形化的管理工具来对 Mongodb 进行交互,这里演示两个图形化工具

Robo 3T 免费 https://github.com/Studio3T/robomongo/releases

Navicat 收费 https://www.navicat.com.cn/

8.案例优化

为了简便起见,将之前的accounts项目拿过来,跑起来。做的修改只是将里面的lowdb改为mongodb。

1、在项目中创建config.js,db.js,并编写代码,创建models文件夹。

image-20231001194908404

2、在项目的bin/www文件中,引入db,将原文件里面的所有代码放入到db的第一个函数里面,原文件的代码不需要任何修改(当然,第一行的代码\#!/usr/bin/env node不需要动)。这一步的目的是:先连接数据库,再启动服务。

image-20231001195448124

红框里面的代码是www文件中原本的代码,不做任何改变,只放进db的第一个回调函数里面即可。

3、创建模型文件

在models文件夹中创建AccountModel.js,编写代码:

注意:编写好了模型文件之后,就可以直接操作了,不需要预先创建accounts这个集合,为什么呢?因为有一个重要点:如果没有即创建,这就是ORM模型带来的好处。这一点顾虑打消掉很重要啊。

4、插入数据库

之前是将添加数据的路由放在了routes/index.js中,只需要改写里面的存取代码即可。

先看一下提交表格的数据:

image-20231001201203120

可以看到time不是Date类型,需要使用moment库来帮忙转成Date类型。

测试一下127.0.0.1:3000/account/create,插入成功,查看数据库:

image-20231001203327917

其实在这里我有一个疑问:文档结构对象里面规定了type和account的类型为Number,但是实际传递给后端的是String类型(我输出查看了确实是String类型),为什么不会报错呢?而且即使传递过去的是String类型,数据库保存的还是Number类型,这又是为什么呢?

这个疑问暂时不知道,等有时间一定要搜索一下。

5、读取数据库

读取数据库很简单,还是在路由里面进行处理:

ejs文件里面使用moment,更改时间显示格式:

显示效果:

image-20231001205006260

 

6、删除文档

根据id来删除,mongodb里面保存的是_id,所以在涉及到id的地方,要注意。

7、优化

为了防止用户误删,先弹出弹窗给用户确认。在列表页添加“新增”按钮,方便用户新增。

显示效果:

image-20231001210725119

image-20231001210742207

案例小结:

里面的模型对象的方法then用法和5.x小节学习的用法不一样了,之前在then里面还可以写err,data参数,但是现在只有一个参数了,要在catch里面写err参数才行。(可能与mongoose版本有关)

10_接口

1.简介

1.1 接口是什么

接口是 前后端通信的桥梁

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

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

这里的接口指的是『数据接口』, 与编程语言(Java,Go 等)中的接口语法不同

1.2 接口的作用

实现 前后端通信

image-20230414143617827

1.3 接口的开发与调用

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

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

1.4 接口的组成

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

一个接口示例 https://www.free-api.com/doc/325

image-20230414143758342

体验一下:https://api.asilu.com/idcard/?id=371522199111299668

2.RESTful API

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

规则示例:

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

扩展阅读:https://www.ruanyifeng.com/blog/2014/05/restful_api.html

3.json-server

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

注意:json-server只是快速搭建了restful api服务,并不是把代码都给写出来了。只是可以很方便的调用一些接口,给前端使用,可以说是临时使用的好方法。

官方地址: https://github.com/typicode/json-server

操作步骤:

  1. 全局安装json-server
  1. 创建 JSON 文件(db.json),编写基本结构
  1. 以 JSON 文件所在文件夹作为工作目录 ,执行命令:json-server --watch db.json。默认监听端口为 3000

image-20231002090333631

注意看Resources,里面只有一个资源http://localhost:3000/song,如果db.json里面添加了集合的话,资源会增加。

  1. 访问http://localhost:3000

image-20231002091034839

测试GET请求:

image-20231002091132857

image-20231002091152857

  1. 使用APIPOST或POSTMAN等工具来测试接口服务

在浏览器中可以测试get请求,但是post/put/delete等请求,使用APIPOST等工具来测试更方便。在下一小节会讲到这些工具的使用。

4.接口测试工具

介绍几个接口测试工具

4.1 apipost的使用

1、基本使用

image-20231002092014665

image-20231002092252171

2、设置默认环境

image-20231002092534428

image-20231002092626929

image-20231002092734809

3、添加新增接口

image-20231002094728860

4、删除接口

image-20231002094925531

5、更新接口

image-20231002095447466

6、apipost的公共参数

image-20231002100414316

image-20231002100602503

7、apipost的文档功能

image-20231002100944876

image-20231002101013135

 

既然apipost这么好,是不是写接口都在apipost里面写就行了呢?

肯定不是啊,上面使用apipost有一个前提,就是使用了json-server,帮助我们构造了restful api,这样编写文档的时候可以很轻松。但是一般情况下,接口都是先写好再来测试的,接口里面具体执行的代码肯定是有点复杂的,要考虑很多问题。那么文档功能怎么加呢?是加在代码里面还是在apipost里面生成呢?我觉得还是在代码里面加比较简单,apipost与代码的结合能力我还没有体会到,除非apipost可以添加到代码里面去,然后代码执行之后,在apipost里面生成测试接口的包、生成文档,否则还是很麻烦。

可以找一下apipost有没有这样的功能,有的话就完美了。

5.案例优化

其实面对接口,刚开始我还不知道该怎么写?其实还是routes,只不过之前的案例中访问routes,得到的是ejs页面,访问api,得到的是数据。这一点要搞清楚。

这个案例就是将接口写出来,用apifox测试即可。

将接口改为api接口,纯粹提供数据服务,具体的操作交给前端来做。

1、在routes里面新建api文件夹,创建account.js,将routes/index.js里面的代码复制过来。并在app.js中导入account.js。

image-20231002102733550

为api接口的路由地址添加了前缀api,测试OK:

image-20231002102905884

 

下面就可以更改相应的接口了,更改也很简单,只需要将返回结果改写一下即可。说明一下:这里只是写接口,在apipost里面进行测试,并没有做页面的渲染,因为这不是重点

2、获取账单接口

测试:

image-20231002104133519

为了测试获取失败的场景,我在任务管理器中将MongoDB的服务停了:

image-20231002104734564

image-20231002104748681

3、创建账单接口

测试:

image-20231002105812953

image-20231002105934263

4、删除账单接口

测试:

image-20231002110340947

5、获取单条账单接口

测试:

image-20231002110812239

6、更新账单接口

测试:

image-20231002112006860

11_会话控制

1.介绍

所谓会话控制就是 对会话进行控制。什么是“会话”?就是客户端通过http协议与服务端进行交流沟通。

HTTP 是一种无状态的协议,它没有办法区分多次的请求是否来自于同一个客户端, 无法区分用户

而产品中又大量存在的这样的需求,所以我们需要通过 会话控制 来解决该问题。

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

2.cookie

2.1 cookie 是什么

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

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

cookie 是按照域名划分保存的

简单示例:

域名cookie
www.baidu.coma=100;b=200
www.bilibili.comxid=1020abce121;hm=112411213
jd.comx=100;ocw=12414cce

2.2 cookie 的特点

浏览器向服务器发送请求时,会自动当前域名下 可用的 cookie 设置在请求头中,然后传递给服务器。这个请求头的名字也叫 cookie ,所以将 cookie 理解为一个 HTTP 的请求头也是可以的

2.3 cookie 的运行流程

从上面的信息就可以知道下面的cookie运行流程了。之前关于cookie我只知道可以用来存取,比如说不方便存放在vuex中的数据可以放在cookie中,但这些都是浏览器端cookie。这里讲的是服务端和浏览器通信的cookie,两者还是不一样的。

1、浏览器端向服务端传递信息。

2、服务端接受到浏览器端的信息,校验通过之后,下发给浏览器端。

3、后续浏览器端发送请求时,会自动带上cookie。服务端就可以根据cookie来做后续处理。

那么真正要做的,就是前两步。第一步可以说是正常登录流程都会做的,那么重点就在第2步了。而第3步,就可以设置一个中间件,这样大部分接口都可以很方便处理cookie了。

浏览器端填写账号和密码校验身份,服务端校验通过后下发 cookie给浏览器端。

image-20230414145530526

有了 cookie 之后,后续浏览器端向服务器发送请求时,就会自动携带 cookie。服务端就可以根据这个cookie来进行后续接口的校验。

image-20230414145544671

2.4 浏览器操作 cookie

浏览器操作 cookie 的操作,使用相对较少,大家了解即可

1、禁用所有 cookie

image-20231002152751479

image-20231002152836847

2、删除 cookie

image-20231002153007802

image-20231002153041258

3、查看 cookie

chrome浏览器中已经不允许查看cookie了,但只是不允许直接看,通过F12调出开发者工具还是可以看到的。

image-20231002154033601

2.5 cookie 的代码操作

express 中可以使用 cookie-parser 进行处理cookie。使用express基本框架来做,不使用express-generator,为什么?因为express-generator会把东西全部搭建好。

1、搭建express基本框架,安装cookie-parser,npm i cookie-parser

2、 设置cookie

image-20231002155350251

3、删除cookie

image-20231002160443942

image-20231002160740847

再访问别的路由地址:

image-20231002160840737

4、读取cookie

image-20231002161931245

image-20231002161949404

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

3.session

3.1 session 是什么

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

3.2 session 的作用

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

3.3 session 运行流程

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

image-20230414150024164

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

image-20230414150035559

3.4 session 的代码操作

express 中可以使用 express-session 对 session 进行操作。使用express的基本代码结构,一步一步实现。

1、编写expres基本结构代码。安装express-sessionconnect-mongoconnect-mongo用于连接MongoDB数据库,这里是要把session存储到数据库中。

2、express-session安装与配置

将服务启动后,project数据库中就会出现sessions集合。

image-20231002165059450

3、express中session的操作

创建session

image-20231002165927558

image-20231002170132268

image-20231002170430337

获取session

image-20231002171014804

销毁session

image-20231002171405145

3.5 案例完善

还是拿上一个案例来做完善,这里完善的只是session相关的部分,别的内容先不管。

3.5.1 响应注册页面

在views下创建reg.ejs文件,用作注册页面。文件老师已经提供了。

在routes里面创建auth.js,用作注册相关路由:

在app.js中导入auth路由文件:

image-20231002185629869

运行并访问:

image-20231002185713160

3.5.2 注册用户

创建UserModel.js,保存注册用户的相关信息:

编写注册路由:

效果:

image-20231002191206064

因为用户的密码很重要,所以这里用到了md5来加密用户的密码再保存。但md5是单向加密,即加密后不能解密,那用户登录的时候怎么看用户输入的密码是否正确呢?可以在登录时使用md5加密账户密码,然后比较数据库中存储的加密后的密码是否一致。安装:npm i md5

image-20231002191732790

3.5.3 用户登录

创建登录界面:

编写路由规则:

3.5.4 写入session

在上一小节中,其实忘记做了一件事情,就是写session,这样跳转到account页面之后,有session才能获取到数据。(更进一步的,需要session才能获取到用户对应的数据,这一步这个案例没做)

项目中安装npm i express-session connect-mongo,按照之前案例的写法,将这两个中间件用起来。因为session涉及到大部分接口,所以必须在app.js里面进行配置。

image-20231002194009342

写入session:

image-20231002194429959

3.5.5 用户登录检测

需求:检测用户是否登录,只有登录后才能查看列表。

因为登录检测大部分接口都需要用到,所以做成一个中间件。创建middlewares文件夹,里面创建具体的中间件。

在需要用到中间件的地方,使用:

image-20231002195643659

打开一个没有session的浏览器窗口,查看效果,可以看到一输入/account之后,就立即跳转到登录界面了。

3.5.6 退出登录

在list.ejs里面添加一个退出登录的按钮:

image-20231002200329366

编写路由规则:

查看效果:

 

 

3.5.7 CSRF跨站请求伪造

用live-server打开attack.html,这样我的account账号就退出登录了。

image-20231002201220788

并且从attack.html发送请求的时候,会带上cookie。这是由于logout方法是GET造成的,需要将这个方法改为POST,就能解决问题。

将list.ejs里面的退出元素改为form表单:

image-20231002201806869

将退出的路由方法改为post:

image-20231002201837407

3.5.8 首页和404页面

首页路由配置:

404页面配置。app.js已经有了404相关代码,只需要进行修改即可:

image-20231002202411325

image-20231002202522400

4.session 和 cookie 的区别

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

  1. 存储的位置
  1. 安全性
  1. 网络传输量
  1. 存储限制

5.token

5.1 token 是什么

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

image-20231122155040476

image-20231122155144524

5.2 token 的作用

实现会话控制,可以识别用户的身份,主要用于移动端 APP(安卓、IOS、小程序、H5等等)。

5.3 token 的工作流程

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

image-20230414150508924

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

image-20230414150525937

5.4 token 的特点

5.5 JWT

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

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

我们可以使用 jsonwebtoken 包 来操作 token。

查看一下token是什么:

image-20231002204604885

查看一下校验结果:

image-20231002204734517

扩展阅读:https://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html

从上面的例子可以看到,token的操作非常简单。

接受浏览器端传过来的参数→创建token→设置中间件,在需要的地方校验token。

同时校验token得到的信息,可以从数据库中找到相应的数据,这样就可以返回给不同的用户以不同的结果了。

5.6 案例完善

需求:对api接口进行限制,加上token。就在上一个案例中完善,安装npm i jsonwebtoken

5.6.1 登录响应token

在routes/api里面创建auth.js,添加api登录的接口:

在app.js中导入并使用这个路由文件:

image-20231003093151497

使用apipost来测试接口,有种熟悉的感觉了:

image-20231003093224977

image-20231003093253191

5.6.2 token校验

服务端将token传递给浏览器端之后,浏览器端需要在后续的请求中,在请求头上加上token,这样在服务端会校验请求头里面的token是否正确。

为了能在大部分api接口里面校验token,需要创建一个中间件,api接口直接使用中间件即可。(注意这个中间件不能是全局中间件,因为有一些接口是不能使用token校验的,比如说注册、登录的接口)

在需要使用中间件的地方,引入:

image-20231003095107861

测试:

image-20231003095140282

image-20231003095214944

5.6.3 token功能完善

将jwt用到的密匙放在配置文件中,用到的地方就导入使用:

在token校验OK之后,在req上挂载用户信息,这样在使用此中间件的路由规则里面,就可以获取到用户信息,根据用户信息可以获取相应的数据:

5.6.4 本地域名配置

需求:将127.0.0.1的本地域名配置为www.baidu.com,这样就可以通过www.baidu.com:3000来访问此项目了(注意要清除缓存,不想清除可以打开浏览器的无痕窗口来查看)。

image-20231003100846348

image-20231003100859325

6.附录

6.1 本地域名

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

6.1.1 操作流程

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

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

image-20230414150850726

6.1.2 原理

在地址栏输入 域名 之后,浏览器会先进行 DNS(Domain Name System) 查询,获取该域名对应的 IP 地址。请求会发送到 DNS 服务器,可以 根据域名返回 IP 地址

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

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

7 项目上线

7.1 代码上传仓库

这个不用讲了。/public/upload文件夹应该加入到.gitignore里面,因为项目上线之后,所有的数据和数据库都要清空,这个文件夹专门是附件的文件夹,所以不用上传。

7.2 购买云服务器

7.3 连接服务器和软件安装

image-20231003103501562

打开远程桌面连接:

image-20231003103542613

image-20231003103623142

image-20231003103718688

在服务器中安装项目相关的软件,git、nodejs、vscode、数据库等等。

7.4 代码克隆,启动服务

在GitHub或Gitee里面克隆项目代码到服务器,将项目启动。

我还以为nodejs-express项目会进行打包操作,哪知道老师只是使用npm start将项目启动起来了(注意package.json里面的命令不要使用nodemon,而要使用node,因为nodemon是调试用的)。有没有打包呢?放到更专业的服务器上?以后找一下答案。

image-20231003104533591

7.5 域名购买与解析

image-20231003104833871

image-20231003105008463

image-20231003105222059

image-20231003105312615

7.6 配置https证书

https本意是http + SSL(secure sockets layer 安全套接层)。https可以加密http报文,所以大家也可以理解为是安全的http

工具官网:https://certbot.eff.org/

在服务器中操作:

1、下载工具:https://dl.eff.org/certbot-beta-installer-win_amd64.exe

2、安装工具

3、管理员运行命令certbot certonly --standalone

image-20231003110612441

4、代码配置如下:

将项目的bin/www里面用http启动的服务,换成用https启动的服务,并将加密工具的地址换成安装的实际地址:

image-20231003111251502

image-20231003110942558

使用https启动服务,需要换成默认的443端口。

如果想要http和https协议都可以访问网站,可以在bin/www里面同时用http和https启动项目,具体可以看老师的讲解。

5、证书更新

证书有效期为三个月,可以通过下面的命令更新。

7.7 前后端开发扩展介绍

image-20231003111924864