一、登录
核心:登录校验 & 登录信息存储
1. cookie 和 session
- 什么是 cookie
- 存储在浏览器的一段字符串 最大4kb
- 跨域不共享
- 格式如 k1=v1;k2=v2;因此可以存储结构化数据
- 每次发送 http 请求,会将请求域的 cookie 一起发送给 server
- server 可以修改 cookie 并返回给浏览器
- 浏览器中也可以通过 javascript 修改 cookie (有限制)
- javascript 操作 cookie,浏览器中查看 cookie
- 客户端查看 cookie,三种方式
- javascript 查看、修改 cookie (有限制)
- server端 nodejs 操作 cookie,实现登录验证
- 查看 cookie
- 解析cookiejavascript
req.cookie = {} const cookieStr = req.headers.cookie || ''; cookieStr.split(';').forEach(item => { if (!item) return; const arr = item.split("="); const key = arr[0].trim(); const val = arr[1].trim(); req.cookie[key] = val })
- 解析cookie
- 修改 cookie
- 服务端设置javascript
res.setHeader('Set-Cookie', `username=${data.username}; path=/`)
- 限制 HttpOnly 只允许通过后端更改javascript
res.setHeader('Set-Cookie', `username=${data.username}; path=/; httpOnly `)
- 限制过期时间javascript
const getCookieExpires = () => { const d = new Date(); d.setTime(d.getTime() + (24 * 60 * 60 * 1000)) return d.toGMTString() } res.setHeader('Set-Cookie', `username=${data.username}; path=/; httpOnly; expires=${getCookieExpires()}`)
- 服务端设置
- 实现登录验证javascript
const handlerUserRouter = (req, res) => { const { method, path } = req // 登录 if (method === 'GET' && path === '/api/user/login') { const { username, password } = req.query; const result = login(username, password); return result.then(data => { if (data.username) { // 操作 cookie res.setHeader('Set-Cookie', `username=${data.username}; path=/; httpOnly; expires=${getCookieExpires()}`) return new SuccessModel('登录成功') } return new ErrorModel('登录失败') }) } }
- 查看 cookie
2. session 写入 redis
暴露username
- 解决:cookie 中存储 userId, server 端对应 username
解析session
js// 解析 session let needSetCookie = false; let userId = req.cookie.userId; if (userId) { if (!SESSION_DATA[userId]) { SESSION_DATA[userId] = {} } } else { needSetCookie = true; userId = `${Date.now()}_${Math.random()}` SESSION_DATA[userId] = {} } req.session = SESSION_DATA[userId];
app.js 通用返回处理 设置 cookie
jsconst blogResult = handlerBlogRouter(req, res); if (blogResult) { blogResult.then(blogData => { if (needSetCookie) { res.setHeader('Set-Cookie', `userId=${userId}; path=/; httpOnly; expires=${getCookieExpires()}`) } res.end(JSON.stringify(blogData)); }) return }
user router 中 返回设置
js// 登录 if (method === 'GET' && path === '/api/user/login') { const { username, password } = req.query; const result = login(username, password); return result.then(data => { if (data.username) { req.session.username = data.username; req.session.realname = data.realname; return new SuccessModel('登录成功') } return new ErrorModel('登录失败') }) }
目前 session 直接是 js 变量,放在 nodejs 进程内存中
- 进程内存有限,访问量过大,内存暴增怎么办?
- 正式线上运行是多进程,进程之间内存无法共享。
- 解决方案 redis
- web server 最常用的缓存数据库,数据存放在内存中
- 相比 mysql 访问速度快(内存和硬盘不是一个数量级的)
- 但是成本更高,可存储的数量更小(内存的硬伤)
- 框架图
- 将 web server 和 redis 拆分为两个单独的服务
- 双方都是独立的,都是可扩展的
- 为何 session 适合用 redis?
- session 访问频繁,对性能要求极高
- session 可不考虑断电丢失数据的问题(内存的硬伤)
- session 数据量不会太大
- 为何网站数据不适合用 redis
- 操作频率不是太高
- 断电不能丢失,必须保留
- 数据量太大,内存成本太高
安装 redis
- mac 使用 brew install redis
nodejs 连接 redis
安装
shell# 版本改变导致接口混乱【放弃】 npm i redis --save # 建议安装 参考地址:https://www.npmjs.com/package/ioredis npm i ioredis --save
工具类
jsconst Redis = require('ioredis'); // 创建客户端 const redisClient = new Redis(6379, '139.129.240.151'); redisClient.on('error', err => { console.error(err) }); // 测试 redisClient.set('myName', 'zhangsan2'); redisClient.get('myName', (err, val) => { if (err) { console.error(err); return; } console.log('val', val); // 退出 redisClient.quit() })
jsconst Redis = require('ioredis'); const { REDIS_CONF } = require('../config/db'); // 创建客户端 const redisClient = new Redis(REDIS_CONF.port, REDIS_CONF.host); redisClient.on('error', err => { console.error(err) }); function set(key,val) { if (typeof val === 'object') { val = JSON.stringify(val) } redisClient.set(key, val); } function get(key) { const promise = new Promise((resolve, reject) => { const val = redisClient.get(key, (err, val) => { if (err) { reject(err) return; } if (val === null) { resolve(null); return; } try { resolve(JSON.parse(val)) } catch (ex) { resolve(val) } }) resolve(val) }) return promise; } module.exports = { set, get }
3. 开发登录功能和前端联调(用到 nginx 反向代理)
nginx
- 高性能的 web 服务器,开源免费
- 一般用于做静态服务、负载均衡
- 反向代理
nginx 下载
- mac:brew install nginx
- 配置文件:/usr/local/etc/nginx.conf
- 相关命令
- nginx -t
- nginx -s reload
- nginx -s stop
- 相关配置nginx
localtion / { proxy_pass http://localhost:8001; } localtion /api/ { proxy_pass http://localhost:8000; proxy_set_header Host $host; }