更新
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user