koa

session:https://chenshenhai.github.io/koa2-note/note/jsonp/info.html

koa2:https://koa.bootcss.com/

sequelize 中文文档:https://itbilu.com/nodejs/npm/V1PExztfb.html

搭建基础环境

Koa

使用 Koa-generator 搭建项目

梳理项目结构

Live Reload

问题: 每次修改后台应用代码, 需要重新运行命令修改才生效

解决: 使用 nodemon 包

下载: npm install --save-dev nodemon

测试: 修改后台任何代码, 会自动重新运行最新的代码 (按下 ctrl + S)

使用 Sequelize 操作 Mysql

参考 Sequelize

路由

使用路由

测试

Koa 基本使用

不知道为什么:

const getInterviewerInfo = async function(ctx,next){
  const pk = ctx.query.id
  const candidateInfo = await Candidate.getInterviewerByPk(pk)
  const result = await User.getUserByPk(candidateInfo.foreignKey)
  ctx.response.body = result
}

只能直接返回 result,给 result 添加属性或者复制 result 或者直接返回 JSON 啥的都不行 @@@

Get 方法

ctx.query.id

Post 方法

userInfo = ctx.request.body

HTTP 响应控制

常用字段

response.status

配置跨域

控制缓存

Koa-session

基本使用

const session = require('koa-session');
const Koa = require('koa');
const app = new Koa();
app.keys = ['some secret hurr'];

const CONFIG = {
  key: 'koa:sess', /** (string) cookie key (default is koa:sess) */
  
  /** (number || 'session') maxAge in ms (default is 1 days) */
  /** 'session' will result in a cookie that expires when session/browser is closed */
  /** Warning: If a session cookie is stolen, this cookie will never expire */
  maxAge: 86400000,
  overwrite: true, /** (boolean) can overwrite by js or not (default true) */
  httpOnly: true, /** (boolean) httpOnly or not (default true) */
  signed: true, /** (boolean) signed or not (default true) */
  rolling: false, /** (boolean) Force a session identifier cookie to be set on every response. The expiration is reset to the original maxAge, resetting the expiration countdown. (default is false) */
  renew: false, /** (boolean) renew session when session is nearly expired, so we can always keep user logged in. (default is false)*/
};

app.use(session(CONFIG, app));

app.use(ctx => {
  // ignore favicon
  if (ctx.path === '/favicon.ico') return;

  let n = ctx.session.views || 0;
  ctx.session.views = ++n;
  ctx.body = n + ' views';
});

app.listen(3000);

我们看到这个在这个会话状态中,session 中保存了页面访问次数,每次请求的时候,会增加计数再把结果返回给用户。

对于 session 的存储方式,koa-session 同时支持 cookie 和外部存储。

默认配置下,会使用 cookie 来存储 session 信息,也就是实现了一个 "cookie session"。这种方式对服务端是比较轻松的,不需要额外记录任何 session 信息,但是也有不少限制,比如大小的限制以及安全性上的顾虑。用 cookie 保存时,实现上非常简单,就是对 session(包括过期时间) 序列化后做一个简单的 base64 编码。其结果类似 :

koa:sess=eyJwYXNzcG9ydCI6eyJ1c2VyIjozMDM0MDg1MTQ4OTcwfSwiX2V4cGlyZSI6MTUxNzI3NDE0MTI5MiwiX21heEFnZSI6ODY0MDAwMDB9;

示例

  const session = require("koa-session")
  app.use(session({
      signed:false,
      maxAge: 20 * 1000
  },app))

外部存储

在实际项目中,会话相关信息往往需要再服务端持久化,因此一般都会使用外部存储来记录 session 信息。

外部存储可以是任何的存储系统:

但是这不意味着我们不需要 cookie 了,由于 http 协议的无状态特性,我们依然需要通过 cookie 来获取 session 的标识 (这里叫 externalKey)。

koa-session 里的 external key 默认是一个时间戳加上一个随机串,因此 cookie 的内容类似:

koa:sess=1517188075739-wnRru1LrIv0UFDODDKo8trbmFubnVmMU;

在内存中存储 Sessoin

要实现一个外置的存储,用户需要自定义 get(), set() 和 destroy() 函数,分别用于获取、更新和删除 session。一个最简单的实现,我们就采用一个 object 来存储 session,那么可以这么来配置:

let store = {
  storage: {},
  get (key, maxAge) {
    return this.storage[key]
  },
  set (key, sess, maxAge) {
    this.storage[key] = sess
  },
  destroy (key) {
    delete this.storage[key]
  }
}
app.use(session({store}, app))

在数据库中储存 Session

使用 mongoDB 储存 session

https://juejin.im/post/5a6ad754f265da3e513352e5#heading-5

const session = require("koa-session")
const MongoStore = require("koa-session-mongo2")
app.use(session({
    store: new MongoStore({
        url:  DB_URL  // your mongodb url  required
        db:'user',
        collection: optional, db session collection name,default "__session"
        signed:false // if true, please set app.keys = ['...']
    }),
    signed:false,
    maxAge: 20 * 1000
},app))

使用 mysql 储存 session

https://chenshenhai.github.io/koa2-note/note/session/info.html

const Koa = require('koa')
const session = require('koa-session-minimal')
const MysqlSession = require('koa-mysql-session')

const app = new Koa()

// 配置存储session信息的mysql
let store = new MysqlSession({
  user: 'root',
  password: 'abc123',
  database: 'koa_demo',
  host: '127.0.0.1',
})

// 存放sessionId的cookie配置
let cookie = {
  maxAge: '', // cookie有效时长
  expires: '',  // cookie失效时间
  path: '', // 写cookie所在的路径
  domain: '', // 写cookie所在的域名
  httpOnly: '', // 是否只用于http请求中获取
  overwrite: '',  // 是否允许重写
  secure: '',
  sameSite: '',
  signed: '',

}

// 使用session中间件
app.use(session({
  key: 'SESSION_ID',
  store: store,
  cookie: cookie
}))

app.use( async ( ctx ) => {

  // 设置session
  if ( ctx.url === '/set' ) {
    ctx.session = {
      user_id: Math.random().toString(36).substr(2),
      count: 0
    }
    ctx.body = ctx.session
  } else if ( ctx.url === '/' ) {

    // 读取session信息
    ctx.session.count = ctx.session.count + 1
    ctx.body = ctx.session
  } 

})

app.listen(3000)
console.log('[demo] session is starting at port 3000')

分布式 Session

首先是为什么会有这样的概念出现?

先考虑这样一个问题,现在我的应用需要部署在 3 台机器上。是不是出现这样一种情况,我第一次登陆,请求去了机器 1,然后再机器 1 上创建了一个 session;但是我第二次访问时,请求被路由到机器 2 了,但是机器 2 上并没有我的 session 信息,所以得重新登录。当然这种可以通过 nginx 的 IP HASH 负载策略来解决。对于同一个 IP 请求都会去同一个机器。

但是业务发展的越来越大,拆分的越来越多,机器数不断增加;很显然那种方案就不行了。那么这个时候就需要考虑是不是应该将 session 信息放在一个独立的机器上,所以分布式 session 要解决的问题其实就是分布式环境下的 session 共享的问题。

上图中的关于 session 独立部署的方式有很多种,可以是一个独立的数据库服务,也可以是一个缓存服务 (redis,目前比较常用的一种方式,即使用 Redis 来作为 session 缓存服务器)。

Koa-session 源码

https://segmentfault.com/a/1190000013039187#articleHeader6

主要流程

index.js 入口

Session 初始化

extendContext(app.context, opts)

ContextSession 类

get() 方法

initFromExternal()

set() 方法

Session 提交

commit()

_shouldSaveSession()

save()

流程图

index.js=>start: index.js
extendContext=>operation: extendContext(app.context, opts) 
[CONTEXT_SESSION]=>subroutine: 添加[CONTEXT_SESSION]访问器属性
session=>subroutine: 添加sessoin访问器属性
sess=>operation: sess = ctx[CONTEXT_SESSION]
getter=>subroutine: 触发[[CONTEXT_SESSION]]getter函数
sess_is_exist=>condition: this[_CONTEXT_SESSION] 存在?
return=>subroutine: 返回ctx[_CONTEXT_SESSION]
create=>subroutine: 新建一个ContextSession对象,赋值给ctx[_CONTEXT_SESSION]
initFromCookie=>subroutine: ContextSession类的getter函数用于从Cookie初始化sessoin
cond=>condition: sess.store === true
initFromExternal=>operation: sess.initFromExternal()
next=>operation: next()
someOp=>subroutine: 执行其他操作,可能包含对sessoin的读写
commit=>operation: commit(),用于提交sessoin
session_getter=>subroutine: 访问ctx.sessoin
如果不是外部储存,会触发initFromCookie()
should_save=>condition: 是否应该保存session
yes_save=>subroutine: changed rolling renew 均会更新session
save=>subroutine: 更新后,存入cookie/外部储存中
no_save=>subroutine: sesion没有更新
不用保存
end=>end


index.js->extendContext
extendContext->[CONTEXT_SESSION]->session->sess
sess(bottom)->getter->sess_is_exist
sess_is_exist(no)->create->initFromCookie->cond
sess_is_exist(yes)->return->cond
cond(yes)->initFromExternal(bottom)->next
cond(no)->next
next->commit->session_getter->should_save
should_save(yes)->yes_save->save->end
should_save(no)->no_save->end