You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.05 21:09:03