在Adonis 5的@beforeSave钩子中能否访问ctx上下文?如何实现?
Adonis 5 @beforeSave钩子访问ctx及审计日志实现方案
核心结论
Adonis 5的模型钩子默认无法直接访问ctx上下文对象,模型层设计上和HTTP请求上下文解耦,直接在钩子内调用ctx会抛出未定义错误,你可以通过以下三种方案实现审计日志需求。
审计日志功能实现方案
推荐优先使用ALS方案,无需额外适配代码,侵入性最低:
- 方案1:通过Async Local Storage(ALS)直接获取上下文
Adonis 5默认开启了ALS支持,框架会自动把当前请求的上下文对象存入ALS中,你可以直接在模型钩子内调用HttpContext.get()获取:
import { BaseModel, beforeSave } from '@ioc:Adonis/Lucid/Orm' // 引入HttpContext模块 import HttpContext from '@ioc:Adonis/Core/HttpContext' import Audit from 'App/Models/Audit' export default class YourModel extends BaseModel { @beforeSave() public static async audit(model: YourModel) { // 获取当前请求的ctx对象,非HTTP场景下返回null const ctx = HttpContext.get() if (ctx?.auth?.user) { await Audit.create({ model_type: this.name, // 记录模型名 model_id: model.id, // 记录操作数据ID operator_id: ctx.auth.user.id, // 记录操作用户ID // 其他你需要存储的字段 }) } } }
注意:如果你的项目手动关闭了ALS功能,需要在
config/app.ts中把allowAsyncLocalStorage设置为true即可生效。非HTTP请求场景(如命令行、队列任务)下ctx为null,需要做好空判断避免报错。
- 方案2:通过全局中间件挂载操作用户到模型
如果你不想依赖ALS,可以通过中间件把当前操作用户挂载到模型的静态属性上:
第一步:在需要审计的模型中新增静态属性存储操作用户
// app/Models/YourModel.ts import { BaseModel, beforeSave } from '@ioc:Adonis/Lucid/Orm' import Audit from 'App/Models/Audit' export default class YourModel extends BaseModel { // 存储当前请求的操作用户 public static currentOperator: any | null = null @beforeSave() public static async audit(model: YourModel) { if (this.currentOperator) { await Audit.create({ model_type: this.name, model_id: model.id, operator_id: this.currentOperator.id }) } } }
第二步:新增全局中间件,请求时挂载用户,请求结束后清空属性避免并发串数据
// app/Middleware/BindOperatorToModel.ts import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext' import YourModel from 'App/Models/YourModel' export default class BindOperatorToModel { public async handle(ctx: HttpContextContract, next: () => Promise<void>) { YourModel.currentOperator = ctx.auth.user || null await next() // 请求结束后清空属性 YourModel.currentOperator = null } }
第三步:将中间件注册到start/kernel.ts的全局中间件数组中:
Server.middleware.register([ () => import('@ioc:Adonis/Core/BodyParser'), // 新增你的中间件 () => import('App/Middleware/BindOperatorToModel'), ])
- 方案3:操作模型时手动传入操作用户
适合仅少数接口需要审计的场景,无需全局配置:
首先在控制器操作模型时,把操作用户存入模型的$extras属性:
// 控制器代码示例 public async store({ request, auth }: HttpContextContract) { const data = request.only(['title', 'content']) const post = new Post() post.merge(data) // 手动挂载操作用户 post.$extras.operator = auth.user await post.save() }
然后在钩子中直接读取该属性:
@beforeSave() public static async audit(model: Post) { const operator = model.$extras.operator if (operator) { await Audit.create({ model_id: model.id, operator_id: operator.id }) } }
内容的提问来源于stack exchange,提问作者Jonatha Chaves
相关产品推荐
相关产品推荐

