数据库(database)是用来组织、存储和管理数据的仓库。常见的数据库有如下几个:
MySQL、Oracle、SQL Server 属于传统型数据库(又叫做:关系型数据库 或 SQL 数据库),这三者的 设计理念相同,用法比较类似。而 Mongodb 属于新型数据库(又叫做:非关系型数据库 或 NoSQL 数据库),它在一定程度上弥补了传统型 数据库的缺陷。
在传统型数据库中,数据的组织结构分为数据库(database)、数据表(table)、数据行(row)、字段(field)这 4 大部分组成。库、表、行、字段的关系:
对于开发人员来说,需要安装 MySQL Server 和 MySQL Workbench 这两个软件,来满足开发的需要:
略
npm i mysql
// 配置 mysql 模块
const mysql = require('mysql');
const db = mysql.createPool({
host: 'localhost',
port: '3306',
user: 'root',
password: 'admin123',
database: 'my_db_01'
});
于是可以调用 db.query() 函数,指定要执行的 SQL 语句,通过回调函数拿到执行的结果来测试 mysql 模块能否正常工作。
// 插入数据
db.query('insert into users (username, password) values (?, ?)', ['王五', 'ww'], function(error, result) {
if (error) return console.log(error.message);
if (result.affectedRows == 1) console.log('插入数据成功');
});
// 便捷方式插入数据
db.query('insert into users set ?', {username: '赵六', password: 'zl'}, function(error, result) {
if (error) return console.log(error.message);
if (result.affectedRows == 1) console.log('便捷方式插入数据成功');
});
// 更新数据
db.query('update users set username=?, password=? where id=?', ['李白', 'lb', 1], function(error, result) {
if (error) return console.log(error.message);
if (result.affectedRows == 1) console.log('更新数据成功');
});
// 更新数据的便捷方式
const user = { id: 4, username: '王唯', password: 'ww' };
db.query('update users set ? where id=?', [user, user.id], function(error, result) {
if (error) return console.log(error.message);
if (result.affectedRows == 1) console.log('便捷方式更新数据成功');
})
// 删除数据
db.query('delete from users where id=?', 3, function(error, result) {
if (error) return console.log(error.message);
if (result.affectedRows == 1) console.log('删除数据成功');
});
// 查询所有数据
db.query('select * from users', (error, result)=> {
if (error) return console.log(error.message);
console.log(result);
});
// 标记删除
db.query('update users set status=? where id=?', [1, 5], function(error, result) {
if (error) console.log(error.message);
if (result) console.log('标记删除数据成功');
});
目前主流的 Web 开发模式有两种,分别是: 1、基于服务端渲染的传统 Web 开发模式,2、基于前后端分离的新型 Web 开发模式。
服务端渲染的概念: 服务器发送给客户端的 HTML 页面,是在服务器通过字符串的拼接,动态生成的。因此,客户端不需要使用 Ajax 这样的技术额外请求页面的数据。
优点:
缺点:
前后端分离的概念: 前后端分离的开发模式,依赖于 Ajax 技术的广泛应用。简而言之,前后端分离的 Web 开发模式,就是后端只负责提供 API 接口,前端使用 Ajax 调用接口的开发模式。
优点:
缺点:
具体使用何种开发模式并不是绝对的,为了同时兼顾了首页的渲染速度和前后端分离的开发效率,一些网站采用了 首屏服务器端渲染 + 其他页面前后端分离的开发模式。
身份认证(Authentication)又称“身份验证”、“鉴权”,是指通过一定的手段,完成对用户身份的确认。
身份认证的目的,是为了确认当前所声称为某种身份的用户,确实是所声称的用户。例如,你去找快递员取快递,你要怎么证明这份快递是你的。
对于服务端渲染和前后端分离这两种开发模式来说,分别有着不同的身份认证方案:
了解 HTTP 协议的无状态性是进一步学习 Session 认证机制的必要前提。HTTP 协议的无状态性,指的是客户端的每次 HTTP 请求都是独立的,连续多个请求之间没有直接的关系,服务器不会主动保留每次 HTTP 请求的状态。
Cookie 是存储在用户浏览器中的一段不超过 4 KB 的字符串。它由一个名称(Name)、一个值(Value)和其它几个用于控制 Cookie 有效期、安全性、使用范围的可选属性组成。不同域名下的 Cookie 各自独立,每当客户端发起请求时,会自动把当前域名下所有未过期的 Cookie 一同发送到服务器。 Cookie的几大特性:
客户端第一次请求服务器的时候,服务器通过响应头的形式,向客户端发送一个身份认证的 Cookie,客户端会自动将 Cookie 保存在浏览器中。随后,当客户端浏览器每次请求服务器的时候,浏览器会自动将身份认证相关的 Cookie,通过请求头的形式发送给 服务器,服务器即可验明客户端的身份。
由于 Cookie 是存储在浏览器中的,而且浏览器也提供了读写 Cookie 的 API,因此 Cookie 很容易被伪造,不具有安全性。因此不建议服务器将重要的隐私数据,通过 Cookie 的形式发送给浏览器。注意: 千万不要使用 Cookie 存储重要且隐私的数据!比如用户的身份信息、密码等。
npm install express-session
// 导入模块
const express = require('express');
const session = require('express-session');
const app = express();
// 配置中间件
app.use(session({
secret: 'keyword: myKeyword', //可以为任意字符串
resave: false, // 固定写法
saveUninitialized: true // 固定写法
}));
app.post('/api/login', function(req, res) {
if (req.body.username !== 'zs' && req.body.password !== '123456') {
return res.send({ status: 1, message: '登录失败' });
}
// 将用户信息、登录状态存储到session中
req.session.user = { username: req.body.username };
req.session.islogin = true;
res.send({ status: 0, message: '登录成功' });
});
app.get('/api/username', function(req, res) {
if (!req.session.islogin) {
return res.send({ status: 1, message: '请求失败' });
}
res.send({ status: 0, username: req.session.username, message: '请求成功' });
});
app.post('/api/logout', function(req, res) {
// 清空当前客户端 session 信息
req.session.destroy();
res.send({ status: 0, message: '退出成功' });
});
Session 认证机制需要配合 Cookie 才能实现。由于 Cookie 默认不支持跨域访问,所以,当涉及到前端跨域请求后端接
口的时候,需要做很多额外的配置,才能实现跨域 Session 认证。注意:
JWT(英文全称:JSON Web Token)是目前最流行的跨域认证解决方案。用户的信息通过 Token 字符串的形式,保存在客户端浏览器中。服务器通过还原 Token 字符串的形式来认证用户的身份。
JWT 通常由三部分组成,分别是 Header(头部)、Payload(有效荷载)、Signature(签名)。
三者之间使用英文的“.”分隔,格式如下:
Header.Payload.Signature
JWT 字符串的示例:
eyJhbciOiJIUZI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwidXN1cm5hbwUi0iJhZG1pbiIsInBhc3N3b3JkIjoiIiwibm|ja25
hbwUi0iLms6X1t7T1t7QiLCJ1bWFpbCI6Im5pYmFiYUBpdGNhc3QuY24iLCJ1c2VyX3BpYy]6IiIsImlhdCI6MTU30DAzNjY4Miw
iZXhwIjoxNTc4MDcyNjgyfQ.wq7GqCxJPK-EA8LNrtMG0411KdZ3359KBL3XeuBxuI
JWT 的三个组成部分,从前到后分别是 Header、Payload、Signature。其中:
客户端收到服务器返回的 JWT 之后,通常会将它储存在 localStorage 或 sessionStorage 中。此后,客户端每次与服务器通信,都要带上这个 JWT 的字符串,从而进行身份认证。推荐的做法是把 JWT 放在 HTTP 请求头的 Authorization 字段中,格式如下:
Authorization: Bearer
npm install jsonwebtoken express-jwt
// 导入生成 JWT 字符串的包
const jwt = require('jsonwebtoken');
// 导入将 JWT 字符串还原成 JSON 对象的包
var {expressjwt: expressJWT} = require('express-jwt');
// 定义秘钥
const secretKey = 'abc123ABC!@#';
app.post('/api/login', function(req, res) {
if (req.body.username === 'zs' && req.body.password === '123456') {
// jwt.sign() 方法生成 JWT 字符串。并通过 token 属性发送给客户端
// 参数1:用户的信息对象
// 参数2:加密的秘钥
// 参数3:配置对象,可以配置当前 token 的有效期
// 记住:千万不要把密码加密到 token 字符中
const token = jwt.sign({ username: req.body.username }, secretKey, { expiresIn: '2m'});
res.send({
status: 0,
token: token,
message: '登录成功'
})
} else {
res.send({
status: 1,
message: '登录失败'
})
}
});
// 注册将 JWT 字符串解析还原成 JSON 对象的中间件
// 注意:只要配置成功了 express-jwt 这个中间件,就可以把解析出来的用户信息,挂载到 req.user 属性上
// use(expressJWT({ secret: secretKey, algorithms: ['HS256'] }) 用来解析 token 的中间件
// unless({ path: [/^\/api\//] }) 指定哪些接口不需要访问权限
app.use(expressJWT({ secret: secretKey, algorithms: ['HS256'] }).unless({ path: [/^\/api\//] }))
app.get('/my/info', function(req, res) {
console.log(req.auth);
res.send({
status: 0,
userinfo: req.auth,
message: '请求成功'
});
});
// 使用全局错误处理中间件,捕获解析 JWT 失败后产生的错误
app.use(function(err, req, res, next) {
console.log(err, req, res);
// err.name === 'UnauthorizedError' 的错误是由 token 解析失败导致的
if (err.name === 'UnauthorizedError') {
res.send({
status: 2,
message: '无效的token'
});
} else {
res.send({
status: 3,
message: '未知错误'
});
}
});