Session
@adonisjs/session
包提供了对 session 的支持。该软件包预先配置了 web
入门模板。安装和配置它也相对简单。
// 安装
npm i @adonisjs/session
// 配置
node ace configure @adonisjs/session
# 创建:config/session.ts
# 更新:.env {"SESSION_DRIVER = cookie"}
# 更新: .adonisrc.json {providers += "@adonisjs/session"}
// 验证环境变量
/**
* 确保将以下验证规则添加到
* `env.ts` 文件来验证环境变量。
*/
export default Env.rules({
// ...现有规则
SESSION_DRIVER: Env.schema.string(),
})
- 支持多个驱动程序。 Cookies、File 和 Redis
- 允许以只读模式实例化 session 存储(在 websocket 请求期间很有帮助)。
- 支持 session 闪存消息
- 在 npm 上查看
- 在 GitHub 上查看
session 配置
你可以通过调整 config/session.ts
文件来配置 session 的行为。以下是默认配置文件。
import { sessionConfig } from '@adonisjs/session/build/config'
export default sessionConfig({
enabled: true,
driver: Env.get('SESSION_DRIVER'),
cookieName: 'adonis-session',
clearWithBrowser: false,
age: '2h',
cookie: {}, // 查看 cookie 驱动程序
file: {}, // 查看文件驱动
redisConnection: 'local', // 查看 redis 驱动
})
- 启用 用作打开/关闭整个应用程序的 session 开关。
- driver 属性定义用于存储 session 数据的驱动程序。
- cookieName 是保存会话 id 的 cookie 的名称。可以随意将名称更改为你喜欢的任何名称。
- clearWithBrowser 属性与
true
值创建一个临时 cookie。退出浏览器时会删除临时 cookie。 - age 属性控制会话的生命周期。
Session 驱动
session 包允许你选择一种可用的驱动来保存 session 数据。
你可以在 config/session.ts
文件中配置驱动程序。driver
属性依赖于 SESSION_DRIVER
环境变量。
// config/session.ts 文件
{
driver: Env.get('SESSION_DRIVER'),
}
Cookie 驱动
cookie 驱动使用 HTTP cookie 来存储会话数据。会话数据在 cookie 中加密,因此你不必担心泄露敏感信息。
即时你的应用配置了负载均衡,cookie 驱动也能很好地工作,因为服务器上没有存储任何信息。
你可以在 config/session.ts
文件中调整 cookie
驱动的设置。
{
/*
|---------------------------------------------------------------
| Cookies 设置
|---------------------------------------------------------------
|
| cookie 设置用于设置 session id 的 cookie
| 并且驱动将使用相同的值。
|
*/
cookie: {
path: '/',
httpOnly: true,
sameSite: false,
},
}
文件驱动
file
驱动程序将 session 数据存储在服务器文件系统上。你可以通过更新 config/session.ts
文件中的 file.location
属性的值来配置存储位置。
注:使用
file
驱动在配置了负载均衡的服务器运行时,需要你在负载均衡上启用粘性会话。
{
file: {
location: Application.tmp('sessions'),
},
}
Redis
redis
驱动是将 session 数据保留在服务器上,并需要配置负载均衡的最佳选择。
注:redis 驱动依赖于
@adonisjs/redis
包,首先应对其进行配置。
redis 驱动程序的配置引用了config/redis.ts
文件中预定义的 redis 连接之一。
// 标题: config/session.ts
{
driver: 'redis',
// highlight-start
redisConnection: 'local',
// highlight-end
}
接下来,在config/redis.ts
文件中定义一个名为local
的连接。
// 标题: config/redis.ts
{
connections: {
// 高亮开始
local: {
host: Env.get('REDIS_HOST'),
port: Env.get('REDIS_PORT'),
password: Env.get('REDIS_PASSWORD', ''),
db: 0,
}
// 高亮结束
}
}
读/写 session 值
你可以使用ctx.session
属性与会话进行交互。
Route.get('/', async ({ session }) => {
// 读取值
const cartTotal = session.get('cart_total')
// 写入值
session.put('cart_total', cartTotal + 10)
})
Edge 模板中还提供了会话的只读版本。你可以使用session
全局帮助程序访问它。
<p> Cart total: {{ session.get('cart_total', 0) }} </p>
以下是工作会话的可用方法列表。
get
从 session 存储中读取给定键的值。或者,你可以定义一个默认值以在实际值为undefined
或null
时返回。
注:模板中也可以使用以下方法。
session.get('cart_total')
session.get('cart_total', 0)
put
将键值对写入会话存储。该值应该是 cookie 支持的数据类型 之一。
session.put('cart_total', 1900)
all
从会话存储中读取所有内容。将始终是键值对的对象。
注:模板中也可以使用以下方法。
console.log(session.all())
Redis驱动的配置使用了config/redis.ts
文件中的一个预定义的redis连接。
// title: config/session.ts
{
driver: 'redis',
// highlight-start
redisConnection: 'local',
// highlight-end
}
接下来,在config/redis.ts
文件中定义一个名为local
的连接。
// title: config/redis.ts
{
connections: {
// highlight-start
local: {
host: Env.get('REDIS_HOST'),
port: Env.get('REDIS_PORT'),
password: Env.get('REDIS_PASSWORD', ''),
db: 0,
}
// highlight-end
}
}
读/写 session 值
你可以通过使用ctx.session
属性与session
会话进行交互。
Route.get('/', async ({ session }) => {
// 获取session值
const cartTotal = session.get('cart_total')
// 设置session值
session.put('cart_total', cartTotal + 10)
})
会话的只读版本在Edge模板中也是可用的。你可以使用session
全局帮助器来访问它。
<p> Cart total: {{ session.get('cart_total', 0) }} </p>
以下是可用的session
方法列表
get
从会话存储中读取一个给定键的值。你可以选择定义一个默认值,当实际值为 undefined
或 null
时返回。
注:以下方法在模板内部也是可用的。
session.get('cart_total')
session.get('cart_total', 0)
put
写一个键值对到会话存储。该值应该是 cookie支持的数据类型 之一。
session.put('cart_total', 1900)
all
从会话存储中读取所有内容。将返回一个键值对的对象。
注:以下方法在模板内部也是可用的。
console.log(session.all())
forget
从 session 存储中删除给定键的值。
// Remove
session.forget('cart_total')
session.get('cart_total') // undefined
increment
增加给定键的值。确保原始值始终是数字。对非数字值调用increment
将导致异常。
session.increment('page_views')
decrement
减少给定键的值。确保原始值始终是数字。对非数字值调用decrement
将导致异常。
session.decrement('score')
clear
将 session 存储清除为空状态。
session.clear()
Session id 生命周期
AdonisJS 创建一个空的会话存储,并在第一个 HTTP 请求上将其分配给一个唯一的会话 id,即使请求/响应生命周期不与会话交互。
每个后续请求都会更新会话 id cookie 的maxAge
属性,以确保它不会过期。此外,会话驱动程序会收到有关更改(如果有)的通知,以更新和保留更改。
sessionId
你可以使用sessionId
属性访问会话 ID 的值。
console.log(session.sessionId)
initiated
查找会话存储是否已启动。在 HTTP 请求期间,这将始终为true
。
if (!session.initiated) {
await session.initiate(false)
}
fresh
查看当前 HTTP 请求期间是否生成了会话 ID。首次生成会话 id 或调用session.regenerate
方法时,该值为 true。
if (!session.fresh) {
session.regenerate()
}
regenerate
重新生成会话 id 并进行现有会话数据追加。 auth 包使用这种方法来防止会话劫持攻击。
session.regenerate()
会话闪存消息
闪存消息存储在会话存储中,仅可用于下一个 HTTP 请求。你可以使用它们在 HTTP 请求之间传递消息。例如:
Route.get('/', async ({ session, response }) => {
session.flash('message', 'Hello world')
response.redirect('/see-message')
})
Route.get('/see-message', async ({ session }) => {
return session.flashMessages.get('message')
})
flash
session.flash
方法将键值对添加到闪存消息中。
session.flash('errors', {
title: 'Post title is required',
description: 'Post description is required',
})
你也可以将对象直接传递给 flash
方法。
session.flash({
errors: {
title: 'Post title is required',
description: 'Post description is required',
},
})
flashAll
flashAll
方法将请求正文添加到闪存消息中,这允许你在模板中获取表单数据并在验证失败重定向后预填充用户输入。
session.flashAll()
flashOnly
session.flashOnly
方法类似于 flashAll
方法,但它允许挑选字段。
session.flashOnly(['title', 'description'])
flashExcept
session.flashExcept
方法与 flashOnly
方法不同,允许忽略字段。
session.flashExcept(['_csrf', 'submit'])
reflash
session.reflash
方法会刷新上一个请求中的数据。
session.reflash()
reflashOnly
session.reflashOnly
方法仅刷新选定的键。
session.reflashOnly(['errors'])
reflashExcept
session.reflashExcept
方法刷新所有数据,除了选定的键。
session.reflashExcept(['success', 'username', 'password'])
访问闪存消息
你可以使用 Edge 模板中的 session.flashMessages
属性或 flashMessages
帮助程序访问上一个请求设置的闪存消息。
// 文件名: 内部模板
{{-- Get value for a given key --}}
{{ flashMessages.get('errors.title') }}
{{-- With optional default value --}}
{{ flashMessages.get('title', '') }}
{{-- Find if a key exists --}}
{{ flashMessages.has('errors.title') }}
{{-- Get all --}}
{{ flashMessages.all() }}
{{-- Find if store is empty --}}
{{ flashMessages.isEmpty }}
Route.get('/', async ({ session }) => {
// 获取给定键的值
session.flashMessages.get('errors.title')
// 带有可选的默认值
session.flashMessages.get('title', '')
// 判断键是否存在
session.flashMessages.has('errors.title')
// 获取所有
session.flashMessages.all()
// 判断 store 是否为空
session.flashMessages.isEmpty
})
其他方法/属性
以下是 Session 类上其他可用方法和属性的列表。
初始化
session.initiate
方法为当前 HTTP 请求启用会话存储。或者,你可以在 readonly
模式下启用存储。
注意:
此方法由 AdonisJS 自动调用,不必自己手动调用它。
await session.initiate(false)
// 只读存储
await session.initiate(true)
fresh
查看当前 HTTP 请求期间是否生成了 session id。首次生成 session id 或调用 session.regenerate
方法时,该值为 true。
if (!session.fresh) {
session.regenerate()
}
readonly
查找存储是否已在 readonly
模式下启动。
注:在 HTTP 请求期间,存储处于 从不 只读模式。此标志是为将来保留的,用于 WebSocket 连接的只读会话。
if (!session.readonly) {
session.put('key', 'value')
}
commit
commit
方法保持会话驱动程序的更改并更新包含 session id 的 cookie 的 maxAge
。 commit
方法由 AdonisJS 自动调用,你不必自己调用它。
await session.commit()
创建自定义 session 驱动
session 包公开了用于添加自定义会话驱动程序的 API。每个 session 驱动都必须遵守 SessionDriverContract。
interface SessionDriverContract {
read(
sessionId: string
): Promise<Record<string, any> | null> | Record<string, any> | null
write(sessionId: string, values: Record<string, any>): Promise<void> | void
destroy(sessionId: string): Promise<void> | void
touch(sessionId: string): Promise<void> | void
}
read
read
方法接收 sessionId
并且必须返回会话数据或 null
。返回值应是一个类似于传递给 write
方法的对象。
write
write
方法接收要存储的 sessionId
和 values
对象。你可以轻易地将值对象转换为您想要的任何其他数据类型。例如,redis
驱动器使用消息生成器将对象转换为字符串。
destroy
destroy
方法会从存储中删除会话 ID 及其相关数据。
touch
touch
方法能重置过期时间。此方法仅适用于具有内置到期的驱动器。例如,redis 驱动程序会更新 redis 键的 ttl
属性。
从外到内的扩展
任何你扩展框架核心的时候,最好假设你无权访问应用程序代码及其依赖项。换句话说,像编写第三方包一样编写扩展,并使用依赖注入来依赖其他依赖。
来演示一下,从创建一个会话驱动程序来将会话存储在内存中,并建一些文件和文件夹开始。
mkdir providers/SessionDriver
touch providers/SessionDriver/index.ts
目录结构如下所示:
providers
└── SessionDriver
└── index.ts
打开 SessionDriver/index.ts
文件并将以下内容粘贴到其中。
// 文件名: providers/SessionDriver/index.ts
import { SessionDriverContract } from '@ioc:Adonis/Addons/Session'
const SESSIONS: Map<string, Record<string, any>> = new Map()
export class MemoryDriver implements SessionDriverContract {
public async read(sessionId: string) {
return SESSIONS.get(sessionId) || null
}
public async write(sessionId: string, values: Record<string, any>) {
SESSIONS.set(sessionId, values)
}
public async destroy(sessionId: string) {
SESSIONS.delete(sessionId)
}
public async touch() {}
}
最后,打开 providers/AppProvider.ts
文件并在 boot
方法中添加自定义驱动器。
import { ApplicationContract } from '@ioc:Adonis/Core/Application'
export default class AppProvider {
public static needsApplication = true
constructor(protected app: ApplicationContract) {}
public async boot() {
const { MemoryDriver } = await import('./SessionDriver')
const Session = this.app.container.use('Adonis/Addons/Session')
Session.extend('memory', () => {
return new MemoryDriver()
})
}
}
完成了!memory
驱动程序现在已准备就绪。只需更新 .env
文件中的 SESSION_DRIVER
属性,就可以继续你的后续操作了。
驱动器生命周期
每个 HTTP 请求会创建一个新的驱动器实例,你可以从 Session.extend
方法回调参数访问 HTTP 上下文。例如:
Session.get('memory', (sessionManager, config, ctx) => {
// 如果你的驱动器需要上下文
return new Driver(ctx)
})
注入依赖
如前所述,扩展不应直接依赖应用程序依赖项,而是利用依赖项注入。
例如,如果你的驱动程序需要访问加密模块,它应该作为构造函数参数而不是直接进行导入。
/**
* 以下是一旦TypeScript 被编译成 JavaScript,仅类型会导
* 入,其他会被删除。
* 因此,理想情况下,你不依赖任何顶级
* 导入,仅使用接口进行类型提示。
*/
import type { EncryptionContract } from '@ioc:Adonis/Core/Encryption'
export class MemoryDriver {
constructor(private encryption: EncryptionContract) {}
public async write(sessionId: string, values: { [key: string]: any }) {
this.encryption.encrypt(JSON.stringify(values))
}
}
最后,你可以在 Session.extend
调用期间注入加密模块。
Session.extend('memory', ({ app }) => {
return new MemoryDriver(app.container.use('Adonis/Core/Encryption'))
})
驱动配置
你还必须通过构造函数注入驱动程序的配置。session.extend
方法为你提供保存在 config/session.ts
文件中的配置。
驱动程序的配置存储在与驱动程序名称匹配的属性中。例如:
// 文件名: config/session.ts
{
// 以下对象用于内存驱动器
memory: {}
}
Session.extend('memory', (app, config) => {
/**
* 配置是“内存”属性的值
*/
return new MemoryDriver(config)
})
本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。