Node.js 学习之数据库与身份认证

本文详细介绍了SQL的基本概念和常用语句,包括查询、插入、更新和删除数据,以及排序和统计函数。接着讲解了在Express中如何连接和操作MySQL数据库,包括安装配置、执行SQL和数据操作。最后,探讨了Web开发中的身份认证机制,包括Session和JWT,阐述了它们的工作原理和在Express中的实现方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

数据库与身份认证

1、SQL 的相关学习

1. SQL 的概念

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

  1. SQL 是一门数据库编程语言
  2. 使用 SQL 语言编写出来的代码叫做 SQL 语句
  3. SQL 语言只能在关系型数据库中使用
    • 关系型数据库,例如:MySQL、Oracle、SQL Server
    • 非关系型数据库,例如:Mongodb

2. SQL 语句学习

a. 查询数据(select)、插入数据(insert into)、更新数据(update)、删除数据(delete)
/*
	ps: sql语句中单行注释用 -- 多行注释用 /* ... */
	    sql中关键字大小写不敏感
*/

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

-- 从 table_name 表中查询出所有数据
SELECT * FROM table_name
-- 从 table_name 表中查询出列名为 row_name 的列数据
SELECT row_name FROM table_name


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

-- 向指定的表中插入如下几列数据,列的值通过 values 一一指定
-- 列和值要一一对应,多个列和多个值之间,使用英文的逗号分隔
INSERT INTO table_name (row1, row2, ...) VALUES (value1, value2, ...)


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

/*
1. UPDATE 指定要更新哪个表中的数据
2. SET 指定列对应的新值(修改多个值时使用英文逗号分隔)
3. WHERE 指定更新的条件
*/
UPDATE table_name SET row_name = value WHERE condition


-- DELETE 语句用于删除表中的行

-- 从指定的表中,根据 WHERE 条件,删除对应的数据行
DELETE FROM table_name WHERE condition
b. where 条件、and 和 or 运算符
-- WHERE 字句用于限定选择的标准。在 SELECT、UPDATE、DELETE 语句中,皆可使用 WHERE 子句来限定选择的标准

SELECT row_name FROM table_name WHERE row operator value;
UPDATE table_name SET row=newValue WHERE row operator value;
DELETE FROM table_name WHERE row operator value;


-- AND 和 OR 可在 WHERE 子句中把两个或多个条件结合起来
-- AND 表示必须同时满足多个条件,相当于 js 中的 && 运算符
-- OR 表示只要满足任意一个条件即可,相当于 js 中的 || 运算符


以下是可在 WHERE 子句中使用的运算符

Where 子句中的运算符

还有 in(); is null; or; and; not like 等模糊查询

c. order by 排序、count(*) 函数、AS 设置列别名

ORDER BY 语句用于根据指定的列对结果集进行排序,**默认按照升序(ASC)**对记录进行排序,如果想要降序排列,可以使用关键词 DESC 进行设置。

注意:指定多种排序时使用英文的逗号进行分隔

COUNT(*) 函数用于返回查询结果的总数据条数

使用 AS 可以为列设置别名

-- 对 user 表中的数据,按照 id 字段进行升序排序(默认升序排序)
SELECT * FROM user ORDER BY id
SELECT * FROM user ORDER BY id ASC

-- 对 user 表中的数据,按照 id 字段进行降序排序
SELECT * FROM user ORDER BY id DESC

-- 对 user 表中的数据,先按照 status 进行降序排序,再按照 username 字母的顺序进行升序排序
SELECT * FROM user ORDER BY status DESC, username ASC


SELECT COUNT(*) FROM table_name
-- 查询 user 表中 status 为 0 的总数据条数
SELECT COUNT(*) FROM user WHERE status=0

-- 将列名称从 COUNT(*) 修改为 total
SELECT COUNT(*) AS total FROM user WHERE status=0

2、Express 中操作 MySQL

2.1、在项目中操作数据库的步骤

  1. 安装操作 MySQL 数据库的第三方模块(mysql)
  2. 通过 mysql 模块连接到 MySQL 数据库
  3. 通过 mysql 模块执行 SQL 语句

Express中操作MySQL

2.2、安装与配置 mysql 模块

a. 安装 mysql 模块

mysql 是托管于 npm 上的第三方模块,提供了在 Node.js 项目中连接和操作 MySQL 数据库的能力。

npm install mysql
b. 配置 mysql 模块

使用 mysql 模块操作 MySQL 数据库之前,必须先对 mysql 模块进行必要的配置

// 1. 导入 mysql 模块
const mysql = require('mysql')
// 2. 建立与 MySQL 数据库的连接关系
const db = mysql.createPool({
  host: 'http://127.0.0.1',  // 数据库的 IP 地址
  user: 'root',  // 登录数据库的账号
  password: '123456',  // 登录数据库的密码
  database: 'my_db_01'  // 指定要操作哪个数据库
})
c. 测试数据库连接是否成功
// 测试 mysql 模块能否正常工作
db.query('select 1', (err, results) => {
  // mysql 模块工作期间报错
  if (err) {
    return console.log(err.message)
  }
  // 能够成功执行 SQL 语句,正确结果为 [ RowDataPacket { '1': 1 } ]
  console.log(results)
})

2.3、使用 mysql 操作数据库

a. 查询数据
// 查询 user 表中的所有数据
const sqlStr = 'select * from user'
db.query(sqlStr, (err, results) => {
  // 查询数据失败
  if(err) return console.log(err.message)
  // 查询数据成功
  // 如果执行的是 select 查询语句,则执行的结果是数组
  console.log(results)
})
b. 插入数据
// 向 user 表中插入 username 为 Spider-Man,password 为 pcc321 的数据
// 1. 要插入到 user 表中的数据对象
const user = { username: 'Spider-Man', password: 'pcc321'}
// 2. 待执行的 SQL 语句,其中英文的 ? 表示占位符
const sqlStr = 'INSERT INTO user (username, password) VALUES (?, ?)'
// 3. 使用数组的形式,依次为 ? 占位符指定具体的值
db.query(sqlStr, [user.username, user.password], (err, results) => {
  //失败
  if (err) { 
    return console.log(err.message)
  }
  // 成功
  // 如果执行的是 insert into 插入语句,则 results 是一个对象
  // 可以通过 affectedRows 属性,来判断是否插入数据成功
  if(results.affectedRows === 1) {
    console.log('插入数据成功!') 
  }
})
c. 插入数据的便捷方式
// 插入数据的便捷方式
const user = { username: 'Spider-Man2', password: 'pcc4321'}
// 待执行的 SQL 语句
const sqlStr = 'INSERT INTO user SET ?'
// 执行 SQL 语句
db.query(sqlStr, user, (err, results) => {
  //失败
  if (err) { 
    return console.log(err.message)
  }
  // 成功
  if(results.affectedRows === 1) {
    console.log('插入数据成功!') 
  }
})
d. 更新数据
// 1. 要更新的数据对象
const user = { id: 7, username: 'iron-man', password: '000'}
// 2. 要执行的 SQL 语句
const sqlStr = 'UPDATE user SET username=?, password=? WHERE id=?'
// 3. 调用 db.query() 执行 SQL 语句的同时,使用数组依次为占位符指定具体的值
db.query(sqlStr, [user.username, user.password, user.id], (err, results) => {
  //失败
  if (err) { 
    return console.log(err.message)
  }
  // 成功
  // 执行 update 语句之后,执行的结果 results 也是一个对象,可以通过 affectedRows 属性判断是否更新成功
  if(results.affectedRows === 1) {
    console.log('更新数据成功!') 
  }
})
e. 更新数据便捷方式

更新表数据时,如果数据对象的每个属性和数据表的字段一一对应,则可以通过如下便捷方式快速更新数据

// 更新数据的便捷方式
const user = { id: 7, username: 'iron-man01', password: '0000'}
// 定义 SQL 语句
const sqlStr = 'UPDATE user SET ? WHERE id=?'
// 执行 SQL 语句
db.query(sqlStr, [user, user.id], (err, results) => {
  //失败
  if (err) { 
    return console.log(err.message)
  }
  // 成功
  // 执行 update 语句之后,执行的结果 results 也是一个对象,可以通过 affectedRows 属性判断是否更新成功
  if(results.affectedRows === 1) {
    console.log('更新数据成功!') 
  }
})
f. 删除数据
// 1. 待执行的 SQL 语句
const sqlStr = 'DELETE FROM user WHERE id=?'
// 2. 调用 db.query() 执行 SQL 语句的同时,为占位符指定具体的值
// 注意:如果 SQL 语句中有多个占位符,则必须使用数组为每个占位符指定具体的值
//       如果 SQL 语句中只有一个占位符,则可以省略数组
db.query(sqlStr, 7, (err, results) => {
  //失败
  if (err) { 
    return console.log(err.message)
  }
  // 成功
  // 执行 delete 语句之后,执行的结果 results 也是一个对象,也包 affectedRows 属性
  if(results.affectedRows === 1) {
    console.log('删除数据成功!') 
  }
})
g. 标记删除

使用 DELETE 语句会真正把数据从表中删除。保险起见,推荐使用标记删除的形式,来模拟删除的动作

标记删除,就是在表中设置类似于 status 这样的状态字段,来标记当前这条数据是否被删除。

当用户执行了删除的动作时,并不需要执行 DELETE 语句把数据删除,而是执行 UPDATE 语句,将这条数据对应的 status 字段标记为删除即可

// 标记删除:使用 UPDATE 语句替代 DELETE 语句,只更新数据的状态,并没有真正删除
db.query('UPDATE user SET status=1 where id=?', 6, (err, results) => {
  //失败
  if (err) { 
    return console.log(err.message)
  }
  // 成功
  if(results.affectedRows === 1) {
    console.log('删除数据成功!') 
  }
})

3、前后端的身份认证

3.1、Web 开发模式

a. 服务端渲染的 Web 开发模式

服务端渲染的概念:服务器发送给客户端的 HTML 页面,是在服务器通过字符串的拼接,动态生成的。因而客户端不需要使用 Ajax 这样的技术额外请求页面的数据

app.get('/index.html', (req, res) => {
    // 1. 要渲染的数据
    const user = { name: 'zs', age: 20 }
    // 2. 服务器端通过字符串的拼接,动态生成 HTML 内容
    const html = '<h1>姓名:${user.name}, 年龄:${user.age}</h1>'
    // 3. 把生成好的页面内容响应给客户端,客户端拿到的是真实的 HTML 页面
    res.send(html)
    
})

服务端渲染的优缺点:

服务端渲染的优缺点

b. 前后端分离的 Web 开发模式

概念:前后端分离的开发技术,依赖于 Ajax 技术的广泛应用,在这种开发模式下,后端只负责提供 API 接口,前端使用 Ajax 调用接口

前后端分离的优缺点:

前后端分离的优缺点

c. 如何选择 Web 开发模式

不谈业务场景而盲目选择使用何种开发模式都是耍流氓。

  • 比如企业级网站,主要功能是展示而没有复杂的交互,并且需要良好的 SEO,这时就需要使用服务器端渲染
  • 而类似后台管理项目,交互性比较强,不需要考虑 SEO,这是就可以使用前后端分离的开发模式
  • 为了同时兼顾首页的渲染速度前后端分离的开发效率,一些网站采用了首屏服务器端渲染 + 其他页面前后端分离的开发模式。

3.2、身份认证

身份认证(Authentication)又称 “身份验证”、“鉴权”,是指通过一定的手段,完成对用户身份的确认。

在 Web 开发中,涉及到身份认证的场景,例如:各大网站的手机验证码登录邮箱密码登录二维码登录等。

身份认证的目的,是为了确认当前所声称为某种身份的用户,确实是所声称的用户

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

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

3.3、Session 认证机制

a. HTTP 协议的无状态性和 Cookie 的相关知识

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

Cookie 是存储在用户浏览器中的一段不超过 4KB 的字符串。它由一个名称(Name)、一个值(Value)和其它几个用于控制 Cookie 有效期、安全性、适用范围的可选属性组成。

不同域名下的 Cookie 各自独立,每当客户端发起请求时,会自动当前域名下所有未过期的 Cookie 一同发送到服务器。

Cookie的几大特性

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

Cookie 在身份认证中的作用:

客户端第一次请求服务器时,服务器通过响应头的形式,向客户端发送一个身份认证的 Cookie,客户端会自动将 Cookie 保存在浏览器中。

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

Cookie 在身份认证中的用处

Cookie 不具有安全性:Cookie存储在浏览器中,而且浏览器也提供了读写 Cookie 的 API,因此 Cookie 很容易被伪造,不具有安全性,不建议服务器将重要的隐私数据,通过 Cookie 的形式发给浏览器。

注意:千万不要使用 Cookie 存储重要且隐私的数据!

b. 在 Express 中使用 Session 认证
Session 的工作原理

Session工作原理

安装并配置 express-session 中间件
// 安装 express-session 中间件
npm install express-session

// 通过 app.use() 来注册 session 中间件
// 1. 导入 session 中间件
const session = require('express-session')

// 2. 配置 session 中间件
app.use(session({
  secret: 'keyboard cat',  // secret 属性的值可以为任意字符串
  resave: false,           // 固定写法
  saveUninitialized: true  // 固定写法
}))
向 session 中存数据

当 express-session 中间件配置成功后,即可通过 req.session 来访问和使用 session 对象,从而存储用户的关键信息:

app.post('/api/login', (req, res) => {
  // 判断用户提交的登录信息是否正确
  if (req.body.username !== 'admin' || req.body.password !== '000000') {
    return res.send({ status: 1, msg: '登录失败' })
  }

  // 将登录成功后的用户信息,保存到 session 中
  // 注意:只有 express-session 中间件配置成功后, req 中才会有 session 这个属性
  req.session.user = req.body  // 存储用户信息
  req.session.isLogin = true   // 存储登录状态

  res.send({ status: 0, msg: '登录成功' })
})
从 session 中取数据

可以直接从 req.session 对象上获取之前存储的数据

// 获取用户姓名的接口
app.get('/api/username', (req, res) => {
  // 从 session 中获取用户的名称,响应给客户端
  if(!req.session.isLogin) {
    return res.send({ status: 1, msg: 'fail!' })
  }
  res.send({
    status: 0,
    msg: 'success!',
    username: req.session.user.username
  })
})
清空 session

调用 req.session.destory() 函数,即可清空服务器保存的 session 信息

// 退出登录的接口
app.post('/api/logout', (req, res) => {
  // 清空 session 信息
  req.session.destroy() 
  res.send({
    status: 0,
    msg: '退出登录成功'
  })
})

3.4、JWT 认证机制

a. Session 认证的局限性

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

注意:

  • 当前端请求后端接口不存在跨域问题的时候,推荐使用 Session 身份认证机制
  • 当前端需要跨域请求后端接口的时候,推荐使用 JWT 认证机制
b. JWT的概念

JWT(JSON Web Token)是目前最流行跨域认证解决方案

JWT 的工作原理:用户的信息通过 Token 字符串的形式,保存在客户端浏览器中,服务器通过还原 Token 字符串的形式来认证用户身份

JWT 工作原理

JWT 的组成部分:JWT 通常由三部分组成,分别是 Header(头部)、Payload(有效荷载)、Signature(签名)。三者之间使用英文的 “.” 分隔,格式如下:

Header.Payload.Signature

各部分代表的含义:

  • Payload 部分才是真正的用户信息,它是用户信息经过加密之后生成的字符串。
  • Header 和 Signature 是安全性相关的部分,只是为了保证 Token 的安全性

JWT 的使用方式:客户端收到服务器返回的 JWT 之后,通常会将它存储在 localStoragesessionStorage 中。此后,客户端每次与服务器通信,都要带上这个 JWT 的字符串,从而进行身份认证。推荐的做法是把 JWT 放在 HTTP 请求头的 Authorization 字段中,格式如下:

Authorization: Bearer <token>
c. 在 Express 中 使用 JWT
安装与配置 JWT 相关的包

两个 JWT 的相关包:

  • jsonwebtoken 用于生成 JWT 字符串
  • express-jwt 用于将 JWT 字符串解析还原成 JSON 对象
定义 secret 密钥

为了保证 JWT 字符串的安全性,防止 JWT 字符串在网络传输过程中被别人破解,需要专门定义一个用于加密和解密的 secret密钥

  1. 生成 JWT 字符串时,使用 secret 密钥对用户信息进行加密,最终得到加密好的 JWT 字符串
  2. 当把 JWT 字符串解析还原成 JSON 对象时,需要使用 secret 密钥进行解密
const secretKey = 'sakura_tt'
登录成功后生成 JWT 字符串

调用 jsonwebtoken 包提供的 sign() 方法,将用户信息加密成 JWT 字符串

app.post('/api/login', (req, res) => {
  // 将 req.body 请求体中的数据,转存为 userinfo 常量
  const userinfo = req.body
  // 登录失败
  if (userinfo.username !== 'admin' || userinfo.password !== '000000') {
    return res.send({
      status: 400,
      message: '登录失败!'
    })
  }

  // 登录成功
  // 3. 登录成功之后,调用 jwt.sign() 方法生成 JWT 字符串,并通过 token 属性发送给客户端
  // 参数1:用户的信息对象
  // 参数2:加密的密钥
  // 参数3:配置对象,可以配置当前 token 的有效期
  const tokenStr = jwt.sign({ username: userinfo.username }, secretKey, { expiresIn: '30s' })
  res.send({
    status: 200,
    message: '登录成功',
    token: tokenStr,  //要发送给客户端的 token 字符串
  })
})
将 JWT 字符串还原为 JSON 对象

客户端每次在访问那些有权限接口的时候,都需要主动通过请求头中的 Authorization 字段,将 Token 字符串发送到服务器进行身份认证。此时,服务器可以通过 express-jwt 这个中间件,自动将客户端发送过来的 Token 解析还原成 JSON 对象:

// expressJWT({ secret: secretKey }) 就是使用密钥 secretKey 来对 JWT 字符串进行解密
// .unless({ path: [/^\/api\//] }) 用来指定哪些接口不需要访问权限

app.use(expressJWT({ secret: secretKey }).unless({ path: [/^\/api\//] }))
使用 req.user 获取用户信息

当express-jwt 这个中间件配置成功之后,即可在那些有权限的接口中,使用 req.user 对象来访问从 JWT 字符串中解析出来的用户信息。

app.get('/admin/getinfo', (req, res) => {
  res.send({
    status: 200,
    message: '获取用户信息成功!',
    data: req.user  //要发送给客户端的用户信息
  })
})

向服务器传入 token 时,需要在 token 字符串前加上 'Bearer '

捕获解析 JWT 失败后产生的错误

使用 express-jwt 解析 Token 字符串时,如果客户端发送过来的 Token 字符串过期或不合法,会产生一个解析失败的错误,影响项目的正常运行,我们可以通过 Express 的错误中间件,捕获这个错误并进行相关的处理

// 使用全局错误处理中间件,捕获解析 JWT 失败后产生的错误
app.use((err, req, res, next) => {
  // 错误是由 token 解析失败导致的
  if(err.name === 'UnauthorizedError') {
    return res.send({
      status: 401,
      message: '无效的token'
    })
  }
  res.send({
    status: 500, 
    message: '未知的错误'
  })
})

extra. 连接数据库报错

报错提示:

Error: ER_NOT_SUPPORTED_AUTH_MODE: Client does not support authentication protocol request
ed by server; consider upgrading MySQL client

报错原因:

mysql8.0以上加密方式,Node还不支持。

解决方案:

打开 MySQL 命令行终端输入命令(123456 是我本地登录 mysql 的密码)

alter user 'root'@'localhost' identified with mysql_native_password by '1234';
flush privileges;

然后就解决了,如果不行就下载个低版本的 mysql。

flush privileges 命令本质上的作用是将当前 user 和 privilige 表中的用户信息/权限设置从 mysql 库(MySQL 数据库的内置库)中提取到内存里。经常用在改密码和授权超用户。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值