Files
links/application/common/service/SplitNumberRatioDeferredService.php
T

185 lines
5.2 KiB
PHP

<?php
declare(strict_types=1);
namespace app\common\service;
use app\admin\model\split\Number;
use think\Db;
/**
* 下号比率触线降权(ratio_deferred)规则与降权池清理
*
* 降权前提:已有落地页访问(visit_count > last_sync_visit_count),
* 且自上次同步以来进线未增长时累计 streak,触线才标记降权。
*/
class SplitNumberRatioDeferredService
{
/**
* 是否具备计入 streak 的落地页访问基数
*
* @param array<string, mixed>|Number $number
*/
public function hasLandingVisitBaseline($number): bool
{
$visitCount = $this->readInt($number, 'visit_count');
if ($visitCount <= 0) {
return false;
}
$lastVisit = $this->readInt($number, 'last_sync_visit_count');
return $visitCount > $lastVisit;
}
/**
* 根据访问与进线快照计算新的 streak
*
* @param array<string, mixed>|Number $number
*/
public function computeStreakForVisit($number, int $currentStreak): int
{
if (!$this->hasLandingVisitBaseline($number)) {
return $currentStreak;
}
$inboundCount = $this->readInt($number, 'inbound_count');
$lastInbound = $this->readInt($number, 'last_sync_inbound_count');
if ($inboundCount <= $lastInbound) {
return $currentStreak + 1;
}
return 0;
}
/**
* 同步侧:根据 visit / inbound 与上次同步检查点重算 streak
*
* @param array<string, mixed>|Number $number
*/
public function computeStreakForSync($number, int $currentStreak): int
{
$visitCount = $this->readInt($number, 'visit_count');
if ($visitCount <= 0) {
return 0;
}
$lastVisit = $this->readInt($number, 'last_sync_visit_count');
$inboundCount = $this->readInt($number, 'inbound_count');
$lastInbound = $this->readInt($number, 'last_sync_inbound_count');
if ($inboundCount > $lastInbound) {
return 0;
}
if ($visitCount > $lastVisit) {
return $currentStreak + ($visitCount - $lastVisit);
}
return $currentStreak;
}
/**
* 是否应标记 ratio_deferred(访问侧触线降权,不关 status)
*/
public function shouldMarkDeferred(int $streak, int $assignRatio, bool $hasLandingBaseline): bool
{
return $hasLandingBaseline && $assignRatio > 0 && $streak >= $assignRatio;
}
/**
* 是否为应清理的无效降权记录
*
* @param array<string, mixed>|Number $number
*/
public function isInvalidDeferredRecord($number): bool
{
if ($this->readInt($number, 'ratio_deferred') !== 1) {
return false;
}
return !$this->hasLandingVisitBaseline($number);
}
/**
* 清理无效降权标记(ratio_deferred=1 但无有效落地页访问基线)
*
* @param int[]|null $adminIds 数据权限管理员 ID 列表,null 表示不限制
* @return int 清理条数
*/
public function cleanupInvalidDeferred(?array $adminIds = null): int
{
$query = Number::where('ratio_deferred', 1);
if ($adminIds !== null && $adminIds !== []) {
$query->where('admin_id', 'in', $adminIds);
}
$rows = $query->field('id,visit_count,last_sync_visit_count,ratio_deferred,no_inbound_click_streak')->select();
if ($rows === null || $rows === []) {
return 0;
}
$now = time();
$count = 0;
Db::startTrans();
try {
foreach ($rows as $row) {
if (!$this->isInvalidDeferredRecord($row)) {
continue;
}
$update = [
'ratio_deferred' => 0,
'ratio_deferred_at' => null,
'updatetime' => $now,
];
if ($this->readInt($row, 'visit_count') <= 0) {
$update['no_inbound_click_streak'] = 0;
}
Number::where('id', (int) $row['id'])->update($update);
$count++;
}
Db::commit();
} catch (\Throwable $e) {
Db::rollback();
throw $e;
}
return $count;
}
/**
* 统计当前权限范围内无效降权条数(清理前预览)
*
* @param int[]|null $adminIds
*/
public function countInvalidDeferred(?array $adminIds = null): int
{
$query = Number::where('ratio_deferred', 1);
if ($adminIds !== null && $adminIds !== []) {
$query->where('admin_id', 'in', $adminIds);
}
$total = 0;
foreach ($query->field('id,visit_count,last_sync_visit_count,ratio_deferred')->select() as $row) {
if ($this->isInvalidDeferredRecord($row)) {
$total++;
}
}
return $total;
}
/**
* @param array<string, mixed>|Number $number
*/
private function readInt($number, string $field): int
{
if (is_array($number)) {
return (int) ($number[$field] ?? 0);
}
return (int) $number->getAttr($field);
}
}