84 lines
2.9 KiB
PHP
84 lines
2.9 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace app\common\service\qywx;
|
|
|
|
use InvalidArgumentException;
|
|
|
|
/** 获客方案成员的加权随机选择;数据库锁与持久化由调度服务负责。 */
|
|
class QywxPromotionWeightedRandom
|
|
{
|
|
/**
|
|
* @param list<array<string,mixed>> $members
|
|
* @param int|null $draw 测试用固定抽签值;生产环境留空并使用 random_int。
|
|
* @return array{selected_id:int,members:list<array<string,mixed>>,eligible_count:int,total_weight:int}
|
|
*/
|
|
public static function select(array $members, string $today, int $now, ?int $draw = null): array
|
|
{
|
|
$eligibleIndexes = [];
|
|
$totalWeight = 0;
|
|
foreach ($members as $index => &$member) {
|
|
if ((string) ($member['today_date'] ?? '') !== $today) {
|
|
$member['today_date'] = $today;
|
|
$member['today_count'] = 0;
|
|
}
|
|
// current_weight 是旧版平滑轮询游标;随机模式不再使用,统一归零。
|
|
$member['current_weight'] = 0;
|
|
if (!self::eligible($member, $now)) {
|
|
continue;
|
|
}
|
|
$weight = max(1, (int) ($member['weight'] ?? 1));
|
|
$eligibleIndexes[] = ['index' => $index, 'weight' => $weight];
|
|
$totalWeight += $weight;
|
|
}
|
|
unset($member);
|
|
|
|
if ($eligibleIndexes === []) {
|
|
return [
|
|
'selected_id' => 0,
|
|
'members' => array_values($members),
|
|
'eligible_count' => 0,
|
|
'total_weight' => 0,
|
|
];
|
|
}
|
|
|
|
$ticket = $draw ?? random_int(1, $totalWeight);
|
|
if ($ticket < 1 || $ticket > $totalWeight) {
|
|
throw new InvalidArgumentException('加权随机抽签值超出有效范围');
|
|
}
|
|
$cursor = 0;
|
|
$selectedIndex = (int) $eligibleIndexes[0]['index'];
|
|
foreach ($eligibleIndexes as $candidate) {
|
|
$cursor += (int) $candidate['weight'];
|
|
if ($ticket <= $cursor) {
|
|
$selectedIndex = (int) $candidate['index'];
|
|
break;
|
|
}
|
|
}
|
|
|
|
return [
|
|
'selected_id' => (int) ($members[$selectedIndex]['id'] ?? 0),
|
|
'members' => array_values($members),
|
|
'eligible_count' => count($eligibleIndexes),
|
|
'total_weight' => $totalWeight,
|
|
];
|
|
}
|
|
|
|
/** @param array<string,mixed> $member */
|
|
public static function eligible(array $member, int $now): bool
|
|
{
|
|
if ((int) ($member['enabled'] ?? 0) !== 1 || (int) ($member['weight'] ?? 0) <= 0) {
|
|
return false;
|
|
}
|
|
$start = max(0, (int) ($member['active_start'] ?? 0));
|
|
$end = max(0, (int) ($member['active_end'] ?? 0));
|
|
if (($start > 0 && $start > $now) || ($end > 0 && $end < $now)) {
|
|
return false;
|
|
}
|
|
$limit = max(0, (int) ($member['daily_limit'] ?? 0));
|
|
|
|
return $limit === 0 || (int) ($member['today_count'] ?? 0) < $limit;
|
|
}
|
|
}
|