88 lines
2.9 KiB
PHP
88 lines
2.9 KiB
PHP
|
|
<?php
|
|||
|
|
|
|||
|
|
declare(strict_types=1);
|
|||
|
|
|
|||
|
|
namespace app\common\service;
|
|||
|
|
|
|||
|
|
use app\admin\model\split\Link;
|
|||
|
|
use app\admin\model\split\Number;
|
|||
|
|
use think\Collection;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 分流链接落地页:查链、轮转选号、拼接跳转 URL、访问计数
|
|||
|
|
*/
|
|||
|
|
class SplitRedirectService
|
|||
|
|
{
|
|||
|
|
private SplitRoundRobinStore $roundRobinStore;
|
|||
|
|
|
|||
|
|
public function __construct(?SplitRoundRobinStore $roundRobinStore = null)
|
|||
|
|
{
|
|||
|
|
$this->roundRobinStore = $roundRobinStore ?? new SplitRoundRobinStore();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 根据链接码解析本次应跳转的加好友 URL;无效时返回 null
|
|||
|
|
*
|
|||
|
|
* @param string $linkCode 9 位链接码
|
|||
|
|
* @param string $clientIp 访客 IP(IP 防护开启时用于国家校验)
|
|||
|
|
*/
|
|||
|
|
public function resolveRedirectUrl(string $linkCode, string $clientIp = ''): ?string
|
|||
|
|
{
|
|||
|
|
$linkCode = SplitLinkCodeService::normalize($linkCode);
|
|||
|
|
if (!SplitLinkCodeService::isValidFormat($linkCode)) {
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$link = Link::where('link_code', $linkCode)
|
|||
|
|
->where('status', 'normal')
|
|||
|
|
->find();
|
|||
|
|
if (!$link) {
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$ipProtect = (int) $link->getAttr('ip_protect');
|
|||
|
|
$countries = (string) $link->getAttr('countries');
|
|||
|
|
if (!SplitIpProtectService::isAllowed($ipProtect, $countries, $clientIp)) {
|
|||
|
|
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')
|
|||
|
|
->select();
|
|||
|
|
|
|||
|
|
$count = is_countable($numbers) ? count($numbers) : 0;
|
|||
|
|
if ($count === 0) {
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$linkId = (int) $link->getAttr('id');
|
|||
|
|
$index = $this->roundRobinStore->nextIndex($linkId, $count);
|
|||
|
|
$list = $numbers instanceof \think\Collection ? $numbers->all() : (array) $numbers;
|
|||
|
|
$picked = $list[$index] ?? $list[0] ?? null;
|
|||
|
|
if ($picked === null) {
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$numberType = is_array($picked) ? (string) ($picked['number_type'] ?? '') : (string) $picked->getAttr('number_type');
|
|||
|
|
$numberValue = is_array($picked) ? (string) ($picked['number'] ?? '') : (string) $picked->getAttr('number');
|
|||
|
|
$numberCustom = is_array($picked)
|
|||
|
|
? (string) ($picked['number_type_custom'] ?? '')
|
|||
|
|
: (string) $picked->getAttr('number_type_custom');
|
|||
|
|
|
|||
|
|
$redirectUrl = SplitFriendUrlBuilder::build($numberType, $numberValue, $numberCustom);
|
|||
|
|
if ($redirectUrl === '') {
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$numberId = is_array($picked) ? (int) ($picked['id'] ?? 0) : (int) $picked->getAttr('id');
|
|||
|
|
if ($numberId > 0) {
|
|||
|
|
Number::where('id', $numberId)->setInc('visit_count');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return $redirectUrl;
|
|||
|
|
}
|
|||
|
|
}
|