需创建audit_log表并继承baseactiverecord实现查询审计:先执行迁移建表,再通过重写find()拦截select操作,自动记录用户、模型、字段、敏感性等信息到数据库。

你需要在Yii模型查询执行时自动记录操作日志,用于安全审计——比如谁在什么时候查了哪些敏感字段、是否绕过权限直接调用find()、有没有未授权的批量查询行为。
创建审计日志数据表
第一步:生成迁移文件
运行命令:php yii migrate/create create_audit_log_table
第二步:编辑生成的迁移文件(通常位于@console/migrations/或@common/migrations/),将up()方法替换为以下内容:
```php
public function up($this) {
$tableOptions = null;
if ($this->db->driverName === 'mysql') {
$tableOptions = 'CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE=InnoDB COMMENT="审计日志主表"';
}
$this->createTable('{{%audit_log}}', [
'id' => $this->primaryKey(),
'user_id' => $this->integer()->unsigned()->notNull()->comment('操作用户ID'),
'username' => $this->string(100)->notNull()->comment('用户名'),
'model_class' => $this->string(255)->notNull()->comment('查询模型类名,如 app\models\User'),
'query_type' => $this->string(20)->notNull()->defaultValue('select')->comment('查询类型:select/update/delete/insert'),
'fields' => $this->text()->comment('SELECT字段列表,JSON格式'),
'where_conditions' => $this->text()->comment('WHERE条件摘要,JSON格式'),
'is_sensitive' => $this->boolean()->defaultValue(false)->comment('是否含敏感字段(如password、id_card)'),
'ip' => $this->string(45)->comment('客户端IP'),
'user_agent' => $this->text()->comment('浏览器标识'),
'created_at' => $this->integer()->notNull(),
], $tableOptions);
$this->createIndex('idx_user_id_created', '{{%audit_log}}', ['user_id', 'created_at']);
$this->createIndex('idx_model_class', '{{%audit_log}}', 'model_class');
}
第三步:执行迁移php yii migrate
【必须确认数据库连接配置正确,否则迁移失败后不会自动回滚】
拦截ActiveRecord查询行为
Yii不提供原生的“查询前钩子”,但可通过重写ActiveRecord基类的find()静态方法实现统一拦截。所有业务模型需继承你自定义的基类。
新建common/components/BaseActiveRecord.php:
```php
namespace common\components;
use Yii;
use yii\db\ActiveRecord;
use yii\helpers\Json;
use yii\helpers\StringHelper;
class BaseActiveRecord extends ActiveRecord
{
public static function find()
{
$query = parent::find();
// 仅对前台Web请求记录审计日志
if (Yii::$app->has('request') && Yii::$app->request->isGet) {
$query->on(self::EVENT_BEFORE_SELECT, [self::class, 'logSelectQuery']);
}
return $query;
}
public static function logSelectQuery($event)
{
$modelClass = get_called_class();
$user = Yii::$app->user->isGuest ? null : Yii::$app->user->identity;
if (!$user) return;
// 提取SELECT字段(简化版,不解析复杂表达式)
$selectFields = [];
if (isset($event->sender->select) && is_array($event->sender->select)) {
$selectFields = $event->sender->select;
} elseif (is_string($event->sender->select)) {
$selectFields = [$event->sender->select];
}
// 检测敏感字段
$sensitiveFields = ['password', 'pwd', 'id_card', 'phone', 'email'];
$isSensitive = false;
foreach ($selectFields as $field) {
if (is_string($field)) {
$cleanField = StringHelper::basename($field, [' AS ', ' as ']);
if (in_array(strtolower($cleanField), $sensitiveFields)) {
$isSensitive = true;
break;
}
}
}
// 记录日志
Yii::$app->db->createCommand()->insert('{{%audit_log}}', [
'user_id' => $user->id,
'username' => $user->username,
'model_class' => $modelClass,
'query_type' => 'select',
'fields' => Json::encode($selectFields),
'where_conditions' => Json::encode($event->sender->where ?? []),
'is_sensitive' => $isSensitive,
'ip' => Yii::$app->request->userIP,
'user_agent' => Yii::$app->request->userAgent,
'created_at' => time(),
])->execute();
}
}
修改所有业务模型(如models/User.php)的继承关系:class User extends \common\components\BaseActiveRecord
配置日志目标并启用审计级别
方法一:复用Yii内置FileTarget(适合开发与测试环境)
在config/main.php的'components' => ['log' => [...]]中添加:
```php
['class' => 'yii\log\FileTarget',
'levels' => ['info'],
'categories' => ['audit'],
'logFile' => '@app/runtime/logs/audit.log',
'maxFileSize' => 10240,
'maxLogFiles' => 20,
]```
方法二:独立DbTarget(生产环境推荐)
新增一个DbTarget实例,专用于写入audit_log表:
```php
['class' => 'yii\log\DbTarget',
'levels' => ['info'],
'categories' => ['audit'],
'logTable' => '{{%audit_log_legacy}}', // 注意:此表需手动建,字段与前面迁移不同,仅用于日志组件直写
'prefix' => function ($message) {
return ''; // 不加前缀,避免污染结构化字段
},
]```
⚠️注意:DbTarget写入的是原始日志消息,不是结构化审计事件;上面的BaseActiveRecord::logSelectQuery()已直接写库,此处DbTarget可留空或用于记录异常场景。
最后,在模型查询处显式触发审计日志:Yii::info("User model queried with email field", 'audit');
这行代码会进入FileTarget或DbTarget,但【不替代数据库表记录,二者互补】。











