真正能落地的后台操作日志必须在中间件里做,因其能稳定卡在路由解析完成、控制器执行前,确保action、controller、auth::id()等关键上下文可用,且避免控制器手动记录的漏操作、无上下文、登录态失效等问题。

真正能落地的后台操作日志,必须在中间件里做,而不是往控制器里塞 Log::write() —— 后者漏操作、没上下文、查不出谁干的。
为什么必须用中间件,而不是控制器或 AOP
ThinkPHP 原生不支持 Java 那种注解式 AOP,硬套 @Before 或自定义切面,要写一堆反射和代理逻辑,维护成本高、兼容性差。控制器里手动记日志更危险:异常分支没覆盖、参数没抓全、登录态可能已失效。中间件是唯一能稳定卡在「路由解析完成、控制器执行之前」的位置,确保 $request->action()、$request->controller()、Auth::id() 全部可用。
- 别信“在基类控制器
initialize()里统一写日志”——它晚于路由匹配,但早于权限检查,Auth::id()可能为 null - 中间件注册点选
app/middleware/AdminLogMiddleware.php,全局注册,不依赖任何路由分组 - 免日志接口(如登录、登出、验证码)必须用
in_array($request->action(), $ignoreList)过滤,不能靠 URL 匹配,否则/admin/login和/api/login容易漏判
怎么记录才算结构化、可审计
日志字段不是越多越好,关键字段缺一不可:用户 ID、IP、请求方法、完整 URL(含 query)、原始参数(JSON 编码)、操作结果状态。别用 $response->getCode() 判断成败——500 异常未被捕获时它也返回 200。真正在意的是业务是否成功,得结合异常捕获 + 响应体 code 字段。
-
input字段必须用json_encode($request->param(), JSON_UNESCAPED_UNICODE),但要显式过滤敏感键:unset($param['password'], $param['token']) - 状态字段别只写
status => 200,改成result_code => $e ? 'fail' : ($response->getData()['code'] ?? 'success') - 渠道必须显式指定:
Log::channel('admin')->info(...),否则默认进single渠道,配置再好也白搭 - 数据库驱动配置里,
log_table的created_at字段设为CURRENT_TIMESTAMP,禁用 ThinkPHP 自动时间戳填充,避免和主表时间戳混淆
容易被忽略的性能与安全坑
日志写入本身不能拖慢响应,更不能破坏事务一致性。常见错误是把日志插入和主业务塞进同一个事务,或者用 MyISAM 表存日志——它不支持事务,一出错就导致主操作回滚失败。
- 日志写入必须脱离当前事务:
Db::connect('log_db')->table('admin_log')->insert(...),单独建连接 - 敏感操作(如删除用户、修改权限)加
is_sensitive = 1字段,方便后续按需筛选,别等出事了再翻全量日志 - 别用
think-queue做日志异步——延迟入库会导致“操作成功但日志没生成”,审计断档;改用file_put_contents(..., FILE_APPEND | LOCK_EX)或 Redis List + 独立消费者 - 测试时务必模拟日志表不可写场景,确认主业务仍能正常提交,这是上线前必过的一关
最麻烦的不是记日志,而是让日志字段真正对得上人、对得上操作、对得上时间。中间件位置、渠道显式调用、状态判断逻辑、敏感字段过滤——这四点少一个,日志就只是摆设。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











