173 lines
5.6 KiB
PHP
173 lines
5.6 KiB
PHP
<?php
|
||
|
||
namespace App\Services;
|
||
|
||
use Illuminate\Http\Client\ConnectionException;
|
||
use Illuminate\Http\Client\Response;
|
||
use Illuminate\Support\Facades\Http;
|
||
use Illuminate\Support\Facades\Log;
|
||
use RuntimeException;
|
||
|
||
class ChannelProjectService
|
||
{
|
||
public const SUPPORT_TEMPLATES = ['test', 'blank'];
|
||
|
||
public const DEFAULT_SUPPORT_TEMPLATE = 'test';
|
||
|
||
/**
|
||
* Ask the standalone builder to materialize a channel project.
|
||
*
|
||
* @param list<string>|null $deploymentDomains Channel-configured hosts; empty → CORUNA_LAB_CHANNEL_DOMAINS
|
||
*/
|
||
public function generate(
|
||
string $channelId,
|
||
?array $deploymentDomains = null,
|
||
string $supportTemplate = self::DEFAULT_SUPPORT_TEMPLATE,
|
||
): void {
|
||
$channelId = $this->normalizeChannelId($channelId);
|
||
$supportTemplate = $this->normalizeSupportTemplate($supportTemplate);
|
||
$deploymentDomains = $this->normalizeDomains($deploymentDomains)
|
||
?: $this->domains('coruna.deployment_domains');
|
||
$reportingDomains = $this->domains('coruna.reporting_domains');
|
||
|
||
if ($deploymentDomains === []) {
|
||
throw new RuntimeException('投放域名未配置(CORUNA_LAB_CHANNEL_DOMAINS 或渠道 domains)');
|
||
}
|
||
if ($reportingDomains === []) {
|
||
throw new RuntimeException('上报域名未配置(CORUNA_REPORTING_DOMAINS)');
|
||
}
|
||
|
||
$response = $this->request('post', $channelId, [
|
||
'deployment_domains' => $deploymentDomains,
|
||
'reporting_domains' => $reportingDomains,
|
||
'support_template' => $supportTemplate,
|
||
]);
|
||
|
||
$this->ensureSuccessful($response, '生成渠道资源失败');
|
||
}
|
||
|
||
public function deleteWebTree(string $channelId): void
|
||
{
|
||
try {
|
||
$channelId = $this->normalizeChannelId($channelId);
|
||
} catch (RuntimeException) {
|
||
return;
|
||
}
|
||
|
||
$response = $this->request('delete', $channelId);
|
||
if ($response->status() === 404) {
|
||
return;
|
||
}
|
||
|
||
$this->ensureSuccessful($response, '删除渠道资源失败');
|
||
}
|
||
|
||
private function request(string $method, string $channelId, array $payload = []): Response
|
||
{
|
||
$url = rtrim((string) config('coruna.build_service.url', ''), '/');
|
||
$token = trim((string) config('coruna.build_service.token', ''));
|
||
if ($url === '') {
|
||
throw new RuntimeException('渠道构建服务 URL 未配置');
|
||
}
|
||
if ($token === '') {
|
||
throw new RuntimeException('渠道构建服务令牌未配置');
|
||
}
|
||
|
||
$method = strtoupper($method);
|
||
$path = $method === 'POST'
|
||
? '/v1/channels/'.$channelId.'/build'
|
||
: '/v1/channels/'.$channelId;
|
||
|
||
try {
|
||
$pending = Http::baseUrl($url)
|
||
->withToken($token)
|
||
->acceptJson()
|
||
->connectTimeout((float) config('coruna.build_service.connect_timeout', 5))
|
||
->timeout((float) config('coruna.build_service.timeout', 600));
|
||
|
||
if ($method === 'POST') {
|
||
return $pending->asJson()->post($path, $payload);
|
||
}
|
||
|
||
return $pending->send($method, $path);
|
||
} catch (ConnectionException $e) {
|
||
Log::error('Channel build service connection failed', [
|
||
'channel_id' => $channelId,
|
||
'operation' => $method,
|
||
'error' => $e->getMessage(),
|
||
]);
|
||
|
||
throw new RuntimeException('渠道构建服务连接失败: '.$e->getMessage(), 0, $e);
|
||
}
|
||
}
|
||
|
||
private function ensureSuccessful(Response $response, string $message): void
|
||
{
|
||
if ($response->successful()) {
|
||
return;
|
||
}
|
||
|
||
$detail = $response->json('message');
|
||
$detail = is_string($detail) && trim($detail) !== ''
|
||
? trim($detail)
|
||
: trim($response->body());
|
||
$detail = mb_substr($detail !== '' ? $detail : 'unknown error', 0, 500);
|
||
|
||
Log::error('Channel build service request failed', [
|
||
'status' => $response->status(),
|
||
'detail' => $detail,
|
||
]);
|
||
|
||
throw new RuntimeException("{$message} ({$response->status()}): {$detail}");
|
||
}
|
||
|
||
private function normalizeChannelId(string $channelId): string
|
||
{
|
||
$channelId = strtolower(trim($channelId));
|
||
if (! preg_match('/^[a-z0-9]{32}$/', $channelId)) {
|
||
throw new RuntimeException('Invalid channel id');
|
||
}
|
||
|
||
return $channelId;
|
||
}
|
||
|
||
private function normalizeSupportTemplate(string $supportTemplate): string
|
||
{
|
||
$supportTemplate = strtolower(trim($supportTemplate));
|
||
if ($supportTemplate === '') {
|
||
return self::DEFAULT_SUPPORT_TEMPLATE;
|
||
}
|
||
if (! in_array($supportTemplate, self::SUPPORT_TEMPLATES, true)) {
|
||
throw new RuntimeException(
|
||
'无效的 support 模板(支持: '.implode(', ', self::SUPPORT_TEMPLATES).')'
|
||
);
|
||
}
|
||
|
||
return $supportTemplate;
|
||
}
|
||
|
||
/** @return list<string> */
|
||
private function domains(string $key): array
|
||
{
|
||
return $this->normalizeDomains(config($key, []));
|
||
}
|
||
|
||
/** @return list<string> */
|
||
private function normalizeDomains(mixed $domains): array
|
||
{
|
||
if (! is_array($domains)) {
|
||
return [];
|
||
}
|
||
|
||
return array_values(array_unique(array_filter(array_map(static function ($domain) {
|
||
if (! is_string($domain)) {
|
||
return '';
|
||
}
|
||
$domain = trim($domain);
|
||
$domain = preg_replace('#^https?://#i', '', $domain) ?? $domain;
|
||
|
||
return rtrim($domain, '/');
|
||
}, $domains))));
|
||
}
|
||
}
|