咱们公司的大佬,搭建了一套基于koa2的node框架,虽说是重复造轮子,但适用当前场景的轮子才是最好的,况且不少人还造不出轮子呢~ 大佬搭建的框架命名为 Sponse
,其中有不少出色的设计,这里对 session
的设计作个总结(实际上是为了本身加深印象,学习大佬的设计)前端
Sponse意思是海绵,而咱们这套框架就如同海绵同样,经过不断吸取其余框架的优秀设计丰满本身node
作开发的小伙伴对这两个应该在熟悉不过了,这两个一块儿构建起先后端状态的联系,常见的如维护用户登陆状态,用户登陆后,须要在服务端记录下该用户的登陆状态,前端才可使用须要登陆态的接口,此时,浏览器中的 cookie
就是查询用户是否登陆的凭证,在服务端,一般是将用户状态信息存储在缓存中,简单说,就是基于 cookie的session
redis
优秀的架构中,调用方式必须是友好的json
指望可以经过上下文直接调用,如:获取session对象 ctx.session
; 设置session的值 ctx.session.name = name
后端
总体实现逻辑以下api
详细代码以下:浏览器
首先构建一个session对象缓存
核心方法:bash
save
用于同步cookiechanged
用于检测session对象是否发生修改,为了同步更新缓存中的值export class Session {
private _ctx;
isNew: boolean;
_json; // session对象的json串,用于比较session对象是否发生变化
constructor(ctx, obj) {
this._ctx = ctx;
if (!obj) this.isNew = true;
else {
for (const k in obj) {
this[k] = obj[k];
}
}
}
/**
* Save session changes by
* performing a Set-Cookie.
*
* @api private
*/
save() {
const ctx = this._ctx;
const json = this._json || JSON.stringify(this.inspect());
const sid = ctx.sessionId;
const opts = ctx.cookieOption;
const key = ctx.sessionKey;
if (ctx.cookies.get(key) !== sid) {
// 设置cookies的值
ctx.cookies.set(key, sid, opts);
}
return json;
};
/**
* JSON representation of the session.
*
* @return {Object}
* @api public
*/
inspect() {
const self = this;
const obj = {};
Object.keys(this).forEach(function (key) {
if ('isNew' === key) return;
if ('_' === key[0]) return;
obj[key] = self[key];
});
return obj;
}
/**
* Check if the session has changed relative to the `prev`
* JSON value from the request.
*
* @param {String} [prev]
* @return {Boolean}
* @api private
*/
changed(prev) {
if (!prev) return true;
this._json = JSON.stringify(this.inspect());
return this._json !== prev;
};
}
复制代码
koa 固然是离不开中间件了,洋葱模型酷炫到不行,很是方便的解决了session对象与缓存的同步cookie
decorator(app) {
app.keys = ['signed-key'];
const CONFIG = {
key: app.config.name + '.sess', /** (string) cookie key (default is koa:sess) */
cookie: {
// maxAge: 1000, /** (number) maxAge in ms (default is 1 days) */
overwrite: false, /** (boolean) can overwrite or not (default true) */
httpOnly: true, /** (boolean) httpOnly or not (default true) */
signed: true, /** (boolean) signed or not (default true) */
}
};
app.use(async (ctx, next) => {
let sess: Session;
let sid;
let json;
ctx.cookieOption = CONFIG.cookie;
ctx.sessionKey = CONFIG.key;
ctx.sessionId = null;
// 获取cookie 对应的值, 即sessionID,就是缓存中的key
sid = ctx.cookies.get(CONFIG.key, ctx.cookieOption);
// 获取session值
if (sid) {
try {
// 若key存在,则从缓存中获取对应的值
json = await app.redisClient.get(sid);
} catch (e) {
console.log('从缓存中读取session失败: %s\n', e);
json = null;
}
}
// 实例化session
if (json) {
// 若缓存中有值,则基于缓存中的值构建session对象
ctx.sessionId = sid;
try {
sess = new Session(ctx, json);
} catch (err) {
if (!(err instanceof SyntaxError)) throw err;
sess = new Session(ctx, null);
}
} else {
sid = ctx.sessionId = sid || Uuid.gen();
sess = new Session(ctx, null);
}
// 为了便于使用,将session挂载到上下文,这样就能够 ctx.session 这么使用了
Object.defineProperty(ctx, 'session', {
get: function () {
return sess;
},
set: function (val) {
if (null === val) return sess = null;
if ('object' === typeof val) return sess = new Session(this, val);
throw new Error('this.session can only be set as null or an object.');
}
});
try {
await next();
} catch (err) {
throw err;
} finally {
if (null === sess) {
// 设置session=null表示清空session
ctx.cookies.set(CONFIG.key, '', ctx.cookieOption);
} else if (sess.changed(json)) {
// 检查 session 是否发生变化,如有变化,更新缓存中的值
json = sess.save();
await app.redisClient.set(sid, json);
app.redisClient.ttl(sid);
// 设置redis值过时时间为60分钟
app.redisClient.expire(sid, 7200);
} else {
// session 续期
app.redisClient.expire(sid, 7200);
}
}
});
复制代码
多多学习~ 点点进步~