Skip to main content

Session

Session 在计算机中,尤其是在网络应用中,称为“会话控制”。Session 对象存储特定用户会话所需的属性及配置信息。这样,当用户在应用程序的 Web 页之间跳转时,存储在 Session 对象中的变量将不会丢失,而是在整个用户会话中一直存在下去。当用户请求来自应用程序的 Web 页时,如果该用户还没有会话,则 Web 服务器将自动创建一个 Session 对象。当会话过期或被放弃后,服务器将终止该会话。

在 @midwayjs/web 使用#

框架内置了 Session 插件,给我们提供了 ctx.session 来访问或者修改当前用户 Session 。

比如 Controller 中。

// src/controller/home.ts
import { Controller, Get, Provide, Inject } from '@midwayjs/decorator';import { Context } from 'egg';
@Provide()@Controller('/')export class HomeController {  @Inject()  ctx: Context;
  @Get('/')  async home() {    // 获取 Session 上的内容    const userId = this.ctx.session.userId;    // ...
    // 修改 Session 的值    this.ctx.session.visited = this.ctx.session.visited ? this.ctx.session.visited + 1 : 1;  }}

Session 的使用方法非常直观,直接读取它或者修改它就可以了,如果要删除它,直接将它赋值为 null:

ctx.session = null;

需要 特别注意 的是:设置 session 属性时需要避免以下几种情况(会造成字段丢失,详见koa-session源码)

  • 不要以 _ 开头
  • 不能为 isNew
// ❌ 错误的用法ctx.session._visited = 1; //    --> 该字段会在下一次请求时丢失ctx.session.isNew = 'HeHe'; //    --> 为内部关键字, 不应该去更改
// ✔️ 正确的用法ctx.session.visited = 1; //   -->  此处没有问题

Session 的实现是基于 Cookie 的,默认配置下,用户 Session 的内容加密后直接存储在 Cookie 中的一个字段中,用户每次请求我们网站的时候都会带上这个 Cookie,我们在服务端解密后使用。Session 的默认配置如下:

export const session = {  key: 'EGG_SESS',  maxAge: 24 * 3600 * 1000, // 1 天  httpOnly: true,  encrypt: true,};

可以看到这些参数除了 key 都是 Cookie 的参数,key 代表了存储 Session 的 Cookie 键值对的 key 是什么。在默认的配置下,存放 Session 的 Cookie 将会加密存储、不可被前端 js 访问,这样可以保证用户的 Session 是安全的。

Session 默认存放在 Cookie 中,如需放在其他存储中,具体可以查阅 Cookie 与 Session

在 @midwayjs/koa 使用#

koa 原生功能只提供了 Cookie 的操作,但是没有提供 Session 操作。Session 就只用自己实现或者通过第三方中间件实现。

常用的 koa session 中间件有 koa-session。koa-session 默认基于 cookie 来实现 Session 数据存储,同时也支持其他存储的扩展。

首先需要安装中间件。

$ npm install koa-session --save

在启动时加载该中间件,示例如下:

// configuration.ts
import { Configuration, App } from '@midwayjs/decorator';import { Application } from '@midwayjs/koa';import * as session from 'koa-session';
@Configuration()export class AutoConfiguration {  @App()  app: Application;
  async onReady() {    this.app.use(      session(        {          key: 'koa.sess', // cookie key          maxAge: 24 * 3600 * 1000, // 1天        },        this.app      )    );  }}

这里的配置也可以放到 config 中,然后使用 @Config 装饰器注入。

这里中间件的更多选项可以参考 koa-session 文档

之后就可以在其他地方使用了,比如:

// src/controller/home.ts
import { Controller, Get, Provide, Inject } from '@midwayjs/decorator';import { Context } from '@midwayjs/koa';
@Provide()@Controller('/')export class HomeController {  @Inject()  ctx: Context;
  @Get('/')  async home() {    this.ctx.session.visits = this.ctx.session.visits ? this.ctx.session.visits + 1 : 1;  }}

也可以使用 @Session 装饰器获取 Session。

// src/controller/home.ts
import { Controller, Get, Provide, Inject, Session, ALL } from '@midwayjs/decorator';import { Context } from '@midwayjs/koa';
@Provide()@Controller('/')export class HomeController {  @Inject()  ctx: Context;
  @Get('/')  async home(@Session() visits = 1) {    this.req.session.visits = session.visits = visits + 1;  }}

或者直接获取整个 Session。

// src/controller/home.ts
import { Controller, Get, Provide, Session, ALL } from '@midwayjs/decorator';
@Provide()@Controller('/')export class HomeController {  @Get('/')  async home(@Session(ALL) session) {    session.visits = session.visits ? session.visits + 1 : 1;  }}

如需使用不同的存储,可以使用类似的 generic-session 来替换该中间件。

在 @midwayjs/express 使用#

首先安装依赖。

$ npm i express-session$ npm i -D @types/express-session

加到中间件中。

// configuration.ts
import { Configuration, App } from '@midwayjs/decorator';import { Application } from '@midwayjs/express';import * as session from 'express-session';
@Configuration()export class AutoConfiguration {  @App()  app: Application;
  async onReady() {    this.app.use(      session({        secret: 'my-secret',        resave: false,        saveUninitialized: false,      })    );  }}

secret 用于对 sessionId cookie 进行加密。它可以是字符串,也可以是多个秘钥字符串组成的数组。如果提供了数组,则使用第一个字符串进行签名,而在验证请求中的签名时会循环所有的加密串。

这个秘钥最好是随机的一组字符。

通过获取 req 对象设置获取 Session 中的值。

// src/controller/home.ts
import { Controller, Get, Provide, Inject } from '@midwayjs/decorator';import { Context } from '@midwayjs/express';import { Request } from 'express';
@Provide()@Controller('/')export class HomeController {  @Inject()  req: Request;
  @Get('/')  async home() {    this.req.session.visits = this.req.session.visits ? this.req.session.visits + 1 : 1;  }}

也可以使用 @Session 装饰器获取 Session。

// src/controller/home.ts
import { Controller, Get, Provide, Inject, Session, ALL } from '@midwayjs/decorator';import { Context } from '@midwayjs/express';import { Request } from 'express';
@Provide()@Controller('/')export class HomeController {  @Inject()  req: Request;
  @Get('/')  async home(@Session() visits = 1) {    this.req.session.visits = session.visits = visits + 1;  }}

或者直接获取整个 Session。

// src/controller/home.ts
import { Controller, Get, Provide, Session, ALL } from '@midwayjs/decorator';import { Context } from '@midwayjs/express';
@Provide()@Controller('/')export class HomeController {  @Get('/')  async home(@Session(ALL) session) {    session.visits = session.visits ? session.visits + 1 : 1;  }}

在 Serverless 场景使用#

Serverless 场景有与弹性的存在,没有维护 Session 的必要性,最多只能使用基于 Cookie 的 Session。