scaffold-audit 审计模块

模块概述

scaffold-audit 提供业务数据变更审计和登录审计两大能力。通过 @AuditLog 注解标记 Controller 方法,结合 AOP 切面自动记录实体字段级别的变更详情(旧值 -> 新值)。登录审计模块记录用户的登录/登出事件,支持近 N 天登录统计。审计模块完全异步执行,不影响业务接口性能。

功能列表

  • 数据变更审计:自动记录实体的字段级变更(旧值、新值、字段名、字段标签)
  • 登录审计:记录登录/登出日志,支持按时间段统计
  • 实体快照:通过 ThreadLocal 在更新前保存旧实体 JSON 快照
  • 字段标签解析:自动读取实体字段的 @Schema(description) 注解作为中文标签
  • 审计日志清理:按保留天数清理历史审计日志
  • 变更摘要生成:自动生成人类可读的字段变更摘要文本

核心组件

类名 职责
Annotation @AuditLog 标记在 Controller 方法上,声明审计元数据
Aspect AOP 切面 拦截 @AuditLog 注解方法,自动采集审计数据
Controller AuditLogController 审计日志查询接口,路径 /v1/audit/log
Controller LoginAuditController 登录审计接口,路径 /v1/audit/login
Core AuditDataCollector 对比新旧实体数据,生成字段变更列表
Core EntityStateSnapshot ThreadLocal 实体快照管理,在 update 前保存旧值 JSON
Core FieldLabelResolver 解析实体字段的中文名称(读取 @Schema 注解)
Service AuditLogService 审计日志的存储与查询
Service LoginAuditService 登录审计的存储、查询与统计
Entity AuditLog 审计日志实体
Entity AuditLoginLog 登录审计日志实体
Entity AuditFieldChange 字段变更记录实体
Config AuditAutoConfiguration 自动配置类,启用异步和组件扫描
Config AuditProperties 配置属性类

配置参数

配置前缀:scaffold.audit

参数 类型 默认值 说明
enabled boolean true 模块开关
retainDays Integer 90 审计日志保留天数
maxFieldValueLength Integer 2000 字段值最大记录长度
loginAuditEnabled boolean true 是否启用登录审计

API 接口列表

审计日志 /v1/audit/log

方法 路径 说明
GET /page 分页查询审计日志
GET /get/{id} 审计日志详情(含字段变更列表)
GET /getByTraceId/{traceId} 按链路追踪ID查询审计日志
POST /clean?retainDays=N 清理历史审计日志(保留最近 N 天)

登录审计 /v1/audit/login

方法 路径 说明
GET /page 分页查询登录日志
GET /stats/recent?days=7 近 N 天登录统计

@AuditLog 注解说明

@AuditLog 标注在 Controller 方法上,声明该操作的审计元数据。支持 SpEL 表达式从方法参数中动态提取业务 ID 和名称。

属性 类型 默认值 说明
module String - 业务模块名称(必填)
operation String - 操作描述(必填)
businessType String "" 业务实体类名,支持 SpEL:#dto.class.simpleName
businessId String "" 业务ID,支持 SpEL:#id#dto.id
businessName String "" 业务名称,支持 SpEL
operType int 0 操作类型:0-其他,1-新增,2-修改,3-删除
trackFields String[] {} 要追踪的字段名列表,空数组=追踪所有变更字段
recordParams boolean true 是否记录请求参数

注意:审计日志的 status 字段遵循布尔语义规范,1 = 正常,0 = 异常。

使用示例

在 Controller 上使用审计注解

@PostMapping("/update")
@AuditLog(
    module = "商品管理",
    operation = "更新商品",
    businessType = "#dto.class.simpleName",
    businessId = "#dto.id",
    businessName = "#dto.productName",
    operType = 2,
    trackFields = {"name", "price", "status"}
)
public Result<Boolean> updateProduct(@RequestBody ProductUpdateDTO dto) {
    return Result.success(productService.updateProduct(dto));
}

在 Service 中保存实体快照(用于修改操作的变更对比)

public Boolean updateProduct(ProductUpdateDTO dto) {
    // 更新前:保存旧实体快照到 ThreadLocal
    Product oldProduct = productRepository.getById(dto.getId());
    EntityStateSnapshot.set(String.valueOf(dto.getId()), JSONUtil.toJsonStr(oldProduct));

    // 执行更新
    Product newProduct = BeanCopyUtils.copy(dto, Product.class);
    productRepository.updateById(newProduct);
    return true;
}

查询审计日志

// 分页查询审计日志
const { data } = await get('/v1/audit/log/page', {
  params: { pageNum: 1, pageSize: 20, module: '商品管理' }
})

// 查看审计详情(含字段变更列表)
const { data: detail } = await get(`/v1/audit/log/get/${id}`)
// detail.fieldChanges = [
//   { fieldName: 'price', fieldLabel: '价格', oldValue: '99.00', newValue: '129.00' }
// ]

注意事项

  • @AuditLogbusinessIdbusinessName 支持 SpEL 表达式,可以引用方法参数。
  • 修改操作需要在 Service 层手动调用 EntityStateSnapshot.set() 保存旧实体快照,否则无法生成字段变更记录。
  • FieldLabelResolver 通过读取实体字段的 @Schema(description) 注解解析中文标签,建议在实体类上标注该注解。
  • 审计日志默认保留 90 天,可通过 scaffold.audit.retain-days 调整。
  • 字段变更记录的最大值长度为 2000 字符,超出部分会被截断。
  • 系统字段(id、createTime、updateTime、createBy、updateBy、deleted、tenantId)自动跳过,不记录变更。
  • 模块启用条件:scaffold.audit.enabled=true(默认启用),通过 AuditAutoConfiguration 自动配置。
  • 状态枚举:审计日志的 status 字段遵循布尔语义规范,1 = 正常,0 = 异常。