Skip to content
一、登录

核心:登录校验 & 登录信息存储

  • 什么是 cookie
    • 存储在浏览器的一段字符串 最大4kb
    • 跨域不共享
    • 格式如 k1=v1;k2=v2;因此可以存储结构化数据
    • 每次发送 http 请求,会将请求域的 cookie 一起发送给 server
    • server 可以修改 cookie 并返回给浏览器
    • 浏览器中也可以通过 javascript 修改 cookie (有限制)
  • javascript 操作 cookie,浏览器中查看 cookie
    • 客户端查看 cookie,三种方式
    • javascript 查看、修改 cookie (有限制)
  • server端 nodejs 操作 cookie,实现登录验证
    • 查看 cookie
      • 解析cookie
        javascript
          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
      • 服务端设置
        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('登录失败')
            })
          }
      }
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

    js
      const 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 访问速度快(内存和硬盘不是一个数量级的)
      • 但是成本更高,可存储的数量更小(内存的硬伤)
      • 框架图 redis.png
        • 将 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
    • 工具类

      js
      const 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()
      })
      js
      const 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;    
        }

Released under the MIT License.