From 508caebdcd42dec7e7191b6830a5a7d4a85cd693 Mon Sep 17 00:00:00 2001 From: xboard Date: Fri, 18 Jul 2025 15:42:58 +0800 Subject: [PATCH] refactor: refactor subscription delivery logic, change payment return_url to origin_url concatenation - Unify protocol filter configuration to client.type.field (dot-path, three-segment) format, support strict whitelist mode - Refactor AbstractProtocol and all protocol classes for more flexible and maintainable subscription delivery - Change payment callback logic: use origin_url concatenation instead of return_url for more accurate redirects --- app/Helpers/Functions.php | 25 ++++-- app/Protocols/Clash.php | 20 +++-- app/Protocols/ClashMeta.php | 94 +++++++++++----------- app/Protocols/General.php | 53 +++++-------- app/Protocols/Loon.php | 24 +++--- app/Protocols/QuantumultX.php | 12 ++- app/Protocols/Shadowrocket.php | 39 ++++----- app/Protocols/Shadowsocks.php | 5 ++ app/Protocols/SingBox.php | 31 +++++--- app/Protocols/Stash.php | 28 +++++-- app/Protocols/Surfboard.php | 12 ++- app/Protocols/Surge.php | 23 +++--- app/Services/PaymentService.php | 19 +++-- app/Support/AbstractProtocol.php | 132 ++++++++++++++++++++++++++----- 14 files changed, 334 insertions(+), 183 deletions(-) diff --git a/app/Helpers/Functions.php b/app/Helpers/Functions.php index bc3af0b..d10b2de 100644 --- a/app/Helpers/Functions.php +++ b/app/Helpers/Functions.php @@ -2,7 +2,7 @@ use App\Support\Setting; use Illuminate\Support\Facades\App; -if (! function_exists('admin_setting')) { +if (!function_exists('admin_setting')) { /** * 获取或保存配置参数. * @@ -13,7 +13,7 @@ if (! function_exists('admin_setting')) { function admin_setting($key = null, $default = null) { $setting = app(Setting::class); - + if ($key === null) { return $setting->toArray(); } @@ -22,13 +22,13 @@ if (! function_exists('admin_setting')) { $setting->save($key); return ''; } - - $default = config('v2board.'. $key) ?? $default; + + $default = config('v2board.' . $key) ?? $default; return $setting->get($key) ?? $default; } } -if (! function_exists('admin_settings_batch')) { +if (!function_exists('admin_settings_batch')) { /** * 批量获取配置参数,性能优化版本 * @@ -40,3 +40,18 @@ if (! function_exists('admin_settings_batch')) { return app(Setting::class)->getBatch($keys); } } + +if (!function_exists('origin_url')) { + /** + * 根据 HTTP_ORIGIN 拼接完整 URL + * @param string $path + * @return string + */ + function origin_url(string $path = ''): string + { + $origin = request()->getSchemeAndHttpHost(); // 自动带端口 + $origin = rtrim($origin, '/'); + $path = ltrim($path, '/'); + return $origin . '/' . $path; + } +} diff --git a/app/Protocols/Clash.php b/app/Protocols/Clash.php index 6a246a0..d112926 100644 --- a/app/Protocols/Clash.php +++ b/app/Protocols/Clash.php @@ -2,6 +2,7 @@ namespace App\Protocols; +use App\Models\Server; use Illuminate\Support\Facades\File; use Symfony\Component\Yaml\Yaml; use App\Support\AbstractProtocol; @@ -12,6 +13,13 @@ class Clash extends AbstractProtocol const CUSTOM_TEMPLATE_FILE = 'resources/rules/custom.clash.yaml'; const DEFAULT_TEMPLATE_FILE = 'resources/rules/default.clash.yaml'; + public $allowedProtocols = [ + Server::TYPE_SHADOWSOCKS, + Server::TYPE_VMESS, + Server::TYPE_TROJAN, + Server::TYPE_SOCKS, + Server::TYPE_HTTP, + ]; public function handle() { $servers = $this->servers; @@ -30,7 +38,7 @@ class Clash extends AbstractProtocol foreach ($servers as $item) { if ( - $item['type'] === 'shadowsocks' + $item['type'] === Server::TYPE_SHADOWSOCKS && in_array(data_get($item['protocol_settings'], 'cipher'), [ 'aes-128-gcm', 'aes-192-gcm', @@ -41,19 +49,19 @@ class Clash extends AbstractProtocol array_push($proxy, self::buildShadowsocks($item['password'], $item)); array_push($proxies, $item['name']); } - if ($item['type'] === 'vmess') { + if ($item['type'] === Server::TYPE_VMESS) { array_push($proxy, self::buildVmess($item['password'], $item)); - array_push($proxies, $item['name']); + array_push($proxies, $item['name']); } - if ($item['type'] === 'trojan') { + if ($item['type'] === Server::TYPE_TROJAN) { array_push($proxy, self::buildTrojan($item['password'], $item)); array_push($proxies, $item['name']); } - if ($item['type'] === 'socks') { + if ($item['type'] === Server::TYPE_SOCKS) { array_push($proxy, self::buildSocks5($item['password'], $item)); array_push($proxies, $item['name']); } - if ($item['type'] === 'http') { + if ($item['type'] === Server::TYPE_HTTP) { array_push($proxy, self::buildHttp($item['password'], $item)); array_push($proxies, $item['name']); } diff --git a/app/Protocols/ClashMeta.php b/app/Protocols/ClashMeta.php index b7a53d6..2553660 100644 --- a/app/Protocols/ClashMeta.php +++ b/app/Protocols/ClashMeta.php @@ -2,6 +2,7 @@ namespace App\Protocols; +use App\Models\Server; use App\Utils\Helper; use Illuminate\Support\Facades\File; use Symfony\Component\Yaml\Yaml; @@ -13,49 +14,48 @@ class ClashMeta extends AbstractProtocol const CUSTOM_TEMPLATE_FILE = 'resources/rules/custom.clashmeta.yaml'; const CUSTOM_CLASH_TEMPLATE_FILE = 'resources/rules/custom.clash.yaml'; const DEFAULT_TEMPLATE_FILE = 'resources/rules/default.clash.yaml'; + public $allowedProtocols = [ + Server::TYPE_SHADOWSOCKS, + Server::TYPE_VMESS, + Server::TYPE_TROJAN, + Server::TYPE_VLESS, + Server::TYPE_HYSTERIA, + Server::TYPE_TUIC, + Server::TYPE_ANYTLS, + Server::TYPE_SOCKS, + Server::TYPE_HTTP, + Server::TYPE_MIERU, + ]; protected $protocolRequirements = [ - 'nekobox' => [ - 'hysteria' => [ - 'protocol_settings.version' => [ - '2' => '1.2.7' - ], + '*.vless.protocol_settings.network' => [ + 'whitelist' => [ + 'tcp' => '0.0.0', + 'ws' => '0.0.0', + 'grpc' => '0.0.0', + 'http' => '0.0.0', + 'h2' => '0.0.0', ], + 'strict' => true, ], - 'clashmetaforandroid' => [ - 'hysteria' => [ - 'protocol_settings.version' => [ - '2' => '2.9.0' - ], - ], + 'nekobox.hysteria.protocol_settings.version' => [ + 1 => '0.0.0', + 2 => '1.2.7', ], - 'nekoray' => [ - 'hysteria' => [ - 'protocol_settings.version' => [ - '2' => '3.24' - ], - ], + 'clashmetaforandroid.hysteria.protocol_settings.version' => [ + 2 => '2.9.0', ], - 'verge' => [ - 'hysteria' => [ - 'protocol_settings.version' => [ - '2' => '1.3.8' - ], - ], + 'nekoray.hysteria.protocol_settings.version' => [ + 2 => '3.24', ], - 'ClashX Meta' => [ - 'hysteria' => [ - 'protocol_settings.version' => [ - '2' => '1.3.5' - ], - ], + 'verge.hysteria.protocol_settings.version' => [ + 2 => '1.3.8', ], - 'flclash' => [ - 'hysteria' => [ - 'protocol_settings.version' => [ - '2' => '0.8.0' - ], - ], + 'ClashX Meta.hysteria.protocol_settings.version' => [ + 2 => '1.3.5', + ], + 'flclash.hysteria.protocol_settings.version' => [ + 2 => '0.8.0', ], ]; @@ -78,47 +78,43 @@ class ClashMeta extends AbstractProtocol $proxies = []; foreach ($servers as $item) { - $protocol_settings = $item['protocol_settings']; - if ($item['type'] === 'shadowsocks') { + if ($item['type'] === Server::TYPE_SHADOWSOCKS) { array_push($proxy, self::buildShadowsocks($item['password'], $item)); array_push($proxies, $item['name']); } - if ($item['type'] === 'vmess') { + if ($item['type'] === Server::TYPE_VMESS) { array_push($proxy, self::buildVmess($item['password'], $item)); array_push($proxies, $item['name']); } - if ($item['type'] === 'trojan') { + if ($item['type'] === Server::TYPE_TROJAN) { array_push($proxy, self::buildTrojan($item['password'], $item)); array_push($proxies, $item['name']); } - if ( - $item['type'] === 'vless' - && in_array(data_get($protocol_settings, 'network'), ['tcp', 'ws', 'grpc', 'http', 'h2']) - ) { + if ($item['type'] === Server::TYPE_VLESS) { array_push($proxy, self::buildVless($item['password'], $item)); array_push($proxies, $item['name']); } - if ($item['type'] === 'hysteria') { + if ($item['type'] === Server::TYPE_HYSTERIA) { array_push($proxy, self::buildHysteria($item['password'], $item, $user)); array_push($proxies, $item['name']); } - if ($item['type'] === 'tuic') { + if ($item['type'] === Server::TYPE_TUIC) { array_push($proxy, self::buildTuic($item['password'], $item)); array_push($proxies, $item['name']); } - if ($item['type'] === 'anytls') { + if ($item['type'] === Server::TYPE_ANYTLS) { array_push($proxy, self::buildAnyTLS($item['password'], $item)); array_push($proxies, $item['name']); } - if ($item['type'] === 'socks') { + if ($item['type'] === Server::TYPE_SOCKS) { array_push($proxy, self::buildSocks5($item['password'], $item)); array_push($proxies, $item['name']); } - if ($item['type'] === 'http') { + if ($item['type'] === Server::TYPE_HTTP) { array_push($proxy, self::buildHttp($item['password'], $item)); array_push($proxies, $item['name']); } - if ($item['type'] === 'mieru') { + if ($item['type'] === Server::TYPE_MIERU) { array_push($proxy, self::buildMieru($item['password'], $item)); array_push($proxies, $item['name']); } diff --git a/app/Protocols/General.php b/app/Protocols/General.php index a409105..7b23fd1 100644 --- a/app/Protocols/General.php +++ b/app/Protocols/General.php @@ -2,6 +2,7 @@ namespace App\Protocols; +use App\Models\Server; use App\Utils\Helper; use Illuminate\Support\Arr; use App\Support\AbstractProtocol; @@ -10,21 +11,18 @@ class General extends AbstractProtocol { public $flags = ['general', 'v2rayn', 'v2rayng', 'passwall', 'ssrplus', 'sagernet']; + public $allowedProtocols = [ + Server::TYPE_VMESS, + Server::TYPE_VLESS, + Server::TYPE_SHADOWSOCKS, + Server::TYPE_TROJAN, + Server::TYPE_HYSTERIA, + Server::TYPE_SOCKS, + ]; + protected $protocolRequirements = [ - 'v2rayng' => [ - 'hysteria' => [ - 'protocol_settings.version' => [ - '2' => '1.9.5' - ], - ], - ], - 'v2rayN' => [ - 'hysteria' => [ - 'protocol_settings.version' => [ - '2' => '6.31' - ], - ], - ], + 'v2rayng.hysteria.protocol_settings.version' => [2 => '1.9.5'], + 'v2rayn.hysteria.protocol_settings.version' => [2 => '6.31'], ]; public function handle() @@ -34,24 +32,15 @@ class General extends AbstractProtocol $uri = ''; foreach ($servers as $item) { - if ($item['type'] === 'vmess') { - $uri .= self::buildVmess($item['password'], $item); - } - if ($item['type'] === 'vless') { - $uri .= self::buildVless($item['password'], $item); - } - if ($item['type'] === 'shadowsocks') { - $uri .= self::buildShadowsocks($item['password'], $item); - } - if ($item['type'] === 'trojan') { - $uri .= self::buildTrojan($item['password'], $item); - } - if ($item['type'] === 'hysteria') { - $uri .= self::buildHysteria($item['password'], $item); - } - if ($item['type'] === 'socks') { - $uri .= self::buildSocks($item['password'], $item); - } + $uri .= match ($item['type']) { + Server::TYPE_VMESS => self::buildVmess($item['password'], $item), + Server::TYPE_VLESS => self::buildVless($item['password'], $item), + Server::TYPE_SHADOWSOCKS => self::buildShadowsocks($item['password'], $item), + Server::TYPE_TROJAN => self::buildTrojan($item['password'], $item), + Server::TYPE_HYSTERIA => self::buildHysteria($item['password'], $item), + Server::TYPE_SOCKS => self::buildSocks($item['password'], $item), + default => '', + }; } return response(base64_encode($uri))->header('content-type', 'text/plain'); } diff --git a/app/Protocols/Loon.php b/app/Protocols/Loon.php index e54384e..b38823d 100644 --- a/app/Protocols/Loon.php +++ b/app/Protocols/Loon.php @@ -3,19 +3,21 @@ namespace App\Protocols; use App\Support\AbstractProtocol; +use App\Models\Server; class Loon extends AbstractProtocol { public $flags = ['loon']; + public $allowedProtocols = [ + Server::TYPE_SHADOWSOCKS, + Server::TYPE_VMESS, + Server::TYPE_TROJAN, + Server::TYPE_HYSTERIA, + ]; + protected $protocolRequirements = [ - 'loon' => [ - 'hysteria' => [ - 'protocol_settings.version' => [ - '2' => '637' - ], - ], - ], + 'loon.hysteria.protocol_settings.version' => [2 => '637'], ]; public function handle() @@ -27,17 +29,17 @@ class Loon extends AbstractProtocol foreach ($servers as $item) { if ( - $item['type'] === 'shadowsocks' + $item['type'] === Server::TYPE_SHADOWSOCKS ) { $uri .= self::buildShadowsocks($item['password'], $item); } - if ($item['type'] === 'vmess') { + if ($item['type'] === Server::TYPE_VMESS) { $uri .= self::buildVmess($item['password'], $item); } - if ($item['type'] === 'trojan') { + if ($item['type'] === Server::TYPE_TROJAN) { $uri .= self::buildTrojan($item['password'], $item); } - if ($item['type'] === 'hysteria') { + if ($item['type'] === Server::TYPE_HYSTERIA) { $uri .= self::buildHysteria($item['password'], $item, $user); } } diff --git a/app/Protocols/QuantumultX.php b/app/Protocols/QuantumultX.php index 30d69c9..1e27168 100644 --- a/app/Protocols/QuantumultX.php +++ b/app/Protocols/QuantumultX.php @@ -3,10 +3,16 @@ namespace App\Protocols; use App\Support\AbstractProtocol; +use App\Models\Server; class QuantumultX extends AbstractProtocol { public $flags = ['quantumult%20x', 'quantumult-x']; + public $allowedProtocols = [ + Server::TYPE_SHADOWSOCKS, + Server::TYPE_VMESS, + Server::TYPE_TROJAN, + ]; public function handle() { @@ -14,13 +20,13 @@ class QuantumultX extends AbstractProtocol $user = $this->user; $uri = ''; foreach ($servers as $item) { - if ($item['type'] === 'shadowsocks') { + if ($item['type'] === Server::TYPE_SHADOWSOCKS) { $uri .= self::buildShadowsocks($item['password'], $item); } - if ($item['type'] === 'vmess') { + if ($item['type'] === Server::TYPE_VMESS) { $uri .= self::buildVmess($item['password'], $item); } - if ($item['type'] === 'trojan') { + if ($item['type'] === Server::TYPE_TROJAN) { $uri .= self::buildTrojan($item['password'], $item); } } diff --git a/app/Protocols/Shadowrocket.php b/app/Protocols/Shadowrocket.php index 4b99441..6c4ec0b 100644 --- a/app/Protocols/Shadowrocket.php +++ b/app/Protocols/Shadowrocket.php @@ -4,22 +4,25 @@ namespace App\Protocols; use App\Utils\Helper; use App\Support\AbstractProtocol; +use App\Models\Server; class Shadowrocket extends AbstractProtocol { public $flags = ['shadowrocket']; + public $allowedProtocols = [ + Server::TYPE_SHADOWSOCKS, + Server::TYPE_VMESS, + Server::TYPE_VLESS, + Server::TYPE_TROJAN, + Server::TYPE_HYSTERIA, + Server::TYPE_TUIC, + Server::TYPE_ANYTLS, + Server::TYPE_SOCKS, + ]; protected $protocolRequirements = [ - 'shadowrocket' => [ - 'hysteria' => [ - 'protocol_settings.version' => [ - '2' => '1993' - ], - ], - 'anytls' => [ - 'base_version' => '2592' - ], - ], + 'shadowrocket.hysteria.protocol_settings.version' => [2 => '1993'], + 'shadowrocket.anytls.base_version' => '2592', ]; public function handle() @@ -35,28 +38,28 @@ class Shadowrocket extends AbstractProtocol $expiredDate = date('Y-m-d', $user['expired_at']); $uri .= "STATUS=🚀↑:{$upload}GB,↓:{$download}GB,TOT:{$totalTraffic}GB💡Expires:{$expiredDate}\r\n"; foreach ($servers as $item) { - if ($item['type'] === 'shadowsocks') { + if ($item['type'] === Server::TYPE_SHADOWSOCKS) { $uri .= self::buildShadowsocks($item['password'], $item); } - if ($item['type'] === 'vmess') { + if ($item['type'] === Server::TYPE_VMESS) { $uri .= self::buildVmess($item['password'], $item); } - if ($item['type'] === 'vless') { + if ($item['type'] === Server::TYPE_VLESS) { $uri .= self::buildVless($item['password'], $item); } - if ($item['type'] === 'trojan') { + if ($item['type'] === Server::TYPE_TROJAN) { $uri .= self::buildTrojan($item['password'], $item); } - if ($item['type'] === 'hysteria') { + if ($item['type'] === Server::TYPE_HYSTERIA) { $uri .= self::buildHysteria($item['password'], $item); } - if ($item['type'] === 'tuic') { + if ($item['type'] === Server::TYPE_TUIC) { $uri .= self::buildTuic($item['password'], $item); } - if ($item['type'] === 'anytls') { + if ($item['type'] === Server::TYPE_ANYTLS) { $uri .= self::buildAnyTLS($item['password'], $item); } - if ($item['type'] === 'socks') { + if ($item['type'] === Server::TYPE_SOCKS) { $uri .= self::buildSocks($item['password'], $item); } } diff --git a/app/Protocols/Shadowsocks.php b/app/Protocols/Shadowsocks.php index c0a6da6..36f2d2a 100644 --- a/app/Protocols/Shadowsocks.php +++ b/app/Protocols/Shadowsocks.php @@ -3,11 +3,16 @@ namespace App\Protocols; use App\Support\AbstractProtocol; +use App\Models\Server; class Shadowsocks extends AbstractProtocol { public $flags = ['shadowsocks']; + public $allowedProtocols = [ + Server::TYPE_SHADOWSOCKS, + ]; + public function handle() { $servers = $this->servers; diff --git a/app/Protocols/SingBox.php b/app/Protocols/SingBox.php index ae0a4e9..97cd66a 100644 --- a/app/Protocols/SingBox.php +++ b/app/Protocols/SingBox.php @@ -4,12 +4,23 @@ namespace App\Protocols; use App\Utils\Helper; use Illuminate\Support\Arr; use Illuminate\Support\Facades\File; -use Illuminate\Support\Facades\Log; use App\Support\AbstractProtocol; +use App\Models\Server; class SingBox extends AbstractProtocol { public $flags = ['sing-box', 'hiddify', 'sfm']; + public $allowedProtocols = [ + Server::TYPE_SHADOWSOCKS, + Server::TYPE_TROJAN, + Server::TYPE_VMESS, + Server::TYPE_VLESS, + Server::TYPE_HYSTERIA, + Server::TYPE_TUIC, + Server::TYPE_ANYTLS, + Server::TYPE_SOCKS, + Server::TYPE_HTTP, + ]; private $config; const CUSTOM_TEMPLATE_FILE = 'resources/rules/custom.sing-box.json'; const DEFAULT_TEMPLATE_FILE = 'resources/rules/default.sing-box.json'; @@ -85,42 +96,42 @@ class SingBox extends AbstractProtocol $proxies = []; foreach ($this->servers as $item) { $protocol_settings = $item['protocol_settings']; - if ($item['type'] === 'shadowsocks') { + if ($item['type'] === Server::TYPE_SHADOWSOCKS) { $ssConfig = $this->buildShadowsocks($item['password'], $item); $proxies[] = $ssConfig; } - if ($item['type'] === 'trojan') { + if ($item['type'] === Server::TYPE_TROJAN) { $trojanConfig = $this->buildTrojan($this->user['uuid'], $item); $proxies[] = $trojanConfig; } - if ($item['type'] === 'vmess') { + if ($item['type'] === Server::TYPE_VMESS) { $vmessConfig = $this->buildVmess($this->user['uuid'], $item); $proxies[] = $vmessConfig; } if ( - $item['type'] === 'vless' + $item['type'] === Server::TYPE_VLESS && in_array(data_get($protocol_settings, 'network'), ['tcp', 'ws', 'grpc', 'http', 'quic', 'httpupgrade']) ) { $vlessConfig = $this->buildVless($this->user['uuid'], $item); $proxies[] = $vlessConfig; } - if ($item['type'] === 'hysteria') { + if ($item['type'] === Server::TYPE_HYSTERIA) { $hysteriaConfig = $this->buildHysteria($this->user['uuid'], $item); $proxies[] = $hysteriaConfig; } - if ($item['type'] === 'tuic') { + if ($item['type'] === Server::TYPE_TUIC) { $tuicConfig = $this->buildTuic($this->user['uuid'], $item); $proxies[] = $tuicConfig; } - if ($item['type'] === 'anytls') { + if ($item['type'] === Server::TYPE_ANYTLS) { $anytlsConfig = $this->buildAnyTLS($this->user['uuid'], $item); $proxies[] = $anytlsConfig; } - if ($item['type'] === 'socks') { + if ($item['type'] === Server::TYPE_SOCKS) { $socksConfig = $this->buildSocks($this->user['uuid'], $item); $proxies[] = $socksConfig; } - if ($item['type'] === 'http') { + if ($item['type'] === Server::TYPE_HTTP) { $httpConfig = $this->buildHttp($this->user['uuid'], $item); $proxies[] = $httpConfig; } diff --git a/app/Protocols/Stash.php b/app/Protocols/Stash.php index d9f9ae5..c27ea26 100644 --- a/app/Protocols/Stash.php +++ b/app/Protocols/Stash.php @@ -6,10 +6,22 @@ use Symfony\Component\Yaml\Yaml; use App\Utils\Helper; use Illuminate\Support\Facades\File; use App\Support\AbstractProtocol; +use App\Models\Server; class Stash extends AbstractProtocol { public $flags = ['stash']; + public $allowedProtocols = [ + Server::TYPE_SHADOWSOCKS, + Server::TYPE_VMESS, + Server::TYPE_VLESS, + Server::TYPE_HYSTERIA, + Server::TYPE_TROJAN, + Server::TYPE_TUIC, + // Server::TYPE_ANYTLS, + Server::TYPE_SOCKS, + Server::TYPE_HTTP, + ]; protected $protocolRequirements = [ 'stash' => [ 'anytls' => [ @@ -80,27 +92,27 @@ class Stash extends AbstractProtocol $proxies = []; foreach ($servers as $item) { - if ($item['type'] === 'shadowsocks') { + if ($item['type'] === Server::TYPE_SHADOWSOCKS) { array_push($proxy, self::buildShadowsocks($item['password'], $item)); array_push($proxies, $item['name']); } - if ($item['type'] === 'vmess') { + if ($item['type'] === Server::TYPE_VMESS) { array_push($proxy, self::buildVmess($item['password'], $item)); array_push($proxies, $item['name']); } - if ($item['type'] === 'vless') { + if ($item['type'] === Server::TYPE_VLESS) { array_push($proxy, $this->buildVless($item['password'], $item)); array_push($proxies, $item['name']); } - if ($item['type'] === 'hysteria') { + if ($item['type'] === Server::TYPE_HYSTERIA) { array_push($proxy, self::buildHysteria($item['password'], $item)); array_push($proxies, $item['name']); } - if ($item['type'] === 'trojan') { + if ($item['type'] === Server::TYPE_TROJAN) { array_push($proxy, self::buildTrojan($item['password'], $item)); array_push($proxies, $item['name']); } - if ($item['type'] === 'tuic') { + if ($item['type'] === Server::TYPE_TUIC) { array_push($proxy, self::buildTuic($item['password'], $item)); array_push($proxies, $item['name']); } @@ -108,11 +120,11 @@ class Stash extends AbstractProtocol // array_push($proxy, self::buildAnyTLS($item['password'], $item)); // array_push($proxies, $item['name']); // } - if ($item['type'] === 'socks') { + if ($item['type'] === Server::TYPE_SOCKS) { array_push($proxy, self::buildSocks5($item['password'], $item)); array_push($proxies, $item['name']); } - if ($item['type'] === 'http') { + if ($item['type'] === Server::TYPE_HTTP) { array_push($proxy, self::buildHttp($item['password'], $item)); array_push($proxies, $item['name']); } diff --git a/app/Protocols/Surfboard.php b/app/Protocols/Surfboard.php index edf0d3a..9e6716f 100644 --- a/app/Protocols/Surfboard.php +++ b/app/Protocols/Surfboard.php @@ -5,10 +5,16 @@ namespace App\Protocols; use App\Utils\Helper; use Illuminate\Support\Facades\File; use App\Support\AbstractProtocol; +use App\Models\Server; class Surfboard extends AbstractProtocol { public $flags = ['surfboard']; + public $allowedProtocols = [ + Server::TYPE_SHADOWSOCKS, + Server::TYPE_VMESS, + Server::TYPE_TROJAN, + ]; const CUSTOM_TEMPLATE_FILE = 'resources/rules/custom.surfboard.conf'; const DEFAULT_TEMPLATE_FILE = 'resources/rules/default.surfboard.conf'; @@ -25,7 +31,7 @@ class Surfboard extends AbstractProtocol foreach ($servers as $item) { if ( - $item['type'] === 'shadowsocks' + $item['type'] === Server::TYPE_SHADOWSOCKS && in_array(data_get($item, 'protocol_settings.cipher'), [ 'aes-128-gcm', 'aes-192-gcm', @@ -38,13 +44,13 @@ class Surfboard extends AbstractProtocol // [Proxy Group] $proxyGroup .= $item['name'] . ', '; } - if ($item['type'] === 'vmess') { + if ($item['type'] === Server::TYPE_VMESS) { // [Proxy] $proxies .= self::buildVmess($item['password'], $item); // [Proxy Group] $proxyGroup .= $item['name'] . ', '; } - if ($item['type'] === 'trojan') { + if ($item['type'] === Server::TYPE_TROJAN) { // [Proxy] $proxies .= self::buildTrojan($item['password'], $item); // [Proxy Group] diff --git a/app/Protocols/Surge.php b/app/Protocols/Surge.php index 7cdd42d..646a57e 100644 --- a/app/Protocols/Surge.php +++ b/app/Protocols/Surge.php @@ -5,6 +5,7 @@ namespace App\Protocols; use App\Utils\Helper; use Illuminate\Support\Facades\File; use App\Support\AbstractProtocol; +use App\Models\Server; class Surge extends AbstractProtocol { @@ -12,14 +13,14 @@ class Surge extends AbstractProtocol const CUSTOM_TEMPLATE_FILE = 'resources/rules/custom.surge.conf'; const DEFAULT_TEMPLATE_FILE = 'resources/rules/default.surge.conf'; + public $allowedProtocols = [ + Server::TYPE_SHADOWSOCKS, + Server::TYPE_VMESS, + Server::TYPE_TROJAN, + Server::TYPE_HYSTERIA, + ]; protected $protocolRequirements = [ - 'surge' => [ - 'hysteria' => [ - 'protocol_settings.version' => [ - '2' => '2398' - ], - ], - ], + 'surge.hysteria.protocol_settings.version' => [2 => '2398'], ]; public function handle() @@ -34,7 +35,7 @@ class Surge extends AbstractProtocol foreach ($servers as $item) { if ( - $item['type'] === 'shadowsocks' + $item['type'] === Server::TYPE_SHADOWSOCKS && in_array(data_get($item, 'protocol_settings.cipher'), [ 'aes-128-gcm', 'aes-192-gcm', @@ -45,15 +46,15 @@ class Surge extends AbstractProtocol $proxies .= self::buildShadowsocks($item['password'], $item); $proxyGroup .= $item['name'] . ', '; } - if ($item['type'] === 'vmess') { + if ($item['type'] === Server::TYPE_VMESS) { $proxies .= self::buildVmess($item['password'], $item); $proxyGroup .= $item['name'] . ', '; } - if ($item['type'] === 'trojan') { + if ($item['type'] === Server::TYPE_TROJAN) { $proxies .= self::buildTrojan($item['password'], $item); $proxyGroup .= $item['name'] . ', '; } - if ($item['type'] === 'hysteria') { + if ($item['type'] === Server::TYPE_HYSTERIA) { $proxies .= self::buildHysteria($item['password'], $item); $proxyGroup .= $item['name'] . ', '; } diff --git a/app/Services/PaymentService.php b/app/Services/PaymentService.php index d1d7572..56c3538 100644 --- a/app/Services/PaymentService.php +++ b/app/Services/PaymentService.php @@ -17,9 +17,12 @@ class PaymentService { $this->method = $method; $this->class = '\\App\\Payments\\' . $this->method; - if (!class_exists($this->class)) throw new ApiException('gate is not found'); - if ($id) $payment = Payment::find($id)->toArray(); - if ($uuid) $payment = Payment::where('uuid', $uuid)->first()->toArray(); + if (!class_exists($this->class)) + throw new ApiException('gate is not found'); + if ($id) + $payment = Payment::find($id)->toArray(); + if ($uuid) + $payment = Payment::where('uuid', $uuid)->first()->toArray(); $this->config = []; if (isset($payment)) { $this->config = $payment['config']; @@ -27,13 +30,15 @@ class PaymentService $this->config['id'] = $payment['id']; $this->config['uuid'] = $payment['uuid']; $this->config['notify_domain'] = $payment['notify_domain']; - }; + } + ; $this->payment = new $this->class($this->config); } public function notify($params) { - if (!$this->config['enable']) throw new ApiException('gate is not enable'); + if (!$this->config['enable']) + throw new ApiException('gate is not enable'); return $this->payment->notify($params); } @@ -45,10 +50,10 @@ class PaymentService $parseUrl = parse_url($notifyUrl); $notifyUrl = $this->config['notify_domain'] . $parseUrl['path']; } - + return $this->payment->pay([ 'notify_url' => $notifyUrl, - 'return_url' => url('/#/order/' . $order['trade_no']), + 'return_url' => origin_url('/#/order/' . $order['trade_no']), 'trade_no' => $order['trade_no'], 'total_amount' => $order['total_amount'], 'user_id' => $order['user_id'], diff --git a/app/Support/AbstractProtocol.php b/app/Support/AbstractProtocol.php index 558e6a5..03d6c40 100644 --- a/app/Support/AbstractProtocol.php +++ b/app/Support/AbstractProtocol.php @@ -36,6 +36,11 @@ abstract class AbstractProtocol */ protected $protocolRequirements = []; + /** + * @var array 允许的协议类型(白名单) 为空则不进行过滤 + */ + protected $allowedProtocols = []; + /** * 构造函数 * @@ -50,6 +55,7 @@ abstract class AbstractProtocol $this->servers = $servers; $this->clientName = $clientName; $this->clientVersion = $clientVersion; + $this->protocolRequirements = $this->normalizeProtocolRequirements($this->protocolRequirements); $this->servers = HookManager::filter('protocol.servers.filtered', $this->filterServersByVersion()); } @@ -77,19 +83,22 @@ abstract class AbstractProtocol */ protected function filterServersByVersion() { - // 如果没有客户端信息,直接返回所有服务器 - if (empty($this->clientName) || empty($this->clientVersion)) { + $this->filterByAllowedProtocols(); + $hasGlobalConfig = isset($this->protocolRequirements['*']); + $hasClientConfig = isset($this->protocolRequirements[$this->clientName]); + + if ((blank($this->clientName) || blank($this->clientVersion)) && !$hasGlobalConfig) { return $this->servers; } - // 检查当前客户端是否有特殊配置 - if (!isset($this->protocolRequirements[$this->clientName])) { + if (!$hasGlobalConfig && !$hasClientConfig) { return $this->servers; } - return collect($this->servers)->filter(function ($server) { - return $this->isCompatible($server); - })->values()->all(); + return collect($this->servers) + ->filter(fn($server) => $this->isCompatible($server)) + ->values() + ->all(); } /** @@ -101,30 +110,73 @@ abstract class AbstractProtocol protected function isCompatible($server) { $serverType = $server['type'] ?? null; - // 如果该协议没有特定要求,则认为兼容 + if (isset($this->protocolRequirements['*'][$serverType])) { + $globalRequirements = $this->protocolRequirements['*'][$serverType]; + if (!$this->checkRequirements($globalRequirements, $server)) { + return false; + } + } + if (!isset($this->protocolRequirements[$this->clientName][$serverType])) { return true; } $requirements = $this->protocolRequirements[$this->clientName][$serverType]; + return $this->checkRequirements($requirements, $server); + } - if (isset($requirements['base_version']) && version_compare($this->clientVersion, $requirements['base_version'], '<')) { - return false; - } + /** + * 检查版本要求 + * + * @param array $requirements 要求配置 + * @param array $server 服务器信息 + * @return bool + */ + private function checkRequirements(array $requirements, array $server): bool + { + foreach ($requirements as $field => $filterRule) { + if (in_array($field, ['base_version', 'incompatible'])) { + continue; + } - // 检查每个路径的版本要求 - foreach ($requirements as $path => $valueRequirements) { - $actualValue = data_get($server, $path); + $actualValue = data_get($server, $field); + + if (is_array($filterRule) && isset($filterRule['whitelist'])) { + $allowedValues = $filterRule['whitelist']; + $strict = $filterRule['strict'] ?? false; + if ($strict) { + if ($actualValue === null) { + return false; + } + if (!is_string($actualValue) && !is_int($actualValue)) { + return false; + } + if (!isset($allowedValues[$actualValue])) { + return false; + } + $requiredVersion = $allowedValues[$actualValue]; + if ($requiredVersion !== '0.0.0' && version_compare($this->clientVersion, $requiredVersion, '<')) { + return false; + } + continue; + } + } else { + $allowedValues = $filterRule; + $strict = false; + } if ($actualValue === null) { continue; } - - if (isset($valueRequirements[$actualValue])) { - $requiredVersion = $valueRequirements[$actualValue]; - if (version_compare($this->clientVersion, $requiredVersion, '<')) { - return false; - } + if (!is_string($actualValue) && !is_int($actualValue)) { + continue; + } + if (!isset($allowedValues[$actualValue])) { + continue; + } + $requiredVersion = $allowedValues[$actualValue]; + if ($requiredVersion !== '0.0.0' && version_compare($this->clientVersion, $requiredVersion, '<')) { + return false; } } @@ -160,4 +212,44 @@ abstract class AbstractProtocol return true; } + + /** + * 根据白名单过滤服务器 + * + * @return void + */ + protected function filterByAllowedProtocols(): void + { + if (!empty($this->allowedProtocols)) { + $this->servers = collect($this->servers) + ->filter(fn($server) => in_array($server['type'], $this->allowedProtocols)) + ->values() + ->all(); + } + } + + /** + * 将平铺的协议需求转换为树形结构 + * + * @param array $flat 平铺的协议需求 + * @return array 树形结构的协议需求 + */ + protected function normalizeProtocolRequirements(array $flat): array + { + $result = []; + foreach ($flat as $key => $value) { + if (!str_contains($key, '.')) { + $result[$key] = $value; + continue; + } + $segments = explode('.', $key, 3); + if (count($segments) < 3) { + $result[$segments[0]][$segments[1] ?? '*'][''] = $value; + continue; + } + [$client, $type, $field] = $segments; + $result[$client][$type][$field] = $value; + } + return $result; + } } \ No newline at end of file