Files
zyt/server/app/common/service/qywx/QywxPromotionWeightedRandom.php
2026-08-25 09:36:25 +08:00

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;
}
}