Manage.php 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965
  1. <?php
  2. namespace app\admin\library\module;
  3. use Throwable;
  4. use ba\Version;
  5. use ba\Depends;
  6. use ba\Exception;
  7. use ba\Filesystem;
  8. use FilesystemIterator;
  9. use think\facade\Config;
  10. use RecursiveDirectoryIterator;
  11. use RecursiveIteratorIterator;
  12. /**
  13. * 模块管理类
  14. */
  15. class Manage
  16. {
  17. public const UNINSTALLED = 0;
  18. public const INSTALLED = 1;
  19. public const WAIT_INSTALL = 2;
  20. public const CONFLICT_PENDING = 3;
  21. public const DEPENDENT_WAIT_INSTALL = 4;
  22. public const DIRECTORY_OCCUPIED = 5;
  23. public const DISABLE = 6;
  24. /**
  25. * @var ?Manage 对象实例
  26. */
  27. protected static ?Manage $instance = null;
  28. /**
  29. * @var string 安装目录
  30. */
  31. protected string $installDir;
  32. /**
  33. * @var string 备份目录
  34. */
  35. protected string $backupsDir;
  36. /**
  37. * @var string 模板唯一标识
  38. */
  39. protected string $uid;
  40. /**
  41. * @var string 模板根目录
  42. */
  43. protected string $modulesDir;
  44. /**
  45. * 初始化
  46. * @access public
  47. * @param string $uid
  48. * @return Manage
  49. */
  50. public static function instance(string $uid = ''): Manage
  51. {
  52. if (is_null(self::$instance)) {
  53. self::$instance = new static($uid);
  54. }
  55. return self::$instance;
  56. }
  57. public function __construct(string $uid)
  58. {
  59. $this->installDir = root_path() . 'modules' . DIRECTORY_SEPARATOR;
  60. $this->backupsDir = $this->installDir . 'backups' . DIRECTORY_SEPARATOR;
  61. if (!is_dir($this->installDir)) {
  62. mkdir($this->installDir, 0755, true);
  63. }
  64. if (!is_dir($this->backupsDir)) {
  65. mkdir($this->backupsDir, 0755, true);
  66. }
  67. if ($uid) {
  68. $this->uid = $uid;
  69. $this->modulesDir = $this->installDir . $uid . DIRECTORY_SEPARATOR;
  70. }
  71. }
  72. public function getInstallState()
  73. {
  74. if (!is_dir($this->modulesDir)) {
  75. return self::UNINSTALLED;
  76. }
  77. $info = $this->getInfo();
  78. if ($info && isset($info['state'])) {
  79. return $info['state'];
  80. }
  81. // 目录已存在,但非正常的模块
  82. return Filesystem::dirIsEmpty($this->modulesDir) ? self::UNINSTALLED : self::DIRECTORY_OCCUPIED;
  83. }
  84. /**
  85. * 下载模块文件
  86. * @param string $token 官网会员token
  87. * @param int $orderId 订单号
  88. * @return string 下载文件路径
  89. * @throws Throwable
  90. */
  91. public function download(string $token, int $orderId): string
  92. {
  93. if (!$orderId) {
  94. throw new Exception('Order not found');
  95. }
  96. // 下载 - 系统版本号要求、已安装模块的互斥和依赖检测
  97. $zipFile = Server::download($this->uid, $this->installDir, [
  98. 'sysVersion' => Config::get('buildadmin.version'),
  99. 'nuxtVersion' => Server::getNuxtVersion(),
  100. 'ba-user-token' => $token,
  101. 'order_id' => $orderId,
  102. 'installed' => Server::getInstalledIds($this->installDir),
  103. ]);
  104. // 删除旧版本代码
  105. Filesystem::delDir($this->modulesDir);
  106. // 解压
  107. Filesystem::unzip($zipFile);
  108. // 删除下载的zip
  109. @unlink($zipFile);
  110. // 检查是否完整
  111. $this->checkPackage();
  112. // 设置为待安装状态
  113. $this->setInfo([
  114. 'state' => self::WAIT_INSTALL,
  115. ]);
  116. return $zipFile;
  117. }
  118. /**
  119. * 上传安装
  120. * @param string $file 已经上传完成的文件
  121. * @return array 模块的基本信息
  122. * @throws Throwable
  123. */
  124. public function upload(string $token, string $file): array
  125. {
  126. $file = Filesystem::fsFit(root_path() . 'public' . $file);
  127. if (!is_file($file)) {
  128. // 包未找到
  129. throw new Exception('Zip file not found');
  130. }
  131. $copyTo = $this->installDir . 'uploadTemp' . date('YmdHis') . '.zip';
  132. copy($file, $copyTo);
  133. // 解压
  134. $copyToDir = Filesystem::unzip($copyTo);
  135. $copyToDir .= DIRECTORY_SEPARATOR;
  136. // 删除zip
  137. @unlink($file);
  138. @unlink($copyTo);
  139. // 读取ini
  140. $info = Server::getIni($copyToDir);
  141. if (empty($info['uid'])) {
  142. Filesystem::delDir($copyToDir);
  143. // 基本配置不完整
  144. throw new Exception('Basic configuration of the Module is incomplete');
  145. }
  146. // 安装预检 - 系统版本号要求、已安装模块的互斥和依赖检测
  147. try {
  148. Server::installPreCheck([
  149. 'uid' => $info['uid'],
  150. 'sysVersion' => Config::get('buildadmin.version'),
  151. 'nuxtVersion' => Server::getNuxtVersion(),
  152. 'ba-user-token' => $token,
  153. 'installed' => Server::getInstalledIds($this->installDir),
  154. 'server' => 1,
  155. ]);
  156. } catch (Throwable $e) {
  157. Filesystem::delDir($copyToDir);
  158. throw $e;
  159. }
  160. $this->uid = $info['uid'];
  161. $this->modulesDir = $this->installDir . $info['uid'] . DIRECTORY_SEPARATOR;
  162. $upgrade = false;
  163. if (is_dir($this->modulesDir)) {
  164. $oldInfo = $this->getInfo();
  165. if ($oldInfo && !empty($oldInfo['uid'])) {
  166. $versions = explode('.', $oldInfo['version']);
  167. if (isset($versions[2])) {
  168. $versions[2]++;
  169. }
  170. $nextVersion = implode('.', $versions);
  171. $upgrade = Version::compare($nextVersion, $info['version']);
  172. if (!$upgrade) {
  173. Filesystem::delDir($copyToDir);
  174. // 模块已经存在
  175. throw new Exception('Module already exists');
  176. }
  177. }
  178. if (!Filesystem::dirIsEmpty($this->modulesDir) && !$upgrade) {
  179. Filesystem::delDir($copyToDir);
  180. // 模块目录被占
  181. throw new Exception('The directory required by the module is occupied');
  182. }
  183. }
  184. $newInfo = ['state' => self::WAIT_INSTALL];
  185. if ($upgrade) {
  186. $newInfo['update'] = 1;
  187. // 清理旧版本代码
  188. Filesystem::delDir($this->modulesDir);
  189. }
  190. // 放置新模块
  191. rename($copyToDir, $this->modulesDir);
  192. // 检查新包是否完整
  193. $this->checkPackage();
  194. // 设置为待安装状态
  195. $this->setInfo($newInfo);
  196. return $info;
  197. }
  198. /**
  199. * 更新
  200. * @throws Throwable
  201. */
  202. public function update(string $token, int $orderId): void
  203. {
  204. $state = $this->getInstallState();
  205. if ($state != self::DISABLE) {
  206. throw new Exception('Please disable the module before updating');
  207. }
  208. $this->download($token, $orderId);
  209. // 标记需要执行更新脚本,并在安装请求执行(当前请求未自动加载到新文件不方便执行)
  210. $info = $this->getInfo();
  211. $info['update'] = 1;
  212. $this->setInfo([], $info);
  213. }
  214. /**
  215. * 安装模块
  216. * @param string $token 用户token
  217. * @param int $orderId 订单号
  218. * @return array 模块基本信息
  219. * @throws Throwable
  220. */
  221. public function install(string $token, int $orderId): array
  222. {
  223. $state = $this->getInstallState();
  224. if ($state == self::INSTALLED || $state == self::DIRECTORY_OCCUPIED || $state == self::DISABLE) {
  225. throw new Exception('Module already exists');
  226. }
  227. if ($state == self::UNINSTALLED) {
  228. $this->download($token, $orderId);
  229. }
  230. // 导入sql
  231. Server::importSql($this->modulesDir);
  232. // 如果是更新,先执行更新脚本
  233. $info = $this->getInfo();
  234. if (isset($info['update']) && $info['update']) {
  235. Server::execEvent($this->uid, 'update');
  236. unset($info['update']);
  237. $this->setInfo([], $info);
  238. }
  239. // 执行安装脚本
  240. Server::execEvent($this->uid, 'install');
  241. // 启用插件
  242. $this->enable('install');
  243. return $info;
  244. }
  245. /**
  246. * 卸载
  247. * @throws Throwable
  248. */
  249. public function uninstall(): void
  250. {
  251. $info = $this->getInfo();
  252. if ($info['state'] != self::DISABLE) {
  253. throw new Exception('Please disable the module first', 0, [
  254. 'uid' => $this->uid,
  255. ]);
  256. }
  257. // 执行卸载脚本
  258. Server::execEvent($this->uid, 'uninstall');
  259. Filesystem::delDir($this->modulesDir);
  260. }
  261. /**
  262. * 修改模块状态
  263. * @param bool $state 新状态
  264. * @return array 模块基本信息
  265. * @throws Throwable
  266. */
  267. public function changeState(bool $state): array
  268. {
  269. $info = $this->getInfo();
  270. if (!$state) {
  271. $canDisable = [
  272. self::INSTALLED,
  273. self::CONFLICT_PENDING,
  274. self::DEPENDENT_WAIT_INSTALL,
  275. ];
  276. if (!in_array($info['state'], $canDisable)) {
  277. throw new Exception('The current state of the module cannot be set to disabled', 0, [
  278. 'uid' => $this->uid,
  279. 'state' => $info['state'],
  280. ]);
  281. }
  282. return $this->disable();
  283. }
  284. if ($info['state'] != self::DISABLE) {
  285. throw new Exception('The current state of the module cannot be set to enabled', 0, [
  286. 'uid' => $this->uid,
  287. 'state' => $info['state'],
  288. ]);
  289. }
  290. $this->setInfo([
  291. 'state' => self::WAIT_INSTALL,
  292. ]);
  293. return $info;
  294. }
  295. /**
  296. * 启用
  297. * @param string $trigger 触发启用的标志,比如:install=安装
  298. * @throws Throwable
  299. */
  300. public function enable(string $trigger): void
  301. {
  302. // 安装 WebBootstrap
  303. Server::installWebBootstrap($this->uid, $this->modulesDir);
  304. // 建立 .runtime
  305. Server::createRuntime($this->modulesDir);
  306. // 冲突检查
  307. $this->conflictHandle($trigger);
  308. // 执行启用脚本
  309. Server::execEvent($this->uid, 'enable');
  310. $this->dependUpdateHandle();
  311. }
  312. /**
  313. * 禁用
  314. * @return array 模块基本信息
  315. * @throws Throwable
  316. */
  317. public function disable(): array
  318. {
  319. $update = request()->post("update/b", false);
  320. $confirmConflict = request()->post("confirmConflict/b", false);
  321. $dependConflictSolution = request()->post("dependConflictSolution/a", []);
  322. $info = $this->getInfo();
  323. $zipFile = $this->backupsDir . $this->uid . '-install.zip';
  324. $zipDir = false;
  325. if (is_file($zipFile)) {
  326. try {
  327. $zipDir = $this->backupsDir . $this->uid . '-install' . DIRECTORY_SEPARATOR;
  328. Filesystem::unzip($zipFile, $zipDir);
  329. } catch (Exception) {
  330. // skip
  331. }
  332. }
  333. $conflictFile = Server::getFileList($this->modulesDir, true);
  334. $dependConflict = $this->disableDependCheck();
  335. if (($conflictFile || !self::isEmptyArray($dependConflict)) && !$confirmConflict) {
  336. $dependConflictTemp = [];
  337. foreach ($dependConflict as $env => $item) {
  338. foreach ($item as $depend => $v) {
  339. $dependConflictTemp[] = [
  340. 'env' => $env,
  341. 'depend' => $depend,
  342. 'dependTitle' => $depend . ' ' . $v,
  343. 'solution' => 'delete',
  344. ];
  345. }
  346. }
  347. throw new Exception('Module file updated', -1, [
  348. 'uid' => $this->uid,
  349. 'conflictFile' => $conflictFile,
  350. 'dependConflict' => $dependConflictTemp,
  351. ]);
  352. }
  353. // 执行禁用脚本
  354. Server::execEvent($this->uid, 'disable', ['update' => $update]);
  355. // 是否需要备份依赖?
  356. $delNpmDepend = false;
  357. $delNuxtNpmDepend = false;
  358. $delComposerDepend = false;
  359. foreach ($dependConflictSolution as $env => $depends) {
  360. if (!$depends) continue;
  361. if ($env == 'require' || $env == 'require-dev') {
  362. $delComposerDepend = true;
  363. } elseif ($env == 'dependencies' || $env == 'devDependencies') {
  364. $delNpmDepend = true;
  365. } elseif ($env == 'nuxtDependencies' || $env == 'nuxtDevDependencies') {
  366. $delNuxtNpmDepend = true;
  367. }
  368. }
  369. // 备份
  370. $dependJsonFiles = [
  371. 'composer' => 'composer.json',
  372. 'webPackage' => 'web' . DIRECTORY_SEPARATOR . 'package.json',
  373. 'webNuxtPackage' => 'web-nuxt' . DIRECTORY_SEPARATOR . 'package.json',
  374. ];
  375. $dependWaitInstall = [];
  376. if ($delComposerDepend) {
  377. $conflictFile[] = $dependJsonFiles['composer'];
  378. $dependWaitInstall[] = [
  379. 'pm' => false,
  380. 'command' => 'composer.update',
  381. 'type' => 'composer_dependent_wait_install',
  382. ];
  383. }
  384. if ($delNpmDepend) {
  385. $conflictFile[] = $dependJsonFiles['webPackage'];
  386. $dependWaitInstall[] = [
  387. 'pm' => true,
  388. 'command' => 'web-install',
  389. 'type' => 'npm_dependent_wait_install',
  390. ];
  391. }
  392. if ($delNuxtNpmDepend) {
  393. $conflictFile[] = $dependJsonFiles['webNuxtPackage'];
  394. $dependWaitInstall[] = [
  395. 'pm' => true,
  396. 'command' => 'nuxt-install',
  397. 'type' => 'nuxt_npm_dependent_wait_install',
  398. ];
  399. }
  400. if ($conflictFile) {
  401. // 如果是模块自带文件需要备份,加上模块目录前缀
  402. $overwriteDir = Server::getOverwriteDir();
  403. foreach ($conflictFile as $key => $item) {
  404. $paths = explode(DIRECTORY_SEPARATOR, $item);
  405. if (in_array($paths[0], $overwriteDir) || in_array($item, $dependJsonFiles)) {
  406. $conflictFile[$key] = $item;
  407. } else {
  408. $conflictFile[$key] = Filesystem::fsFit(str_replace(root_path(), '', $this->modulesDir . $item));
  409. }
  410. if (!is_file(root_path() . $conflictFile[$key])) {
  411. unset($conflictFile[$key]);
  412. }
  413. }
  414. $backupsZip = $this->backupsDir . $this->uid . '-disable-' . date('YmdHis') . '.zip';
  415. Filesystem::zip($conflictFile, $backupsZip);
  416. }
  417. // 删除依赖
  418. $serverDepend = new Depends(root_path() . 'composer.json', 'composer');
  419. $webDep = new Depends(root_path() . 'web' . DIRECTORY_SEPARATOR . 'package.json');
  420. $webNuxtDep = new Depends(root_path() . 'web-nuxt' . DIRECTORY_SEPARATOR . 'package.json');
  421. foreach ($dependConflictSolution as $env => $depends) {
  422. if (!$depends) continue;
  423. $dev = !(stripos($env, 'dev') === false);
  424. if ($env == 'require' || $env == 'require-dev') {
  425. $serverDepend->removeDepends($depends, $dev);
  426. } elseif ($env == 'dependencies' || $env == 'devDependencies') {
  427. $webDep->removeDepends($depends, $dev);
  428. } elseif ($env == 'nuxtDependencies' || $env == 'nuxtDevDependencies') {
  429. $webNuxtDep->removeDepends($depends, $dev);
  430. }
  431. }
  432. // 删除 composer.json 中的 config
  433. $composerConfig = Server::getConfig($this->modulesDir, 'composerConfig');
  434. if ($composerConfig) {
  435. $serverDepend->removeComposerConfig($composerConfig);
  436. }
  437. // 配置了不删除的文件
  438. $protectedFiles = Server::getConfig($this->modulesDir, 'protectedFiles');
  439. foreach ($protectedFiles as &$protectedFile) {
  440. $protectedFile = Filesystem::fsFit(root_path() . $protectedFile);
  441. }
  442. // 模块文件列表
  443. $moduleFile = Server::getFileList($this->modulesDir);
  444. // 删除模块文件
  445. foreach ($moduleFile as &$file) {
  446. // 纯净模式下,模块文件将被删除,此处直接检查模块目录中是否有该文件并恢复(不检查是否开启纯净模式,因为开关可能被调整)
  447. $moduleFilePath = Filesystem::fsFit($this->modulesDir . $file);
  448. $file = Filesystem::fsFit(root_path() . $file);
  449. if (!file_exists($file)) continue;
  450. if (!file_exists($moduleFilePath)) {
  451. if (!is_dir(dirname($moduleFilePath))) {
  452. mkdir(dirname($moduleFilePath), 0755, true);
  453. }
  454. copy($file, $moduleFilePath);
  455. }
  456. if (in_array($file, $protectedFiles)) {
  457. continue;
  458. }
  459. if (file_exists($file)) {
  460. unlink($file);
  461. }
  462. Filesystem::delEmptyDir(dirname($file));
  463. }
  464. // 恢复备份文件
  465. if ($zipDir) {
  466. $unrecoverableFiles = [
  467. Filesystem::fsFit(root_path() . 'composer.json'),
  468. Filesystem::fsFit(root_path() . 'web/package.json'),
  469. Filesystem::fsFit(root_path() . 'web-nuxt/package.json'),
  470. ];
  471. foreach (
  472. new RecursiveIteratorIterator(
  473. new RecursiveDirectoryIterator($zipDir, FilesystemIterator::SKIP_DOTS),
  474. RecursiveIteratorIterator::SELF_FIRST
  475. ) as $item
  476. ) {
  477. $backupsFile = Filesystem::fsFit(root_path() . str_replace($zipDir, '', $item->getPathname()));
  478. // 在模块包中,同时不在 $protectedFiles 列表的文件不恢复,这些文件可能是模块升级时备份的
  479. if (in_array($backupsFile, $moduleFile) && !in_array($backupsFile, $protectedFiles)) {
  480. continue;
  481. }
  482. if ($item->isDir()) {
  483. if (!is_dir($backupsFile)) {
  484. mkdir($backupsFile, 0755, true);
  485. }
  486. } else {
  487. if (!in_array($backupsFile, $unrecoverableFiles)) {
  488. copy($item, $backupsFile);
  489. }
  490. }
  491. }
  492. }
  493. // 删除解压后的备份文件
  494. Filesystem::delDir($zipDir);
  495. // 卸载 WebBootstrap
  496. Server::uninstallWebBootstrap($this->uid);
  497. $this->setInfo([
  498. 'state' => self::DISABLE,
  499. ]);
  500. if ($update) {
  501. $token = request()->post("token/s", '');
  502. $order = request()->post("order/d", 0);
  503. $this->update($token, $order);
  504. throw new Exception('update', -3, [
  505. 'uid' => $this->uid,
  506. ]);
  507. }
  508. if ($dependWaitInstall) {
  509. throw new Exception('dependent wait install', -2, [
  510. 'uid' => $this->uid,
  511. 'wait_install' => $dependWaitInstall,
  512. ]);
  513. }
  514. return $info;
  515. }
  516. /**
  517. * 处理依赖和文件冲突,并完成与前端的冲突处理交互
  518. * @throws Throwable
  519. */
  520. public function conflictHandle(string $trigger): bool
  521. {
  522. $info = $this->getInfo();
  523. if ($info['state'] != self::WAIT_INSTALL && $info['state'] != self::CONFLICT_PENDING) {
  524. return false;
  525. }
  526. $fileConflict = Server::getFileList($this->modulesDir, true);// 文件冲突
  527. $dependConflict = Server::dependConflictCheck($this->modulesDir);// 依赖冲突
  528. $installFiles = Server::getFileList($this->modulesDir);// 待安装文件
  529. $depends = Server::getDepend($this->modulesDir);// 待安装依赖
  530. $coverFiles = [];// 要覆盖的文件-备份
  531. $discardFiles = [];// 抛弃的文件-复制时不覆盖
  532. $serverDep = new Depends(root_path() . 'composer.json', 'composer');
  533. $webDep = new Depends(root_path() . 'web' . DIRECTORY_SEPARATOR . 'package.json');
  534. $webNuxtDep = new Depends(root_path() . 'web-nuxt' . DIRECTORY_SEPARATOR . 'package.json');
  535. if ($fileConflict || !self::isEmptyArray($dependConflict)) {
  536. $extend = request()->post('extend/a', []);
  537. if (!$extend) {
  538. // 发现冲突->手动处理->转换为方便前端使用的格式
  539. $fileConflictTemp = [];
  540. foreach ($fileConflict as $key => $item) {
  541. $fileConflictTemp[$key] = [
  542. 'newFile' => $this->uid . DIRECTORY_SEPARATOR . $item,
  543. 'oldFile' => $item,
  544. 'solution' => 'cover',
  545. ];
  546. }
  547. $dependConflictTemp = [];
  548. foreach ($dependConflict as $env => $item) {
  549. $dev = !(stripos($env, 'dev') === false);
  550. foreach ($item as $depend => $v) {
  551. $oldDepend = '';
  552. if (in_array($env, ['require', 'require-dev'])) {
  553. $oldDepend = $depend . ' ' . $serverDep->hasDepend($depend, $dev);
  554. } elseif (in_array($env, ['dependencies', 'devDependencies'])) {
  555. $oldDepend = $depend . ' ' . $webDep->hasDepend($depend, $dev);
  556. } elseif (in_array($env, ['nuxtDependencies', 'nuxtDevDependencies'])) {
  557. $oldDepend = $depend . ' ' . $webNuxtDep->hasDepend($depend, $dev);
  558. }
  559. $dependConflictTemp[] = [
  560. 'env' => $env,
  561. 'newDepend' => $depend . ' ' . $v,
  562. 'oldDepend' => $oldDepend,
  563. 'depend' => $depend,
  564. 'solution' => 'cover',
  565. ];
  566. }
  567. }
  568. $this->setInfo([
  569. 'state' => self::CONFLICT_PENDING,
  570. ]);
  571. throw new Exception('Module file conflicts', -1, [
  572. 'fileConflict' => $fileConflictTemp,
  573. 'dependConflict' => $dependConflictTemp,
  574. 'uid' => $this->uid,
  575. 'state' => self::CONFLICT_PENDING,
  576. ]);
  577. }
  578. // 处理冲突
  579. if ($fileConflict && isset($extend['fileConflict'])) {
  580. foreach ($installFiles as $ikey => $installFile) {
  581. if (isset($extend['fileConflict'][$installFile])) {
  582. if ($extend['fileConflict'][$installFile] == 'discard') {
  583. $discardFiles[] = $installFile;
  584. unset($installFiles[$ikey]);
  585. } else {
  586. $coverFiles[] = $installFile;
  587. }
  588. }
  589. }
  590. }
  591. if (!self::isEmptyArray($dependConflict) && isset($extend['dependConflict'])) {
  592. foreach ($depends as $fKey => $fItem) {
  593. foreach ($fItem as $cKey => $cItem) {
  594. if (isset($extend['dependConflict'][$fKey][$cKey])) {
  595. if ($extend['dependConflict'][$fKey][$cKey] == 'discard') {
  596. unset($depends[$fKey][$cKey]);
  597. }
  598. }
  599. }
  600. }
  601. }
  602. }
  603. // 如果有依赖更新,增加要备份的文件
  604. if ($depends) {
  605. foreach ($depends as $key => $item) {
  606. if (!$item) {
  607. continue;
  608. }
  609. if ($key == 'require' || $key == 'require-dev') {
  610. $coverFiles[] = 'composer.json';
  611. continue;
  612. }
  613. if ($key == 'dependencies' || $key == 'devDependencies') {
  614. $coverFiles[] = 'web' . DIRECTORY_SEPARATOR . 'package.json';
  615. }
  616. if ($key == 'nuxtDependencies' || $key == 'nuxtDevDependencies') {
  617. $coverFiles[] = 'web-nuxt' . DIRECTORY_SEPARATOR . 'package.json';
  618. }
  619. }
  620. }
  621. // 备份将被覆盖的文件
  622. if ($coverFiles) {
  623. $backupsZip = $trigger == 'install' ? $this->backupsDir . $this->uid . '-install.zip' : $this->backupsDir . $this->uid . '-cover-' . date('YmdHis') . '.zip';
  624. Filesystem::zip($coverFiles, $backupsZip);
  625. }
  626. if ($depends) {
  627. $npm = false;
  628. $composer = false;
  629. $nuxtNpm = false;
  630. // composer config 更新
  631. $composerConfig = Server::getConfig($this->modulesDir, 'composerConfig');
  632. if ($composerConfig) {
  633. $serverDep->setComposerConfig($composerConfig);
  634. }
  635. foreach ($depends as $key => $item) {
  636. if (!$item) {
  637. continue;
  638. }
  639. if ($key == 'require') {
  640. $composer = true;
  641. $serverDep->addDepends($item, false, true);
  642. } elseif ($key == 'require-dev') {
  643. $composer = true;
  644. $serverDep->addDepends($item, true, true);
  645. } elseif ($key == 'dependencies') {
  646. $npm = true;
  647. $webDep->addDepends($item, false, true);
  648. } elseif ($key == 'devDependencies') {
  649. $npm = true;
  650. $webDep->addDepends($item, true, true);
  651. } elseif ($key == 'nuxtDependencies') {
  652. $nuxtNpm = true;
  653. $webNuxtDep->addDepends($item, false, true);
  654. } elseif ($key == 'nuxtDevDependencies') {
  655. $nuxtNpm = true;
  656. $webNuxtDep->addDepends($item, true, true);
  657. }
  658. }
  659. if ($npm) {
  660. $info['npm_dependent_wait_install'] = 1;
  661. $info['state'] = self::DEPENDENT_WAIT_INSTALL;
  662. }
  663. if ($composer) {
  664. $info['composer_dependent_wait_install'] = 1;
  665. $info['state'] = self::DEPENDENT_WAIT_INSTALL;
  666. }
  667. if ($nuxtNpm) {
  668. $info['nuxt_npm_dependent_wait_install'] = 1;
  669. $info['state'] = self::DEPENDENT_WAIT_INSTALL;
  670. }
  671. if ($info['state'] != self::DEPENDENT_WAIT_INSTALL) {
  672. // 无冲突
  673. $this->setInfo([
  674. 'state' => self::INSTALLED,
  675. ]);
  676. } else {
  677. $this->setInfo([], $info);
  678. }
  679. } else {
  680. // 无冲突
  681. $this->setInfo([
  682. 'state' => self::INSTALLED,
  683. ]);
  684. }
  685. // 复制文件
  686. $overwriteDir = Server::getOverwriteDir();
  687. foreach ($overwriteDir as $dirItem) {
  688. $baseDir = $this->modulesDir . $dirItem;
  689. $destDir = root_path() . $dirItem;
  690. if (!is_dir($baseDir)) {
  691. continue;
  692. }
  693. foreach (
  694. new RecursiveIteratorIterator(
  695. new RecursiveDirectoryIterator($baseDir, FilesystemIterator::SKIP_DOTS),
  696. RecursiveIteratorIterator::SELF_FIRST
  697. ) as $item
  698. ) {
  699. $destDirItem = Filesystem::fsFit($destDir . DIRECTORY_SEPARATOR . str_replace($baseDir, '', $item->getPathname()));
  700. if ($item->isDir()) {
  701. Filesystem::mkdir($destDirItem);
  702. } else {
  703. if (!in_array(str_replace(root_path(), '', $destDirItem), $discardFiles)) {
  704. Filesystem::mkdir(dirname($destDirItem));
  705. copy($item, $destDirItem);
  706. }
  707. }
  708. }
  709. // 纯净模式
  710. if (Config::get('buildadmin.module_pure_install')) {
  711. Filesystem::delDir($baseDir);
  712. }
  713. }
  714. return true;
  715. }
  716. /**
  717. * 依赖升级处理
  718. * @throws Throwable
  719. */
  720. public function dependUpdateHandle(): void
  721. {
  722. $info = $this->getInfo();
  723. if ($info['state'] == self::DEPENDENT_WAIT_INSTALL) {
  724. $waitInstall = [];
  725. if (isset($info['composer_dependent_wait_install'])) {
  726. $waitInstall[] = 'composer_dependent_wait_install';
  727. }
  728. if (isset($info['npm_dependent_wait_install'])) {
  729. $waitInstall[] = 'npm_dependent_wait_install';
  730. }
  731. if (isset($info['nuxt_npm_dependent_wait_install'])) {
  732. $waitInstall[] = 'nuxt_npm_dependent_wait_install';
  733. }
  734. if ($waitInstall) {
  735. throw new Exception('dependent wait install', -2, [
  736. 'uid' => $this->uid,
  737. 'state' => self::DEPENDENT_WAIT_INSTALL,
  738. 'wait_install' => $waitInstall,
  739. ]);
  740. } else {
  741. $this->setInfo([
  742. 'state' => self::INSTALLED,
  743. ]);
  744. }
  745. }
  746. }
  747. /**
  748. * 依赖安装完成标记
  749. * @throws Throwable
  750. */
  751. public function dependentInstallComplete(string $type): void
  752. {
  753. $info = $this->getInfo();
  754. if ($info['state'] == self::DEPENDENT_WAIT_INSTALL) {
  755. if ($type == 'npm') {
  756. unset($info['npm_dependent_wait_install']);
  757. }
  758. if ($type == 'nuxt_npm') {
  759. unset($info['nuxt_npm_dependent_wait_install']);
  760. }
  761. if ($type == 'composer') {
  762. unset($info['composer_dependent_wait_install']);
  763. }
  764. if ($type == 'all') {
  765. unset($info['npm_dependent_wait_install'], $info['composer_dependent_wait_install'], $info['nuxt_npm_dependent_wait_install']);
  766. }
  767. if (!isset($info['npm_dependent_wait_install']) && !isset($info['composer_dependent_wait_install']) && !isset($info['nuxt_npm_dependent_wait_install'])) {
  768. $info['state'] = self::INSTALLED;
  769. }
  770. $this->setInfo([], $info);
  771. }
  772. }
  773. /**
  774. * 禁用依赖检查
  775. * @throws Throwable
  776. */
  777. public function disableDependCheck(): array
  778. {
  779. // 读取模块所有依赖
  780. $depend = Server::getDepend($this->modulesDir);
  781. if (!$depend) {
  782. return [];
  783. }
  784. // 读取所有依赖中,系统上已经安装的依赖
  785. $serverDep = new Depends(root_path() . 'composer.json', 'composer');
  786. $webDep = new Depends(root_path() . 'web' . DIRECTORY_SEPARATOR . 'package.json');
  787. $webNuxtDep = new Depends(root_path() . 'web-nuxt' . DIRECTORY_SEPARATOR . 'package.json');
  788. foreach ($depend as $key => $depends) {
  789. $dev = !(stripos($key, 'dev') === false);
  790. if ($key == 'require' || $key == 'require-dev') {
  791. foreach ($depends as $dependKey => $dependItem) {
  792. if (!$serverDep->hasDepend($dependKey, $dev)) {
  793. unset($depends[$dependKey]);
  794. }
  795. }
  796. $depend[$key] = $depends;
  797. } elseif ($key == 'dependencies' || $key == 'devDependencies') {
  798. foreach ($depends as $dependKey => $dependItem) {
  799. if (!$webDep->hasDepend($dependKey, $dev)) {
  800. unset($depends[$dependKey]);
  801. }
  802. }
  803. $depend[$key] = $depends;
  804. } elseif ($key == 'nuxtDependencies' || $key == 'nuxtDevDependencies') {
  805. foreach ($depends as $dependKey => $dependItem) {
  806. if (!$webNuxtDep->hasDepend($dependKey, $dev)) {
  807. unset($depends[$dependKey]);
  808. }
  809. }
  810. $depend[$key] = $depends;
  811. }
  812. }
  813. return $depend;
  814. }
  815. /**
  816. * 检查包是否完整
  817. * @throws Throwable
  818. */
  819. public function checkPackage(): bool
  820. {
  821. if (!is_dir($this->modulesDir)) {
  822. throw new Exception('Module package file does not exist');
  823. }
  824. $info = $this->getInfo();
  825. $infoKeys = ['uid', 'title', 'intro', 'author', 'version', 'state'];
  826. foreach ($infoKeys as $value) {
  827. if (!array_key_exists($value, $info)) {
  828. Filesystem::delDir($this->modulesDir);
  829. throw new Exception('Basic configuration of the Module is incomplete');
  830. }
  831. }
  832. return true;
  833. }
  834. /**
  835. * 获取模块基本信息
  836. */
  837. public function getInfo(): array
  838. {
  839. return Server::getIni($this->modulesDir);
  840. }
  841. /**
  842. * 设置模块基本信息
  843. * @throws Throwable
  844. */
  845. public function setInfo(array $kv = [], array $arr = []): bool
  846. {
  847. if ($kv) {
  848. $info = $this->getInfo();
  849. foreach ($kv as $k => $v) {
  850. $info[$k] = $v;
  851. }
  852. return Server::setIni($this->modulesDir, $info);
  853. } elseif ($arr) {
  854. return Server::setIni($this->modulesDir, $arr);
  855. }
  856. throw new Exception('Parameter error');
  857. }
  858. /**
  859. * 检查多维数组是否全部为空
  860. */
  861. public static function isEmptyArray($arr): bool
  862. {
  863. foreach ($arr as $item) {
  864. if (is_array($item)) {
  865. $empty = self::isEmptyArray($item);
  866. if (!$empty) return false;
  867. } elseif ($item) {
  868. return false;
  869. }
  870. }
  871. return true;
  872. }
  873. }