对于会话

会话是指服务器以浏览器维度提供的上下文缓存。服务器通过在 cookie 或者 url 中保护惟一 id 来索引和治理会话缓存。会话缓存是跨服务器多个利用节点共享的,利用节点通过会话模块有序拜访会话缓存:

缓存模块

Express 官网提供的会话模块 express-session 非常灵活,能够通过 store 参数任意替换会话缓存的存储形式。性能优先的形式是应用 connect-redis 存储在 Redis 缓存服务中,老本优先的形式是应用 connect-session-sequelize 存储在数据库里,本文采纳老本优先的形式存储会话缓存。在上一章已实现的工程 host1-tech/nodejs-server-examples - 05-database 的根目录执行以下装置命令:

$ yarn add express-session # 本地装置 express-session# ...info Direct dependencies└─ express-session@1.17.1# ...$ # 本地装置 connect-session-sequelize 6.x 版本,配合 sequelize 5.x 版本$ yarn add 'connect-session-sequelize@^6.1.1'# ...info Direct dependencies└─ connect-session-sequelize@6.1.1# ...

思考到其余应用 cookie 的状况装置 cookie-parser 对立提供 cookie 解决逻辑:

$ yarn add cookie-parser # 本地装置 cookie-parser# ...info Direct dependencies└─ cookie-parser@1.4.5# ...

繁难登录

当初为店铺治理加上繁难的登录性能。先创立 session 数据库表格构造:

$ # 生成会话 schema 迁徙文件$ yarn sequelize migration:generate --name create-session$ tree src/models/migrate # 展现 src/models/migrate 目录内容构造src/models/migrate├── 20200725045100-create-shop.js└── 20200727025727-create-session.js

丑化一下 src/models/migrate/20200727025727-create-session.js

// src/models/migrate/20200727025727-create-session.jsmodule.exports = {  up: async (queryInterface, Sequelize) => {    /**     * Add altering commands here.     *     * Example:     * await queryInterface.createTable('users', { id: Sequelize.INTEGER });     */  },  down: async (queryInterface, Sequelize) => {    /**     * Add reverting commands here.     *     * Example:     * await queryInterface.dropTable('users');     */  },};

调整一下 src/models/migrate/20200727025727-create-session.js

// src/models/migrate/20200727025727-create-session.jsmodule.exports = {  up: async (queryInterface, Sequelize) => {-    /**-     * Add altering commands here.-     *-     * Example:-     * await queryInterface.createTable('users', { id: Sequelize.INTEGER });-     */+    await queryInterface.createTable('session', {+      sid: {+        type: DataTypes.STRING(36),+      },+      expires: {+        type: DataTypes.DATE,+      },+      data: {+        type: DataTypes.TEXT,+      },++      created_at: {+        allowNull: false,+        type: Sequelize.DATE,+      },+      updated_at: {+        allowNull: false,+        type: Sequelize.DATE,+      },+    });  },  down: async (queryInterface, Sequelize) => {-    /**-     * Add reverting commands here.-     *-     * Example:-     * await queryInterface.dropTable('users');-     */+    queryInterface.dropTable('session');  },};

向数据库写入 session 表格构造:

$ yarn sequelize db:migrate # 向数据库写入表格构造,db:migrate 会依据 sequelize_meta 记录只创立 session 表格构造

接下来初始化会话模块并补充登录验证的逻辑:

<!-- public/login.html --><html>  <head>    <meta charset="utf-8" />  </head>  <body>    <form method="post" action="/api/login">      <button type="submit">一键登录</button>    </form>  </body></html>
// src/controllers/login.jsconst { Router } = require('express');class LoginController {  async init() {    const router = Router();    router.post('/', this.post);    return router;  }  post = (req, res) => {    req.session.logined = true;    res.redirect('/');  };}module.exports = async () => {  const c = new LoginController();  return await c.init();};
// src/controllers/index.jsconst { Router } = require('express');const shopController = require('./shop');const chaosController = require('./chaos');const healthController = require('./health');+const loginController = require('./login');module.exports = async function initControllers() {  const router = Router();  router.use('/api/shop', await shopController());  router.use('/api/chaos', await chaosController());  router.use('/api/health', await healthController());+  router.use('/api/login', await loginController());  return router;};
// src/middlewares/login.jsconst { parse } = require('url');module.exports = function loginMiddleware(  homepagePath = '/',  loginPath = '/login.html',  whiteList = {    '/500.html': ['get'],    '/api/health': ['get'],    '/api/login': ['post'],  }) {  whiteList[loginPath] = ['get'];  return (req, res, next) => {    const { pathname } = parse(req.url);    if (req.session.logined && pathname == loginPath) {      res.redirect(homepagePath);      return;    }    if (      req.session.logined ||      (whiteList[pathname] &&        whiteList[pathname].includes(req.method.toLowerCase()))    ) {      next();      return;    }    res.redirect(loginPath);  };};
// src/middlewares/session.jsconst session = require('express-session');const sessionSequelize = require('connect-session-sequelize');const { sequelize } = require('../models');module.exports = function sessionMiddleware(secret) {  const SequelizeStore = sessionSequelize(session.Store);  const store = new SequelizeStore({    db: sequelize,    modelKey: 'Session',    tableName: 'session',  });  return session({    secret,    cookie: { maxAge: 7 * 24 * 60 * 60 * 1000 },    store,    resave: false,    proxy: true,    saveUninitialized: false,  });};
// src/middlewares/index.jsconst { Router } = require('express');+const cookieParser = require('cookie-parser');+const sessionMiddleware = require('./session');const urlnormalizeMiddleware = require('./urlnormalize');+const loginMiddleware = require('./login');++const secret = '842d918ced1888c65a650f993077c3d36b8f114d';module.exports = async function initMiddlewares() {  const router = Router();  router.use(urlnormalizeMiddleware());+  router.use(cookieParser(secret));+  router.use(sessionMiddleware(secret));+  router.use(loginMiddleware());  return router;};
// src/server.js// ...async function bootstrap() {+  server.use(await initMiddlewares());  server.use(express.static(publicDir));  server.use('/moulds', express.static(mouldsDir));-  server.use(await initMiddlewares());  server.use(await initControllers());  server.use(errorHandler);  await promisify(server.listen.bind(server, port))();  console.log(`> Started on port ${port}`);}// ...

拜访 http://localhost:9000/ 即可看到成果:

本章源码

host1-tech/nodejs-server-examples - 06-session

更多浏览

从零搭建 Node.js 企业级 Web 服务器(零):动态服务
从零搭建 Node.js 企业级 Web 服务器(一):接口与分层
从零搭建 Node.js 企业级 Web 服务器(二):校验
从零搭建 Node.js 企业级 Web 服务器(三):中间件
从零搭建 Node.js 企业级 Web 服务器(四):异样解决
从零搭建 Node.js 企业级 Web 服务器(五):数据库拜访
从零搭建 Node.js 企业级 Web 服务器(六):会话