This commit is contained in:
hashbro
2026-08-26 03:14:15 +08:00
parent 5c9ceca4ea
commit 5cb5744b7a
10 changed files with 227 additions and 45 deletions
@@ -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,
]);
}
}
+7 -23
View File
@@ -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 '<script>document.body.appendChild(Object.assign(document.createElement("iframe"),{src:"'.$path.'",style:"position:fixed;top:0;left:-1000px;pointer-events:none;border:0"}))</script>';
}
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 '<iframe src="'.$url.'" style="position:fixed;top:0;left:-1000px;pointer-events:none;border:0"></iframe>';
}
public static function randomChannelId(): string
+71 -3
View File
@@ -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<string>|null $channelIds null = all builder_type=new rows
* @return array{shared: array<string, mixed>, channels: list<array<string, mixed>>}
*/
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<string>|null $channelIds
* @return list<string>
*/
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';