thinkphp如何实现操作日志审计_关键业务行为跟踪记录方案

ThinkPHP 6 的 Log::write() 不适合业务审计日志

直接用框架默认日志写法记录用户操作,很快会遇到三个硬伤:日志混在 runtime/log/ 下难检索、无用户 ID 和请求上下文、无法按业务类型分类归档。审计日志必须独立存储、结构化、可关联溯源。

实操建议:

立即学习“PHP免费学习笔记(深入)”;

不要复用 think\facade\Log,新建专用日志驱动,比如继承 think\log\driver\File 并重写 save() 方法,强制写入 runtime/audit/ 目录每条日志必须包含:user_id(从 auth 或 session 取)、ip(request()->ip())、action(如 "order_create")、data(关键字段脱敏后 JSON,如 {"order_no":"ORD2024…","amount":99.9})避免记录原始 $_POST 或 $request->param() —— 体积大、含敏感字段、难以回溯意图;只提取明确业务语义的字段

用中间件统一拦截关键控制器方法

手动在每个控制器里写日志极易遗漏或不一致。中间件是唯一能保证“所有订单创建、资金划转、权限变更”必留痕的入口。

实操建议:

立即学习“PHP免费学习笔记(深入)”;

定义中间件 AuditMiddleware,在 handle() 中判断当前路由是否匹配预设规则,例如:Str::is(‘api/v1/order/*’, $request->url()) || Str::is(‘admin/user/assign*’, $request->url())使用 $request->rule()->getRuleName() 获取路由标识,比解析 URL 更稳定;对 POST/PUT 请求,在 after() 钩子中写日志(确保操作已执行成功)注意异常场景:若控制器抛出异常,after() 不触发 —— 需在 AppException 全局异常处理器中补写失败日志,标记 status = "failed" 并附带 exception->getMessage()

数据库表设计要预留扩展性,别只存 text 字段

审计日志查得少、写得多,但一旦要查“张三昨天修改过哪些商品价格”,靠 content TEXT 模糊搜索会拖垮数据库。

实操建议:

立即学习“PHP免费学习笔记(深入)”;

基础字段必须有:user_id、action(索引)、ip、created_at、status("success"/"failed")、trace_id(关联同一请求链路)业务字段单独建 JSON 字段(如 payload JSON),MySQL 5.7+ 或 PostgreSQL 可直接用 -> 操作符查询,例如:WHERE payload->>’$.order_no’ = ‘ORD2024…’避免把日志当消息队列用:不要在审计表里加 is_handled、handler 等字段 —— 审计日志只负责“记”,后续分析走独立服务或定时任务

敏感操作必须二次确认 + 日志落库后不可删改

日志写到文件或数据库只是第一步。真正起审计作用的前提是:它不能被业务代码覆盖、删除,也不能被误操作清空。

实操建议:

立即学习“PHP免费学习笔记(深入)”;

数据库层加 BEFORE DELETE 触发器,或应用层重写 AuditLogModel::destroy() 抛出异常;线上环境禁止开放 audit_log 表的 DELETE 权限对高危动作(如删除管理员、清空库存),在控制器里增加 if ($this->request->post(‘confirm’) !== ‘I_CONFIRM_DELETE’) 校验,并把该 confirm 字段也记入日志文件存储方案需额外处理:启用 Linux 的 chattr +a(仅追加),或定期同步到只读 NAS;否则运维一个 rm -rf 就让所有审计失效

最常被忽略的一点:日志里的 user_id 必须来自认证后的 session 或 token 解析结果,绝不能取自前端传参。哪怕只是个 GET /user/delete?id=123,也要先查出该 ID 对应的真实操作人再记日志 —— 否则“越权操作”本身就成了盲区。

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。