*/ public function listFiles(Channel $channel): array { $dir = $channel->embedAssetDir(); if ($dir === null) { return []; } return $this->collectFiles($dir); } public function build(Channel $channel): string { $dir = $channel->embedAssetDir(); if ($dir === null) { throw new RuntimeException('渠道静态资源不存在,请先构建'); } $files = $this->collectFiles($dir); if ($files === []) { throw new RuntimeException('渠道目录里没有可打包的浏览器资源'); } if (! class_exists(ZipArchive::class)) { throw new RuntimeException('PHP ZipArchive 不可用'); } $tmp = tempnam(sys_get_temp_dir(), 'coruna-embed-'); if ($tmp === false) { throw new RuntimeException('无法创建临时文件'); } @unlink($tmp); $zipPath = $tmp.'.zip'; $zip = new ZipArchive(); if ($zip->open($zipPath, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== true) { throw new RuntimeException('无法创建 zip'); } $statOrigin = $this->statOrigin(); foreach ($files as $rel) { $abs = $dir.DIRECTORY_SEPARATOR.str_replace('/', DIRECTORY_SEPARATOR, $rel); $contents = file_get_contents($abs); if ($contents === false) { continue; } if ($rel === 'index.js' && $statOrigin !== '') { $contents = $this->bakeStatOrigin($contents, $statOrigin); } $zip->addFromString($rel, $contents); } $zip->addFromString('README.txt', $this->readme($channel)); $zip->close(); return $zipPath; } private function statOrigin(): string { $domains = Channel::normalizeDomainList(config('coruna.channel_domains', [])); $host = trim((string) ($domains[0] ?? '')); if ($host === '') { return ''; } if (preg_match('#^https?://#i', $host)) { return rtrim($host, '/'); } $scheme = trim((string) config('coruna.static_site.scheme', 'https')) ?: 'https'; return $scheme.'://'.rtrim($host, '/'); } private function bakeStatOrigin(string $boot, string $origin): string { $quoted = json_encode($origin, JSON_UNESCAPED_SLASHES); $updated = preg_replace( '/var STAT_ORIGIN = ([\'"][^\'"]*[\'"]|__STAT_ORIGIN__)/', 'var STAT_ORIGIN = '.$quoted, $boot, 1, ); return is_string($updated) ? $updated : $boot; } private function readme(Channel $channel): string { $id = (string) $channel->channel_id; return "把本 zip 解压到站点根目录(与首页同级),页面中加入:\n" ."\n\n" ."渠道 {$id} 已写入 index.js。iframe 投放仍可用原落地页链接。\n"; } /** * @return list */ private function collectFiles(string $dir): array { $skipNames = ['.DS_Store', 'manifest.json', 'README.md', 'README.txt']; $skipDirs = ['templates', '_bak', '__pycache__']; $files = []; $iterator = new \RecursiveIteratorIterator( new \RecursiveDirectoryIterator($dir, \FilesystemIterator::SKIP_DOTS) ); foreach ($iterator as $file) { if (! $file->isFile()) { continue; } $abs = $file->getPathname(); $rel = ltrim(str_replace('\\', '/', substr($abs, strlen($dir))), '/'); $parts = explode('/', $rel); if (array_intersect($parts, $skipDirs) !== []) { continue; } if (in_array(end($parts), $skipNames, true)) { continue; } $ext = strtolower((string) $file->getExtension()); if (! in_array($ext, ['js', 'html', 'htm', 'css'], true)) { continue; } $files[] = $rel; } sort($files); return $files; } }