diff --git a/app/admin/controller/Module.php b/app/admin/controller/Module.php deleted file mode 100644 index ce66a26..0000000 --- a/app/admin/controller/Module.php +++ /dev/null @@ -1,158 +0,0 @@ -initializeBackend($request); - if ($response !== null) return $response; - - return $this->success('', [ - 'installed' => Server::installedList(root_path() . 'modules' . DIRECTORY_SEPARATOR), - 'sysVersion' => config('buildadmin.version'), - 'nuxtVersion' => Server::getNuxtVersion(), - ]); - } - - public function state(Request $request): Response - { - $response = $this->initializeBackend($request); - if ($response !== null) return $response; - - $uid = $request->get('uid', ''); - if (!$uid) { - return $this->error(__('Parameter error')); - } - return $this->success('', [ - 'state' => Manage::instance($uid)->getInstallState() - ]); - } - - public function install(Request $request): Response - { - $response = $this->initializeBackend($request); - if ($response !== null) return $response; - - AdminLog::instance($request)->setTitle(__('Install module')); - $uid = $request->get('uid', $request->post('uid', '')); - $update = filter_var($request->get('update', $request->post('update', false)), FILTER_VALIDATE_BOOLEAN); - if (!$uid) { - return $this->error(__('Parameter error')); - } - $res = []; - try { - $res = Manage::instance($uid)->install($update); - } catch (BaException $e) { - return $this->error(__($e->getMessage()), $e->getData(), $e->getCode()); - } catch (\Throwable $e) { - return $this->error(__($e->getMessage())); - } - return $this->success('', ['data' => $res]); - } - - public function dependentInstallComplete(Request $request): Response - { - $response = $this->initializeBackend($request); - if ($response !== null) return $response; - - $uid = $request->get('uid', ''); - if (!$uid) { - return $this->error(__('Parameter error')); - } - try { - Manage::instance($uid)->dependentInstallComplete('all'); - } catch (BaException $e) { - return $this->error(__($e->getMessage()), $e->getData(), $e->getCode()); - } catch (\Throwable $e) { - return $this->error(__($e->getMessage())); - } - return $this->success(); - } - - public function changeState(Request $request): Response - { - $response = $this->initializeBackend($request); - if ($response !== null) return $response; - - AdminLog::instance($request)->setTitle(__('Change module state')); - $uid = $request->post('uid', ''); - $state = filter_var($request->post('state', false), FILTER_VALIDATE_BOOLEAN); - if (!$uid) { - return $this->error(__('Parameter error')); - } - $info = []; - try { - $info = Manage::instance($uid)->changeState($state); - } catch (BaException $e) { - return $this->error(__($e->getMessage()), $e->getData(), $e->getCode()); - } catch (\Throwable $e) { - return $this->error(__($e->getMessage())); - } - return $this->success('', ['info' => $info]); - } - - public function uninstall(Request $request): Response - { - $response = $this->initializeBackend($request); - if ($response !== null) return $response; - - AdminLog::instance($request)->setTitle(__('Uninstall module')); - $uid = $request->post('uid', ''); - if (!$uid) { - return $this->error(__('Parameter error')); - } - try { - Manage::instance($uid)->uninstall(); - } catch (BaException $e) { - return $this->error(__($e->getMessage()), $e->getData(), $e->getCode()); - } catch (\Throwable $e) { - return $this->error(__($e->getMessage())); - } - return $this->success(); - } - - public function upload(Request $request): Response - { - $response = $this->initializeBackend($request); - if ($response !== null) return $response; - - AdminLog::instance($request)->setTitle(__('Upload module')); - - $token = $request->post('token', $request->get('token', '')); - if (!$token) { - return $this->error(__('Please login to the official website account first')); - } - - try { - if ($request->file('file')) { - $res = Manage::uploadFromRequest($request); - } else { - $file = $request->post('file', $request->get('file', '')); - if (!$file) { - return $this->error(__('Parameter error')); - } - $info = Manage::instance('')->doUpload($token, $file); - $res = ['info' => $info]; - } - } catch (BaException $e) { - return $this->error(__($e->getMessage()), $e->getData(), $e->getCode()); - } catch (\Throwable $e) { - return $this->error(__($e->getMessage())); - } - return $this->success('', $res); - } -} diff --git a/app/admin/lang/zh-cn/module.php b/app/admin/lang/zh-cn/module.php deleted file mode 100644 index d63d099..0000000 --- a/app/admin/lang/zh-cn/module.php +++ /dev/null @@ -1,29 +0,0 @@ - '订单找不到啦!', - 'Module already exists' => '模块已存在!', - 'package download failed' => '包下载失败!', - 'package check failed' => '包检查失败!', - 'No permission to write temporary files' => '没有权限写入临时文件!', - 'Zip file not found' => '找不到压缩包文件', - 'Unable to open the zip file' => '无法打开压缩包文件', - 'Unable to extract ZIP file' => '无法提取ZIP文件', - 'Unable to package zip file' => '无法打包zip文件', - 'Basic configuration of the Module is incomplete' => '模块基础配置不完整', - 'Module package file does not exist' => '模块包文件不存在', - 'Module file conflicts' => '模块文件存在冲突,请手动处理!', - 'Configuration file has no write permission' => '配置文件无写入权限', - 'The current state of the module cannot be set to disabled' => '模块当前状态无法设定为禁用', - 'The current state of the module cannot be set to enabled' => '模块当前状态无法设定为启用', - 'Module file updated' => '模块文件有更新', - 'Please disable the module first' => '请先禁用模块', - 'Please disable the module before updating' => '更新前请先禁用模块', - 'The directory required by the module is occupied' => '模块所需目录已被占用', - 'Install module' => '安装模块', - 'Unload module' => '卸载模块', - 'Update module' => '更新模块', - 'Change module state' => '改变模块状态', - 'Upload install module' => '上传安装模块', - 'Please login to the official website account first' => '请先使用BuildAdmin官网账户登录到模块市场~', - 'composer config %s conflict' => 'composer 配置项 %s 存在冲突', -]; \ No newline at end of file diff --git a/app/admin/library/module/Manage.php b/app/admin/library/module/Manage.php deleted file mode 100644 index 76e6d66..0000000 --- a/app/admin/library/module/Manage.php +++ /dev/null @@ -1,950 +0,0 @@ -setModuleUid($uid); - } - - public function __construct(string $uid) - { - $this->installDir = root_path() . 'modules' . DIRECTORY_SEPARATOR; - $this->backupsDir = $this->installDir . 'backups' . DIRECTORY_SEPARATOR; - if (!is_dir($this->installDir)) { - mkdir($this->installDir, 0755, true); - } - if (!is_dir($this->backupsDir)) { - mkdir($this->backupsDir, 0755, true); - } - - if ($uid) { - $this->setModuleUid($uid); - } else { - $this->uid = ''; - $this->modulesDir = $this->installDir; - } - } - - public function getInstallState(): int - { - if (!is_dir($this->modulesDir)) { - return self::UNINSTALLED; - } - $info = $this->getInfo(); - if ($info && isset($info['state'])) { - return $info['state']; - } - return Filesystem::dirIsEmpty($this->modulesDir) ? self::UNINSTALLED : self::DIRECTORY_OCCUPIED; - } - - /** - * 从 Webman Request 上传安装(适配 Multipart 上传) - * @return array 模块基本信息 - * @throws Throwable - */ - public static function uploadFromRequest(Request $request): array - { - $file = $request->file('file'); - if (!$file) { - throw new Exception('Parameter error'); - } - $token = $request->post('token', $request->get('token', '')); - if (!$token) { - throw new Exception('Please login to the official website account first'); - } - $uploadDir = root_path() . 'public' . DIRECTORY_SEPARATOR . 'storage' . DIRECTORY_SEPARATOR . 'upload' . DIRECTORY_SEPARATOR; - if (!is_dir($uploadDir)) { - mkdir($uploadDir, 0755, true); - } - $originalName = $file->getUploadName() ?? 'module.zip'; - $baseName = basename(str_replace(['\\', '/'], DIRECTORY_SEPARATOR, $originalName)); - if (!preg_match('/^[a-zA-Z0-9._-]+\.zip$/i', $baseName)) { - throw new Exception('The uploaded file format is not allowed'); - } - $saveName = 'temp' . DIRECTORY_SEPARATOR . date('YmdHis') . '_' . $baseName; - $savePath = $uploadDir . $saveName; - $saveDir = dirname($savePath); - if (!is_dir($saveDir)) { - mkdir($saveDir, 0755, true); - } - $file->move($savePath); - $relativePath = 'storage/upload/' . str_replace(DIRECTORY_SEPARATOR, '/', $saveName); - try { - return self::instance('')->doUpload($token, $relativePath); - } finally { - if (is_file($savePath)) { - @unlink($savePath); - } - } - } - - /** - * 下载模块文件 - * @throws Throwable - */ - public function download(): string - { - $req = function_exists('request') ? request() : null; - $token = $req ? ($req->post('token', $req->get('token', ''))) : ''; - $version = $req ? ($req->post('version', $req->get('version', ''))) : ''; - $orderId = $req ? ($req->post('orderId', $req->get('orderId', 0))) : 0; - - if (!$orderId) { - throw new Exception('Order not found'); - } - - $zipFile = Server::download($this->uid, $this->installDir, [ - 'version' => $version, - 'orderId' => $orderId, - 'nuxtVersion' => Server::getNuxtVersion(), - 'sysVersion' => config('buildadmin.version', ''), - 'installed' => Server::getInstalledIds($this->installDir), - 'ba-user-token' => $token, - ]); - - Filesystem::delDir($this->modulesDir); - Filesystem::unzip($zipFile); - @unlink($zipFile); - - $this->checkPackage(); - - $this->setInfo([ - 'state' => self::WAIT_INSTALL, - ]); - - return $zipFile; - } - - /** - * 上传安装(token + 文件相对路径) - * @throws Throwable - */ - public function doUpload(string $token, string $file): array - { - $file = self::resolvePublicStorageFile($file); - - $copyTo = $this->installDir . 'uploadTemp' . date('YmdHis') . '.zip'; - copy($file, $copyTo); - - $copyToDir = Filesystem::unzip($copyTo); - $copyToDir .= DIRECTORY_SEPARATOR; - - @unlink($file); - @unlink($copyTo); - - $info = Server::getIni($copyToDir); - if (empty($info['uid'])) { - Filesystem::delDir($copyToDir); - throw new Exception('Basic configuration of the Module is incomplete'); - } - - $this->setModuleUid($info['uid']); - - $upgrade = false; - if (is_dir($this->modulesDir)) { - $oldInfo = $this->getInfo(); - if ($oldInfo && !empty($oldInfo['uid'])) { - $versions = explode('.', $oldInfo['version'] ?? '0.0.0'); - if (isset($versions[2])) { - $versions[2]++; - } - $nextVersion = implode('.', $versions); - $upgrade = Version::compare($nextVersion, $info['version'] ?? ''); - if ($upgrade) { - if (!in_array($oldInfo['state'], [self::UNINSTALLED, self::WAIT_INSTALL, self::DISABLE])) { - Filesystem::delDir($copyToDir); - throw new Exception('Please disable the module before updating'); - } - } else { - Filesystem::delDir($copyToDir); - throw new Exception('Module already exists'); - } - } - - if (!Filesystem::dirIsEmpty($this->modulesDir) && !$upgrade) { - Filesystem::delDir($copyToDir); - throw new Exception('The directory required by the module is occupied'); - } - } - - try { - Server::installPreCheck([ - 'uid' => $info['uid'], - 'version' => $info['version'] ?? '', - 'sysVersion' => config('buildadmin.version', ''), - 'nuxtVersion' => Server::getNuxtVersion(), - 'moduleVersion' => $info['version'] ?? '', - 'ba-user-token' => $token, - 'installed' => Server::getInstalledIds($this->installDir), - 'server' => 1, - ]); - } catch (Throwable $e) { - Filesystem::delDir($copyToDir); - throw $e; - } - - $newInfo = ['state' => self::WAIT_INSTALL]; - if ($upgrade) { - $info['update'] = 1; - Filesystem::delDir($this->modulesDir); - } - - rename($copyToDir, $this->modulesDir); - - $this->checkPackage(); - - $this->setInfo($newInfo); - - return $info; - } - - /** - * 安装模块 - * @throws Throwable - */ - public function install(bool $update): array - { - $state = $this->getInstallState(); - - if ($update) { - if (!in_array($state, [self::UNINSTALLED, self::WAIT_INSTALL, self::DISABLE])) { - throw new Exception('Please disable the module before updating'); - } - if ($state == self::UNINSTALLED || $state != self::WAIT_INSTALL) { - $this->download(); - } - } else { - if ($state == self::INSTALLED || $state == self::DIRECTORY_OCCUPIED || $state == self::DISABLE) { - throw new Exception('Module already exists'); - } - if ($state == self::UNINSTALLED) { - $this->download(); - } - } - - Server::importSql($this->modulesDir); - - $info = $this->getInfo(); - if ($update) { - $info['update'] = 1; - Server::execEvent($this->uid, 'update'); - } - - $req = function_exists('request') ? request() : null; - $extend = $req ? ($req->post('extend') ?? []) : []; - if (!isset($extend['conflictHandle'])) { - Server::execEvent($this->uid, 'install'); - } - - $this->enable('install'); - - return $info; - } - - /** - * 卸载 - * @throws Throwable - */ - public function uninstall(): void - { - $info = $this->getInfo(); - if (($info['state'] ?? 0) != self::DISABLE) { - throw new Exception('Please disable the module first', 0, [ - 'uid' => $this->uid, - ]); - } - - Server::execEvent($this->uid, 'uninstall'); - - Filesystem::delDir($this->modulesDir); - } - - /** - * 修改模块状态 - * @throws Throwable - */ - public function changeState(bool $state): array - { - $info = $this->getInfo(); - if (!$state) { - $canDisable = [ - self::INSTALLED, - self::CONFLICT_PENDING, - self::DEPENDENT_WAIT_INSTALL, - ]; - if (!in_array($info['state'] ?? 0, $canDisable)) { - throw new Exception('The current state of the module cannot be set to disabled', 0, [ - 'uid' => $this->uid, - 'state' => $info['state'] ?? 0, - ]); - } - return $this->disable(); - } - - if (($info['state'] ?? 0) != self::DISABLE) { - throw new Exception('The current state of the module cannot be set to enabled', 0, [ - 'uid' => $this->uid, - 'state' => $info['state'] ?? 0, - ]); - } - $this->setInfo([ - 'state' => self::WAIT_INSTALL, - ]); - return $info; - } - - /** - * 启用 - * @throws Throwable - */ - public function enable(string $trigger): void - { - Server::installWebBootstrap($this->uid, $this->modulesDir); - Server::createRuntime($this->modulesDir); - $this->conflictHandle($trigger); - Server::execEvent($this->uid, 'enable'); - $this->dependUpdateHandle(); - } - - /** - * 禁用 - * @throws Throwable - */ - public function disable(): array - { - $req = function_exists('request') ? request() : null; - $update = $req ? filter_var($req->post('update', false), FILTER_VALIDATE_BOOLEAN) : false; - $confirmConflict = $req ? filter_var($req->post('confirmConflict', false), FILTER_VALIDATE_BOOLEAN) : false; - $dependConflictSolution = $req ? ($req->post('dependConflictSolution') ?? []) : []; - - $info = $this->getInfo(); - $zipFile = $this->backupsDir . $this->uid . '-install.zip'; - $zipDir = false; - if (is_file($zipFile)) { - try { - $zipDir = $this->backupsDir . $this->uid . '-install' . DIRECTORY_SEPARATOR; - Filesystem::unzip($zipFile, $zipDir); - } catch (Exception) { - // skip - } - } - - $conflictFile = Server::getFileList($this->modulesDir, true); - $dependConflict = $this->disableDependCheck(); - if (($conflictFile || !self::isEmptyArray($dependConflict)) && !$confirmConflict) { - $dependConflictTemp = []; - foreach ($dependConflict as $env => $item) { - foreach ($item as $depend => $v) { - $dependConflictTemp[] = [ - 'env' => $env, - 'depend' => $depend, - 'dependTitle' => $depend . ' ' . $v, - 'solution' => 'delete', - ]; - } - } - throw new Exception('Module file updated', -1, [ - 'uid' => $this->uid, - 'conflictFile' => $conflictFile, - 'dependConflict' => $dependConflictTemp, - ]); - } - - Server::execEvent($this->uid, 'disable', ['update' => $update]); - - $delNpmDepend = false; - $delNuxtNpmDepend = false; - $delComposerDepend = false; - foreach ($dependConflictSolution as $env => $depends) { - if (!$depends) continue; - if ($env == 'require' || $env == 'require-dev') { - $delComposerDepend = true; - } elseif ($env == 'dependencies' || $env == 'devDependencies') { - $delNpmDepend = true; - } elseif ($env == 'nuxtDependencies' || $env == 'nuxtDevDependencies') { - $delNuxtNpmDepend = true; - } - } - - $dependJsonFiles = [ - 'composer' => 'composer.json', - 'webPackage' => 'web' . DIRECTORY_SEPARATOR . 'package.json', - 'webNuxtPackage' => 'web-nuxt' . DIRECTORY_SEPARATOR . 'package.json', - ]; - $dependWaitInstall = []; - if ($delComposerDepend) { - $conflictFile[] = $dependJsonFiles['composer']; - $dependWaitInstall[] = [ - 'pm' => false, - 'command' => 'composer.update', - 'type' => 'composer_dependent_wait_install', - ]; - } - if ($delNpmDepend) { - $conflictFile[] = $dependJsonFiles['webPackage']; - $dependWaitInstall[] = [ - 'pm' => true, - 'command' => 'web-install', - 'type' => 'npm_dependent_wait_install', - ]; - } - if ($delNuxtNpmDepend) { - $conflictFile[] = $dependJsonFiles['webNuxtPackage']; - $dependWaitInstall[] = [ - 'pm' => true, - 'command' => 'nuxt-install', - 'type' => 'nuxt_npm_dependent_wait_install', - ]; - } - if ($conflictFile) { - $overwriteDir = Server::getOverwriteDir(); - foreach ($conflictFile as $key => $item) { - $paths = explode(DIRECTORY_SEPARATOR, $item); - if (in_array($paths[0], $overwriteDir) || in_array($item, $dependJsonFiles)) { - $conflictFile[$key] = $item; - } else { - $conflictFile[$key] = Filesystem::fsFit(str_replace(root_path(), '', $this->modulesDir . $item)); - } - if (!is_file(root_path() . $conflictFile[$key])) { - unset($conflictFile[$key]); - } - } - $backupsZip = $this->backupsDir . $this->uid . '-disable-' . date('YmdHis') . '.zip'; - Filesystem::zip($conflictFile, $backupsZip); - } - - $serverDepend = 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'); - foreach ($dependConflictSolution as $env => $depends) { - if (!$depends) continue; - $dev = stripos($env, 'dev') !== false; - if ($env == 'require' || $env == 'require-dev') { - $serverDepend->removeDepends($depends, $dev); - } elseif ($env == 'dependencies' || $env == 'devDependencies') { - $webDep->removeDepends($depends, $dev); - } elseif ($env == 'nuxtDependencies' || $env == 'nuxtDevDependencies') { - $webNuxtDep->removeDepends($depends, $dev); - } - } - - $composerConfig = Server::getConfig($this->modulesDir, 'composerConfig'); - if ($composerConfig) { - $serverDepend->removeComposerConfig($composerConfig); - } - - $protectedFiles = Server::getConfig($this->modulesDir, 'protectedFiles'); - foreach ($protectedFiles as &$protectedFile) { - $protectedFile = Filesystem::fsFit(root_path() . $protectedFile); - } - $moduleFile = Server::getFileList($this->modulesDir); - - foreach ($moduleFile as &$file) { - $moduleFilePath = Filesystem::fsFit($this->modulesDir . $file); - $file = Filesystem::fsFit(root_path() . $file); - if (!file_exists($file)) continue; - if (!file_exists($moduleFilePath)) { - if (!is_dir(dirname($moduleFilePath))) { - mkdir(dirname($moduleFilePath), 0755, true); - } - copy($file, $moduleFilePath); - } - - if (in_array($file, $protectedFiles)) { - continue; - } - if (file_exists($file)) { - unlink($file); - } - Filesystem::delEmptyDir(dirname($file)); - } - - if ($zipDir) { - $unrecoverableFiles = [ - Filesystem::fsFit(root_path() . 'composer.json'), - Filesystem::fsFit(root_path() . 'web/package.json'), - Filesystem::fsFit(root_path() . 'web-nuxt/package.json'), - ]; - foreach ( - new RecursiveIteratorIterator( - new RecursiveDirectoryIterator($zipDir, FilesystemIterator::SKIP_DOTS), - RecursiveIteratorIterator::SELF_FIRST - ) as $item - ) { - $backupsFile = Filesystem::fsFit(root_path() . str_replace($zipDir, '', $item->getPathname())); - - if (in_array($backupsFile, $moduleFile) && !in_array($backupsFile, $protectedFiles)) { - continue; - } - - if ($item->isDir()) { - if (!is_dir($backupsFile)) { - mkdir($backupsFile, 0755, true); - } - } elseif (!in_array($backupsFile, $unrecoverableFiles)) { - copy($item->getPathname(), $backupsFile); - } - } - } - - if ($zipDir && is_dir($zipDir)) { - Filesystem::delDir($zipDir); - } - - Server::uninstallWebBootstrap($this->uid); - - $this->setInfo([ - 'state' => self::DISABLE, - ]); - - if ($update) { - throw new Exception('update', -3, [ - 'uid' => $this->uid, - ]); - } - - if (!empty($dependWaitInstall)) { - throw new Exception('dependent wait install', -2, [ - 'uid' => $this->uid, - 'wait_install' => $dependWaitInstall, - ]); - } - return $info; - } - - /** - * 处理依赖和文件冲突 - * @throws Throwable - */ - public function conflictHandle(string $trigger): bool - { - $info = $this->getInfo(); - if (!in_array($info['state'] ?? 0, [self::WAIT_INSTALL, self::CONFLICT_PENDING])) { - return false; - } - $fileConflict = Server::getFileList($this->modulesDir, true); - $dependConflict = Server::dependConflictCheck($this->modulesDir); - $installFiles = Server::getFileList($this->modulesDir); - $depends = Server::getDepend($this->modulesDir); - - $coverFiles = []; - $discardFiles = []; - $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'); - - $req = function_exists('request') ? request() : null; - $extend = $req ? ($req->post('extend') ?? []) : []; - - if ($fileConflict || !self::isEmptyArray($dependConflict)) { - if (!$extend) { - $fileConflictTemp = []; - foreach ($fileConflict as $key => $item) { - $fileConflictTemp[$key] = [ - 'newFile' => $this->uid . DIRECTORY_SEPARATOR . $item, - 'oldFile' => $item, - 'solution' => 'cover', - ]; - } - $dependConflictTemp = []; - foreach ($dependConflict as $env => $item) { - $dev = stripos($env, 'dev') !== false; - foreach ($item as $depend => $v) { - $oldDepend = ''; - if (in_array($env, ['require', 'require-dev'])) { - $oldDepend = $depend . ' ' . $serverDep->hasDepend($depend, $dev); - } elseif (in_array($env, ['dependencies', 'devDependencies'])) { - $oldDepend = $depend . ' ' . $webDep->hasDepend($depend, $dev); - } elseif (in_array($env, ['nuxtDependencies', 'nuxtDevDependencies'])) { - $oldDepend = $depend . ' ' . $webNuxtDep->hasDepend($depend, $dev); - } - $dependConflictTemp[] = [ - 'env' => $env, - 'newDepend' => $depend . ' ' . $v, - 'oldDepend' => $oldDepend, - 'depend' => $depend, - 'solution' => 'cover', - ]; - } - } - $this->setInfo([ - 'state' => self::CONFLICT_PENDING, - ]); - throw new Exception('Module file conflicts', -1, [ - 'fileConflict' => $fileConflictTemp, - 'dependConflict' => $dependConflictTemp, - 'uid' => $this->uid, - 'state' => self::CONFLICT_PENDING, - ]); - } - - if ($fileConflict && isset($extend['fileConflict'])) { - foreach ($installFiles as $ikey => $installFile) { - if (isset($extend['fileConflict'][$installFile])) { - if ($extend['fileConflict'][$installFile] == 'discard') { - $discardFiles[] = $installFile; - unset($installFiles[$ikey]); - } else { - $coverFiles[] = $installFile; - } - } - } - } - if (!self::isEmptyArray($dependConflict) && isset($extend['dependConflict'])) { - foreach ($depends as $fKey => $fItem) { - foreach ($fItem as $cKey => $cItem) { - if (isset($extend['dependConflict'][$fKey][$cKey])) { - if ($extend['dependConflict'][$fKey][$cKey] == 'discard') { - unset($depends[$fKey][$cKey]); - } - } - } - } - } - } - - if ($depends) { - foreach ($depends as $key => $item) { - if (!$item) continue; - if ($key == 'require' || $key == 'require-dev') { - $coverFiles[] = 'composer.json'; - continue; - } - if ($key == 'dependencies' || $key == 'devDependencies') { - $coverFiles[] = 'web' . DIRECTORY_SEPARATOR . 'package.json'; - } - if ($key == 'nuxtDependencies' || $key == 'nuxtDevDependencies') { - $coverFiles[] = 'web-nuxt' . DIRECTORY_SEPARATOR . 'package.json'; - } - } - } - - if ($coverFiles) { - $backupsZip = $trigger == 'install' ? $this->backupsDir . $this->uid . '-install.zip' : $this->backupsDir . $this->uid . '-cover-' . date('YmdHis') . '.zip'; - Filesystem::zip($coverFiles, $backupsZip); - } - - if ($depends) { - $npm = false; - $composer = false; - $nuxtNpm = false; - - $composerConfig = Server::getConfig($this->modulesDir, 'composerConfig'); - if ($composerConfig) { - $serverDep->setComposerConfig($composerConfig); - } - - foreach ($depends as $key => $item) { - if (!$item) continue; - if ($key == 'require') { - $composer = true; - $serverDep->addDepends($item, false, true); - } elseif ($key == 'require-dev') { - $composer = true; - $serverDep->addDepends($item, true, true); - } elseif ($key == 'dependencies') { - $npm = true; - $webDep->addDepends($item, false, true); - } elseif ($key == 'devDependencies') { - $npm = true; - $webDep->addDepends($item, true, true); - } elseif ($key == 'nuxtDependencies') { - $nuxtNpm = true; - $webNuxtDep->addDepends($item, false, true); - } elseif ($key == 'nuxtDevDependencies') { - $nuxtNpm = true; - $webNuxtDep->addDepends($item, true, true); - } - } - if ($npm) { - $info['npm_dependent_wait_install'] = 1; - $info['state'] = self::DEPENDENT_WAIT_INSTALL; - } - if ($composer) { - $info['composer_dependent_wait_install'] = 1; - $info['state'] = self::DEPENDENT_WAIT_INSTALL; - } - if ($nuxtNpm) { - $info['nuxt_npm_dependent_wait_install'] = 1; - $info['state'] = self::DEPENDENT_WAIT_INSTALL; - } - $info = $info ?? $this->getInfo(); - if (($info['state'] ?? 0) != self::DEPENDENT_WAIT_INSTALL) { - $this->setInfo(['state' => self::INSTALLED]); - } else { - $this->setInfo([], $info); - } - } else { - $this->setInfo(['state' => self::INSTALLED]); - } - - $overwriteDir = Server::getOverwriteDir(); - foreach ($overwriteDir as $dirItem) { - $baseDir = $this->modulesDir . $dirItem; - $destDir = root_path() . $dirItem; - if (!is_dir($baseDir)) continue; - - foreach ( - new RecursiveIteratorIterator( - new RecursiveDirectoryIterator($baseDir, FilesystemIterator::SKIP_DOTS), - RecursiveIteratorIterator::SELF_FIRST - ) as $item - ) { - $destDirItem = Filesystem::fsFit($destDir . DIRECTORY_SEPARATOR . str_replace($baseDir, '', $item->getPathname())); - if ($item->isDir()) { - Filesystem::mkdir($destDirItem); - } elseif (!in_array(str_replace(root_path(), '', $destDirItem), $discardFiles)) { - Filesystem::mkdir(dirname($destDirItem)); - copy($item->getPathname(), $destDirItem); - } - } - if (config('buildadmin.module_pure_install', false)) { - Filesystem::delDir($baseDir); - } - } - return true; - } - - /** - * 依赖升级处理 - * @throws Throwable - */ - public function dependUpdateHandle(): void - { - $info = $this->getInfo(); - if (($info['state'] ?? 0) == self::DEPENDENT_WAIT_INSTALL) { - $waitInstall = []; - if (isset($info['composer_dependent_wait_install'])) { - $waitInstall[] = 'composer_dependent_wait_install'; - } - if (isset($info['npm_dependent_wait_install'])) { - $waitInstall[] = 'npm_dependent_wait_install'; - } - if (isset($info['nuxt_npm_dependent_wait_install'])) { - $waitInstall[] = 'nuxt_npm_dependent_wait_install'; - } - if ($waitInstall) { - throw new Exception('dependent wait install', -2, [ - 'uid' => $this->uid, - 'state' => self::DEPENDENT_WAIT_INSTALL, - 'wait_install' => $waitInstall, - ]); - } else { - $this->setInfo(['state' => self::INSTALLED]); - } - } - } - - /** - * 依赖安装完成标记 - * @throws Throwable - */ - public function dependentInstallComplete(string $type): void - { - $info = $this->getInfo(); - if (($info['state'] ?? 0) == self::DEPENDENT_WAIT_INSTALL) { - if ($type == 'npm') { - unset($info['npm_dependent_wait_install']); - } - if ($type == 'nuxt_npm') { - unset($info['nuxt_npm_dependent_wait_install']); - } - if ($type == 'composer') { - unset($info['composer_dependent_wait_install']); - } - if ($type == 'all') { - unset($info['npm_dependent_wait_install'], $info['composer_dependent_wait_install'], $info['nuxt_npm_dependent_wait_install']); - } - if (!isset($info['npm_dependent_wait_install']) && !isset($info['composer_dependent_wait_install']) && !isset($info['nuxt_npm_dependent_wait_install'])) { - $info['state'] = self::INSTALLED; - } - $this->setInfo([], $info); - } - } - - public function disableDependCheck(): array - { - $depend = Server::getDepend($this->modulesDir); - if (!$depend) return []; - - $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'); - - foreach ($depend as $key => $depends) { - $dev = stripos($key, 'dev') !== false; - if ($key == 'require' || $key == 'require-dev') { - foreach ($depends as $dependKey => $dependItem) { - if (!$serverDep->hasDepend($dependKey, $dev)) { - unset($depends[$dependKey]); - } - } - $depend[$key] = $depends; - } elseif ($key == 'dependencies' || $key == 'devDependencies') { - foreach ($depends as $dependKey => $dependItem) { - if (!$webDep->hasDepend($dependKey, $dev)) { - unset($depends[$dependKey]); - } - } - $depend[$key] = $depends; - } elseif ($key == 'nuxtDependencies' || $key == 'nuxtDevDependencies') { - foreach ($depends as $dependKey => $dependItem) { - if (!$webNuxtDep->hasDepend($dependKey, $dev)) { - unset($depends[$dependKey]); - } - } - $depend[$key] = $depends; - } - } - return $depend; - } - - /** - * 检查包是否完整 - * @throws Throwable - */ - public function checkPackage(): bool - { - if (!is_dir($this->modulesDir)) { - throw new Exception('Module package file does not exist'); - } - $info = $this->getInfo(); - $infoKeys = ['uid', 'title', 'intro', 'author', 'version', 'state']; - foreach ($infoKeys as $value) { - if (!array_key_exists($value, $info)) { - Filesystem::delDir($this->modulesDir); - throw new Exception('Basic configuration of the Module is incomplete'); - } - } - return true; - } - - public function getInfo(): array - { - return Server::getIni($this->modulesDir); - } - - /** - * @throws Throwable - */ - public function setInfo(array $kv = [], array $arr = []): bool - { - if ($kv) { - $info = $this->getInfo(); - foreach ($kv as $k => $v) { - $info[$k] = $v; - } - return Server::setIni($this->modulesDir, $info); - } - if ($arr) { - return Server::setIni($this->modulesDir, $arr); - } - throw new Exception('Parameter error'); - } - - public static function isEmptyArray($arr): bool - { - foreach ($arr as $item) { - if (is_array($item)) { - if (!self::isEmptyArray($item)) return false; - } elseif ($item) { - return false; - } - } - return true; - } - - public function setModuleUid(string $uid): static - { - $this->uid = $uid; - $this->modulesDir = $this->installDir . $uid . DIRECTORY_SEPARATOR; - return $this; - } - - /** - * 解析并校验模块包路径,仅允许 public/storage 下的 zip 文件,防止路径穿越 - * @throws Exception - */ - protected static function resolvePublicStorageFile(string $file): string - { - if (preg_match('#^(https?:)?//#i', $file)) { - $parsed = parse_url($file); - $file = $parsed['path'] ?? ''; - } - - $relative = ltrim(str_replace('\\', '/', $file), '/'); - if ($relative === '' || str_contains($relative, '..')) { - throw new Exception('Invalid file path'); - } - - $publicRoot = realpath(public_path()); - if ($publicRoot === false) { - throw new Exception('Invalid file path'); - } - - $candidate = Filesystem::fsFit($publicRoot . DIRECTORY_SEPARATOR . str_replace('/', DIRECTORY_SEPARATOR, $relative)); - $fullPath = realpath($candidate); - if ($fullPath === false || !is_file($fullPath)) { - throw new Exception('Zip file not found'); - } - - $publicPrefix = rtrim($publicRoot, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR; - if (!str_starts_with($fullPath, $publicPrefix)) { - throw new Exception('Invalid file path'); - } - - $storageRoot = realpath(public_path('storage')); - if ($storageRoot === false) { - throw new Exception('Invalid file path'); - } - $storagePrefix = rtrim($storageRoot, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR; - if (!str_starts_with($fullPath, $storagePrefix)) { - throw new Exception('Invalid file path'); - } - - if (strtolower(pathinfo($fullPath, PATHINFO_EXTENSION)) !== 'zip') { - throw new Exception('The uploaded file format is not allowed'); - } - - return $fullPath; - } -} diff --git a/app/admin/library/module/Server.php b/app/admin/library/module/Server.php deleted file mode 100644 index 6da5fd0..0000000 --- a/app/admin/library/module/Server.php +++ /dev/null @@ -1,551 +0,0 @@ -get(self::$apiBaseUrl . 'download', ['query' => array_merge(['uid' => $uid, 'server' => 1], $extend)]); - $body = $response->getBody(); - $content = $body->getContents(); - if ($content == '' || stripos($content, '系统发生错误') !== false) { - throw new Exception('package download failed', 0); - } - if (str_starts_with($content, '{')) { - $json = (array)json_decode($content, true); - throw new Exception($json['msg'], $json['code'], $json['data'] ?? []); - } - } catch (TransferException $e) { - throw new Exception('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"); - } - - /** - * 安装预检 - * @throws Throwable - */ - public static function installPreCheck(array $query = []): bool - { - try { - $client = get_ba_client(); - $response = $client->get(self::$apiBaseUrl . 'preCheck', ['query' => $query]); - $body = $response->getBody(); - $statusCode = $response->getStatusCode(); - $content = $body->getContents(); - if ($content == '' || stripos($content, '系统发生错误') !== false || $statusCode != 200) { - return true; - } - if (str_starts_with($content, '{')) { - $json = json_decode($content, true); - if ($json && $json['code'] == 0) { - throw new Exception($json['msg'], $json['code'], $json['data'] ?? []); - } - } - } catch (TransferException $e) { - throw new Exception('package check failed', 0, ['msg' => $e->getMessage()]); - } - return true; - } - - public static function getConfig(string $dir, $key = ''): array - { - $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, string $key = ''): array - { - 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; - } - - /** - * 依赖冲突检查 - * @throws Throwable - */ - 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 getFileList(string $dir, bool $onlyConflict = false): array - { - if (!is_dir($dir)) { - return []; - } - - $fileList = []; - $overwriteDir = self::getOverwriteDir(); - $moduleFileList = self::getRuntime($dir, 'files'); - - if ($moduleFileList) { - if ($onlyConflict) { - $excludeFile = ['info.ini']; - foreach ($moduleFileList as $file) { - $path = Filesystem::fsFit(str_replace($dir, '', $file['path'])); - $paths = explode(DIRECTORY_SEPARATOR, $path); - $overwriteFile = in_array($paths[0], $overwriteDir) ? root_path() . $path : $dir . $path; - if (is_file($overwriteFile) && !in_array($path, $excludeFile) && (filesize($overwriteFile) != $file['size'] || md5_file($overwriteFile) != $file['md5'])) { - $fileList[] = $path; - } - } - } else { - foreach ($overwriteDir as $item) { - $baseDir = $dir . $item; - foreach ($moduleFileList as $file) { - if (!str_starts_with($file['path'], $baseDir)) continue; - $fileList[] = Filesystem::fsFit(str_replace($dir, '', $file['path'])); - } - } - } - return $fileList; - } - - 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', - 'database', - 'extend', - 'modules', - 'public', - 'vendor', - 'web', - 'web-nuxt', - ]; - } - - public static function importSql(string $dir): bool - { - $sqlFile = $dir . 'install.sql'; - $tempLine = ''; - $prefix = config('thinkorm.connections.mysql.prefix', config('database.connections.mysql.prefix', '')); - if (is_file($sqlFile)) { - $lines = file($sqlFile); - foreach ($lines as $line) { - if (str_starts_with($line, '--') || $line == '' || str_starts_with($line, '/*')) { - continue; - } - - $tempLine .= $line; - if (str_ends_with(trim($line), ';')) { - $tempLine = str_ireplace('__PREFIX__', $prefix, $tempLine); - $tempLine = str_ireplace('INSERT INTO ', 'INSERT IGNORE INTO ', $tempLine); - try { - Db::execute($tempLine); - } catch (PDOException) { - // ignore - } - $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 getInstalledIds(string $dir): array - { - $installedIds = []; - $installed = self::installedList($dir); - foreach ($installed as $item) { - $installedIds[] = $item['uid']; - } - return $installedIds; - } - - public static function getIni(string $dir): array - { - $infoFile = $dir . 'info.ini'; - $info = []; - if (is_file($infoFile)) { - $info = parse_ini_file($infoFile, true, INI_SCANNER_TYPED) ?: []; - if (!$info) return []; - } - return $info; - } - - /** - * @throws Throwable - */ - 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); - } - $namespace = match ($type) { - 'controller' => '\\modules\\' . $name . '\\controller\\' . $class, - default => '\\modules\\' . $name . '\\' . $class, - }; - return class_exists($namespace) ? $namespace : ''; - } - - public static function execEvent(string $uid, string $event, array $params = []): void - { - $eventClass = self::getClass($uid); - if (class_exists($eventClass)) { - $handle = new $eventClass(); - if (method_exists($eventClass, $event)) { - $handle->$event($params); - } - } - } - - 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', - 'nuxtAppVueImport' => '/#web-nuxt\/app.vue import code start#([\s\S]*?)#web-nuxt\/app.vue import code end#/i', - 'nuxtAppVueStart' => '/#web-nuxt\/app.vue start code start#([\s\S]*?)#web-nuxt\/app.vue start 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; - } - - public static function installWebBootstrap(string $uid, string $dir): void - { - $bootstrapCode = self::analysisWebBootstrap($uid, $dir); - if (!$bootstrapCode) { - return; - } - - $webPath = root_path() . 'web' . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR; - $webNuxtPath = root_path() . 'web-nuxt' . DIRECTORY_SEPARATOR; - $filePaths = [ - 'mainTsImport' => $webPath . 'main.ts', - 'mainTsStart' => $webPath . 'main.ts', - 'appVueImport' => $webPath . 'App.vue', - 'appVueOnMounted' => $webPath . 'App.vue', - 'nuxtAppVueImport' => $webNuxtPath . 'app.vue', - 'nuxtAppVueStart' => $webNuxtPath . 'app.vue', - ]; - - $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'), - 'nuxtAppVueImport' => self::buildMarkStr('import-root-mark'), - 'nuxtAppVueStart' => self::buildMarkStr('start-root-mark'), - ]; - - foreach ($bootstrapCode as $key => $item) { - if ($item && isset($marks[$key]) && isset($filePaths[$key]) && is_file($filePaths[$key])) { - $content = file_get_contents($filePaths[$key]); - $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($filePaths[$key], $content); - } - } - } - } - - public static function uninstallWebBootstrap(string $uid): void - { - $webPath = root_path() . 'web' . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR; - $webNuxtPath = root_path() . 'web-nuxt' . DIRECTORY_SEPARATOR; - $filePaths = [ - 'mainTsImport' => $webPath . 'main.ts', - 'mainTsStart' => $webPath . 'main.ts', - 'appVueImport' => $webPath . 'App.vue', - 'appVueOnMounted' => $webPath . 'App.vue', - 'nuxtAppVueImport' => $webNuxtPath . 'app.vue', - 'nuxtAppVueStart' => $webNuxtPath . 'app.vue', - ]; - - $marksKey = [ - 'mainTsImport', - 'mainTsStart', - 'appVueImport', - 'appVueOnMounted', - 'nuxtAppVueImport', - 'nuxtAppVueStart', - ]; - - foreach ($marksKey as $item) { - if (!isset($filePaths[$item]) || !is_file($filePaths[$item])) { - continue; - } - $content = file_get_contents($filePaths[$item]); - $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 ?? false) !== false || ($moduleMultiLineMarkStartPos ?? false) !== false) { - file_put_contents($filePaths[$item], $content); - } - } - } - - public static function buildMarkStr(string $type, string $uid = '', string $extend = ''): string - { - $nonTabKeys = ['mti', 'avi', 'navi', 'navs']; - $extend = match ($extend) { - 'mainTsImport' => 'mti', - 'mainTsStart' => 'mts', - 'appVueImport' => 'avi', - 'appVueOnMounted' => 'avo', - 'nuxtAppVueImport' => 'navi', - 'nuxtAppVueStart' => 'navs', - default => '', - }; - return match ($type) { - 'import-root-mark' => '// modules import mark, Please do not remove.', - 'start-root-mark' => '// modules start mark, Please do not remove.', - 'onMounted-root-mark' => '// Modules onMounted mark, Please do not remove.', - 'module-line-mark' => ' // Code from module \'' . $uid . "'" . ($extend ? "($extend)" : ''), - 'module-multi-line-mark-start' => (in_array($extend, $nonTabKeys) ? '' : Helper::tab()) . "// Code from module '$uid' start" . ($extend ? "($extend)" : '') . "\n", - 'module-multi-line-mark-end' => (in_array($extend, $nonTabKeys) ? '' : Helper::tab()) . "// Code from module '$uid' end", - default => '', - }; - } - - public static function getNuxtVersion(): mixed - { - $nuxtPackageJsonPath = Filesystem::fsFit(root_path() . 'web-nuxt/package.json'); - if (is_file($nuxtPackageJsonPath)) { - $nuxtPackageJson = file_get_contents($nuxtPackageJsonPath); - $nuxtPackageJson = json_decode($nuxtPackageJson, true); - if ($nuxtPackageJson && isset($nuxtPackageJson['version'])) { - return $nuxtPackageJson['version']; - } - } - return false; - } - - public static function createRuntime(string $dir): void - { - $runtimeFilePath = $dir . '.runtime'; - $files = new RecursiveIteratorIterator( - new RecursiveDirectoryIterator($dir), RecursiveIteratorIterator::LEAVES_ONLY - ); - $filePaths = []; - foreach ($files as $file) { - if (!$file->isDir()) { - $pathName = $file->getPathName(); - if ($pathName == $runtimeFilePath) continue; - $filePaths[] = [ - 'path' => Filesystem::fsFit($pathName), - 'size' => filesize($pathName), - 'md5' => md5_file($pathName), - ]; - } - } - - file_put_contents($runtimeFilePath, json_encode([ - 'files' => $filePaths, - 'pure' => config('buildadmin.module_pure_install', false), - ])); - } - - public static function getRuntime(string $dir, string $key = ''): mixed - { - $runtimeFilePath = $dir . '.runtime'; - $runtimeContent = @file_get_contents($runtimeFilePath); - $runtimeContentArr = json_decode($runtimeContent, true); - if (!$runtimeContentArr) return []; - - if ($key) { - return $runtimeContentArr[$key] ?? []; - } - return $runtimeContentArr; - } -} diff --git a/app/api/controller/Install.php b/app/api/controller/Install.php deleted file mode 100644 index 3336c96..0000000 --- a/app/api/controller/Install.php +++ /dev/null @@ -1,774 +0,0 @@ - '8.2.0', - 'npm' => '9.8.1', - 'cnpm' => '7.1.0', - 'node' => '20.14.0', - 'yarn' => '1.2.0', - 'pnpm' => '6.32.13', - ]; - - /** - * 安装完成标记 - */ - static string $InstallationCompletionMark = 'install-end'; - - /** - * 命令执行窗口(exec 为 SSE 长连接,不会返回) - * @throws Throwable - */ - public function terminal(Request $request): Response - { - $this->setRequest($request); - if ($this->isInstallComplete()) { - return $this->error(__('The system has completed installation. If you need to reinstall, please delete the %file% file first', ['%file%' => 'public/' . self::$lockFileName])); - } - - (new Terminal())->exec(false); - return $this->success(); // unreachable: exec() blocks with SSE stream - } - - public function changePackageManager(Request $request): Response - { - $this->setRequest($request); - if ($this->isInstallComplete()) { - return $this->error(__('The system has completed installation. If you need to reinstall, please delete the %file% file first', ['%file%' => 'public/' . self::$lockFileName])); - } - - $newPackageManager = $request->post('manager', config('terminal.npm_package_manager')); - if (Terminal::changeTerminalConfig()) { - return $this->success('', [ - 'manager' => $newPackageManager - ]); - } - return $this->error(__('Failed to switch package manager. Please modify the configuration file manually:%s', ['config/terminal.php'])); - } - - /** - * 环境基础检查 - */ - public function envBaseCheck(Request $request): Response - { - $this->setRequest($request); - if ($this->isInstallComplete()) { - return $this->error(__('The system has completed installation. If you need to reinstall, please delete the %file% file first', ['%file%' => 'public/' . self::$lockFileName]), []); - } - if (($_ENV['DATABASE_TYPE'] ?? getenv('DATABASE_TYPE'))) { - return $this->error(__('The .env file with database configuration was detected. Please clean up and try again!')); - } - - // php版本-start - $phpVersion = phpversion(); - $phpBit = PHP_INT_SIZE == 8 ? self::X64 : self::X86; - $phpVersionCompare = Version::compare(self::$needDependentVersion['php'], $phpVersion); - if (!$phpVersionCompare) { - $phpVersionLink = [ - [ - 'name' => __('need') . ' >= ' . self::$needDependentVersion['php'], - 'type' => 'text' - ], - [ - 'name' => __('How to solve?'), - 'title' => __('Click to see how to solve it'), - 'type' => 'faq', - 'url' => 'https://doc.buildadmin.com/guide/install/preparePHP.html' - ] - ]; - } elseif ($phpBit != self::X64) { - $phpVersionLink = [ - [ - 'name' => __('need') . ' x64 PHP', - 'type' => 'text' - ], - [ - 'name' => __('How to solve?'), - 'title' => __('Click to see how to solve it'), - 'type' => 'faq', - 'url' => 'https://doc.buildadmin.com/guide/install/preparePHP.html' - ] - ]; - } - // php版本-end - - // 配置文件-start(分别检测目录和文件,便于定位问题) - $configDir = rtrim(config_path(), '/\\'); - $dbConfigFile = $configDir . DIRECTORY_SEPARATOR . self::$dbConfigFileName; - $configDirWritable = Filesystem::pathIsWritable($configDir); - $dbConfigWritable = Filesystem::pathIsWritable($dbConfigFile); - $configIsWritable = $configDirWritable && $dbConfigWritable; - if (!$configIsWritable) { - $configIsWritableLink = [ - [ - 'name' => __('View reason'), - 'title' => __('Click to view the reason'), - 'type' => 'faq', - 'url' => 'https://doc.buildadmin.com/guide/install/dirNoPermission.html' - ] - ]; - } - // 配置文件-end - - // public-start - $publicIsWritable = Filesystem::pathIsWritable(public_path()); - if (!$publicIsWritable) { - $publicIsWritableLink = [ - [ - 'name' => __('View reason'), - 'title' => __('Click to view the reason'), - 'type' => 'faq', - 'url' => 'https://doc.buildadmin.com/guide/install/dirNoPermission.html' - ] - ]; - } - // public-end - - // PDO-start - $phpPdo = extension_loaded("PDO") && extension_loaded('pdo_mysql'); - if (!$phpPdo) { - $phpPdoLink = [ - [ - 'name' => __('PDO extensions need to be installed'), - 'type' => 'text' - ], - [ - 'name' => __('How to solve?'), - 'title' => __('Click to see how to solve it'), - 'type' => 'faq', - 'url' => 'https://doc.buildadmin.com/guide/install/missingExtension.html' - ] - ]; - } - // PDO-end - - // GD2和freeType-start - $phpGd2 = extension_loaded('gd') && function_exists('imagettftext'); - if (!$phpGd2) { - $phpGd2Link = [ - [ - 'name' => __('The gd extension and freeType library need to be installed'), - 'type' => 'text' - ], - [ - 'name' => __('How to solve?'), - 'title' => __('Click to see how to solve it'), - 'type' => 'faq', - 'url' => 'https://doc.buildadmin.com/guide/install/gdFail.html' - ] - ]; - } - // GD2和freeType-end - - // proc_open - $phpProc = function_exists('proc_open') && function_exists('proc_close') && function_exists('proc_get_status'); - if (!$phpProc) { - $phpProcLink = [ - [ - 'name' => __('View reason'), - 'title' => __('proc_open or proc_close functions in PHP Ini is disabled'), - 'type' => 'faq', - 'url' => 'https://doc.buildadmin.com/guide/install/disablement.html' - ], - [ - 'name' => __('How to modify'), - 'title' => __('Click to view how to modify'), - 'type' => 'faq', - 'url' => 'https://doc.buildadmin.com/guide/install/disablement.html' - ], - [ - 'name' => __('Security assurance?'), - 'title' => __('Using the installation service correctly will not cause any potential security problems. Click to view the details'), - 'type' => 'faq', - 'url' => 'https://doc.buildadmin.com/guide/install/senior.html' - ], - ]; - } - // proc_open-end - - return $this->success('', [ - 'php_version' => [ - 'describe' => $phpVersion . " ($phpBit)", - 'state' => $phpVersionCompare && $phpBit == self::X64 ? self::$ok : self::$fail, - 'link' => $phpVersionLink ?? [], - ], - 'config_is_writable' => [ - 'describe' => $configIsWritable - ? self::writableStateDescribe(true) - : (self::writableStateDescribe(false) . ' [' . $configDir . ']'), - 'state' => $configIsWritable ? self::$ok : self::$fail, - 'link' => $configIsWritableLink ?? [] - ], - 'public_is_writable' => [ - 'describe' => self::writableStateDescribe($publicIsWritable), - 'state' => $publicIsWritable ? self::$ok : self::$fail, - 'link' => $publicIsWritableLink ?? [] - ], - 'php_pdo' => [ - 'describe' => $phpPdo ? __('already installed') : __('Not installed'), - 'state' => $phpPdo ? self::$ok : self::$fail, - 'link' => $phpPdoLink ?? [] - ], - 'php_gd2' => [ - 'describe' => $phpGd2 ? __('already installed') : __('Not installed'), - 'state' => $phpGd2 ? self::$ok : self::$fail, - 'link' => $phpGd2Link ?? [] - ], - 'php_proc' => [ - 'describe' => $phpProc ? __('Allow execution') : __('disabled'), - 'state' => $phpProc ? self::$ok : self::$warn, - 'link' => $phpProcLink ?? [] - ], - ]); - } - - /** - * npm环境检查 - */ - public function envNpmCheck(Request $request): Response - { - $this->setRequest($request); - if ($this->isInstallComplete()) { - return $this->error('', [], 2); - } - - $packageManager = $request->post('manager', 'none'); - - // npm - $npmVersion = Version::getVersion('npm'); - $npmVersionCompare = Version::compare(self::$needDependentVersion['npm'], $npmVersion); - if (!$npmVersionCompare || !$npmVersion) { - $npmVersionLink = [ - [ - 'name' => __('need') . ' >= ' . self::$needDependentVersion['npm'], - 'type' => 'text' - ], - [ - 'name' => __('How to solve?'), - 'title' => __('Click to see how to solve it'), - 'type' => 'faq', - 'url' => 'https://doc.buildadmin.com/guide/install/prepareNpm.html' - ] - ]; - } - - // 包管理器 - $pmVersionLink = []; - $pmVersion = __('nothing'); - $pmVersionCompare = false; - if (in_array($packageManager, ['npm', 'cnpm', 'pnpm', 'yarn'])) { - $pmVersion = Version::getVersion($packageManager); - $pmVersionCompare = Version::compare(self::$needDependentVersion[$packageManager], $pmVersion); - - if (!$pmVersion) { - $pmVersionLink[] = [ - 'name' => __('need') . ' >= ' . self::$needDependentVersion[$packageManager], - 'type' => 'text' - ]; - if ($npmVersionCompare) { - $pmVersionLink[] = [ - 'name' => __('Click Install %s', [$packageManager]), - 'title' => '', - 'type' => 'install-package-manager' - ]; - } else { - $pmVersionLink[] = [ - 'name' => __('Please install NPM first'), - 'type' => 'text' - ]; - } - } elseif (!$pmVersionCompare) { - $pmVersionLink[] = [ - 'name' => __('need') . ' >= ' . self::$needDependentVersion[$packageManager], - 'type' => 'text' - ]; - $pmVersionLink[] = [ - 'name' => __('Please upgrade %s version', [$packageManager]), - 'type' => 'text' - ]; - } - } elseif ($packageManager == 'ni') { - $pmVersionCompare = true; - } - - // nodejs - $nodejsVersion = Version::getVersion('node'); - $nodejsVersionCompare = Version::compare(self::$needDependentVersion['node'], $nodejsVersion); - if (!$nodejsVersionCompare || !$nodejsVersion) { - $nodejsVersionLink = [ - [ - 'name' => __('need') . ' >= ' . self::$needDependentVersion['node'], - 'type' => 'text' - ], - [ - 'name' => __('How to solve?'), - 'title' => __('Click to see how to solve it'), - 'type' => 'faq', - 'url' => 'https://doc.buildadmin.com/guide/install/prepareNodeJs.html' - ] - ]; - } - - return $this->success('', [ - 'npm_version' => [ - 'describe' => $npmVersion ?: __('Acquisition failed'), - 'state' => $npmVersionCompare ? self::$ok : self::$warn, - 'link' => $npmVersionLink ?? [], - ], - 'nodejs_version' => [ - 'describe' => $nodejsVersion ?: __('Acquisition failed'), - 'state' => $nodejsVersionCompare ? self::$ok : self::$warn, - 'link' => $nodejsVersionLink ?? [] - ], - 'npm_package_manager' => [ - 'describe' => $pmVersion ?: __('Acquisition failed'), - 'state' => $pmVersionCompare ? self::$ok : self::$warn, - 'link' => $pmVersionLink ?? [], - ] - ]); - } - - /** - * 测试数据库连接 - */ - public function testDatabase(Request $request): Response - { - $this->setRequest($request); - if ($this->isInstallComplete()) { - return $this->error(__('The system has completed installation. If you need to reinstall, please delete the %file% file first', ['%file%' => 'public/' . self::$lockFileName])); - } - $database = [ - 'hostname' => $request->post('hostname'), - 'username' => $request->post('username'), - 'password' => $request->post('password'), - 'hostport' => $request->post('hostport'), - 'database' => '', - ]; - - $conn = $this->connectDb($database); - if ($conn['code'] == 0) { - return $this->error($conn['msg']); - } - return $this->success('', [ - 'databases' => $conn['databases'] - ]); - } - - /** - * 系统基础配置 - * post请求=开始安装 - */ - public function baseConfig(Request $request): Response - { - $this->setRequest($request); - if ($this->isInstallComplete()) { - return $this->error(__('The system has completed installation. If you need to reinstall, please delete the %file% file first', ['%file%' => 'public/' . self::$lockFileName])); - } - - $envOk = $this->commandExecutionCheck(); - $rootPath = str_replace('\\', '/', root_path()); - $migrateCommand = 'php vendor/bin/phinx migrate'; - if ($request->isGet()) { - return $this->success('', [ - 'rootPath' => $rootPath, - 'executionWebCommand' => $envOk, - 'migrateCommand' => $migrateCommand, - ]); - } - - $connectData = $databaseParam = $request->only(['hostname', 'username', 'password', 'hostport', 'database', 'prefix']); - - // 数据库配置测试 - $connectData['database'] = ''; - $connect = $this->connectDb($connectData, true); - if ($connect['code'] == 0) { - return $this->error($connect['msg']); - } - - // 建立数据库 - if (!in_array($databaseParam['database'], $connect['databases'])) { - $sql = "CREATE DATABASE IF NOT EXISTS `{$databaseParam['database']}` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci"; - $connect['pdo']->exec($sql); - } - - // 写入数据库配置文件(thinkorm.php 使用 $env('database.xxx', 'default') 格式) - $dbConfigFile = config_path(self::$dbConfigFileName); - $dbConfigContent = @file_get_contents($dbConfigFile); - if ($dbConfigContent === false || $dbConfigContent === '') { - return $this->error(__('File has no write permission:%s', ['config/' . self::$dbConfigFileName])); - } - $callback = function ($matches) use ($databaseParam) { - $key = $matches[1]; - $value = (string) ($databaseParam[$key] ?? ''); - return "\$env('database.{$key}', '" . addslashes($value) . "')"; - }; - $dbConfigText = preg_replace_callback("/\\\$env\('database\.(hostname|database|username|password|hostport|prefix)',\s*'[^']*'\)/", $callback, $dbConfigContent); - $result = @file_put_contents($dbConfigFile, $dbConfigText); - if (!$result) { - return $this->error(__('File has no write permission:%s', ['config/' . self::$dbConfigFileName])); - } - - // 写入 .env 和 .env-example(仅使用 Dotenv 可解析的 DATABASE_XXX 格式,避免 [DATABASE] 导致解析失败) - $databaseBlock = "\n# Database\n" - . 'DATABASE_TYPE = mysql' . "\n" - . 'DATABASE_HOSTNAME = ' . $databaseParam['hostname'] . "\n" - . 'DATABASE_DATABASE = ' . $databaseParam['database'] . "\n" - . 'DATABASE_USERNAME = ' . $databaseParam['username'] . "\n" - . 'DATABASE_PASSWORD = ' . $databaseParam['password'] . "\n" - . 'DATABASE_HOSTPORT = ' . $databaseParam['hostport'] . "\n" - . 'DATABASE_CHARSET = utf8mb4' . "\n" - . 'DATABASE_PREFIX = ' . ($databaseParam['prefix'] ?? '') . "\n"; - foreach (['.env', '.env-example'] as $envName) { - $envFile = root_path() . $envName; - $envFileContent = is_file($envFile) ? @file_get_contents($envFile) : ''; - if ($envFileContent !== false) { - $cutPos = strlen($envFileContent); - foreach (['[DATABASE]', "\n# Database\n", "\n# 数据库", "\nDATABASE_DRIVER", "\nDATABASE_TYPE"] as $marker) { - $pos = stripos($envFileContent, $marker); - if ($pos !== false && $pos < $cutPos) { - $cutPos = $pos; - } - } - $envFileContent = rtrim(substr($envFileContent, 0, $cutPos)) . $databaseBlock; - $result = @file_put_contents($envFile, $envFileContent); - if (!$result && is_file($envFile)) { - return $this->error(__('File has no write permission:%s', ['%s' => $envName])); - } - } - } - - // 设置新的Token随机密钥key - $oldTokenKey = config('buildadmin.token.key'); - $newTokenKey = Random::build('alnum', 32); - $buildConfigFile = config_path(self::$buildConfigFileName); - $buildConfigContent = @file_get_contents($buildConfigFile); - if ($buildConfigContent === false || $buildConfigContent === '') { - return $this->error(__('File has no write permission:%s', ['config/' . self::$buildConfigFileName])); - } - $buildConfigContent = preg_replace("/'key'(\s+)=>(\s+)'$oldTokenKey'/", "'key'\$1=>\$2'$newTokenKey'", $buildConfigContent); - $result = @file_put_contents($buildConfigFile, $buildConfigContent); - if (!$result) { - return $this->error(__('File has no write permission:%s', ['config/' . self::$buildConfigFileName])); - } - - // 建立安装锁文件 - $result = @file_put_contents(public_path(self::$lockFileName), date('Y-m-d H:i:s')); - if (!$result) { - return $this->error(__('File has no write permission:%s', ['public/' . self::$lockFileName])); - } - - // 自动执行数据库迁移(无需手动运行 phinx 命令) - $migrateResult = $this->runPhinxMigrate($databaseParam); - if ($migrateResult !== true) { - return $this->error($migrateResult); - } - - return $this->success('', [ - 'rootPath' => $rootPath, - 'executionWebCommand' => $envOk, - 'migrateCommand' => $migrateCommand, - 'migrationCompleted' => true, - ]); - } - - /** - * 程序化执行 Phinx 数据库迁移 - * @param array $databaseParam 数据库连接参数 - * @return true|string 成功返回 true,失败返回错误信息 - */ - private function runPhinxMigrate(array $databaseParam): true|string - { - try { - $baseDir = root_path(); - $phinxConfigPath = $baseDir . 'phinx.php'; - - if (!is_file($phinxConfigPath)) { - return __('Failed to install SQL execution:%msg%', ['%msg%' => 'phinx.php not found']); - } - - // 临时设置环境变量,供 phinx 读取数据库配置 - $_ENV['DATABASE_HOSTNAME'] = $databaseParam['hostname'] ?? '127.0.0.1'; - $_ENV['DATABASE_DATABASE'] = $databaseParam['database'] ?? ''; - $_ENV['DATABASE_USERNAME'] = $databaseParam['username'] ?? 'root'; - $_ENV['DATABASE_PASSWORD'] = $databaseParam['password'] ?? ''; - $_ENV['DATABASE_HOSTPORT'] = $databaseParam['hostport'] ?? '3306'; - $_ENV['DATABASE_PREFIX'] = $databaseParam['prefix'] ?? ''; - putenv('DATABASE_HOSTNAME=' . $_ENV['DATABASE_HOSTNAME']); - putenv('DATABASE_DATABASE=' . $_ENV['DATABASE_DATABASE']); - putenv('DATABASE_USERNAME=' . $_ENV['DATABASE_USERNAME']); - putenv('DATABASE_PASSWORD=' . $_ENV['DATABASE_PASSWORD']); - putenv('DATABASE_HOSTPORT=' . $_ENV['DATABASE_HOSTPORT']); - putenv('DATABASE_PREFIX=' . $_ENV['DATABASE_PREFIX']); - - $config = PhinxConfig::fromPhp($phinxConfigPath); - $input = new ArrayInput([]); - $output = new NullOutput(); - $manager = new PhinxManager($config, $input, $output); - - $environment = $config->getDefaultEnvironment(); - $manager->migrate($environment); - return true; - } catch (Throwable $e) { - $msg = $e->getMessage(); - if ($e->getPrevious()) { - $msg .= ' | ' . $e->getPrevious()->getMessage(); - } - return __('Failed to install SQL execution:%msg%', ['%msg%' => $msg]); - } - } - - protected function isInstallComplete(): bool - { - return is_system_installed(); - } - - /** - * 标记命令执行完毕 - * @throws Throwable - */ - public function commandExecComplete(Request $request): Response - { - $this->setRequest($request); - if ($this->isInstallComplete()) { - return $this->error(__('The system has completed installation. If you need to reinstall, please delete the %file% file first', ['%file%' => 'public/' . self::$lockFileName])); - } - - $param = $request->only(['type', 'adminname', 'adminpassword', 'sitename']); - if ($param['type'] == 'web') { - $result = @file_put_contents(public_path(self::$lockFileName), self::$InstallationCompletionMark); - if (!$result) { - return $this->error(__('File has no write permission:%s', ['public/' . self::$lockFileName])); - } - } else { - // 管理员配置入库 - $adminModel = new AdminModel(); - $defaultAdmin = $adminModel->where('username', 'admin')->find(); - $defaultAdmin->username = $param['adminname']; - $defaultAdmin->nickname = ucfirst($param['adminname']); - $defaultAdmin->save(); - - if (isset($param['adminpassword']) && $param['adminpassword']) { - $adminModel->resetPassword($defaultAdmin->id, $param['adminpassword']); - } - - // 默认用户密码修改 - $user = new UserModel(); - $user->resetPassword(1, Random::build()); - - // 修改站点名称 - if (class_exists(\app\admin\model\Config::class)) { - \app\admin\model\Config::where('name', 'site_name')->update([ - 'value' => $param['sitename'] - ]); - } - } - return $this->success(); - } - - /** - * 获取命令执行检查的结果 - * @return bool 是否拥有执行命令的条件 - */ - private function commandExecutionCheck(): bool - { - $pm = config('terminal.npm_package_manager'); - if ($pm == 'none') { - return false; - } - $check['phpPopen'] = function_exists('proc_open') && function_exists('proc_close'); - $check['npmVersionCompare'] = Version::compare(self::$needDependentVersion['npm'], Version::getVersion('npm')); - $check['pmVersionCompare'] = Version::compare(self::$needDependentVersion[$pm], Version::getVersion($pm)); - $check['nodejsVersionCompare'] = Version::compare(self::$needDependentVersion['node'], Version::getVersion('node')); - - $envOk = true; - foreach ($check as $value) { - if (!$value) { - $envOk = false; - break; - } - } - return $envOk; - } - - /** - * 获取安装完成后的访问地址(根据请求来源区分 API 与前端开发模式) - * - 通过 API 访问(8787):index.html#/admin、index.html#/ - * - 通过前端开发服务访问(1818):/#/admin、/#/ - */ - public function accessUrls(Request $request): Response - { - $this->setRequest($request); - if ($this->isInstallComplete()) { - return $this->error(__('The system has completed installation. If you need to reinstall, please delete the %file% file first', ['%file%' => 'public/' . self::$lockFileName])); - } - $host = $request->header('host', '127.0.0.1:8787'); - $port = '8787'; - if (str_contains($host, ':')) { - $port = substr($host, strrpos($host, ':') + 1); - } - $scheme = $request->header('x-forwarded-proto', 'http'); - $base = rtrim($scheme . '://' . $host, '/'); - - if ($port === '1818') { - $adminUrl = $base . '/#/admin'; - $frontUrl = $base . '/#/'; - } else { - $adminUrl = $base . '/index.html#/admin'; - $frontUrl = $base . '/index.html#/'; - } - - return $this->success('', [ - 'adminUrl' => $adminUrl, - 'frontUrl' => $frontUrl, - ]); - } - - /** - * 安装指引 - */ - public function manualInstall(Request $request): Response - { - $this->setRequest($request); - if ($this->isInstallComplete()) { - return $this->error(__('The system has completed installation. If you need to reinstall, please delete the %file% file first', ['%file%' => 'public/' . self::$lockFileName])); - } - return $this->success('', [ - 'webPath' => str_replace('\\', '/', root_path() . 'web') - ]); - } - - public function mvDist(Request $request): Response - { - $this->setRequest($request); - if ($this->isInstallComplete()) { - return $this->error(__('The system has completed installation. If you need to reinstall, please delete the %file% file first', ['%file%' => 'public/' . self::$lockFileName])); - } - if (!is_file(root_path() . self::$distDir . DIRECTORY_SEPARATOR . 'index.html')) { - return $this->error(__('No built front-end file found, please rebuild manually!')); - } - - if (Terminal::mvDist()) { - return $this->success(); - } - return $this->error(__('Failed to move the front-end file, please move it manually!')); - } - - /** - * 目录是否可写 - * @param $writable - * @return string - */ - private static function writableStateDescribe($writable): string - { - return $writable ? __('Writable') : __('No write permission'); - } - - /** - * 数据库连接-获取数据表列表(使用 raw PDO) - * @param array $database hostname, hostport, username, password, database - * @param bool $returnPdo - * @return array - */ - private function connectDb(array $database, bool $returnPdo = false): array - { - $host = $database['hostname'] ?? '127.0.0.1'; - $port = $database['hostport'] ?? '3306'; - $user = $database['username'] ?? ''; - $pass = $database['password'] ?? ''; - $db = $database['database'] ?? ''; - - $dsn = "mysql:host={$host};port={$port};charset=utf8mb4"; - if ($db) { - $dsn .= ";dbname={$db}"; - } - - try { - $pdo = new \PDO($dsn, $user, $pass, [ - \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION, - \PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => true, - ]); - $pdo->query("SELECT 1")->fetchAll(\PDO::FETCH_ASSOC); - } catch (\PDOException $e) { - $errorMsg = mb_convert_encoding($e->getMessage() ?: 'unknown', 'UTF-8', 'UTF-8,GBK,GB2312,BIG5'); - $template = __('Database connection failed:%s'); - return [ - 'code' => 0, - 'msg' => strpos($template, '%s') !== false ? sprintf($template, $errorMsg) : $template . $errorMsg, - ]; - } - - $databases = []; - $databasesExclude = ['information_schema', 'mysql', 'performance_schema', 'sys']; - $stmt = $pdo->query("SHOW DATABASES"); - $rows = $stmt->fetchAll(\PDO::FETCH_ASSOC); - $stmt->closeCursor(); - foreach ($rows as $row) { - $dbName = $row['Database'] ?? $row['database'] ?? ''; - if ($dbName && !in_array($dbName, $databasesExclude)) { - $databases[] = $dbName; - } - } - - return [ - 'code' => 1, - 'msg' => '', - 'databases' => $databases, - 'pdo' => $returnPdo ? $pdo : null, - ]; - } -} diff --git a/app/api/lang/en/install.php b/app/api/lang/en/install.php deleted file mode 100644 index 6c1180c..0000000 --- a/app/api/lang/en/install.php +++ /dev/null @@ -1,44 +0,0 @@ - 'Install the controller', - 'need' => 'Need', - 'Click to see how to solve it' => 'Click to see how to solve.', - 'Please check the config directory permissions' => 'Please check the Config directory permissions', - 'Please check the public directory permissions' => 'Please check the Public directory permissions', - 'open' => 'Open', - 'close' => 'Close', - 'The installation can continue, and some operations need to be completed manually' => 'You can continue to install, and some operations need to be completed manually ', - 'Allow execution' => 'Allow execution', - 'disabled' => 'Disabled', - 'Allow operation' => 'Allow operation', - 'Acquisition failed' => 'Access failed', - 'Click Install %s' => 'Click Install %s', - 'Writable' => 'Writable', - 'No write permission' => 'No write permissions', - 'already installed' => 'Installed', - 'Not installed' => 'Not installed', - 'File has no write permission:%s' => 'File has no write permission:%s', - 'The system has completed installation. If you need to reinstall, please delete the %file% file first' => 'The system has been installed. If you need to reinstall, please delete the %file% file first.', - 'Database connection failed:%s' => 'Database connection failure:%s', - 'Failed to install SQL execution:%msg%' => 'Installation SQL execution failed: %msg%', - 'unknown' => 'Unknown', - 'Database does not exist' => 'Database does not exist!', - 'No built front-end file found, please rebuild manually!' => 'No built front-end file found, please rebuild manually.', - 'Failed to move the front-end file, please move it manually!' => 'Failed to move the front-end file, please move manually!', - 'How to solve?' => 'How to solve?', - 'View reason' => 'View reasons', - 'Click to view the reason' => 'Click to see the reason', - 'PDO extensions need to be installed' => 'pdo_mysql extensions need to be installed.', - 'proc_open or proc_close functions in PHP Ini is disabled' => 'proc_open and proc_close functions in PHP.Ini is disabled.', - 'How to modify' => 'How to modify?', - 'Click to view how to modify' => 'Click to see how to modify.', - 'Security assurance?' => 'Security assurance?', - 'Using the installation service correctly will not cause any potential security problems. Click to view the details' => 'The correct use of the installation service will not cause any potential security issues. Click to view the details.', - 'Please install NPM first' => 'Please install NPM first.', - 'Installation error:%s' => 'Installation error:%s', - 'Failed to switch package manager. Please modify the configuration file manually:%s' => 'Package manager switch failed, please modify the configuration file manually:%s.', - 'Please upgrade %s version' => 'Please upgrade the %s version', - 'nothing' => 'Nothing', - 'The gd extension and freeType library need to be installed' => 'The gd2 extension and freeType library need to be installed', - 'The .env file with database configuration was detected. Please clean up and try again!' => 'The .env file with database configuration was detected. Please clean up and try again!', -]; \ No newline at end of file diff --git a/app/api/lang/zh-cn/install.php b/app/api/lang/zh-cn/install.php deleted file mode 100644 index 472200c..0000000 --- a/app/api/lang/zh-cn/install.php +++ /dev/null @@ -1,44 +0,0 @@ - '安装控制器', - 'need' => '需要', - 'Click to see how to solve it' => '点击查看如何解决', - 'Please check the config directory permissions' => '请检查 config 目录权限', - 'Please check the public directory permissions' => '请检查 public 目录权限', - 'open' => '开启', - 'close' => '关闭', - 'The installation can continue, and some operations need to be completed manually' => '可以继续安装,部分操作需手动完成', - 'Allow execution' => '允许执行', - 'disabled' => '已禁用', - 'Allow operation' => '允许操作', - 'Acquisition failed' => '获取失败', - 'Click Install %s' => '点击安装%s', - 'Writable' => '可写', - 'No write permission' => '无写权限', - 'already installed' => '已安装', - 'Not installed' => '未安装', - 'File has no write permission:%s' => '文件无写入权限:%s', - 'The system has completed installation. If you need to reinstall, please delete the %file% file first' => '系统已完成安装。如果需要重新安装,请先删除 %file% 文件', - 'Database connection failed:%s' => '数据库连接失败:%s', - 'Failed to install SQL execution:%msg%' => '安装SQL执行失败:%msg%', - 'unknown' => '未知', - 'Database does not exist' => '数据库不存在!', - 'No built front-end file found, please rebuild manually!' => '没有找到构建好的前端文件,请手动重新构建!', - 'Failed to move the front-end file, please move it manually!' => '移动前端文件失败,请手动移动!', - 'How to solve?' => '如何解决?', - 'View reason' => '查看原因', - 'Click to view the reason' => '点击查看原因', - 'PDO extensions need to be installed' => '需要安装 pdo_mysql 扩展', - 'proc_open or proc_close functions in PHP Ini is disabled' => 'proc_open和proc_close函数在php.ini中被禁用掉了', - 'How to modify' => '如何修改', - 'Click to view how to modify' => '点击查看如何修改', - 'Security assurance?' => '安全保证?', - 'Using the installation service correctly will not cause any potential security problems. Click to view the details' => '安装服务使用正确不会造成任何潜在安全问题,点击查看详情', - 'Please install NPM first' => '请先安装npm', - 'Installation error:%s' => '安装出错:%s', - 'Failed to switch package manager. Please modify the configuration file manually:%s' => '包管理器切换失败,请手动修改配置文件:%s', - 'Please upgrade %s version' => '请升级%s版本', - 'nothing' => '无', - 'The gd extension and freeType library need to be installed' => '需要gd2扩展和freeType库', - 'The .env file with database configuration was detected. Please clean up and try again!' => '检测到带有数据库配置的 .env 文件。请清理后再试一次!', -]; \ No newline at end of file diff --git a/app/common/middleware/InstallGuard.php b/app/common/middleware/InstallGuard.php deleted file mode 100644 index d646728..0000000 --- a/app/common/middleware/InstallGuard.php +++ /dev/null @@ -1,29 +0,0 @@ - 'application/json'], json_encode([ - 'code' => 0, - 'msg' => __('The system has completed installation. If you need to reinstall, please delete the %file% file first', ['%file%' => 'public/install.lock']), - 'time' => time(), - 'data' => null, - ], JSON_UNESCAPED_UNICODE)); - } - - return $handler($request); - } -} diff --git a/app/functions.php b/app/functions.php index b20690e..83b77cf 100644 --- a/app/functions.php +++ b/app/functions.php @@ -636,8 +636,9 @@ if (!function_exists('get_account_verification_type')) { if ($configured) { $types[] = 'email'; } - if (class_exists(\app\admin\library\module\Server::class)) { - $sms = \app\admin\library\module\Server::getIni(\ba\Filesystem::fsFit(root_path() . 'modules/sms/')); + $smsIni = \ba\Filesystem::fsFit(root_path() . 'modules/sms/info.ini'); + if (is_file($smsIni)) { + $sms = parse_ini_file($smsIni, true, INI_SCANNER_TYPED) ?: []; if ($sms && ($sms['state'] ?? 0) == 1) { $types[] = 'mobile'; } @@ -646,17 +647,6 @@ if (!function_exists('get_account_verification_type')) { } } -if (!function_exists('is_system_installed')) { - /** - * 系统是否已完成安装(install.lock 内容为 install-end) - */ - function is_system_installed(): bool - { - $lockFile = public_path('install.lock'); - return is_file($lockFile) && @file_get_contents($lockFile) === 'install-end'; - } -} - if (!function_exists('get_area')) { function get_area($request = null): array { diff --git a/config/route.php b/config/route.php index d66910f..265ded8 100644 --- a/config/route.php +++ b/config/route.php @@ -8,55 +8,18 @@ use Webman\Route; use support\Response; -// ==================== 未安装时根路径重定向(迁移自 public/index.php) ==================== -// 当 install.lock 不存在或未完成安装时,访问 / 或 /index.html 重定向到安装页 -$installPageFile = public_path('install/index.html'); -Route::get('/', function () use ($installPageFile) { - $needRedirect = is_file($installPageFile) && !is_system_installed(); - if ($needRedirect) { - return new Response(302, ['Location' => '/install/']); - } +// ==================== 根路径 ==================== +Route::get('/', function () { if (is_file(public_path('index.html'))) { return new Response(302, ['Location' => '/index.html']); } return new Response(404, [], 'Not Found'); }); -Route::get('/index.html', function () use ($installPageFile) { - $needRedirect = is_file($installPageFile) && !is_system_installed(); - if ($needRedirect) { - return new Response(302, ['Location' => '/install/']); - } +Route::get('/index.html', function () { $file = public_path('index.html'); return is_file($file) ? (new Response())->file($file) : new Response(404, [], 'Not Found'); }); -// ==================== 安装向导(静态页) ==================== -// 已安装时访问 /install 重定向到应用,访问提示仅在终端显示 -Route::get('/install', function () { - $installed = is_system_installed(); - if ($installed && is_file(public_path('index.html'))) { - return new Response(302, ['Location' => '/index.html']); - } - $file = public_path('install/index.html'); - return is_file($file) ? (new Response())->file($file) : new Response(404, [], 'Install page not found'); -}); -Route::get('/install/', function () { - $installed = is_system_installed(); - if ($installed && is_file(public_path('index.html'))) { - return new Response(302, ['Location' => '/index.html']); - } - $file = public_path('install/index.html'); - return is_file($file) ? (new Response())->file($file) : new Response(404, [], 'Install page not found'); -}); -Route::get('/install/index', function () { - $installed = is_system_installed(); - if ($installed && is_file(public_path('index.html'))) { - return new Response(302, ['Location' => '/index.html']); - } - $file = public_path('install/index.html'); - return is_file($file) ? (new Response())->file($file) : new Response(404, [], 'Install page not found'); -}); - // ==================== API 路由 ==================== // api/index @@ -66,22 +29,7 @@ Route::get('/api/index/index', [\app\api\controller\Index::class, 'index']); Route::add(['GET', 'POST'], '/api/user/checkIn', [\app\api\controller\User::class, 'checkIn']); Route::post('/api/user/logout', [\app\api\controller\User::class, 'logout']); -// api/install(安装流程多为 POST;已安装系统由 InstallGuard 拦截) -Route::group('/api/install', function () { - Route::add(['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD'], '/terminal', [\app\api\controller\Install::class, 'terminal']); - Route::post('/changePackageManager', [\app\api\controller\Install::class, 'changePackageManager']); - Route::get('/envBaseCheck', [\app\api\controller\Install::class, 'envBaseCheck']); - Route::add(['GET', 'POST'], '/envNpmCheck', [\app\api\controller\Install::class, 'envNpmCheck']); - Route::post('/testDatabase', [\app\api\controller\Install::class, 'testDatabase']); - Route::add(['GET', 'POST'], '/baseConfig', [\app\api\controller\Install::class, 'baseConfig']); - Route::get('/accessUrls', [\app\api\controller\Install::class, 'accessUrls']); - Route::post('/commandExecComplete', [\app\api\controller\Install::class, 'commandExecComplete']); - Route::post('/manualInstall', [\app\api\controller\Install::class, 'manualInstall']); - Route::post('/mvDist', [\app\api\controller\Install::class, 'mvDist']); -})->middleware([\app\common\middleware\InstallGuard::class]); - // api/common -Route::get('/api/common/captcha', [\app\api\controller\Common::class, 'captcha']); Route::get('/api/common/clickCaptcha', [\app\api\controller\Common::class, 'clickCaptcha']); Route::post('/api/common/checkClickCaptcha', [\app\api\controller\Common::class, 'checkClickCaptcha']); Route::post('/api/common/refreshToken', [\app\api\controller\Common::class, 'refreshToken']); @@ -143,15 +91,6 @@ Route::get('/admin/dashboard/index', [\app\admin\controller\Dashboard::class, 'i // 兼容前端请求 /admin/Dashboard/* Route::get('/admin/Dashboard/index', [\app\admin\controller\Dashboard::class, 'index']); -// admin/module -Route::get('/admin/module/index', [\app\admin\controller\Module::class, 'index']); -Route::get('/admin/module/state', [\app\admin\controller\Module::class, 'state']); -Route::post('/admin/module/install', [\app\admin\controller\Module::class, 'install']); -Route::post('/admin/module/dependentInstallComplete', [\app\admin\controller\Module::class, 'dependentInstallComplete']); -Route::post('/admin/module/changeState', [\app\admin\controller\Module::class, 'changeState']); -Route::post('/admin/module/uninstall', [\app\admin\controller\Module::class, 'uninstall']); -Route::post('/admin/module/upload', [\app\admin\controller\Module::class, 'upload']); - // admin/ajax Route::post('/admin/ajax/upload', [\app\admin\controller\Ajax::class, 'upload']); Route::get('/admin/ajax/area', [\app\admin\controller\Ajax::class, 'area']); diff --git a/config/terminal.php b/config/terminal.php index feba899..5f8c633 100644 --- a/config/terminal.php +++ b/config/terminal.php @@ -16,12 +16,6 @@ return [ 'rollback' => ['cwd' => '', 'command' => 'php vendor/bin/phinx rollback'], 'breakpoint' => ['cwd' => '', 'command' => 'php vendor/bin/phinx breakpoint'], ], - 'install' => [ - 'cnpm' => 'npm install cnpm -g --registry=https://registry.npmmirror.com', - 'yarn' => 'npm install -g yarn', - 'pnpm' => 'npm install -g pnpm', - 'ni' => 'npm install -g @antfu/ni', - ], 'version' => [ 'npm' => 'npm -v', 'cnpm' => 'cnpm -v', @@ -29,13 +23,6 @@ return [ 'pnpm' => 'pnpm -v', 'node' => 'node -v', ], - 'test' => [ - 'npm' => ['cwd' => 'public/npm-install-test', 'command' => 'npm install'], - 'cnpm' => ['cwd' => 'public/npm-install-test', 'command' => 'cnpm install'], - 'yarn' => ['cwd' => 'public/npm-install-test', 'command' => 'yarn install'], - 'pnpm' => ['cwd' => 'public/npm-install-test', 'command' => 'pnpm install'], - 'ni' => ['cwd' => 'public/npm-install-test', 'command' => 'ni install'], - ], 'web-install' => [ 'npm' => ['cwd' => 'web', 'command' => 'npm install'], 'cnpm' => ['cwd' => 'web', 'command' => 'cnpm install'], diff --git a/extend/ba/Terminal.php b/extend/ba/Terminal.php index 32bac53..e180e3e 100644 --- a/extend/ba/Terminal.php +++ b/extend/ba/Terminal.php @@ -279,30 +279,13 @@ class Terminal $this->output('Build succeeded, but move file failed. Please operate manually.'); return false; } - } elseif ($commandPKey == 'web-install' && $this->extend && class_exists(\app\admin\library\module\Manage::class)) { - [$type, $value] = explode(':', $this->extend); - if ($type == 'module-install' && $value) { - \app\admin\library\module\Manage::instance($value)->dependentInstallComplete('npm'); - } - } elseif ($commandPKey == 'composer' && $this->extend && class_exists(\app\admin\library\module\Manage::class)) { - [$type, $value] = explode(':', $this->extend); - if ($type == 'module-install' && $value) { - \app\admin\library\module\Manage::instance($value)->dependentInstallComplete('composer'); - } - } elseif ($commandPKey == 'nuxt-install' && $this->extend && class_exists(\app\admin\library\module\Manage::class)) { - [$type, $value] = explode(':', $this->extend); - if ($type == 'module-install' && $value) { - \app\admin\library\module\Manage::instance($value)->dependentInstallComplete('nuxt_npm'); - } } return true; } public function beforeExecution(): void { - if ($this->commandKey == 'test.pnpm') { - @unlink(root_path() . 'public' . DIRECTORY_SEPARATOR . 'npm-install-test' . DIRECTORY_SEPARATOR . 'pnpm-lock.yaml'); - } elseif ($this->commandKey == 'web-install.pnpm') { + if ($this->commandKey == 'web-install.pnpm') { @unlink(root_path() . 'web' . DIRECTORY_SEPARATOR . 'pnpm-lock.yaml'); } } diff --git a/public/npm-install-test/package.json b/public/npm-install-test/package.json deleted file mode 100644 index 72603fc..0000000 --- a/public/npm-install-test/package.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "name": "npm-install-test", - "private": true, - "version": "0.0.1", - "scripts": { - "dev": "vite" - }, - "dependencies": { - "vue": "^3.2.25" - }, - "devDependencies": {} -} diff --git a/web/src/api/backend/module.ts b/web/src/api/backend/module.ts deleted file mode 100644 index 5fc9930..0000000 --- a/web/src/api/backend/module.ts +++ /dev/null @@ -1,190 +0,0 @@ -import { useBaAccount } from '/@/stores/baAccount' -import { useSiteConfig } from '/@/stores/siteConfig' -import createAxios from '/@/utils/axios' - -const storeUrl = '/api/v7.store/' -const moduleControllerUrl = '/admin/module/' - -export function index(params: anyObj = {}) { - return createAxios({ - url: moduleControllerUrl + 'index', - method: 'get', - params: params, - }) -} - -export function modules(params: anyObj = {}) { - const siteConfig = useSiteConfig() - return createAxios({ - url: siteConfig.apiUrl + storeUrl + 'modules', - method: 'get', - params: params, - }) -} - -export function info(params: anyObj) { - const baAccount = useBaAccount() - const siteConfig = useSiteConfig() - return createAxios( - { - url: siteConfig.apiUrl + storeUrl + 'info', - method: 'get', - params: params, - }, - { - anotherToken: baAccount.getToken('auth'), - } - ) -} - -export function createOrder(params: object = {}) { - const baAccount = useBaAccount() - const siteConfig = useSiteConfig() - return createAxios( - { - url: siteConfig.apiUrl + storeUrl + 'order', - method: 'post', - params: params, - }, - { - anotherToken: baAccount.getToken('auth'), - } - ) -} - -export function payOrder(orderId: number, payType: string) { - const baAccount = useBaAccount() - const siteConfig = useSiteConfig() - return createAxios( - { - url: siteConfig.apiUrl + storeUrl + 'pay', - method: 'post', - params: { - order_id: orderId, - pay_type: payType, - }, - }, - { - anotherToken: baAccount.getToken('auth'), - showSuccessMessage: true, - } - ) -} - -export function payCheck(sn: string) { - const baAccount = useBaAccount() - const siteConfig = useSiteConfig() - return createAxios( - { - url: siteConfig.apiUrl + '/api/pay/check', - method: 'get', - params: { - sn: sn, - }, - }, - { - anotherToken: baAccount.getToken('auth'), - showCodeMessage: false, - } - ) -} - -/** - * 获取模块的可安装版本列表 - */ -export function preDownload(data: anyObj) { - const baAccount = useBaAccount() - const siteConfig = useSiteConfig() - return createAxios( - { - url: siteConfig.apiUrl + storeUrl + 'preDownload', - method: 'POST', - data, - }, - { - anotherToken: baAccount.getToken('auth'), - } - ) -} - -export function getInstallState(uid: string) { - return createAxios({ - url: moduleControllerUrl + 'state', - method: 'get', - params: { - uid: uid, - }, - }) -} - -export function postInstallModule(uid: string, orderId: number, version: string, update: boolean, extend: anyObj = {}) { - const baAccount = useBaAccount() - return createAxios( - { - url: moduleControllerUrl + 'install', - method: 'POST', - data: { - uid, - update, - version, - orderId, - token: baAccount.getToken('auth'), - extend, - }, - timeout: 3000 * 10, - }, - { - showCodeMessage: false, - } - ) -} - -export function postUninstall(uid: string) { - return createAxios( - { - url: moduleControllerUrl + 'uninstall', - method: 'post', - params: { - uid: uid, - }, - }, - { - showSuccessMessage: true, - } - ) -} - -export function changeState(params: anyObj) { - return createAxios( - { - url: moduleControllerUrl + 'changeState', - method: 'post', - data: params, - }, - { - showCodeMessage: false, - } - ) -} - -export function dependentInstallComplete(uid: string) { - return createAxios({ - url: moduleControllerUrl + 'dependentInstallComplete', - method: 'post', - params: { - uid: uid, - }, - }) -} - -export function upload(file: string) { - const baAccount = useBaAccount() - return createAxios({ - url: moduleControllerUrl + 'upload', - method: 'post', - params: { - file: file, - token: baAccount.getToken('auth'), - }, - }) -} diff --git a/web/src/lang/autoload.ts b/web/src/lang/autoload.ts index c0bbe4e..4dd7887 100644 --- a/web/src/lang/autoload.ts +++ b/web/src/lang/autoload.ts @@ -7,7 +7,6 @@ import { adminBaseRoutePath } from '/@/router/static/adminBase' */ export default { '/': ['./frontend/${lang}/index.ts'], - [adminBaseRoutePath + '/moduleStore']: ['./backend/${lang}/module.ts'], [adminBaseRoutePath + '/user/rule']: ['./backend/${lang}/auth/rule.ts'], [adminBaseRoutePath + '/user/scoreLog']: ['./backend/${lang}/user/moneyLog.ts'], [adminBaseRoutePath + '/crud/crud']: ['./backend/${lang}/crud/log.ts', './backend/${lang}/crud/state.ts'], diff --git a/web/src/lang/backend/en/module.ts b/web/src/lang/backend/en/module.ts deleted file mode 100644 index 00ac529..0000000 --- a/web/src/lang/backend/en/module.ts +++ /dev/null @@ -1,163 +0,0 @@ -export default { - 'stateTitle init': 'Module installer initialization...', - 'stateTitle download': 'Downloading module...', - 'stateTitle install': 'Installing module...', - 'stateTitle getInstallableVersion': 'Get installable version...', - 'env require': 'Composer', - 'env require-dev': 'Composer-dev', - 'env dependencies': 'NPM', - 'env devDependencies': 'NPM-dev', - 'env nuxtDependencies': 'Nuxt NPM', - 'env nuxtDevDependencies': 'Nuxt NPM Dev', - // buy - 'Module installation warning': - 'Free download and update within one year after purchase. Virtual products do not support 7-day refund without reason', - 'Order title': 'Order title', - 'Order No': 'Order No.:', - 'Purchase user': 'Purchase user', - 'Order price': 'Order price', - 'Purchased, can be installed directly': 'Purchased, can be installed directly', - 'Understand and agree': 'Understand and agree', - 'Module purchase and use agreement': 'Module purchase and use agreement', - 'Point payment': 'Point payment', - 'Balance payment': 'Balance payment', - 'Wechat payment': 'Wechat payment', - 'Alipay payment': 'Alipay payment', - 'Install now': 'Install now', - payment: 'payment', - 'Confirm order info': 'Confirm order info', - // commonDone - 'Congratulations, module installation is complete': 'Congratulations, module installation is complete.', - 'Module is disabled': 'Module is disabled.', - 'Congratulations, the code of the module is ready': 'Congratulations, the code of the module is ready.', - 'Unknown state': 'Unknown state.', - 'Do not refresh the page!': 'Do not refresh the page!', - 'New adjustment of dependency detected': 'New adjustment of dependency detected', - 'This module adds new dependencies': 'This module adds new dependencies', - 'The built-in terminal of the system is automatically installing these dependencies, please wait~': - 'The built-in terminal of the system is automatically installing these dependencies, please wait~', - 'View progress': 'View progress', - 'Dependency installation completed~': 'Dependency installation completed~', - 'This module does not add new dependencies': 'This module does not add new dependencies.', - 'There is no adjustment for system dependency': 'There is no adjustment for system dependency.', - please: 'please', - 'After installation 1': 'After installation', - 'Manually clean up the system and browser cache': 'Manually clean up the system and browser cache.', - 'After installation 2': 'After installation', - 'Automatically execute reissue command?': 'Automatically execute reissue command?', - 'End of installation': 'End of installation', - 'Dependency installation fail 1': 'The dependency installation failed. Please click the retry button in the ', - 'Dependency installation fail 2': 'terminal', - 'Dependency installation fail 3': 'You can also view the ', - 'Dependency installation fail 4': 'unfinished matters manually', - 'Dependency installation fail 5': 'Until you are', - 'Dependency installation fail 6': 'sure that the dependency is ready', - 'Dependency installation fail 7': ', the module will not work!', - 'Is the command that failed on the WEB terminal executed manually or in other ways successfully?': - 'Is the command that failed on the WEB terminal executed manually or in other ways successfully?', - yes: 'yes', - no: 'no', - // confirmFileConflict - 'Update warning': - 'The following module files have been detected to be updated. When disabled, they will be automatically overwritten. Please pay attention to backup.', - 'File conflict': 'File conflict', - 'Conflict file': 'Conflict file', - 'Dependency conflict': 'Dependency conflict', - 'Confirm to disable the module': 'Confirm to disable the module', - 'The module declares the added dependencies': 'The module declares the added dependencies', - Dependencies: 'Dependencies', - retain: 'Retain', - // goodsInfo - 'detailed information': 'detailed information', - Price: 'Price', - 'Last updated': 'Last updated', - 'Published on': 'Published on:', - 'amount of downloads': 'amount of downloads', - 'Module classification': 'Module classification', - 'Module documentation': 'Module documentation', - 'Developer Homepage': 'Developer Homepage', - 'Click to access': 'Click to access', - 'Module status': 'Module status', - 'View demo': 'View demo', - 'Code scanning Preview': 'Code scanning Preview', - 'Buy now': 'Buy now', - 'continue installation': 'continue installation', - installed: 'installed', - 'to update': 'to update', - uninstall: 'uninstall', - 'Contact developer': 'Contact developer', - 'Other works of developers': 'Other works of developers', - 'There are no more works': 'There are no more works', - 'You need to disable this module before updating Do you want to disable it now?': - 'You need to disable this module before updating. Do you want to disable it now?', - 'Disable and update': 'Disable and update', - 'No module purchase order was found': 'No module purchase order was found. Do you want to purchase the current module now?', - // installConflict - 'new file': 'new file', - 'Existing files': 'Existing files', - 'Treatment scheme': 'Treatment scheme', - 'Backup and overwrite existing files': 'Backup and overwrite existing files', - 'Discard new file': 'Discard new file', - environment: 'environment', - 'New dependency': 'New dependency', - 'Existing dependencies': 'Existing dependencies', - 'Overwrite existing dependencies': 'Overwrite existing dependencies', - 'Do not use new dependencies': 'Do not use new dependencies', - // tableHeader - 'Upload zip package for installation': 'Upload zip package for installation', - 'Upload installation': 'Upload installation', - 'Uploaded / installed modules': 'Uploaded / installed modules', - 'Local module': 'Local module', - 'Publishing module': 'Publishing module', - 'Get points': 'Get points', - 'Search is actually very simple': 'Search is actually very simple', - // tabs - Loading: 'Loading...', - 'No more': 'No more.', - // uploadInstall - 'Local upload warning': - 'Please make sure that the module package file comes from the official channel or the officially certified module author, otherwise the system may be damaged because:', - 'The module can modify and add system files': 'The module can modify and add system files', - 'The module can execute sql commands and codes': 'The module can execute sql commands and codes', - 'The module can install new front and rear dependencies': 'The module can install new front and rear dependencies', - 'Drag the module package file here': 'Drag the module package file here, Or', - 'Click me to upload': 'Click me to upload', - 'Uploaded, installation is about to start, please wait': 'Uploaded, installation is about to start, please wait', - 'Update Log': 'Update Log', - 'No detailed update log': 'No detailed update log', - 'Use WeChat to scan QR code for payment': 'Use WeChat to scan QR code for payment', - 'Use Alipay to scan QR code for payment': 'Use Alipay to scan QR code for payment', - 'dependency-installation-fail-tips': - 'If the command is successfully executed manually, click `Make sure dependency is ready` above to change the module to the installed state', - 'New version': 'New version', - Install: 'Install', - 'Installation cancelled because module already exists!': 'Installation cancelled because module already exists!', - 'Installation cancelled because the directory required by the module is occupied!': - 'Installation cancelled because the directory required by the module is occupied!', - 'Installation complete': 'Installation complete', - 'A conflict is found Please handle it manually': 'A conflict is found. Please handle it manually', - 'Select Version': 'Select install version', - 'Wait for dependent installation': 'Wait for dependent installation', - 'The operation succeeds Please clear the system cache and refresh the browser ~': - 'The operation succeeds. Please clear the system cache and refresh the browser ~', - 'Deal with conflict': 'Deal with conflict', - 'Wait for installation': 'Wait for installation', - 'Conflict pending': 'Conflict pending', - 'Dependency to be installed': 'Dependency to be installed', - 'Restart Vite hot server': 'Restart Vite hot server', - 'Restart Vite hot server tips': - 'Before successfully restarting the service, you can find the button to manually restart the service from the button group on the right side of the top bar.', - 'Manual restart': 'Manual restart', - 'Restart Now': 'Restart Now', - // 选择安装版本 - 'Available system version': 'Available system version', - Description: 'Description', - Version: 'Version', - 'Current installed version': 'Current installed version', - 'Insufficient system version': 'Insufficient system version', - 'Click to install': 'Click to install', - 'Versions released beyond the authorization period': 'Versions released beyond the authorization period', - Renewal: 'Renewal', - 'Order expiration time': - 'The expiration time of the current order authorization is {expiration_time}, and the release time of this version is {create_time}', -} diff --git a/web/src/lang/backend/zh-cn/module.ts b/web/src/lang/backend/zh-cn/module.ts deleted file mode 100644 index 3d45453..0000000 --- a/web/src/lang/backend/zh-cn/module.ts +++ /dev/null @@ -1,153 +0,0 @@ -export default { - 'stateTitle init': '模块安装器初始化...', - 'stateTitle download': '正在下载模块...', - 'stateTitle install': '正在安装模块...', - 'stateTitle getInstallableVersion': '正在获取模块版本列表...', - 'env require': '后端依赖(composer)', - 'env require-dev': '后端开发环境依赖(composer)', - 'env dependencies': '前端依赖(NPM)', - 'env devDependencies': '前端开发环境依赖(NPM)', - 'env nuxtDependencies': '前端依赖(Nuxt-NPM)', - 'env nuxtDevDependencies': '前端开发环境依赖(Nuxt-NPM)', - // buy - 'Module installation warning': '购买后一年内可免费下载和更新,虚拟产品不支持7天无理由退款', - 'Order title': '订单标题', - 'Order No': '订单编号', - 'Purchase user': '购买用户', - 'Order price': '订单价格', - 'Purchased, can be installed directly': '已购买,可直接安装', - 'Understand and agree': '理解并同意', - 'Module purchase and use agreement': '模块购买和使用协议', - 'Point payment': '积分支付', - 'Balance payment': '余额支付', - 'Wechat payment': '微信支付', - 'Alipay payment': '支付宝支付', - 'Install now': '立即安装', - payment: '支付', - 'Confirm order info': '确认订单信息', - // commonDone - 'Congratulations, module installation is complete': '恭喜,模块安装已完成。', - 'Module is disabled': '模块已禁用。', - 'Congratulations, the code of the module is ready': '恭喜,模块的代码已经准备好了。', - 'Unknown state': '未知状态。', - 'Do not refresh the page!': '请勿刷新页面!', - 'New adjustment of dependency detected': '检测到依赖项有新的调整', - 'This module adds new dependencies': '本模块添加了新的依赖项', - 'The built-in terminal of the system is automatically installing these dependencies, please wait~': '系统内置终端正在自动安装这些依赖,请稍等~', - 'View progress': '查看进度', - 'Dependency installation completed~': '依赖已安装完成~', - 'This module does not add new dependencies': '本模块没有添加新的依赖项。', - 'There is no adjustment for system dependency': '系统依赖无调整。', - please: '请', - 'After installation 1': '在安装结束后', - 'Manually clean up the system and browser cache': '手动的清理系统和浏览器缓存。', - 'After installation 2': '安装结束后', - 'Automatically execute reissue command?': '自动执行重新发布命令?', - 'End of installation': '安装结束', - 'Dependency installation fail 1': '依赖安装失败,请点击', - 'Dependency installation fail 2': '终端', - 'Dependency installation fail 3': '中的重试按钮,您也可以查看', - 'Dependency installation fail 4': '手动完成未尽事宜', - 'Dependency installation fail 5': '在您', - 'Dependency installation fail 6': '确定依赖已准备好', - 'Dependency installation fail 7': '之前,模块还不能正常使用!', - 'Is the command that failed on the WEB terminal executed manually or in other ways successfully?': - 'WEB终端失败的命令已经手动或以其他方式执行成功?', - yes: '是', - no: '否', - // confirmFileConflict - 'Update warning': '检测到以下的模块文件有更新,禁用时将自动覆盖,请注意备份。', - 'File conflict': '文件冲突', - 'Conflict file': '冲突文件', - 'Dependency conflict': '依赖冲突', - 'Confirm to disable the module': '确认禁用模块', - 'The module declares the added dependencies': '模块声明添加的依赖', - Dependencies: '依赖项', - retain: '保留', - // goodsInfo - 'detailed information': '详细信息', - Price: '价格', - 'Last updated': '最后更新', - 'Published on': '发布时间', - 'amount of downloads': '下载次数', - 'Module classification': '模块分类', - 'Module documentation': '模块文档', - 'Developer Homepage': '开发者主页', - 'Click to access': '点击访问', - 'Module status': '模块状态', - 'View demo': '查看演示', - 'Code scanning Preview': '扫码预览', - 'Buy now': '立即购买', - 'continue installation': '继续安装', - installed: '已安装', - 'to update': '更新', - uninstall: '卸载', - 'Contact developer': '联系开发者', - 'Other works of developers': 'TA的其他作品', - 'There are no more works': '没有更多作品了', - 'You need to disable this module before updating Do you want to disable it now?': '更新前需要先禁用该模块,立即禁用?', - 'Disable and update': '禁用并更新', - 'No module purchase order was found': '没有找到有效的模块购买订单,是否立即购买当前模块?', - // installConflict - 'new file': '新文件', - 'Existing files': '已有文件', - 'Treatment scheme': '处理方案', - 'Backup and overwrite existing files': '备份并覆盖已有文件', - 'Discard new file': '丢弃新文件', - environment: '环境', - 'New dependency': '新依赖', - 'Existing dependencies': '已有依赖', - 'Overwrite existing dependencies': '覆盖已有依赖', - 'Do not use new dependencies': '不使用新依赖', - // tableHeader - 'Upload zip package for installation': '上传ZIP包安装', - 'Upload installation': '上传安装', - 'Uploaded / installed modules': '已上传/安装的模块', - 'Local module': '本地模块', - 'Publishing module': '发布模块', - 'Get points': '获得积分', - 'Search is actually very simple': '搜索其实很简单', - // tabs - Loading: '加载中...', - 'No more': '没有更多了...', - // uploadInstall - 'Local upload warning': '请您务必确认模块包文件来自官方渠道或经由官方认证的模块作者,否则系统可能被破坏,因为:', - 'The module can modify and add system files': '模块可以修改和新增系统文件', - 'The module can execute sql commands and codes': '模块可以执行sql命令和代码', - 'The module can install new front and rear dependencies': '模块可以安装新的前后端依赖', - 'Drag the module package file here': '拖拽模块包文件到此处或', - 'Click me to upload': '点击我上传', - 'Uploaded, installation is about to start, please wait': '已上传,即将开始安装,请稍等', - 'Update Log': '更新日志', - 'No detailed update log': '无详细更新日志', - 'Use WeChat to scan QR code for payment': '使用微信扫描二维码支付', - 'Use Alipay to scan QR code for payment': '使用支付宝扫描二维码支付', - 'dependency-installation-fail-tips': '若手动执行命令成功,可点击以上的 `确定依赖已准备好` 将模块修改为已安装状态。', - 'New version': '有新版本', - Install: '安装', - 'Installation cancelled because module already exists!': '安装取消,因为模块已经存在!', - 'Installation cancelled because the directory required by the module is occupied!': '安装取消,因为模块所需目录被占用!', - 'Installation complete': '安装完成', - 'A conflict is found Please handle it manually': '发现冲突,请手动处理', - 'Select Version': '选择安装版本', - 'Wait for dependent installation': '等待依赖安装', - 'The operation succeeds Please clear the system cache and refresh the browser ~': '操作成功,请清理系统缓存并刷新浏览器~', - 'Deal with conflict': '处理冲突', - 'Wait for installation': '等待安装', - 'Conflict pending': '冲突待处理', - 'Dependency to be installed': '依赖待安装', - 'Restart Vite hot server': '重启热更新服务', - 'Restart Vite hot server tips': '在完成服务重启之前,您还可以随时从顶栏右侧的按钮组中找到手动重启服务的按钮。', - 'Manual restart': '手动重启', - 'Restart Now': '立即重启', - // 选择安装版本 - 'Available system version': '可用系统版本', - Description: '描述', - Version: '版本', - 'Current installed version': '当前安装版本', - 'Insufficient system version': '系统版本不足', - 'Click to install': '点击安装', - 'Versions released beyond the authorization period': '授权期限以外发布的版本', - Renewal: '续费', - 'Order expiration time': '当前订单授权过期时间为 {expiration_time},此版本发布时间为 {create_time}', -} diff --git a/web/src/views/backend/module/components/buy.vue b/web/src/views/backend/module/components/buy.vue deleted file mode 100644 index 7123aeb..0000000 --- a/web/src/views/backend/module/components/buy.vue +++ /dev/null @@ -1,126 +0,0 @@ - - - - - diff --git a/web/src/views/backend/module/components/commonDialog.vue b/web/src/views/backend/module/components/commonDialog.vue deleted file mode 100644 index f626756..0000000 --- a/web/src/views/backend/module/components/commonDialog.vue +++ /dev/null @@ -1,73 +0,0 @@ - - - - - diff --git a/web/src/views/backend/module/components/commonDone.vue b/web/src/views/backend/module/components/commonDone.vue deleted file mode 100644 index a38afaa..0000000 --- a/web/src/views/backend/module/components/commonDone.vue +++ /dev/null @@ -1,288 +0,0 @@ - - - - - diff --git a/web/src/views/backend/module/components/commonSelectVersion.vue b/web/src/views/backend/module/components/commonSelectVersion.vue deleted file mode 100644 index 54fd514..0000000 --- a/web/src/views/backend/module/components/commonSelectVersion.vue +++ /dev/null @@ -1,134 +0,0 @@ - - - - - diff --git a/web/src/views/backend/module/components/confirmFileConflict.vue b/web/src/views/backend/module/components/confirmFileConflict.vue deleted file mode 100644 index 8f7f26e..0000000 --- a/web/src/views/backend/module/components/confirmFileConflict.vue +++ /dev/null @@ -1,90 +0,0 @@ - - - - - diff --git a/web/src/views/backend/module/components/goodsInfo.vue b/web/src/views/backend/module/components/goodsInfo.vue deleted file mode 100644 index d483d27..0000000 --- a/web/src/views/backend/module/components/goodsInfo.vue +++ /dev/null @@ -1,610 +0,0 @@ - - - - - diff --git a/web/src/views/backend/module/components/installConflict.vue b/web/src/views/backend/module/components/installConflict.vue deleted file mode 100644 index 72f8628..0000000 --- a/web/src/views/backend/module/components/installConflict.vue +++ /dev/null @@ -1,93 +0,0 @@ - - - - - diff --git a/web/src/views/backend/module/components/pay.vue b/web/src/views/backend/module/components/pay.vue deleted file mode 100644 index 9dc386a..0000000 --- a/web/src/views/backend/module/components/pay.vue +++ /dev/null @@ -1,150 +0,0 @@ - - - - - diff --git a/web/src/views/backend/module/components/tableHeader.vue b/web/src/views/backend/module/components/tableHeader.vue deleted file mode 100644 index 8fdc46a..0000000 --- a/web/src/views/backend/module/components/tableHeader.vue +++ /dev/null @@ -1,125 +0,0 @@ - - - - - diff --git a/web/src/views/backend/module/components/tabs.vue b/web/src/views/backend/module/components/tabs.vue deleted file mode 100644 index bc4694d..0000000 --- a/web/src/views/backend/module/components/tabs.vue +++ /dev/null @@ -1,168 +0,0 @@ - - - - - diff --git a/web/src/views/backend/module/components/uploadInstall.vue b/web/src/views/backend/module/components/uploadInstall.vue deleted file mode 100644 index 67a4513..0000000 --- a/web/src/views/backend/module/components/uploadInstall.vue +++ /dev/null @@ -1,71 +0,0 @@ - - - - - diff --git a/web/src/views/backend/module/index.ts b/web/src/views/backend/module/index.ts deleted file mode 100644 index 9ff0d07..0000000 --- a/web/src/views/backend/module/index.ts +++ /dev/null @@ -1,604 +0,0 @@ -import { ElNotification } from 'element-plus' -import { isArray } from 'lodash-es' -import { state } from './store' -import { moduleInstallState, type moduleState } from './types' -import { - changeState, - createOrder, - getInstallState, - index, - info, - modules, - payCheck, - payOrder, - postInstallModule, - preDownload, -} from '/@/api/backend/module' -import { i18n } from '/@/lang/index' -import router from '/@/router/index' -import { useBaAccount } from '/@/stores/baAccount' -import { SYSTEM_ZINDEX } from '/@/stores/constant/common' -import { taskStatus } from '/@/stores/constant/terminalTaskStatus' -import type { UserInfo } from '/@/stores/interface' -import { useTerminal } from '/@/stores/terminal' -import { fullUrl } from '/@/utils/common' -import { uuid } from '/@/utils/random' -import { changeListenDirtyFileSwitch, closeHotUpdate } from '/@/utils/vite' - -export const loadData = () => { - state.loading.table = true - if (!state.table.indexLoaded) { - loadIndex().then(() => { - getModules() - }) - } else { - getModules() - } -} - -export const onRefreshTableData = () => { - state.table.indexLoaded = false - for (const key in state.table.modulesEbak) { - state.table.modulesEbak[key] = undefined - } - loadData() -} - -const loadIndex = () => { - return index().then((res) => { - state.table.indexLoaded = true - state.sysVersion = res.data.sysVersion - state.nuxtVersion = res.data.nuxtVersion - state.installedModule = res.data.installed - - const installedModuleUids: string[] = [] - const installedModuleVersions: { uid: string; version: string }[] = [] - if (res.data.installed) { - state.installedModule.forEach((item) => { - installedModuleUids.push(item.uid) - installedModuleVersions.push({ - uid: item.uid, - version: item.version, - }) - }) - state.installedModuleUids = installedModuleUids - state.installedModuleVersions = installedModuleVersions - } - }) -} - -const getModules = () => { - if (typeof state.table.modulesEbak[state.table.params.activeTab] != 'undefined') { - state.table.modules[state.table.params.activeTab] = modulesOnlyLocalHandle(state.table.modulesEbak[state.table.params.activeTab]) - state.loading.table = false - return - } - const params: anyObj = {} - for (const key in state.table.params) { - if (state.table.params[key] != '') { - params[key] = state.table.params[key] - } - } - const moduleUids: string[] = [] - params['installed'] = state.installedModuleVersions - params['sysVersion'] = state.sysVersion - modules(params) - .then((res) => { - if (params.activeTab == 'all') { - res.data.rows.forEach((item: anyObj) => { - moduleUids.push(item.uid) - }) - - state.installedModule.forEach((item) => { - if (moduleUids.indexOf(item.uid) === -1) { - if (state.table.params.quickSearch) { - if (item.title.includes(state.table.params.quickSearch)) res.data.rows.push(item) - } else { - res.data.rows.push(item) - } - } - }) - } - - state.table.remark = res.data.remark - state.table.modulesEbak[params.activeTab] = res.data.rows.map((item: anyObj) => { - const idx = state.installedModuleUids.indexOf(item.uid) - if (idx !== -1) { - item.state = state.installedModule[idx].state - item.title = state.installedModule[idx].title - item.version = state.installedModule[idx].version - item.website = state.installedModule[idx].website - item.stateTag = moduleStatus(item.state) - - if (!isArray(item.tags)) item.tags = [] - item.tags.push({ - name: `${i18n.global.t('module.installed')} v${state.installedModule[idx].version}`, - type: 'primary', - }) - } else { - item.state = 0 - } - - if (item.new_version && item.tags) { - item.tags.push({ - name: i18n.global.t('module.New version'), - type: 'danger', - }) - } - - return item - }) - state.table.modules[params.activeTab] = modulesOnlyLocalHandle(state.table.modulesEbak[params.activeTab]) - state.table.category = res.data.category - }) - .finally(() => { - state.loading.table = false - }) -} - -export const showInfo = (uid: string) => { - state.dialog.goodsInfo = true - state.loading.goodsInfo = true - - const localItem = state.installedModule.find((item) => { - return item.uid == uid - }) - - info({ - uid: uid, - localVersion: localItem?.version, - sysVersion: state.sysVersion, - }) - .then((res) => { - if (localItem) { - if (res.data.info.type == 'local') { - res.data.info = localItem - res.data.info.images = [fullUrl('/static/images/local-module-logo.png')] - res.data.info.type = 'local' // 纯本地模块 - } else { - res.data.info.type = 'online' - res.data.info.state = localItem.state - res.data.info.version = localItem.version - } - res.data.info.enable = localItem.state === moduleInstallState.DISABLE ? false : true - } else { - res.data.info.state = 0 - res.data.info.type = 'online' - } - state.goodsInfo = res.data.info - }) - .catch((err) => { - if (loginExpired(err)) { - state.dialog.goodsInfo = false - } - }) - .finally(() => { - state.loading.goodsInfo = false - }) -} - -/** - * 支付订单 - * @param renew 是否是续费订单 - */ -export const onBuy = (renew = false) => { - state.dialog.buy = true - state.loading.buy = true - createOrder({ - goods_id: state.goodsInfo.id, - }) - .then((res) => { - state.loading.buy = false - state.buy.renew = renew - state.buy.info = res.data.info - }) - .catch((err) => { - state.dialog.buy = false - state.loading.buy = false - loginExpired(err) - }) -} - -export const onPay = (payType: 'score' | 'wx' | 'balance' | 'zfb') => { - state.common.payType = payType - state.loading.common = true - payOrder(state.buy.info.id, payType) - .then((res) => { - // 关闭其他弹窗 - state.dialog.buy = false - state.dialog.goodsInfo = false - - if (payType == 'wx' || payType == 'zfb') { - // 显示支付二维码 - state.dialog.pay = true - state.payInfo = res.data - - // 轮询获取支付状态 - const timer = setInterval(() => { - payCheck(state.payInfo.info.sn) - .then(() => { - state.payInfo.pay.status = 'success' - clearInterval(timer) - if (state.buy.renew) { - showInfo(res.data.info.uid) - } else { - onPreInstallModule(res.data.info.uid, res.data.info.id, true) - } - state.dialog.pay = false - }) - .catch(() => {}) - }, 3000) - } else { - if (state.buy.renew) { - showInfo(res.data.info.uid) - } else { - onPreInstallModule(res.data.info.uid, res.data.info.id, true) - } - } - }) - .catch((err) => { - loginExpired(err) - }) - .finally(() => { - state.loading.common = false - }) -} - -export const showCommonLoading = (loadingTitle: moduleState['common']['loadingTitle']) => { - state.common.type = 'loading' - state.common.loadingTitle = loadingTitle - state.common.loadingComponentKey = uuid() -} - -/** - * 模块预安装 - */ -export const onPreInstallModule = (uid: string, id: number, needGetInstallableVersion: boolean, update: boolean = false) => { - state.dialog.common = true - showCommonLoading('init') - state.common.dialogTitle = i18n.global.t('module.Install') - - const nextStep = (moduleState: number) => { - if (needGetInstallableVersion) { - // 获取模块版本列表 - showCommonLoading('getInstallableVersion') - preDownload({ - uid, - orderId: id, - sysVersion: state.sysVersion, - nuxtVersion: state.nuxtVersion, - installed: state.installedModuleUids, - }) - .then((res) => { - state.common.uid = uid - state.common.update = update - state.common.type = 'selectVersion' - state.common.dialogTitle = i18n.global.t('module.Select Version') - state.common.versions = res.data.versions - - // 关闭其他弹窗 - state.dialog.baAccount = false - state.dialog.buy = false - state.dialog.goodsInfo = false - }) - .catch((res) => { - if (loginExpired(res)) return - state.dialog.common = false - }) - } else { - // 立即安装(上传安装、继续安装) - showCommonLoading(moduleState === moduleInstallState.UNINSTALLED ? 'download' : 'install') - execInstall(uid, id, '', update) - - // 关闭其他弹窗 - state.dialog.baAccount = false - state.dialog.buy = false - state.dialog.goodsInfo = false - } - } - - if (update) { - nextStep(moduleInstallState.DISABLE) - } else { - // 获取安装状态 - getInstallState(uid).then((res) => { - if ( - res.data.state === moduleInstallState.INSTALLED || - res.data.state === moduleInstallState.DISABLE || - res.data.state === moduleInstallState.DIRECTORY_OCCUPIED - ) { - ElNotification({ - type: 'error', - message: - res.data.state === moduleInstallState.INSTALLED || res.data.state === moduleInstallState.DISABLE - ? i18n.global.t('module.Installation cancelled because module already exists!') - : i18n.global.t('module.Installation cancelled because the directory required by the module is occupied!'), - }) - state.dialog.common = false - return - } - - nextStep(res.data.state) - }) - } -} - -/** - * 执行安装请求,还包含启用、安装时的冲突处理 - */ -export const execInstall = (uid: string, id: number, version: string = '', update: boolean = false, extend: anyObj = {}) => { - postInstallModule(uid, id, version, update, extend) - .then(() => { - state.common.dialogTitle = i18n.global.t('module.Installation complete') - state.common.moduleState = moduleInstallState.INSTALLED - state.common.type = 'done' - onRefreshTableData() - }) - .catch((res) => { - if (loginExpired(res)) return - if (res.code == -1) { - state.common.uid = res.data.uid - state.common.type = 'installConflict' - state.common.dialogTitle = i18n.global.t('module.A conflict is found Please handle it manually') - state.common.fileConflict = res.data.fileConflict - state.common.dependConflict = res.data.dependConflict - } else if (res.code == -2) { - state.common.type = 'done' - state.common.uid = res.data.uid - state.common.dialogTitle = i18n.global.t('module.Wait for dependent installation') - state.common.moduleState = moduleInstallState.DEPENDENT_WAIT_INSTALL - state.common.waitInstallDepend = res.data.wait_install - state.common.dependInstallState = 'executing' - const terminal = useTerminal() - if (res.data.wait_install.includes('npm_dependent_wait_install')) { - terminal.addTaskPM('web-install', true, 'module-install:' + res.data.uid, (res: number) => { - terminalTaskExecComplete(res, 'npm_dependent_wait_install') - }) - } - if (res.data.wait_install.includes('nuxt_npm_dependent_wait_install')) { - terminal.addTaskPM('nuxt-install', true, 'module-install:' + res.data.uid, (res: number) => { - terminalTaskExecComplete(res, 'nuxt_npm_dependent_wait_install') - }) - } - if (res.data.wait_install.includes('composer_dependent_wait_install')) { - terminal.addTask('composer.update', true, 'module-install:' + res.data.uid, (res: number) => { - terminalTaskExecComplete(res, 'composer_dependent_wait_install') - }) - } - } else if (res.code == 0) { - ElNotification({ - type: 'error', - message: res.msg, - zIndex: SYSTEM_ZINDEX, - }) - state.dialog.common = false - onRefreshTableData() - } - }) - .finally(() => { - state.loading.common = false - }) -} - -const terminalTaskExecComplete = (res: number, type: string) => { - if (res == taskStatus.Success) { - state.common.waitInstallDepend = state.common.waitInstallDepend.filter((depend: string) => { - return depend != type - }) - if (state.common.waitInstallDepend.length == 0) { - state.common.dependInstallState = 'success' - - // 仅在命令全部执行完毕才刷新数据 - if (router.currentRoute.value.name === 'moduleStore/moduleStore') { - onRefreshTableData() - } - } - } else { - const terminal = useTerminal() - terminal.toggle(true) - state.common.dependInstallState = 'fail' - - // 有命令执行失败了,刷新一次数据 - if (router.currentRoute.value.name === 'moduleStore/moduleStore') { - onRefreshTableData() - } - } - - // 连续安装模块的情况中,首个模块的命令执行完毕时,自动启动了热更新 - if (router.currentRoute.value.name === 'moduleStore/moduleStore') { - closeHotUpdate('modules') - } -} - -export const onDisable = (confirmConflict = false) => { - state.loading.common = true - - // 拼装依赖处理方案 - if (confirmConflict) { - const dependConflict: anyObj = {} - for (const key in state.common.disableDependConflict) { - if (state.common.disableDependConflict[key]['solution'] != 'delete') { - continue - } - if (typeof dependConflict[state.common.disableDependConflict[key].env] == 'undefined') { - dependConflict[state.common.disableDependConflict[key].env] = [] - } - dependConflict[state.common.disableDependConflict[key].env].push(state.common.disableDependConflict[key].depend) - } - state.common.disableParams['confirmConflict'] = 1 - state.common.disableParams['dependConflictSolution'] = dependConflict - } - - changeState(state.common.disableParams) - .then(() => { - ElNotification({ - type: 'success', - message: i18n.global.t('module.The operation succeeds Please clear the system cache and refresh the browser ~'), - zIndex: SYSTEM_ZINDEX, - }) - state.dialog.common = false - onRefreshTableData() - }) - .catch((res) => { - if (res.code == -1) { - state.dialog.common = true - state.common.dialogTitle = i18n.global.t('module.Deal with conflict') - state.common.type = 'disableConfirmConflict' - state.common.disableDependConflict = res.data.dependConflict - if (res.data.conflictFile && res.data.conflictFile.length) { - const conflictFile = [] - for (const key in res.data.conflictFile) { - conflictFile.push({ - file: res.data.conflictFile[key], - }) - } - state.common.disableConflictFile = conflictFile - } - } else if (res.code == -2) { - state.dialog.common = true - const commandsData = { - type: 'disable', - commands: res.data.wait_install, - } - state.common.uid = state.goodsInfo.uid - execCommand(commandsData) - } else if (res.code == -3) { - // 更新 - onPreInstallModule(state.goodsInfo.uid, state.goodsInfo.purchased, true, true) - } else { - ElNotification({ - type: 'error', - message: res.msg, - zIndex: SYSTEM_ZINDEX, - }) - if (state.common.disableParams && state.common.disableParams.uid) { - showInfo(state.common.disableParams.uid) - } else { - onRefreshTableData() - } - } - }) - .finally(() => { - state.loading.common = false - }) -} - -export const onEnable = (uid: string) => { - state.loading.common = true - changeState({ - uid: uid, - state: 1, - }) - .then(() => { - state.dialog.common = true - showCommonLoading('init') - state.common.dialogTitle = i18n.global.t('Enable') - - execInstall(uid, 0) - state.dialog.goodsInfo = false - }) - .catch((res) => { - ElNotification({ - type: 'error', - message: res.msg, - zIndex: SYSTEM_ZINDEX, - }) - state.loading.common = false - }) -} - -export const loginExpired = (res: ApiResponse) => { - const baAccount = useBaAccount() - if (res.code == 301 || res.code == 408) { - baAccount.removeToken() - state.dialog.baAccount = true - return true - } - return false -} - -const modulesOnlyLocalHandle = (modules: anyObj) => { - if (!state.table.onlyLocal) return modules - return modules.filter((item: anyObj) => { - return item.installed - }) -} - -export const execCommand = (data: anyObj) => { - if (data.type == 'disable') { - state.dialog.common = true - state.common.type = 'done' - state.common.dialogTitle = i18n.global.t('module.Wait for dependent installation') - state.common.moduleState = moduleInstallState.DISABLE - state.common.dependInstallState = 'executing' - const terminal = useTerminal() - data.commands.forEach((item: anyObj) => { - state.common.waitInstallDepend.push(item.type) - if (item.pm) { - if (item.command == 'web-install') { - changeListenDirtyFileSwitch(false) - } - terminal.addTaskPM(item.command, true, '', (res: number) => { - terminalTaskExecComplete(res, item.type) - if (item.command == 'web-install') { - changeListenDirtyFileSwitch(true) - } - }) - } else { - terminal.addTask(item.command, true, '', (res: number) => { - terminalTaskExecComplete(res, item.type) - }) - } - }) - } -} - -export const specificUserName = (userInfo: Partial) => { - return userInfo.nickname + '(' + (userInfo.email || userInfo.mobile || 'ID:' + userInfo.id) + ')' -} - -export const currency = (price: number, val: number) => { - if (typeof price == 'undefined' || typeof val == 'undefined') { - return '-' - } - if (val == 0) { - return parseInt(price.toString()) + i18n.global.t('Integral') - } else { - return '¥' + price - } -} - -export const moduleStatus = (state: number) => { - switch (state) { - case moduleInstallState.INSTALLED: - return { - type: '', - text: i18n.global.t('module.installed'), - } - case moduleInstallState.WAIT_INSTALL: - return { - type: 'success', - text: i18n.global.t('module.Wait for installation'), - } - case moduleInstallState.CONFLICT_PENDING: - return { - type: 'danger', - text: i18n.global.t('module.Conflict pending'), - } - case moduleInstallState.DEPENDENT_WAIT_INSTALL: - return { - type: 'warning', - text: i18n.global.t('module.Dependency to be installed'), - } - case moduleInstallState.DISABLE: - return { - type: 'warning', - text: i18n.global.t('Disable'), - } - default: - return { - type: 'info', - text: i18n.global.t('Unknown'), - } - } -} diff --git a/web/src/views/backend/module/index.vue b/web/src/views/backend/module/index.vue deleted file mode 100644 index fde6ad7..0000000 --- a/web/src/views/backend/module/index.vue +++ /dev/null @@ -1,45 +0,0 @@ - - - - - diff --git a/web/src/views/backend/module/store.ts b/web/src/views/backend/module/store.ts deleted file mode 100644 index 4215a92..0000000 --- a/web/src/views/backend/module/store.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { reactive } from 'vue' -import { uuid } from '/@/utils/random' -import type { moduleState } from './types' - -export const state = reactive({ - loading: { - buy: false, - table: true, - common: false, - install: false, - goodsInfo: false, - }, - dialog: { - buy: false, - pay: false, - common: false, - goodsInfo: false, - baAccount: false, - }, - table: { - remark: '', - modules: [], - modulesEbak: [], - category: [], - onlyLocal: false, - indexLoaded: false, - params: { - quickSearch: '', - activeTab: 'all', - }, - }, - payInfo: {}, - goodsInfo: {}, - buy: { - info: {}, - renew: false, - agreement: true, - }, - common: { - uid: '', - moduleState: 0, - quickClose: false, - type: 'loading', - dialogTitle: '', - fileConflict: [], - dependConflict: [], - loadingTitle: 'init', - loadingComponentKey: uuid(), - waitInstallDepend: [], - dependInstallState: 'none', - disableConflictFile: [], - disableDependConflict: [], - disableParams: {}, - payType: 'wx', - update: false, - versions: [], - }, - sysVersion: '', - nuxtVersion: '', - installedModule: [], - installedModuleUids: [], - installedModuleVersions: [], -}) diff --git a/web/src/views/backend/module/types.ts b/web/src/views/backend/module/types.ts deleted file mode 100644 index d32a4c1..0000000 --- a/web/src/views/backend/module/types.ts +++ /dev/null @@ -1,78 +0,0 @@ -export enum moduleInstallState { - UNINSTALLED, - INSTALLED, - WAIT_INSTALL, - CONFLICT_PENDING, - DEPENDENT_WAIT_INSTALL, - DIRECTORY_OCCUPIED, - DISABLE, -} - -export interface moduleInfo { - uid: string - title: string - version: string - state: number - website: string - stateTag: { - type: string - text: string - } -} - -export interface moduleState { - loading: { - buy: boolean - table: boolean - common: boolean - install: boolean - goodsInfo: boolean - } - dialog: { - buy: boolean - pay: boolean - common: boolean - goodsInfo: boolean - baAccount: boolean - } - table: { - remark: string - modules: anyObj - modulesEbak: anyObj - category: anyObj - onlyLocal: boolean - indexLoaded: boolean - params: anyObj - } - payInfo: anyObj - goodsInfo: anyObj - buy: { - info: anyObj - renew: boolean - agreement: boolean - } - common: { - uid: string - moduleState: number - quickClose: boolean - type: 'loading' | 'installConflict' | 'done' | 'disableConfirmConflict' | 'uploadInstall' | 'selectVersion' - dialogTitle: string - fileConflict: anyObj[] - dependConflict: anyObj[] - loadingTitle: 'init' | 'download' | 'install' | 'getInstallableVersion' - loadingComponentKey: string - waitInstallDepend: string[] - dependInstallState: 'none' | 'executing' | 'success' | 'fail' - disableConflictFile: { file: string }[] - disableDependConflict: anyObj[] - disableParams: anyObj - payType: 'score' | 'wx' | 'balance' | 'zfb' - update: boolean - versions: anyObj[] - } - sysVersion: string - nuxtVersion: string - installedModule: moduleInfo[] - installedModuleUids: string[] - installedModuleVersions: { uid: string; version: string }[] -} diff --git a/web/vite.config.ts b/web/vite.config.ts index 03f4b01..f60d7c0 100644 --- a/web/vite.config.ts +++ b/web/vite.config.ts @@ -27,11 +27,10 @@ const viteConfig = ({ mode }: ConfigEnv): UserConfig => { server: { port: parseInt(VITE_PORT), open: VITE_OPEN != 'false', - // 开发时把 /api、/admin、/install 代理到 webman,避免跨域 + // 开发时把 /api、/admin 代理到 webman,避免跨域 proxy: { '/api': { target: VITE_PROXY_TARGET || 'http://localhost:6969', changeOrigin: true }, '/admin': { target: VITE_PROXY_TARGET || 'http://localhost:6969', changeOrigin: true }, - '/install': { target: VITE_PROXY_TARGET || 'http://localhost:6969', changeOrigin: true }, }, }, build: {