123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498 |
- <?php
- namespace ba\module;
- use ba\Depends;
- use PhpZip\ZipFile;
- use think\Exception;
- use think\facade\Db;
- use GuzzleHttp\Client;
- use think\facade\Config;
- use FilesystemIterator;
- use RecursiveIteratorIterator;
- use RecursiveDirectoryIterator;
- use PhpZip\Exception\ZipException;
- use GuzzleHttp\Exception\TransferException;
- use think\db\exception\PDOException;
- use app\admin\library\crud\Helper;
- /**
- * 模块服务类
- */
- class Server
- {
- private static $client = null;
- public static function download(string $uid, string $dir, array $extend = []): string
- {
- $tmpFile = $dir . $uid . ".zip";
- try {
- $client = self::getClient();
- $response = $client->get('/api/v4.store/download', ['query' => array_merge(['uid' => $uid, 'server' => 1], $extend)]);
- $body = $response->getBody();
- $content = $body->getContents();
- if ($content == '' || stripos($content, '<title>系统发生错误</title>') !== false) {
- throw new moduleException('package download failed', 0);
- }
- if (substr($content, 0, 1) === '{') {
- $json = (array)json_decode($content, true);
- throw new moduleException($json['msg'], $json['code'], $json['data']);
- }
- } catch (TransferException $e) {
- throw new moduleException('package download failed', 0, ['msg' => $e->getMessage()]);
- }
- if ($write = fopen($tmpFile, 'w')) {
- fwrite($write, $content);
- fclose($write);
- return $tmpFile;
- }
- throw new Exception("No permission to write temporary files");
- }
- public static function unzip(string $file, string $dir = ''): string
- {
- if (!file_exists($file)) {
- throw new Exception("Zip file not found");
- }
- $zip = new ZipFile();
- try {
- $zip->openFile($file);
- } catch (ZipException $e) {
- $zip->close();
- throw new moduleException('Unable to open the zip file', 0, ['msg' => $e->getMessage()]);
- }
- $dir = $dir ?: substr($file, 0, strripos($file, '.zip'));
- if (!is_dir($dir)) {
- @mkdir($dir, 0755);
- }
- try {
- $zip->extractTo($dir);
- } catch (ZipException $e) {
- throw new moduleException('Unable to extract ZIP file', 0, ['msg' => $e->getMessage()]);
- } finally {
- $zip->close();
- }
- return $dir;
- }
- public static function getConfig(string $dir, $key = '')
- {
- $configFile = $dir . 'config.json';
- if (!is_dir($dir) || !is_file($configFile)) {
- return [];
- }
- $configContent = @file_get_contents($configFile);
- $configContent = json_decode($configContent, true);
- if (!$configContent) {
- return [];
- }
- if ($key) {
- return $configContent[$key] ?? [];
- }
- return $configContent;
- }
- public static function getDepend(string $dir, $key = '')
- {
- if ($key) {
- return self::getConfig($dir, $key);
- }
- $configContent = self::getConfig($dir);
- $dependKey = ['require', 'require-dev', 'dependencies', 'devDependencies', 'nuxtDependencies', 'nuxtDevDependencies'];
- $dependArray = [];
- foreach ($dependKey as $item) {
- if (array_key_exists($item, $configContent) && $configContent[$item]) {
- $dependArray[$item] = $configContent[$item];
- }
- }
- return $dependArray;
- }
- public static function dependConflictCheck(string $dir): array
- {
- $depend = self::getDepend($dir);
- $serverDep = new Depends(root_path() . 'composer.json', 'composer');
- $webDep = new Depends(root_path() . 'web' . DIRECTORY_SEPARATOR . 'package.json');
- $webNuxtDep = new Depends(root_path() . 'web-nuxt' . DIRECTORY_SEPARATOR . 'package.json');
- $sysDepend = [
- 'require' => $serverDep->getDepends(),
- 'require-dev' => $serverDep->getDepends(true),
- 'dependencies' => $webDep->getDepends(),
- 'devDependencies' => $webDep->getDepends(true),
- 'nuxtDependencies' => $webNuxtDep->getDepends(),
- 'nuxtDevDependencies' => $webNuxtDep->getDepends(true),
- ];
- $conflict = [];
- foreach ($depend as $key => $item) {
- $conflict[$key] = array_uintersect_assoc($item, $sysDepend[$key], function ($a, $b) {
- return $a == $b ? -1 : 0;
- });
- }
- return $conflict;
- }
- public static function createZip(array $files, string $fileName): bool
- {
- $zip = new ZipFile();
- try {
- foreach ($files as $v) {
- $zip->addFile(root_path() . $v, $v);
- }
- $zip->saveAsFile($fileName);
- } catch (ZipException $e) {
- throw new moduleException('Unable to package zip file', 0, ['msg' => $e->getMessage(), 'file' => $fileName]);
- } finally {
- $zip->close();
- }
- return true;
- }
- public static function getFileList(string $dir, bool $onlyConflict = false): array
- {
- if (!is_dir($dir)) {
- return [];
- }
- $fileList = [];
- $overwriteDir = self::getOverwriteDir();
- foreach ($overwriteDir as $item) {
- $baseDir = $dir . $item;
- if (!is_dir($baseDir)) {
- continue;
- }
- $files = new RecursiveIteratorIterator(
- new RecursiveDirectoryIterator($baseDir, FilesystemIterator::SKIP_DOTS), RecursiveIteratorIterator::CHILD_FIRST
- );
- foreach ($files as $file) {
- if ($file->isFile()) {
- $filePath = $file->getPathName();
- $path = str_replace($dir, '', $filePath);
- $path = str_replace(['/', '\\'], DIRECTORY_SEPARATOR, $path);
- if ($onlyConflict) {
- $overwriteFile = root_path() . $path;
- if (is_file($overwriteFile) && (filesize($overwriteFile) != filesize($filePath) || md5_file($overwriteFile) != md5_file($filePath))) {
- $fileList[] = $path;
- }
- } else {
- $fileList[] = $path;
- }
- }
- }
- }
- return $fileList;
- }
- public static function getOverwriteDir(): array
- {
- return [
- 'app',
- 'config',
- 'extend',
- 'public',
- 'vendor',
- 'web',
- 'web-nuxt',
- ];
- }
- public static function importSql(string $dir): bool
- {
- $sqlFile = $dir . 'install.sql';
- $tempLine = '';
- if (is_file($sqlFile)) {
- $lines = file($sqlFile);
- foreach ($lines as $line) {
- if (substr($line, 0, 2) == '--' || $line == '' || substr($line, 0, 2) == '/*') {
- continue;
- }
- $tempLine .= $line;
- if (substr(trim($line), -1, 1) == ';') {
- $tempLine = str_ireplace('__PREFIX__', Config::get('database.connections.mysql.prefix'), $tempLine);
- $tempLine = str_ireplace('INSERT INTO ', 'INSERT IGNORE INTO ', $tempLine);
- try {
- Db::execute($tempLine);
- } catch (PDOException $e) {
- // $e->getMessage();
- }
- $tempLine = '';
- }
- }
- }
- return true;
- }
- public static function installedList(string $dir): array
- {
- if (!is_dir($dir)) {
- return [];
- }
- $installedDir = scandir($dir);
- $installedList = [];
- foreach ($installedDir as $item) {
- if ($item === '.' or $item === '..' || is_file($dir . $item)) {
- continue;
- }
- $tempDir = $dir . $item . DIRECTORY_SEPARATOR;
- if (!is_dir($tempDir)) {
- continue;
- }
- $info = self::getIni($tempDir);
- if (!isset($info['uid'])) {
- continue;
- }
- $installedList[] = $info;
- }
- return $installedList;
- }
- public static function getIni($dir)
- {
- $infoFile = $dir . 'info.ini';
- $info = [];
- if (is_file($infoFile)) {
- $info = parse_ini_file($infoFile, true, INI_SCANNER_TYPED) ?: [];
- }
- return $info;
- }
- public static function setIni(string $dir, array $arr): bool
- {
- $infoFile = $dir . 'info.ini';
- $ini = [];
- foreach ($arr as $key => $val) {
- if (is_array($val)) {
- $ini[] = "[$key]";
- foreach ($val as $ikey => $ival) {
- $ini[] = "$ikey = $ival";
- }
- } else {
- $ini[] = "$key = $val";
- }
- }
- if (!file_put_contents($infoFile, implode("\n", $ini) . "\n", LOCK_EX)) {
- throw new Exception("Configuration file has no write permission");
- }
- return true;
- }
- public static function getClass(string $uid, string $type = 'event', string $class = null): string
- {
- $name = parse_name($uid);
- if (!is_null($class) && strpos($class, '.')) {
- $class = explode('.', $class);
- $class[count($class) - 1] = parse_name(end($class), 1);
- $class = implode('\\', $class);
- } else {
- $class = parse_name(is_null($class) ? $name : $class, 1);
- }
- switch ($type) {
- case 'controller':
- $namespace = '\\modules\\' . $name . '\\controller\\' . $class;
- break;
- default:
- $namespace = '\\modules\\' . $name . '\\' . $class;
- }
- return class_exists($namespace) ? $namespace : '';
- }
- public static function execEvent(string $uid, string $event, array $params = [])
- {
- $eventClass = self::getClass($uid);
- if (class_exists($eventClass)) {
- $handle = new $eventClass();
- if (method_exists($eventClass, $event)) {
- $handle->$event($params);
- }
- }
- }
- /**
- * 分析 WebBootstrap 代码
- */
- public static function analysisWebBootstrap(string $uid, string $dir): array
- {
- $bootstrapFile = $dir . 'webBootstrap.stub';
- if (!file_exists($bootstrapFile)) return [];
- $bootstrapContent = file_get_contents($bootstrapFile);
- $pregArr = [
- 'mainTsImport' => '/#main.ts import code start#([\s\S]*?)#main.ts import code end#/i',
- 'mainTsStart' => '/#main.ts start code start#([\s\S]*?)#main.ts start code end#/i',
- 'appVueImport' => '/#App.vue import code start#([\s\S]*?)#App.vue import code end#/i',
- 'appVueOnMounted' => '/#App.vue onMounted code start#([\s\S]*?)#App.vue onMounted code end#/i',
- ];
- $codeStrArr = [];
- foreach ($pregArr as $key => $item) {
- preg_match($item, $bootstrapContent, $matches);
- if (isset($matches[1]) && $matches[1]) {
- $mainImportCodeArr = array_filter(preg_split('/\r\n|\r|\n/', $matches[1]));
- if ($mainImportCodeArr) {
- $codeStrArr[$key] = "\n";
- if (count($mainImportCodeArr) == 1) {
- foreach ($mainImportCodeArr as $codeItem) {
- $codeStrArr[$key] .= $codeItem . self::buildMarkStr('module-line-mark', $uid, $key);
- }
- } else {
- $codeStrArr[$key] .= self::buildMarkStr('module-multi-line-mark-start', $uid, $key);
- foreach ($mainImportCodeArr as $codeItem) {
- $codeStrArr[$key] .= $codeItem . "\n";
- }
- $codeStrArr[$key] .= self::buildMarkStr('module-multi-line-mark-end', $uid, $key);
- }
- }
- }
- unset($matches);
- }
- return $codeStrArr;
- }
- /**
- * 安装 WebBootstrap
- */
- public static function installWebBootstrap(string $uid, string $dir)
- {
- $mainTsKeys = ['mainTsImport', 'mainTsStart'];
- $bootstrapCode = self::analysisWebBootstrap($uid, $dir);
- $basePath = root_path() . 'web' . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR;
- $marks = [
- 'mainTsImport' => self::buildMarkStr('import-root-mark'),
- 'mainTsStart' => self::buildMarkStr('start-root-mark'),
- 'appVueImport' => self::buildMarkStr('import-root-mark'),
- 'appVueOnMounted' => self::buildMarkStr('onMounted-root-mark'),
- ];
- foreach ($bootstrapCode as $key => $item) {
- if ($item && isset($marks[$key])) {
- $filePath = $basePath . (in_array($key, $mainTsKeys) ? 'main.ts' : 'App.vue');
- $content = file_get_contents($filePath);
- $markPos = stripos($content, $marks[$key]);
- if ($markPos && strripos($content, self::buildMarkStr('module-line-mark', $uid, $key)) === false && strripos($content, self::buildMarkStr('module-multi-line-mark-start', $uid, $key)) === false) {
- $content = substr_replace($content, $item, $markPos + strlen($marks[$key]), 0);
- file_put_contents($filePath, $content);
- }
- }
- }
- }
- /**
- * 卸载 WebBootstrap
- */
- public static function uninstallWebBootstrap(string $uid, string $dir)
- {
- $mainTsKeys = ['mainTsImport', 'mainTsStart'];
- $basePath = root_path() . 'web' . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR;
- $marksKey = [
- 'mainTsImport',
- 'mainTsStart',
- 'appVueImport',
- 'appVueOnMounted',
- ];
- foreach ($marksKey as $item) {
- $filePath = $basePath . (in_array($item, $mainTsKeys) ? 'main.ts' : 'App.vue');
- $content = file_get_contents($filePath);
- $moduleLineMark = self::buildMarkStr('module-line-mark', $uid, $item);
- $moduleMultiLineMarkStart = self::buildMarkStr('module-multi-line-mark-start', $uid, $item);
- $moduleMultiLineMarkEnd = self::buildMarkStr('module-multi-line-mark-end', $uid, $item);
- // 寻找标记,找到则将其中内容删除
- $moduleLineMarkPos = strripos($content, $moduleLineMark);
- if ($moduleLineMarkPos !== false) {
- $delStartTemp = explode($moduleLineMark, $content);
- $delStartPos = strripos(rtrim($delStartTemp[0], "\n"), "\n");
- $delEndPos = stripos($content, "\n", $moduleLineMarkPos);
- $content = substr_replace($content, '', $delStartPos, $delEndPos - $delStartPos);
- }
- $moduleMultiLineMarkStartPos = stripos($content, $moduleMultiLineMarkStart);
- if ($moduleMultiLineMarkStartPos !== false) {
- $moduleMultiLineMarkStartPos--;
- $moduleMultiLineMarkEndPos = stripos($content, $moduleMultiLineMarkEnd);
- $delLang = ($moduleMultiLineMarkEndPos + strlen($moduleMultiLineMarkEnd)) - $moduleMultiLineMarkStartPos;
- $content = substr_replace($content, '', $moduleMultiLineMarkStartPos, $delLang);
- }
- if ($moduleLineMarkPos || $moduleMultiLineMarkStartPos) {
- file_put_contents($filePath, $content);
- }
- }
- }
- /**
- * 构建 WebBootstrap 需要的各种标记字符串
- * @param string $type
- * @param string $uid 模块UID
- * @param string $extend 扩展数据
- * @return string
- */
- public static function buildMarkStr(string $type, string $uid = '', string $extend = ''): string
- {
- $importKeys = ['mti', 'avi'];
- switch ($extend) {
- case 'mainTsImport':
- $extend = 'mti';
- break;
- case 'mainTsStart':
- $extend = 'mts';
- break;
- case 'appVueImport':
- $extend = 'avi';
- break;
- case 'appVueOnMounted':
- $extend = 'avo';
- break;
- default:
- $extend = '';
- break;
- }
- switch ($type) {
- case 'import-root-mark':
- return '// modules import mark, Please do not remove.';
- case 'start-root-mark':
- return '// modules start mark, Please do not remove.';
- case 'onMounted-root-mark':
- return '// Modules onMounted mark, Please do not remove.';
- case 'module-line-mark':
- return ' // Code from module \'' . $uid . "'" . ($extend ? "($extend)" : '');
- case 'module-multi-line-mark-start':
- return (in_array($extend, $importKeys) ? '' : Helper::tab()) . "// Code from module '$uid' start" . ($extend ? "($extend)" : '') . "\n";
- case 'module-multi-line-mark-end':
- return (in_array($extend, $importKeys) ? '' : Helper::tab()) . "// Code from module '$uid' end";
- default:
- return '';
- }
- }
- /**
- * 获取请求对象
- * @return Client
- */
- protected static function getClient(): Client
- {
- $options = [
- 'base_uri' => Config::get('buildadmin.api_url'),
- 'timeout' => 30,
- 'connect_timeout' => 30,
- 'verify' => false,
- 'http_errors' => false,
- 'headers' => [
- 'X-REQUESTED-WITH' => 'XMLHttpRequest',
- 'Referer' => dirname(request()->root(true)),
- 'User-Agent' => 'BuildAdminClient',
- ]
- ];
- if (is_null(self::$client)) {
- self::$client = new Client($options);
- }
- return self::$client;
- }
- }
|