这个不是一个独立的项目,而是session 传送门
背景
这是express-session的默认存储媒介,没错,就是内存。在上一节,介绍了媒介redis,现在就来讲一下内存,更有意思。这里需要注意的是,因为依赖于内存,所以一旦重启或者意外终止程序,所有的session记录将会清空。
源码分析
原型定义
function MemoryStore() {
Store.call(this)
this.sessions = Object.create(null) // key是id, value是session详情
}
上面的代码无需多讲了,主要是初始化父类以及sessions值,用来存放所有用户的session信息(对象类型)
getSession方法
// 根据sessionid来获取相应的session,如果过期或者不存在,都返回空值
function getSession(sessionId) {
var sess = this.sessions[sessionId]
if (!sess) {
return // 从未访问
}
// parse
sess = JSON.parse(sess)
var expires = typeof sess.cookie.expires === 'string'
? new Date(sess.cookie.expires)
: sess.cookie.expires
// destroy expired session
if (expires && expires <= Date.now()) { // 过期了
delete this.sessions[sessionId]
return
}
return sess
}
代码也挺简单,不多说了
MemoryStore.all
// 获取全部有效的session,并作为第二个参数传入callback函数
MemoryStore.prototype.all = function all(callback) {
var sessionIds = Object.keys(this.sessions) // 所有key(id)
var sessions = Object.create(null) // 用来存储所有有效的session,并且返回
for (var i = 0; i < sessionIds.length; i++) {
var sessionId = sessionIds[i]
var session = getSession.call(this, sessionId) // 判断该session是否有效,并且返回有效的session
if (session) {
sessions[sessionId] = session;
}
}
callback && defer(callback, null, sessions)
}
在调用getSession.call(this, sessionId)
方法的时候,会自动将过期的session清掉。
MemoryStore.clear
MemoryStore.prototype.clear = function clear(callback) {
this.sessions = Object.create(null)
callback && defer(callback)
}
将所有的session都清空掉
MemoryStore.destroy
MemoryStore.prototype.destroy = function destroy(sessionId, callback) {
delete this.sessions[sessionId]
callback && defer(callback)
}
清空某个session,一般用于登出用途
MemoryStore.get
MemoryStore.prototype.get = function get(sessionId, callback) {
defer(callback, null, getSession.call(this, sessionId))
}
直接调用已经存在的getSession方法,若过期,自动清理
MemoryStore.set
// 设置某个session值
MemoryStore.prototype.set = function set(sessionId, session, callback) {
this.sessions[sessionId] = JSON.stringify(session)
callback && defer(callback)
}
过程无需复杂,直接覆盖旧值就可以了
MemoryStore.length
MemoryStore.prototype.length = function length(callback) {
this.all(function (err, sessions) {
if (err) return callback(err)
callback(null, Object.keys(sessions).length)
})
}
为了防止某些session过期,所以this.all方法会调用getSession
方法,所以返回的sessions是当前有效的记录,再求大小即可。
MemoryStore.touch
MemoryStore.prototype.touch = function touch(sessionId, session, callback) {
var currentSession = getSession.call(this, sessionId)
if (currentSession) {
// update expiration
currentSession.cookie = session.cookie
this.sessions[sessionId] = JSON.stringify(currentSession)
}
callback && defer(callback)
}
当前currentSession
存在的时候,才有更新,所以需要获取当前session。
源码就这么少了,分析完了。下面来聊聊这个代码有哪些地方值得学习的(思路和实现技巧)
小结
根据id获取相应session,这是一个很常见的动作,所以需要提取成一个共公用方法。但是考虑到可能会有过期现象,所以还要计算过期时间,如果过期了,则直接清理就好。所以,这个方法完全对外屏蔽了细节,给我id即可,至于过不过期,我等下告诉你,不用你管
Store.call(this)
这里用得不错(确实,大家也这么做的)。初始化父类的时候,这么做是最好用了。上面的
getSession
方法,明明不是MemoryStore所有,怎么可以直接使用this呢?技巧就在这里了。当一个方法不想或者无需暴露给外界的时候,我们可以在文件内定义函数,而不是某个对象的方法。但是,往往有时候我们需要用到对象的某些属性,例如this.sessions,这个时候就可以使用getSession.call(this, sessionId)
,动态改变this,就可以使用啦啦啦。这个写法,我感觉不错。-
最后一个,感觉也是相当有意思的。因为session有过期时间。很多人第一印象就是,要不要用一个定时器来刷新过期时间啊。之前我接触令牌桶算法的时候,我也以为需要这么做。很正常啊,一秒钟刷新一次,不然没法计算过期时间。但是,如果真是这么做了,会导致定时器满天飞,性能大大下降。在这里,是利用当前访问时间和初次访问时间之差来确定是否过期,轻易地省去了定时器。
好了,说完了,不错的一个插件项目