对于会话
会话是指服务器以浏览器维度提供的上下文缓存。服务器通过在 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.js
module.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.js
module.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.js
const {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.js
const {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.js
const {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.js
const 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.js
const {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 服务器(六):会话