修复随机号码前先按权重随机工单,并修复降权号码筛选与清理

This commit is contained in:
root
2026-07-04 22:09:39 +08:00
parent 9b57b0c400
commit 6b72cd7b67
37 changed files with 2201 additions and 473 deletions
@@ -6,14 +6,17 @@ namespace app\common\service;
use app\admin\model\split\Link;
use app\admin\model\split\Number;
use app\admin\model\split\Ticket;
use think\Collection;
/**
* 分流链接落地页:查链、跨工单合并选号、拼接跳转 URL、访问计数
* 分流链接落地页:查链、选号、拼接跳转 URL、访问计数
*
* 号码池同一 split_link_id 下全部 status=normal 的号码;
* 优先选用 ratio_deferred=0 的号码,全部触线降权时回退至降权池仍可轮转
* random_shuffle 关闭时严格轮转,开启时每次访问随机选号。
* 号码池同一 split_link_id 下 status=normal,且所属工单未关闭(ticket.status≠hidden的号码;
* 优先选用 ratio_deferred=0,全部触线降权时回退至降权池。
*
* random_shuffle=0:跨工单合并池,全局严格轮转;
* random_shuffle=1:先按各工单开启号码数量比例随机选工单,再在该工单内随机选号。
*/
class SplitRedirectService
{
@@ -55,20 +58,17 @@ class SplitRedirectService
return null;
}
/** @var Collection<int, Number>|array<int, Number> $numbers */
$numbers = Number::where('split_link_id', (int) $link['id'])
->where('status', 'normal')
->order('id', 'asc')
->field('id,number,number_type,number_type_custom,ratio_deferred')
->select();
$list = $numbers instanceof Collection ? $numbers->all() : (array) $numbers;
$linkId = (int) $link->getAttr('id');
$list = $this->loadEligibleNumbers($linkId);
if ($list === []) {
return null;
}
$linkId = (int) $link->getAttr('id');
$picked = $this->pickNumberRow($linkId, $list);
if (SplitNumberWeighService::isRandomShuffleEnabled($linkId)) {
$picked = $this->pickNumberRowRandomByTicketWeight($linkId, $list);
} else {
$picked = $this->pickNumberRow($linkId, $list);
}
if ($picked === null) {
return null;
}
@@ -103,10 +103,117 @@ class SplitRedirectService
return $redirectUrl;
}
/**
* 可参与选号的号码:status=normal,且 ticket_name 不属于已关闭工单
*
* @return list<Number|array<string, mixed>>
*/
private function loadEligibleNumbers(int $linkId): array
{
if ($linkId <= 0) {
return [];
}
/** @var list<string> $hiddenTicketNames */
$hiddenTicketNames = Ticket::where('split_link_id', $linkId)
->where('status', 'hidden')
->column('ticket_name');
$hiddenTicketNames = array_values(array_unique(array_filter(array_map(
static function ($name): string {
return trim((string) $name);
},
$hiddenTicketNames
))));
$query = Number::where('split_link_id', $linkId)
->where('status', 'normal')
->order('id', 'asc')
->field('id,number,number_type,number_type_custom,ratio_deferred,ticket_name');
if ($hiddenTicketNames !== []) {
$query->where('ticket_name', 'not in', $hiddenTicketNames);
}
$numbers = $query->select();
return $numbers instanceof Collection ? $numbers->all() : (array) $numbers;
}
/**
* random_shuffle=1:先按工单开启号码数加权随机选工单,再在该工单内选号
*
* @param list<Number|array<string, mixed>> $rows
* @return array<string, mixed>|Number|null
*/
private function pickNumberRowRandomByTicketWeight(int $linkId, array $rows)
{
$groups = $this->groupNumbersByTicket($rows);
if ($groups === []) {
return null;
}
$ticketKey = $this->pickTicketGroupKeyWeighted($groups);
if ($ticketKey === null || !isset($groups[$ticketKey])) {
return null;
}
return $this->pickNumberRow($linkId, $groups[$ticketKey]);
}
/**
* @param list<Number|array<string, mixed>> $rows
* @return array<string, list<Number|array<string, mixed>>>
*/
private function groupNumbersByTicket(array $rows): array
{
/** @var array<string, list<Number|array<string, mixed>>> $groups */
$groups = [];
foreach ($rows as $row) {
$name = is_array($row)
? trim((string) ($row['ticket_name'] ?? ''))
: trim((string) $row->getAttr('ticket_name'));
$key = $name !== '' ? $name : '__manual__';
$groups[$key][] = $row;
}
return $groups;
}
/**
* 按各分组号码数量加权随机选一个工单(分组 key)
*
* @param array<string, list<Number|array<string, mixed>>> $groups
*/
private function pickTicketGroupKeyWeighted(array $groups): ?string
{
$total = 0;
foreach ($groups as $rows) {
$total += count($rows);
}
if ($total <= 0) {
return null;
}
$keys = array_keys($groups);
if (count($keys) === 1) {
return $keys[0];
}
$r = random_int(1, $total);
$acc = 0;
foreach ($groups as $key => $rows) {
$acc += count($rows);
if ($r <= $acc) {
return $key;
}
}
return $keys[0];
}
/**
* 双层选号:优先 ratio_deferred=0,否则回退至降权池
*
* @param array<int, array<string, mixed>|Number> $rows
* @param list<Number|array<string, mixed>> $rows
* @return array<string, mixed>|Number|null
*/
private function pickNumberRow(int $linkId, array $rows)