185 lines
5.2 KiB
PHP
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);
|
|
}
|
|
}
|