From 6efedcebd4d25b1b0c591347f3a26745629954f4 Mon Sep 17 00:00:00 2001 From: xboard Date: Wed, 11 Mar 2026 05:47:29 +0800 Subject: [PATCH] refactor: move subscribe templates to dedicated database table --- app/Helpers/Functions.php | 10 ++ .../Controllers/V2/Admin/ConfigController.php | 94 ++++--------------- app/Models/SubscribeTemplate.php | 46 +++++++++ app/Protocols/Clash.php | 4 +- app/Protocols/ClashMeta.php | 8 +- app/Protocols/SingBox.php | 4 +- app/Protocols/Stash.php | 12 +-- app/Protocols/Surfboard.php | 4 +- app/Protocols/Surge.php | 4 +- ...01_create_v2_subscribe_templates_table.php | 91 ++++++++++++++++++ 10 files changed, 175 insertions(+), 102 deletions(-) create mode 100644 app/Models/SubscribeTemplate.php create mode 100644 database/migrations/2025_07_27_000001_create_v2_subscribe_templates_table.php diff --git a/app/Helpers/Functions.php b/app/Helpers/Functions.php index 8f8b804..129e4a1 100644 --- a/app/Helpers/Functions.php +++ b/app/Helpers/Functions.php @@ -28,6 +28,16 @@ if (!function_exists('admin_setting')) { } } +if (!function_exists('subscribe_template')) { + /** + * Get subscribe template content by protocol name. + */ + function subscribe_template(string $name): ?string + { + return \App\Models\SubscribeTemplate::getContent($name); + } +} + if (!function_exists('admin_settings_batch')) { /** * 批量获取配置参数,性能优化版本 diff --git a/app/Http/Controllers/V2/Admin/ConfigController.php b/app/Http/Controllers/V2/Admin/ConfigController.php index 80f8afb..3bb3b18 100644 --- a/app/Http/Controllers/V2/Admin/ConfigController.php +++ b/app/Http/Controllers/V2/Admin/ConfigController.php @@ -4,20 +4,12 @@ namespace App\Http\Controllers\V2\Admin; use App\Http\Controllers\Controller; use App\Http\Requests\Admin\ConfigSave; -use App\Protocols\Clash; -use App\Protocols\ClashMeta; -use App\Protocols\SingBox; -use App\Protocols\Stash; -use App\Protocols\Surfboard; -use App\Protocols\Surge; +use App\Models\SubscribeTemplate; use App\Services\MailService; use App\Services\TelegramService; use App\Services\ThemeService; use App\Utils\Dict; -use Illuminate\Console\Command; use Illuminate\Http\Request; -use Illuminate\Support\Facades\Artisan; -use Illuminate\Support\Facades\File; class ConfigController extends Controller { @@ -57,18 +49,6 @@ class ConfigController extends Controller 'data' => $mailLog, ]); } - /** - * 获取规则模板内容 - * - * @param string $file 文件路径 - * @return string 文件内容 - */ - private function getTemplateContent(string $file): string - { - $path = base_path($file); - return File::exists($path) ? File::get($path) : ''; - } - public function setTelegramWebhook(Request $request) { $hookUrl = $this->resolveTelegramWebhookUrl(); @@ -214,14 +194,14 @@ class ConfigController extends Controller ], 'subscribe_template' => [ 'subscribe_template_singbox' => $this->formatTemplateContent( - admin_setting('subscribe_template_singbox', $this->getDefaultTemplate('singbox')), + subscribe_template('singbox') ?? '', 'json' ), - 'subscribe_template_clash' => admin_setting('subscribe_template_clash', $this->getDefaultTemplate('clash')), - 'subscribe_template_clashmeta' => admin_setting('subscribe_template_clashmeta', $this->getDefaultTemplate('clashmeta')), - 'subscribe_template_stash' => admin_setting('subscribe_template_stash', $this->getDefaultTemplate('stash')), - 'subscribe_template_surge' => admin_setting('subscribe_template_surge', $this->getDefaultTemplate('surge')), - 'subscribe_template_surfboard' => admin_setting('subscribe_template_surfboard', $this->getDefaultTemplate('surfboard')) + 'subscribe_template_clash' => subscribe_template('clash') ?? '', + 'subscribe_template_clashmeta' => subscribe_template('clashmeta') ?? '', + 'subscribe_template_stash' => subscribe_template('stash') ?? '', + 'subscribe_template_surge' => subscribe_template('surge') ?? '', + 'subscribe_template_surfboard' => subscribe_template('surfboard') ?? '' ] ]; } @@ -230,7 +210,20 @@ class ConfigController extends Controller { $data = $request->validated(); + $templateKeys = [ + 'subscribe_template_singbox' => 'singbox', + 'subscribe_template_clash' => 'clash', + 'subscribe_template_clashmeta' => 'clashmeta', + 'subscribe_template_stash' => 'stash', + 'subscribe_template_surge' => 'surge', + 'subscribe_template_surfboard' => 'surfboard', + ]; + foreach ($data as $k => $v) { + if (isset($templateKeys[$k])) { + SubscribeTemplate::setContent($templateKeys[$k], $v); + continue; + } if ($k == 'frontend_theme') { $themeService = app(ThemeService::class); $themeService->switch($v); @@ -273,53 +266,6 @@ class ConfigController extends Controller }; } - /** - * 获取默认模板内容 - * - * @param string $type 模板类型 - * @return string 默认模板内容 - */ - private function getDefaultTemplate(string $type): string - { - $fileMap = [ - 'singbox' => [SingBox::CUSTOM_TEMPLATE_FILE, SingBox::DEFAULT_TEMPLATE_FILE], - 'clash' => [Clash::CUSTOM_TEMPLATE_FILE, Clash::DEFAULT_TEMPLATE_FILE], - 'clashmeta' => [ - ClashMeta::CUSTOM_TEMPLATE_FILE, - ClashMeta::CUSTOM_CLASH_TEMPLATE_FILE, - ClashMeta::DEFAULT_TEMPLATE_FILE - ], - 'stash' => [ - Stash::CUSTOM_TEMPLATE_FILE, - Stash::CUSTOM_CLASH_TEMPLATE_FILE, - Stash::DEFAULT_TEMPLATE_FILE - ], - 'surge' => [Surge::CUSTOM_TEMPLATE_FILE, Surge::DEFAULT_TEMPLATE_FILE], - 'surfboard' => [Surfboard::CUSTOM_TEMPLATE_FILE, Surfboard::DEFAULT_TEMPLATE_FILE], - ]; - - if (!isset($fileMap[$type])) { - return ''; - } - - // 按优先级查找可用的模板文件 - foreach ($fileMap[$type] as $file) { - $content = $this->getTemplateContent($file); - if (!empty($content)) { - // 对于 SingBox,需要格式化 JSON - if ($type === 'singbox') { - $decoded = json_decode($content, true); - if (json_last_error() === JSON_ERROR_NONE) { - return json_encode($decoded, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); - } - } - return $content; - } - } - - return ''; - } - private function getTelegramWebhookBaseUrl(): ?string { $customUrl = trim((string) admin_setting('telegram_webhook_url', '')); diff --git a/app/Models/SubscribeTemplate.php b/app/Models/SubscribeTemplate.php new file mode 100644 index 0000000..fec76e0 --- /dev/null +++ b/app/Models/SubscribeTemplate.php @@ -0,0 +1,46 @@ + 'string', + 'content' => 'string', + ]; + + private static string $cachePrefix = 'subscribe_template:'; + + public static function getContent(string $name): ?string + { + $cacheKey = self::$cachePrefix . $name; + + return Cache::store('redis')->remember($cacheKey, 3600, function () use ($name) { + return self::where('name', $name)->value('content'); + }); + } + + public static function setContent(string $name, ?string $content): void + { + self::updateOrCreate( + ['name' => $name], + ['content' => $content] + ); + Cache::store('redis')->forget(self::$cachePrefix . $name); + } + + public static function getAllContents(): array + { + return self::pluck('content', 'name')->toArray(); + } + + public static function flushCache(string $name): void + { + Cache::store('redis')->forget(self::$cachePrefix . $name); + } +} diff --git a/app/Protocols/Clash.php b/app/Protocols/Clash.php index 844f0e2..0b99664 100644 --- a/app/Protocols/Clash.php +++ b/app/Protocols/Clash.php @@ -27,9 +27,7 @@ class Clash extends AbstractProtocol $appName = admin_setting('app_name', 'XBoard'); // 优先从数据库配置中获取模板 - $template = admin_setting('subscribe_template_clash', File::exists(base_path(self::CUSTOM_TEMPLATE_FILE)) - ? File::get(base_path(self::CUSTOM_TEMPLATE_FILE)) - : File::get(base_path(self::DEFAULT_TEMPLATE_FILE))); + $template = subscribe_template('clash'); $config = Yaml::parse($template); $proxy = []; diff --git a/app/Protocols/ClashMeta.php b/app/Protocols/ClashMeta.php index 4d0cb5b..a1b17db 100644 --- a/app/Protocols/ClashMeta.php +++ b/app/Protocols/ClashMeta.php @@ -65,13 +65,7 @@ class ClashMeta extends AbstractProtocol $user = $this->user; $appName = admin_setting('app_name', 'XBoard'); - $template = admin_setting('subscribe_template_clashmeta', File::exists(base_path(self::CUSTOM_TEMPLATE_FILE)) - ? File::get(base_path(self::CUSTOM_TEMPLATE_FILE)) - : ( - File::exists(base_path(self::CUSTOM_CLASH_TEMPLATE_FILE)) - ? File::get(base_path(self::CUSTOM_CLASH_TEMPLATE_FILE)) - : File::get(base_path(self::DEFAULT_TEMPLATE_FILE)) - )); + $template = subscribe_template('clashmeta'); $config = Yaml::parse($template); $proxy = []; diff --git a/app/Protocols/SingBox.php b/app/Protocols/SingBox.php index 43a1f3e..ce4aaba 100644 --- a/app/Protocols/SingBox.php +++ b/app/Protocols/SingBox.php @@ -83,9 +83,7 @@ class SingBox extends AbstractProtocol protected function loadConfig() { - $jsonData = admin_setting('subscribe_template_singbox', File::exists(base_path(self::CUSTOM_TEMPLATE_FILE)) - ? File::get(base_path(self::CUSTOM_TEMPLATE_FILE)) - : File::get(base_path(self::DEFAULT_TEMPLATE_FILE))); + $jsonData = subscribe_template('singbox'); return is_array($jsonData) ? $jsonData : json_decode($jsonData, true); } diff --git a/app/Protocols/Stash.php b/app/Protocols/Stash.php index 6fc0ef8..8f6910d 100644 --- a/app/Protocols/Stash.php +++ b/app/Protocols/Stash.php @@ -18,14 +18,14 @@ class Stash extends AbstractProtocol Server::TYPE_HYSTERIA, Server::TYPE_TROJAN, Server::TYPE_TUIC, - // Server::TYPE_ANYTLS, + Server::TYPE_ANYTLS, Server::TYPE_SOCKS, Server::TYPE_HTTP, ]; protected $protocolRequirements = [ 'stash' => [ 'anytls' => [ - 'base_version' => '9.9.9' + 'base_version' => '3.3.0' // AnyTLS 协议在3.3.0版本中添加 ], 'vless' => [ 'protocol_settings.tls' => [ @@ -79,13 +79,7 @@ class Stash extends AbstractProtocol $user = $this->user; $appName = admin_setting('app_name', 'XBoard'); - $template = admin_setting('subscribe_template_stash', File::exists(base_path(self::CUSTOM_TEMPLATE_FILE)) - ? File::get(base_path(self::CUSTOM_TEMPLATE_FILE)) - : ( - File::exists(base_path(self::CUSTOM_CLASH_TEMPLATE_FILE)) - ? File::get(base_path(self::CUSTOM_CLASH_TEMPLATE_FILE)) - : File::get(base_path(self::DEFAULT_TEMPLATE_FILE)) - )); + $template = subscribe_template('stash'); $config = Yaml::parse($template); $proxy = []; diff --git a/app/Protocols/Surfboard.php b/app/Protocols/Surfboard.php index 9e6716f..18914c8 100644 --- a/app/Protocols/Surfboard.php +++ b/app/Protocols/Surfboard.php @@ -58,9 +58,7 @@ class Surfboard extends AbstractProtocol } } - $config = admin_setting('subscribe_template_surfboard', File::exists(base_path(self::CUSTOM_TEMPLATE_FILE)) - ? File::get(base_path(self::CUSTOM_TEMPLATE_FILE)) - : File::get(base_path(self::DEFAULT_TEMPLATE_FILE))); + $config = subscribe_template('surfboard'); // Subscription link $subsURL = Helper::getSubscribeUrl($user['token']); $subsDomain = request()->header('Host'); diff --git a/app/Protocols/Surge.php b/app/Protocols/Surge.php index c9fabb6..3d57d6f 100644 --- a/app/Protocols/Surge.php +++ b/app/Protocols/Surge.php @@ -78,9 +78,7 @@ class Surge extends AbstractProtocol } - $config = admin_setting('subscribe_template_surge', File::exists(base_path(self::CUSTOM_TEMPLATE_FILE)) - ? File::get(base_path(self::CUSTOM_TEMPLATE_FILE)) - : File::get(base_path(self::DEFAULT_TEMPLATE_FILE))); + $config = subscribe_template('surge'); // Subscription link $subsDomain = request()->header('Host'); diff --git a/database/migrations/2025_07_27_000001_create_v2_subscribe_templates_table.php b/database/migrations/2025_07_27_000001_create_v2_subscribe_templates_table.php new file mode 100644 index 0000000..873a3df --- /dev/null +++ b/database/migrations/2025_07_27_000001_create_v2_subscribe_templates_table.php @@ -0,0 +1,91 @@ +id(); + $table->string('name')->unique()->comment('Template key, e.g. singbox, clash'); + $table->mediumText('content')->nullable()->comment('Template content'); + $table->timestamps(); + }); + + $this->seedDefaults(); + } + + public function down(): void + { + Schema::dropIfExists('v2_subscribe_templates'); + } + + private function seedDefaults(): void + { + // Fallback order matches original protocol class behavior + $protocols = [ + 'singbox' => [ + 'resources/rules/custom.sing-box.json', + 'resources/rules/default.sing-box.json', + ], + 'clash' => [ + 'resources/rules/custom.clash.yaml', + 'resources/rules/default.clash.yaml', + ], + 'clashmeta' => [ + 'resources/rules/custom.clashmeta.yaml', + 'resources/rules/custom.clash.yaml', + 'resources/rules/default.clash.yaml', + ], + 'stash' => [ + 'resources/rules/custom.stash.yaml', + 'resources/rules/custom.clash.yaml', + 'resources/rules/default.clash.yaml', + ], + 'surge' => [ + 'resources/rules/custom.surge.conf', + 'resources/rules/default.surge.conf', + ], + 'surfboard' => [ + 'resources/rules/custom.surfboard.conf', + 'resources/rules/default.surfboard.conf', + ], + ]; + + foreach ($protocols as $name => $fileFallbacks) { + $existing = DB::table('v2_settings') + ->where('name', "subscribe_template_{$name}") + ->value('value'); + + if ($existing !== null && $existing !== '') { + $content = $existing; + } else { + $content = ''; + foreach ($fileFallbacks as $file) { + $path = base_path($file); + if (File::exists($path)) { + $content = File::get($path); + break; + } + } + } + + DB::table('v2_subscribe_templates')->insert([ + 'name' => $name, + 'content' => $content, + 'created_at' => now(), + 'updated_at' => now(), + ]); + } + + // Clean up old entries from v2_settings + DB::table('v2_settings') + ->where('name', 'like', 'subscribe_template_%') + ->delete(); + } +};