Merge branch 'develop'
# Conflicts: # app/controller/IndexController.php
This commit is contained in:
@@ -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];
|
||||
}
|
||||
}
|
||||
@@ -425,6 +425,7 @@ class AuthLogic
|
||||
'exp' => $exp,
|
||||
'typ' => 'access',
|
||||
'uid' => $user->id,
|
||||
'uname' => $user->name,
|
||||
'role_ids' => $role_ids,
|
||||
'system_ids' => $system_ids
|
||||
];
|
||||
|
||||
@@ -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'; // 避免自记录
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user