Merge branch 'develop'

# Conflicts:
#	app/controller/IndexController.php
This commit is contained in:
mzl
2025-09-26 15:52:45 +08:00
parent b897771541
commit 223d57483c
10 changed files with 450 additions and 5 deletions
+77
View File
@@ -0,0 +1,77 @@
<?php
namespace app\common\audit;
use Webman\Context;
class AuditBuffer
{
const CTX_KEY = '__audit_buffer__';
public array $changes = []; // 每项:['scene','object_id','field','old','new']
public ?string $action = null; // 业务动作(可覆盖)
public array $meta = []; // 业务自定义元信息(如需扩展入库,可在消费者处处理)
public array $maskFields = ['password','pwd','token','secret','access_token','refresh_token'];
public static function instance(): self
{
$buf = Context::get(self::CTX_KEY);
if (!$buf) {
$buf = new self();
Context::set(self::CTX_KEY, $buf);
}
return $buf;
}
public static function reset(): void
{
Context::set(self::CTX_KEY, new self());
}
public function setAction(?string $action): void
{
$this->action = $action;
}
public function setMeta(array $meta): void
{
$this->meta = $meta + $this->meta;
}
/** 模型字段级变更推送(由 Trait 调用) */
public function pushModelChange(string $scene, $objectId, array $old, array $new): void
{
$keys = array_unique(array_merge(array_keys($old), array_keys($new)));
foreach ($keys as $k) {
$ov = $old[$k] ?? null;
$nv = $new[$k] ?? null;
if ($ov === $nv) continue;
[$ovMasked, $nvMasked] = $this->maskIfNeeded($k, $ov, $nv);
$this->changes[] = [
'scene' => $scene,
'object_id' => $objectId,
'field' => $k,
'old' => $ovMasked,
'new' => $nvMasked,
];
}
}
private function maskIfNeeded(string $field, $old, $new): array
{
foreach ($this->maskFields as $m) {
if (stripos($field, $m) !== false) {
return [$old===null?null:'***', $new===null?null:'***'];
}
}
if (preg_match('/(mobile|phone|tel|idcard|id_card)/i', $field)) {
$mask = function ($v) {
if ($v === null) return null;
$s = (string)$v;
return mb_substr($s, 0, 3).'****'.mb_substr($s, -4);
};
return [$mask($old), $mask($new)];
}
return [$old, $new];
}
}
+1
View File
@@ -425,6 +425,7 @@ class AuthLogic
'exp' => $exp,
'typ' => 'access',
'uid' => $user->id,
'uname' => $user->name,
'role_ids' => $role_ids,
'system_ids' => $system_ids
];
+48
View File
@@ -0,0 +1,48 @@
<?php
namespace app\common\traits;
use app\common\audit\AuditBuffer;
use Illuminate\Database\Eloquent\Model;
trait Auditable
{
// 可在模型中设置:
// public $auditDisabled = true; // 关闭该模型的日志
// public $auditScene = 'alias'; // 覆盖默认场景(默认=表名)
public static function bootAuditable()
{
static::created(function (Model $m) {
if (self::isMuted($m)) return;
AuditBuffer::instance()->pushModelChange(self::scene($m), $m->getKey(), [], $m->getAttributes());
});
static::updated(function (Model $m) {
if (self::isMuted($m)) return;
$dirty = $m->getDirty();
if (!$dirty) return;
$old = []; $new = [];
foreach (array_keys($dirty) as $k) {
$old[$k] = $m->getOriginal($k);
$new[$k] = $m->getAttribute($k);
}
AuditBuffer::instance()->pushModelChange(self::scene($m), $m->getKey(), $old, $new);
});
static::deleted(function (Model $m) {
if (self::isMuted($m)) return;
AuditBuffer::instance()->pushModelChange(self::scene($m), $m->getKey(), $m->getAttributes(), []);
});
}
protected static function scene(Model $m): string
{
return property_exists($m, 'auditScene') && $m->auditScene ? $m->auditScene : $m->getTable();
}
protected static function isMuted(Model $m): bool
{
if (property_exists($m, 'auditDisabled') && $m->auditDisabled) return true;
return $m->getTable() === 'sys_op_log'; // 避免自记录
}
}