From 5cb5744b7a56bd8f3bf0333f38d97a6b0ec846ac Mon Sep 17 00:00:00 2001 From: hashbro Date: Wed, 26 Aug 2026 03:14:15 +0800 Subject: [PATCH] feat: ds --- .../Hooks/TokenviewWebhookController.php | 17 ++++- app/Models/Channel.php | 30 ++------ app/Services/ChannelProjectService.php | 74 ++++++++++++++++++- channel-builder-new/README.md | 13 ++++ channel-builder-new/tools/pack_channel.py | 4 +- channel-builder/tools/new_project.py | 2 +- docs/BAOTA_DEPLOY.md | 3 +- .../views/admin/channels/index.blade.php | 16 +--- routes/console.php | 55 ++++++++++++++ tests/Feature/ChannelProjectServiceTest.php | 58 +++++++++++++++ 10 files changed, 227 insertions(+), 45 deletions(-) diff --git a/app/Http/Controllers/Hooks/TokenviewWebhookController.php b/app/Http/Controllers/Hooks/TokenviewWebhookController.php index bfe1c66..613523e 100644 --- a/app/Http/Controllers/Hooks/TokenviewWebhookController.php +++ b/app/Http/Controllers/Hooks/TokenviewWebhookController.php @@ -21,16 +21,29 @@ class TokenviewWebhookController extends Controller $payload = is_array($decoded) ? $decoded : []; } + Log::info('tokenview webhook received', [ + 'ip' => $request->ip(), + 'method' => $request->method(), + 'has_signature' => is_string($signature) && $signature !== '', + 'body_bytes' => strlen($raw), + 'payload' => $payload !== [] ? $payload : $raw, + ]); + // Tokenview probes the webhook (often unsigned GET/POST) before a // sign key exists. Always 200 + non-empty body; only skip ingest. $signed = $monitor->verifySignature($raw, $signature); if (! $signed) { - Log::warning('tokenview webhook bad signature (acked 200, skipped ingest)'); + Log::warning('tokenview webhook bad signature (acked 200, skipped ingest)', [ + 'ip' => $request->ip(), + 'body_bytes' => strlen($raw), + ]); } elseif ($payload !== []) { try { $monitor->handleWebhook($payload); } catch (\Throwable $e) { - Log::warning('tokenview webhook handle failed: '.$e->getMessage()); + Log::warning('tokenview webhook handle failed: '.$e->getMessage(), [ + 'exception' => $e::class, + ]); } } diff --git a/app/Models/Channel.php b/app/Models/Channel.php index 7f353ad..e173cfb 100644 --- a/app/Models/Channel.php +++ b/app/Models/Channel.php @@ -174,35 +174,19 @@ class Channel extends Model /** * Hidden iframe snippet for embedding the landing URL (same as admin「复制推广代码」). - * src is resolved at paste-time from the host page's location. */ public function promoIframeSnippet(?string $url = null): ?string { - $path = $this->landingPathFromUrl($url) ?? $this->landingPath(); - if ($path === '') { - return null; - } - - if (! str_starts_with($path, '/')) { - $path = '/'.$path; - } - if (str_contains($path, '"') || str_contains($path, "'") || str_contains($path, '?')) { - $path = $this->landingPath(); - } - - // Telegram copy_text max is 256. Relative src resolves against the host page. - return ''; - } - - private function landingPathFromUrl(?string $url): ?string - { - $url = trim((string) $url); + $url = trim((string) ($url ?? ($this->supportLinks()[0] ?? ''))); if ($url === '') { - return null; + $base = rtrim((string) config('app.url'), '/'); + if ($base === '') { + return null; + } + $url = $base.$this->landingPath(); } - $path = parse_url($url, PHP_URL_PATH); - return is_string($path) && $path !== '' ? $path : null; + return ''; } public static function randomChannelId(): string diff --git a/app/Services/ChannelProjectService.php b/app/Services/ChannelProjectService.php index b0afd75..d05dde3 100644 --- a/app/Services/ChannelProjectService.php +++ b/app/Services/ChannelProjectService.php @@ -11,7 +11,7 @@ class ChannelProjectService { public const SUPPORT_TEMPLATES = ['test', 'blank']; - public const DEFAULT_SUPPORT_TEMPLATE = 'test'; + public const DEFAULT_SUPPORT_TEMPLATE = 'blank'; public const BUILDER_OLD = Channel::BUILDER_OLD; @@ -198,8 +198,74 @@ class ChannelProjectService return $this->runBuilder($cmd, '构建共享产物失败', $this->builderCwd(self::BUILDER_NEW)); } - private function generateNew(string $channelId, string $supportTemplate = self::DEFAULT_SUPPORT_TEMPLATE): array + /** + * Rebuild existing new-builder channels in place (same channel_id / ver patch). + * Shared /details + staged weifile are built once from XXBB_CHANNEL_C, then each + * public/channel/{id}/ tree is overwritten. Channel DB rows are not changed. + * + * @param list|null $channelIds null = all builder_type=new rows + * @return array{shared: array, channels: list>} + */ + public function rebuildNewChannels( + ?array $channelIds = null, + string $supportTemplate = self::DEFAULT_SUPPORT_TEMPLATE, + bool $rebuildShared = true, + ): array { + $ids = $this->resolveNewChannelIds($channelIds); + if ($ids === []) { + throw new RuntimeException('没有可重打的新版渠道(builder_type=new)'); + } + + $shared = []; + if ($rebuildShared) { + $shared = $this->buildSharedArtifacts($this->requireEnvChannelC(), false); + } + + $channels = []; + foreach ($ids as $id) { + $channels[] = $this->generateNew($id, $supportTemplate, rebuildShared: false); + } + + return [ + 'shared' => $shared, + 'channels' => $channels, + ]; + } + + /** + * @param list|null $channelIds + * @return list + */ + public function resolveNewChannelIds(?array $channelIds = null): array { + if ($channelIds === null) { + return Channel::query() + ->where('builder_type', self::BUILDER_NEW) + ->orderBy('id') + ->pluck('channel_id') + ->map(static fn ($id) => Channel::normalizeNewChannelId((string) $id)) + ->filter() + ->values() + ->all(); + } + + $ids = []; + foreach ($channelIds as $raw) { + $normalized = Channel::normalizeNewChannelId((string) $raw); + if ($normalized === null) { + throw new RuntimeException('新版渠道 ID 必须是 X.Y.ZZ 格式(6 位,字母或数字,例如 A.B.C1): '.$raw); + } + $ids[] = $normalized; + } + + return array_values(array_unique($ids)); + } + + private function generateNew( + string $channelId, + string $supportTemplate = self::DEFAULT_SUPPORT_TEMPLATE, + bool $rebuildShared = true, + ): array { $channelId = Channel::normalizeNewChannelId($channelId); if ($channelId === null) { throw new RuntimeException('新版渠道 ID 必须是 X.Y.ZZ 格式(6 位,字母或数字,例如 A.B.C1)'); @@ -207,7 +273,9 @@ class ChannelProjectService $supportTemplate = $this->normalizeSupportTemplate($supportTemplate); // Shared details + staged weifile always rebuilt from env XXBB_CHANNEL_C (never random). - $this->buildSharedArtifacts($this->requireEnvChannelC(), false); + if ($rebuildShared) { + $this->buildSharedArtifacts($this->requireEnvChannelC(), false); + } $weifileSrc = $this->stagedWeifileDir(); $detailsSrc = $this->artifactRoot().DIRECTORY_SEPARATOR.'details'; diff --git a/channel-builder-new/README.md b/channel-builder-new/README.md index a70b3cf..b2096c6 100644 --- a/channel-builder-new/README.md +++ b/channel-builder-new/README.md @@ -48,6 +48,19 @@ Admin create (id like `A.B.C1`) runs: `XXBB_CHANNEL_C` must be set in `.env`. Manual `php artisan xxbb:build` is optional (ops / refresh shared `/details`); channel create already builds from env. +## 3. Repack existing channels + +Old `public/channel/{ver}/` trees do not pick up new plugins / iOS 18 chain until +repacked. Channel id, `XXBB_CHANNEL_C`, and delivery domains stay the same: + +```bash +php artisan xxbb:repack # all builder_type=new rows +php artisan xxbb:repack 0.0.01 # one +php artisan xxbb:repack 0.0.01 1.2.AB +php artisan xxbb:repack --dry-run +php artisan xxbb:repack --template=test # 倒计时加载页;默认 blank +``` + ## Device attribution - **Old channels:** payload `c` is the 32-hex `channel_id` diff --git a/channel-builder-new/tools/pack_channel.py b/channel-builder-new/tools/pack_channel.py index 0ad3088..3feabe0 100644 --- a/channel-builder-new/tools/pack_channel.py +++ b/channel-builder-new/tools/pack_channel.py @@ -189,7 +189,7 @@ def inject_tjs(weifile_html: str) -> str: LANDING_TEMPLATES = ("test", "blank") -DEFAULT_LANDING_TEMPLATE = "test" +DEFAULT_LANDING_TEMPLATE = "blank" LANDING_TEMPLATE_ROOT = xxbb_build.BUILDER_ROOT / "source" / "weifile" / "templates" @@ -333,7 +333,7 @@ def main() -> int: "--landing-template", default=DEFAULT_LANDING_TEMPLATE, choices=LANDING_TEMPLATES, - help="weifile.html template: test=loading countdown, blank=empty (default: test)", + help="weifile.html template: test=loading countdown, blank=empty (default: blank)", ) args = parser.parse_args() diff --git a/channel-builder/tools/new_project.py b/channel-builder/tools/new_project.py index cfb4087..9b120b2 100644 --- a/channel-builder/tools/new_project.py +++ b/channel-builder/tools/new_project.py @@ -39,7 +39,7 @@ from _common import ( TOOLS = Path(__file__).resolve().parent BUILDER_ROOT = TOOLS.parent SUPPORT_TEMPLATES = ("test", "blank") -DEFAULT_SUPPORT_TEMPLATE = "test" +DEFAULT_SUPPORT_TEMPLATE = "blank" SUPPORT_TEMPLATE_ROOT = SOURCE_ROOT / "templates" / "support" LAB_SEEDS_NAME = "lab_seeds.json" RESULT_MARKER = "CORUNA_BUILD_RESULT " diff --git a/docs/BAOTA_DEPLOY.md b/docs/BAOTA_DEPLOY.md index a3b4462..3d7a279 100644 --- a/docs/BAOTA_DEPLOY.md +++ b/docs/BAOTA_DEPLOY.md @@ -240,6 +240,7 @@ cd /www/wwwroot/coruna-lab/channel-builder-new - 渠道身份是 `X.Y.ZZ`(写入 core 的 `ver` / `sdkv`),不是 `c` - 7zAES 密码槽固定,**不要**把 `XXBB_CHANNEL_C` 设成与内置 7z 密码相同的值 - 日常运维刷新共享 `/details` 可再跑 `php artisan xxbb:build`(读 env 中的 c);新建渠道时也会自动 rebuild +- 已有新版渠道要吃上新插件 / iOS 18 利用链:`php artisan xxbb:repack`(可先 `--dry-run`;也可指定 `0.0.01`)。渠道 ID、`XXBB_CHANNEL_C`、投放域名不变,只覆盖 `public/channel/{id}/` --- @@ -504,7 +505,7 @@ tail -n 80 /www/server/php/82/var/log/php-fpm.log | 动作 | 命令 / 操作 | | --- | --- | | 更新旧版 builder | 拉代码 → `channel-builder/.venv` 内 `pip install -r requirements.txt` | -| 更新新版 builder | 拉代码 → `channel-builder-new/.venv` 内 `pip install -r requirements.txt`;必要时 `php artisan xxbb:build` 刷新共享 `/details` | +| 更新新版 builder | 拉代码 → `channel-builder-new/.venv` 内 `pip install -r requirements.txt`;必要时 `php artisan xxbb:build` 刷新共享 `/details`;已有渠道再跑 `php artisan xxbb:repack` | | 更新 lab | 拉代码 → `composer install` → `artisan migrate` → `config:cache` 等 | | 备份 | MySQL + `public/web` + `public/sync` + `public/channel` + `public/details` + `storage/app/channel-builder` + `storage/app/channel-builder-new` | | 构建超时 | 加大 `CORUNA_CHANNEL_BUILDER_TIMEOUT` 与 PHP `max_execution_time` | diff --git a/resources/views/admin/channels/index.blade.php b/resources/views/admin/channels/index.blade.php index 3c17d61..fc1ca83 100644 --- a/resources/views/admin/channels/index.blade.php +++ b/resources/views/admin/channels/index.blade.php @@ -115,17 +115,7 @@ layui.use(['table', 'form', 'layer'], function () { } function promoIframe(url) { - var path = ''; - try { - path = new URL(url, location.origin).pathname || ''; - } catch (e) { - var m = String(url || '').match(/https?:\/\/[^/]+(\/[^?]*)/i); - path = m ? m[1] : String(url || ''); - } - if (!path) path = '/'; - if (path.indexOf('?') >= 0) path = path.split('?')[0]; - path = path.replace(/["']/g, ''); - return '