77 lines
2.3 KiB
PHP
77 lines
2.3 KiB
PHP
|
|
<?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];
|
||
|
|
}
|
||
|
|
}
|