本文目录导读:

- 核心要素(无论哪种方案都需要记录)
- 方案一:应用层手动记录(最灵活,适合简单项目)
- 方案二:事件驱动(推荐,适合中型项目)
- 方案三:数据库触发器(适合DB管理员偏好的项目)
- 方案四:数据库归档 + 通用中间件(适合复杂企业级)
- 方案选择建议
- 额外注意事项(非常重要)
在 PHP 项目中实现数据审计(审计追踪 / 操作日志),通常涉及记录谁(用户)、在什么时间、对什么数据、做了什么操作(增、删、改)、操作前后数据的变化以及操作结果(成功/失败)。
根据项目的规模和复杂性,有几种主流的实现方案,从简单到复杂排列如下:
核心要素(无论哪种方案都需要记录)
一个标准的审计日志条目通常包含:
user_id(操作者)action(操作类型:CREATE, UPDATE, DELETE, LOGIN, EXPORT 等)table_name(操作的目标表)record_id(操作的目标记录主键)old_value(操作前的数据快照,JSON格式)new_value(操作后的数据快照,JSON格式)ip_address(客户端IP)user_agent(客户端信息)created_at(操作时间)
应用层手动记录(最灵活,适合简单项目)
直接在业务逻辑中手动插入审计表。
步骤:
- 创建审计日志表
audit_logs。 - 在每次数据变更(增删改)的代码中,调用审计日志函数。
// 示例:手动记录(Laravel风格)
class UserController extends Controller {
public function update(Request $request, $id) {
$user = User::findOrFail($id);
$oldData = $user->toArray(); // 操作前数据
$user->update($request->validated());
$newData = $user->fresh()->toArray(); // 操作后数据
// 记录审计日志
AuditLog::create([
'user_id' => auth()->id(),
'action' => 'UPDATE',
'table_name' => 'users',
'record_id' => $id,
'old_value' => json_encode($oldData),
'new_value' => json_encode($newData),
'ip_address' => $request->ip(),
]);
return response()->json($user);
}
}
- 优点:简单直观,完全自定义(比如只记录几个关键字段)。
- 缺点:代码侵入性强,容易遗漏,维护成本高,不适合大型项目。
事件驱动(推荐,适合中型项目)
利用框架的事件系统(如 Laravel 的 Event + Listener,或 Symfony 的 EventDispatcher)在数据变更时触发事件,监听器负责记录审计日志。
步骤(以 Laravel 为例):
- 定义事件(如
UserUpdated)。 - 在 Model 的
booted方法中触发事件(利用模型事件如updated、created、deleted)。// app/Models/User.php class User extends Model { protected $dispatchesEvents = [ 'updated' => \App\Events\UserUpdated::class, 'created' => \App\Events\UserCreated::class, ]; } - 创建监听器
AuditLogListener,在handle方法中记录日志。// app/Listeners/AuditLogListener.php class AuditLogListener { public function handle(UserUpdated $event) { $user = $event->user; // 注意:模型事件中的 $user->getOriginal() 可以拿到修改前的值 AuditLog::create([ 'user_id' => auth()->id() ?? $event->user->id, // 如果是系统操作 'action' => 'UPDATE', 'table_name' => $user->getTable(), 'record_id'=> $user->id, 'old_value'=> json_encode($user->getOriginal()), 'new_value'=> json_encode($user->getAttributes()), // ... ]); } }
- 优点:与业务逻辑解耦,代码清晰,容易扩展(一个表对应一个监听器)。
- 缺点:需要额外的监听器和事件类定义。
数据库触发器(适合DB管理员偏好的项目)
直接在 MySQL/PostgreSQL 层利用触发器(Trigger)记录变更。
CREATE TRIGGER audit_users_update
AFTER UPDATE ON users FOR EACH ROW
BEGIN
INSERT INTO audit_logs (table_name, record_id, old_value, new_value, action, created_at)
VALUES ('users', NEW.id, JSON_OBJECT('name', OLD.name, 'email', OLD.email),
JSON_OBJECT('name', NEW.name, 'email', NEW.email), 'UPDATE', NOW());
END;
- 优点:数据库层保证,PHP代码零侵入,无法绕过(即使通过SQL管理工具修改也会被记录)。
- 缺点:逻辑耦合在数据库中,难以调试,跨数据库迁移困难,无法直接获取
user_id(通常需要应用层传递,比较麻烦)。
数据库归档 + 通用中间件(适合复杂企业级)
核心思路:使用数据版本控制或CDC(Change Data Capture)。
-
使用 Laravel Auditing 或 Spiritix
- Laravel Auditing(最受欢迎):通过 Trait 自动记录,只需在 Model 中添加
use Auditable,它会自动记录所有字段变更。use OwenIt\Auditing\Auditable; class User extends Model { use Auditable; } - Spiritix/Lada-Cache:更侧重缓存,但也支持变更追踪。
- Laravel Auditing(最受欢迎):通过 Trait 自动记录,只需在 Model 中添加
-
使用第三方包(如 Symfony 的 ActivityLogBundle):适合Symfony项目。
-
使用消息队列异步记录:数据变更时将审计信息推入对列(RabbitMQ/Redis),由单独的消费者写入审计表,不阻塞主业务流程。
-
使用 Time Travel / 版本表(PostgreSQL 的
tsvector/ MySQL 的binlog+ Debezium + Kafka):这不是纯PHP可以完成的,而是数据层面的审计,通常用于合规性极高的场景(如金融、医疗)。
方案选择建议
| 项目规模/场景 | 推荐方案 | 理由 |
|---|---|---|
| 小型项目 / 快速原型 | 手动记录 | 简单,不用额外依赖 |
| 中型项目 / 业务逐步增长 | 事件驱动 或 Laravel Auditing | 解耦,维护性好,有成熟包 |
| 大型项目 / 严格合规(如金融) | 数据库触发器 + 第三方包(如Laravel Auditing) + 历史表 | 性能优化、扩展性强 |
| 微服务架构 / 高并发 | 消息队列(异步) + 事件驱动 | 避免写日志影响主流程性能 |
额外注意事项(非常重要)
- user_id 的来源:对于后台管理/API,从 Token/Session 中取;对于队列或CLI任务,需要指定
system或cron。 - 敏感数据处理:审计日志中不应存储明文密码、信用卡号等,记录前要对敏感字段进行脱敏(如
password=> )。 - 性能优化:
- 使用单独的数据库存储审计日志(不与应用主库混合)。
- 使用批量插入而不是逐条 insert。
- 使用队列异步处理。
- 存储空间:审计日志会快速膨胀,需要设计归档策略(如按月分区表)和清理策略(如保留3个月后移至冷存储)。
- 数据完整性:防止攻击者篡改审计日志,可以考虑使用数据库只读权限、数字签名(Hash链) 或写入块存储(如时序数据库 / Amazon S3 对象锁)。
- 如果你用 Laravel,直接使用
Laravel Auditing是最省心的方案。 - 如果项目比较小,手动记录或简单的事件驱动就够了。
- 如果对合规要求极高,建议结合数据库级(触发器)+ 应用级双重记录。