- 2021-10-20
- 6-1 开始
- 6-2 cookie-介绍
- 6-3 cookie用于登录验证
- 2021-10-21
- 6-4 cookie做限制(1)
- 6-5 cookie做限制(2)
- 6-6、6-7 session介绍 、session演示
- 6-8 从 session 到 redis
- 6-9 redis介绍
- 2021-10-22
- 6-10 nodejs 连接 redis 的 demo
- 6-11 nodejs 连接 redis - 封装工具函数
- 2021-10-23
- 6-12 session 存入 redis
- 6-13 完成 server 端登录的代码
- 6-14 联调-介绍html页面
- 2021-10-26
- 6-15 nginx配置
- 2021-10-27
- 6-16 联调演示与总结
2021-10-20
6-1 开始
登录
有业界通用标准,只需学习不用设计
- 核心:登录校验 & 登录信息存储
- 登录校验:如果想要进入到博客管理中心,必须要登录,不然不让你做
- 登录信息存储:存储 username realname 等,管理时比较方便,并且便于操作
- 为何只讲登录,不讲注册?
- 注册和创建博客一样,没有难度
- 二维码注册等一些新的方式层出不穷,暂不讲
目录
- cookie 和 session
- session 写入 redis
- 开发登录功能,和前端联调(用到 nginx 反向代理)
6-2 cookie-介绍
cookie
- 什么是 cookie
- JavaScript 操作 cookie,浏览器中查看 cookie
- server 端操作 cookie,实现登录验证
最大4kb
三种方式
- 查看浏览器-network-headers
- 查看浏览器-application-cookies
- 查看浏览器-console 然后输入 document.cookie 就可以查看
第三种方式还可以自行累加
6-3 cookie用于登录验证
server 端 nodejs 操作 cookie
- 查看 cookie
- 修改 cookie
- 实现登录验证
查看 cookie
在 app.js 中写入解析 cookie 的代码
该段代码就是将浏览器中的 cookie 的这种形式:k1=v1;k2=v2;k3=v3 解析为
{
k1:v1,
k2:v2,
k3:v3
}
const serverHandle = (req, res) => {
...
// 解析 cookie
req.cookie = {}
const cookieStr = req.headers.cookie || '' // k1=v1;k2=v2;k3=v3
cookieStr.split(';').forEach(item => {
if (!item) {
return
}
const arr = item.split('=')
const key = arr[0]
const value = arr[1]
req.cookie[key] = value
})
console.log('req.cookie', req.cookie);
...
}
可以做一个统一:如果 cookie 中有 username 则认为已经登录,如果没有的话就认为没有登录
修改所有的 loginCheck 为 login
实现登录验证
在 router/user.js 中增加一条判断逻辑来判断是否能成功进行登录验证
因为是用 promise 来封装的,所以返回 Promise.resolve() 来做
// 登录验证的测试
if (method === 'GET' & req.path === '/api/user/login-test') {
if (req.cookie.username) {
return Promise.resolve(
new SuccessModel()
)
}
return Promise.resolve(
new ErrorModel('尚未登录')
)
}
在 console 中手动添加 document.cookie = ‘username=zhangsan’
成功!
然后临时将登录的逻辑改为 GET 并且实现一下修改 cookie
修改这三处
因此当输入 api/user/login?username=zhangsan&password=123 时
就将账号和密码通过nodejs后端的方式设置到了浏览器中,并且 path 是 / ,所以所有页面都生效
这样的话再输入 /api/user/login-test 时,后端就可以拿到 req.cookie.username,因此就可以验证登录成功了,并且全都是后端所做的
2021-10-21
6-4 cookie做限制(1)
设置浏览器限制
现在遇到一个问题,就是当 username=lisi 登录后,如果在浏览器修改 username 为数据库存在的其他数据,则会成为别人,现在处理下这个问题
解决:
在 set cookie 时,设置 httpOnly
然后再尝试修改,发现 document.cookie 看不见了,如果改的话也能改成功,但是server端的会覆盖掉前端改的
6-5 cookie做限制(2)
设置过期时间
目前的登录是永久有效的,可以给它设置过期时间
在 router/user.js 中
首先设置获取 cookie 过期时间的函数
// 获取 cookie 的过期时间
const getCookieExpires = () => {
// 获取当前时间
const d = new Date()
// 设置时间为当前时间加24小时
d.setTime(d.getTime() + (24 * 60 * 60 * 1000))
// 最后返回这个时间
console.log('d.toGMTString() is', d.toGMTString());
return d.toGMTString()
}
然后在 setHeader 的地方设置 expires
// 操作 cookie , 设置 cookie 生效的 path 为根路由,这样所有的网页都会生效
res.setHeader('Set-Cookie', `username=${data.username}; path=/; httpOnly; expires=${getCookieExpires()}`)
试一下:
http://localhost:8000/api/user/login?username=lisi&password=123
成功!
总结
- 知道 cookie 的定义和特点
- 前后端如何查看修改 cookie
- 如何使用 cookie 实现登录验证
6-6、6-7 session介绍 、session演示
session
- 上一节的问题:会暴露 username ,很危险
- 各种私人信息,并且很多信息放到cookie中不够放 只有4kb
- 如何解决:cookie 中存 userid,server 端对应 username
- 解决方案:session,即 server 端存储用户信息
不同的浏览器都去 server 中的 session 去取 userid 对应的内容
代码演示
首先在 app.js 中解析 session
- 当没有 userId 时,即用户初次登录时,这时候需要创建cookie,并且创建 userId ,最后将SESSION_DATA[userId] 初始化
- 当有 userId 时,即用户已经登录成功,但是有可能是用户更换账号,这种情况就将SESSION_DATA[userId] 初始化
最后的最后将 SESSION_DATA[userId] 设置为 req.session
// 解析 session
let needSetCookie = false
let userId = req.cookie.userid
// 如果有 userId
if (userId) {
// 就看下 session 中有没有对应的值
// 如果没有的话,就给它初始化一下
if (!SESSION_DATA[userId]) {
SESSION_DATA[userId] = {}
}
// 如果没有 userId, 就需要设置 cookie
// 就把 userId 初始化一下
} else {
needSetCookie = true
userId = `${Date.now()}_${Math.random()}`
SESSION_DATA[userId] = {}
}
// 最后赋值 session 为 SESSION_DATA[userId]
req.session = SESSION_DATA[userId]
然后修改访问 blog 和 user 的逻辑
如果初次登录,即需要设置 cookie 时由客户端设置 cookie
最后修改 路由逻辑
将所有数据库中的数据全部放到 session 中去
总结使用 session 后机制相比 cookie 的改变了
- 在尚未登录且第一次访问的情况下,就会设置一个新的cookie
- 若以未登录的情况下再次访问,就不会设置新的cookie,就以第一次访问设置的cookie来进行访问
- 在登录的时候,若登录成功且已经访问过页面的话他就还是拿旧的 cookie,然后去走login的路由,将query中的 username password 设置到 session 中去
也就是说 前端只存着 sessionid,一个浏览器一个 sessionid,拿着 sessionid 去请求,通过session去做任何请求
总结
- 知道 session 解决的问题
- 如何实现 session
6-8 从 session 到 redis
当前设置 session 的问题
- 目前 session 直接是 js 变量,放在 nodejs 进程内存中
- 第一个问题:内存进程有限,访问量过大,内存暴增怎么办?
- nodejs 在16G内存电脑中只会有1.6g的内存分配,因为操作系统会限制一个进程的最大可用内存
- 同样的,session也只会占据nodejs一块限制的内存
- 第二个问题:正式线上运行的是多进程,进程之间的内存无法共享
- 多进程就会有多 session
- 无法共享就会导致当登录相同的账户时访问不同的进程就会登录失败
解决方案:redis
- web server 最常用的缓存数据库,数据存放在内存中
- 相比 mysql ,访问速度快(内存和硬盘不是一个数量级别的)
- 成本更高,可存储的数据量更小(内存的硬伤)
◆将 web server 和 redis 拆分为两个单独的服务
◆双方都是独立的,都是可扩展的(例如都扩展成集群)
◆(包括mysql,也是一一个单独的服务,也可扩展)
因此将 session 从 nodejs(web server)中抽离出来,这样就不会有上述的问题了
为何 session 适合用 redis?
- session 访问频繁,对性能要求极高(因此不适合将登录数据放到 mysql 中)
- session 可以不考虑断电丢失数据的问题(内存的硬伤)——当然可以做到数据恢复等不丢失的手段
- session 数据量不会很大(相比 mysql 中的数据)
为何网站数据不适合用 redis?
- 操作频率不是太高(相比 session 操作)
- 断电不能丢失,必须保留
- 数据量太大,内存成本太高
6-9 redis介绍
安装 redis
redis 基本使用
进入 redis 访问下面两个命令:
redis-server 和 redis-cli
redis 使用比较简单,使用key value的形式
最基本的使用方式
set key value 然后 get key 即得 value 值
keys * 就可以获得当前所有得到的key
总结
- 为何要用 redis?不用 redis 会有什么问题?
- session 和 redis 分别适合什么场景?
2021-10-22
6-10 nodejs 连接 redis 的 demo
用 redis 存储 session
- nodejs 连接 redis 的 demo
- 封装成工具函数,可供 API 使用
redis-server 启动起来,可以看到端口号是6379 记住
然后创建一个 redis-test 的目录,然后 npm init -y 初始化
然后新建 index.js 然后安装 redis
npm i redis —save
然后在 index.js 中写入
const redis = require('redis')
// 创建客户端
const redisClient = redis.createClient(6379, '127.0.0.1')
// 监控 error
redisClient.on('error', err => {
console.error(err)
})
// 测试
// 这里的 redis.print 用来打印这个 set 是否正确
redisClient.set('myname', 'demonlb', redis.print)
redisClient.get('myname', (err, val) => {
if (err) {
console.error(err)
return
}
console.log('val', val)
// 退出 redis
redisClient.quit()
})
然后尝试运行一下 node index.js
发现这个结果
这里的 “Reply: OK” 就是在 redis.print 出打印出来的
在 redis 中存储的也是成功的
6-11 nodejs 连接 redis - 封装工具函数
回到 blog-1 中
首先安装 redis
然后来到 /src/conf/db.js 新建 redis 的配置
...
// 配置 redis 的值
let REDIS_CONF
...
if (env === 'dev') {
...
// redis
REDIS_CONF = {
port: 6379,
host: '127.0.0.1',
}
}
if (env === 'production') {
...
// redis
REDIS_CONF = {
port: 6379,
host: '127.0.0.1',
}
}
// 最后导出
module.exports = {
...
REDIS_CONF
}
然后配置连接
来到 /src/db/redis.js 中
封装工具函数
const redis = require('redis')
const { REDIS_CONF } = require('../conf/db')
// 创建客户端
const redisClient = redis.createClient(REDIS_CONF.port, REDIS_CONF.host)
// 监控 error
redisClient.on('error', err => {
console.error(err)
})
function set(key, val) {
// 因为 redis 只能存储字符串,所以要将对象改为字符串的形式
if (typeof val === 'object') {
val = JSON.stringify(val)
}
redisClient.set(key, val, redis.print)
}
function get(key) {
const promise = new Promise((resolve, reject) => {
edisClient.get(key, (err, val) => {
if (err) {
reject(err)
return
}
// 如果瞎传了一个key,则 val 就是 null
if (val == null) {
resolve(null)
}
try {
// 将字符串解析为对象来返回
resolve(
JSON.parse(val)
)
// 如果不是的就正常返回
} catch (ex) {
resolve(val)
}
})
})
return promise
}
module.exports = {
set,
get
}
OK。下节课使用这个 set 和 get 方法
2021-10-23
6-12 session 存入 redis
首先把 app.js 中解析 session 的代码中注释一下,因为不再需要在 node 中存 session 的值了
然后将 session 的值存到 redis 中
// 解析 session 使用 redis
let needSetCookie = false
let userId = req.cookie.userid
if (!userId) {
needSetCookie = true
userId = `${Date.now()}_${Math.random()}`
// 如果没有 userId 的话,初始化 redis 中的 session 值
set(userId, {})
}
// 然后获取 userId 对应的值
req.sessionId = userId
get(req.sessionId).then(sessionData => {
if (sessionData == null) {
// 初始化 redis 中的 session 值
set(req.sessionId, {})
// 设置 session
req.session = {}
}
// 设置 session
req.session = sessionData
console.log('req.session', req.session)
return getPostData(req)
})
然后修改路由中的获取逻辑,因为目前在路由中只将数据存到了本地
如下图,引入 set 和同步到 redis 中
梳理下代码逻辑,首先访问
http://localhost:8000/api/user/login-test 时,因为未登录所以,返回 ErrorModel 并且显示 login failed
在 app.js 中走过代码会发现:
会设置 needSetCookie 为 false
然后因为没有 userId ,所以 设置一个新的 userId 为时间戳+随机数的形式,然后通过 set 方法,在 redis 中将这个 userId 设置为 空对象【联动 redis】
然后将这个 userId 存储在 req 中,取名为 req.sessionId,然后通过 get 方法去取 req 中的 sessionId 得到 sessionData,如果 sessionData 为空的话(即现在)就再次赋值一遍上面的操作(在 redis 中将这个 userId 即req.sessionId 设置为 空对象),然后将 req.session 也置为空对象,最后 设置 req.session 为 sessionData(即也置空)【联动 cookie】
然后因为最开始 设置了 needSetCookie 为 false,所以会触发下面这个逻辑,将cookie中的userid设定,这样就可以绑定浏览器和cookie的关系了
if (needSetCookie) {
res.setHeader('Set-Cookie', `userid=${userId}; path=/; httpOnly; expires=${getCookieExpires()}`)
}
然后访问
http://localhost:8000/api/user/login?username=zhangsan&password=123 时,因为此时有了 userId 和 req.sessionId
但是是初次登录,所以 sessionData 依旧是 null,但不需要修改cookie,来到 handleUserRouter 的逻辑中,通过 set 方法,将绑定的 userId 设置其正确的 password 和 username,这样就将 session 中的内容转到了 redis 中,使用其他路由时就没有限制了,而在 cookie 中 只存 user.id 了
6-13 完成 server 端登录的代码
回顾下 login-test 的作用,也就是说当 req.session.username 时就认为此时已经登录了
也就是说做到了:1.登录验证 2.获取登录信息
后面将不再使用这个接口
现在考虑一下在 blog.js 中:新建、更新和删除博客时需要登录验证,不再使用假数据
定义一个统一的登录验证函数
// 统一的登录验证函数
const loginCheck = (req) => {
// 如果没登录就返回 ErrorModel
if (!req.session.username) {
return Promise.resolve(
new ErrorModel('尚未登录')
)
}
}
然后在新建、更新和删除博客时进行登录验证
类似的,在 新建、更新和删除博客 的代码逻辑中添加如下代码
const loginCheckResult = loginCheck(req)
if (loginCheckResult) {
// 未登录
return loginCheck
}
最后将 登录 设置为 post 请求,将登录信息又从 req.query 中获取改为从 req.body 中获取,并且将 /api/user/login-test 置空
后续进行前后端联调
6-14 联调-介绍html页面
和前端联调
- 登录功能依赖 cookie,必须使用浏览器来联调
- cookie 跨域不共享,前端和 server 端必须同域
- 需要用到 nginx 做代理,让前后端同域
开发前端页面
创建一个 html-test 文件夹,放入所有所需要调试的基本页面
里面就是 jQuery 和 ajax 的请求,还有 HTML 代码
为了让这些 HTML 跑起来就安装 http-server
npm install http-server -g
然后启动
输入 http-server -p 8001
然后访问 localhost:8001
发现 list 请求 404 了
因为这个请求是请求的 8001 端口,所以 404 了
后面会使用 nginx 来处理这个问题使用8080端口来让 api 不 404
2021-10-26
6-15 nginx配置
nginx 介绍
- 高性能的 web 服务器,开源免费
- 使用率很高
- 一般用于做静态服务,负载均衡(本课用不到)
- 静态服务:CDN(js文件,css文件,json文件,图片等)
- 负载均衡:集群中,各个服务器均摊流量
- 反向代理(本课用到)
通过 nginx ,对于浏览器来说具体访问什么端口是未知的,只需要访问 index.html,所有内容经过 8000 和8080 端口然后 nginx 代理返回给客户端
nginx配置地址
/usr/local/etc/nginx/nginx.conf
nginx 命令
测试配置文件格式是否正确 nginx -t
启动 nginx: 直接输入 nginx
重启 nginx:nginx -s reload
停止 nginx -s stop
修改 nginx
进入该 nginx.conf 中
这里的 worker_processes 表示启动服务时使用几核,然后在开发环境中一般不考虑,
然后修改代理:location
把原先这里的注释了
然后如下设置
# nodejs project configuration
location / {
proxy_pass http://localhost:8001;
}
location /api/ {
proxy_pass http://localhost:8000;
# set header to send host
proxy_set_header Host $host;
}
然后使用 nginx -t 来测试一下
成功
然后试一下 http://localhost:8080/ 正常访问
总结
- nginx 反向代理的配置
- 前后端如何同域联调
2021-10-27
6-16 联调演示与总结
https://coding.imooc.com/learn/questiondetail/kxNyDXbde7jPGgb7.html
isadmin 用处:
isadmin 是识别后台管理的。
第一,如果是后台管理的请求(即 req.query.isadmin 是 true),则用户名必须是自己的,即只能搜索自己的博客
第二,如果不是后台管理的请求(即 req.query.isadmin 是 false),则用户名可以是别人的,即可以搜索别人的博客。
修改 isadmin
在获取时限制只让获取自己的根据query的isadmin,这个是由后端加入的