67 lines
2.5 KiB
PHP
67 lines
2.5 KiB
PHP
|
|
<?php
|
||
|
|
|
||
|
|
namespace app\common\utils;
|
||
|
|
|
||
|
|
use support\Db;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 询盘公平分配排序器(合塑谷歌三个导入脚本共用)
|
||
|
|
*
|
||
|
|
* 按"最近窗口期内实际分到的条数"升序排用户:分得少的优先、最久未分到的优先、
|
||
|
|
* 再按传入名单顺序。统计池:crm_customer 中 channel_id=4(合塑谷歌)、type=2(导入)、
|
||
|
|
* 未删除、窗口期内新建的记录。
|
||
|
|
*
|
||
|
|
* 场景自适应:
|
||
|
|
* - 名单增删(离职移除/新人加入):每次执行按当次名单统计,无残留状态;
|
||
|
|
* - 暂停一段时间再恢复:暂停期间窗口计数自然衰减,回归后适度优先拉平,
|
||
|
|
* 不会饿死也不会被灌顶。
|
||
|
|
*/
|
||
|
|
class InquiryFairAssigner
|
||
|
|
{
|
||
|
|
/** 默认窗口期(天) */
|
||
|
|
public const DEFAULT_WINDOW_DAYS = 10;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 计算公平分配顺序
|
||
|
|
*
|
||
|
|
* @param array $userIds 本次参与分配的 user_id 列表
|
||
|
|
* @param int $windowDays 统计窗口(天)
|
||
|
|
* @return array{ordered: array, counts: array<int,int>, window_days: int}
|
||
|
|
* ordered=排好序的 user_id 列表;counts=窗口内每人已有条数
|
||
|
|
*/
|
||
|
|
public static function fairOrder(array $userIds, int $windowDays = self::DEFAULT_WINDOW_DAYS): array
|
||
|
|
{
|
||
|
|
$since = time() - $windowDays * 86400;
|
||
|
|
$counts = []; $lastIds = [];
|
||
|
|
foreach (Db::table('crm_customer')
|
||
|
|
->where('channel_id', 4)
|
||
|
|
->where('type', 2)
|
||
|
|
->whereNull('deleted_at')
|
||
|
|
->whereIn('user_id', $userIds)
|
||
|
|
->where('created_at', '>=', $since)
|
||
|
|
->groupBy('user_id')
|
||
|
|
->selectRaw('user_id, COUNT(*) AS cnt, MAX(id) AS last_id')
|
||
|
|
->get() as $r) {
|
||
|
|
$uid = (int)$r->user_id;
|
||
|
|
$counts[$uid] = (int)$r->cnt;
|
||
|
|
$lastIds[$uid] = (int)$r->last_id;
|
||
|
|
}
|
||
|
|
|
||
|
|
$pos = array_flip(array_map('intval', $userIds));
|
||
|
|
$ordered = array_map('intval', $userIds);
|
||
|
|
usort($ordered, function ($a, $b) use ($counts, $lastIds, $pos) {
|
||
|
|
$ca = $counts[$a] ?? 0; $cb = $counts[$b] ?? 0;
|
||
|
|
if ($ca !== $cb) {
|
||
|
|
return $ca <=> $cb; // 窗口内分到少的优先
|
||
|
|
}
|
||
|
|
$la = $lastIds[$a] ?? 0; $lb = $lastIds[$b] ?? 0;
|
||
|
|
if ($la !== $lb) {
|
||
|
|
return $la <=> $lb; // 最久未分到的优先
|
||
|
|
}
|
||
|
|
return $pos[$a] <=> $pos[$b]; // 名单顺序(稳定)
|
||
|
|
});
|
||
|
|
|
||
|
|
return ['ordered' => $ordered, 'counts' => $counts, 'window_days' => $windowDays];
|
||
|
|
}
|
||
|
|
}
|