427 lines
13 KiB
PHP
427 lines
13 KiB
PHP
<?php
|
|
/**
|
|
*+------------------
|
|
* madong
|
|
*+------------------
|
|
* Copyright (c) https://gitee.com/motion-code All rights reserved.
|
|
*+------------------
|
|
* Author: Mr. April (405784684@qq.com)
|
|
*+------------------
|
|
* Official Website: http://www.madong.tech
|
|
*/
|
|
|
|
namespace app\service\core\plugin;
|
|
|
|
use app\service\admin\system\MenuService;
|
|
use core\exception\handler\PluginException;
|
|
|
|
/**
|
|
* 插件构建服务
|
|
*/
|
|
final class PluginBuildService extends PluginBaseService
|
|
{
|
|
private ?string $plugin;
|
|
private ?string $pluginPath;
|
|
|
|
/**
|
|
* 插件打包
|
|
*
|
|
* @param string $plugin 插件名称
|
|
*
|
|
* @return true
|
|
* @throws PluginException
|
|
* @throws \Exception
|
|
*/
|
|
public function build(string $plugin): true
|
|
{
|
|
$this->plugin = $plugin;
|
|
$this->pluginPath = base_path() . 'plugin' . DIRECTORY_SEPARATOR . $plugin . DIRECTORY_SEPARATOR;
|
|
|
|
if (!is_dir($this->pluginPath)) {
|
|
throw new PluginException('目录中不存在此项插件');
|
|
}
|
|
|
|
// 执行各模块打包
|
|
$this->packageAdmin();
|
|
$this->packageUniapp();
|
|
$this->buildUniappPagesJson();
|
|
$this->buildUniappLangJson();
|
|
$this->packageWeb();
|
|
$this->packageResource();
|
|
$this->syncMenu('admin');
|
|
$this->syncMenu('site');
|
|
|
|
// 清理临时文件并准备打包
|
|
$this->cleanupAndPreparePackage();
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* 同步菜单
|
|
*
|
|
* @param string $appType 应用类型
|
|
*
|
|
* @return true
|
|
*/
|
|
public function syncMenu(string $appType): true
|
|
{
|
|
|
|
$menuService = new MenuService();
|
|
$where = [
|
|
['app_type', '=', $appType],
|
|
['plugin', '=', $this->plugin]
|
|
];
|
|
$fields = [
|
|
'menu_name', 'menu_key', 'menu_short_name', 'parent_select_key',
|
|
'parent_key', 'menu_type', 'icon', 'api_url', 'router_path',
|
|
'view_path', 'methods', 'sort', 'status', 'is_show'
|
|
];
|
|
|
|
$menus = $menuService->getModel()
|
|
->query()
|
|
->where($where)
|
|
->order('sort', 'desc')
|
|
->get()
|
|
->toArray();
|
|
|
|
$menuTree = [];
|
|
if (!empty($menus)) {
|
|
$menuTree = $this->buildMenuTree($menus);
|
|
$menuService->getModel()->query()->where($where)->update(['source' => 'system']);
|
|
}
|
|
|
|
$menuConfigPath = $this->pluginPath . 'resource' . DIRECTORY_SEPARATOR . 'menu' . DIRECTORY_SEPARATOR . 'menu' . DIRECTORY_SEPARATOR . $appType . '.php';
|
|
|
|
$deleteMenuKeys = [];
|
|
if (file_exists($menuConfigPath)) {
|
|
$menuConfig = include $menuConfigPath;
|
|
$deleteMenuKeys = $menuConfig['delete'] ?? [];
|
|
}
|
|
|
|
$content = $this->generateMenuConfigContent($menuTree, $deleteMenuKeys);
|
|
file_put_contents($menuConfigPath, $content);
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* 构建菜单树
|
|
*
|
|
* @param array $menuList 菜单列表
|
|
* @param string $pk 主键字段
|
|
* @param string $pid 父级ID字段
|
|
* @param string $child 子节点字段
|
|
* @param string $root 根节点值
|
|
*
|
|
* @return array
|
|
*/
|
|
private function buildMenuTree(array $menuList, string $pk = 'menu_key', string $pid = 'parent_key', string $child = 'children', string $root = ''): array
|
|
{
|
|
$tree = [];
|
|
if (empty($menuList)) {
|
|
return $tree;
|
|
}
|
|
|
|
// 创建基于主键的数组引用
|
|
$menuRefer = [];
|
|
foreach ($menuList as $key => &$menu) {
|
|
$menuRefer[$menu[$pk]] = &$menuList[$key];
|
|
}
|
|
|
|
foreach ($menuList as $key => &$menu) {
|
|
$parentId = $menu[$pid];
|
|
if ($root === $parentId) {
|
|
$tree[] = &$menuList[$key];
|
|
} else {
|
|
if (isset($menuRefer[$parentId])) {
|
|
$parentMenu = &$menuRefer[$parentId];
|
|
$parentMenu[$child][] = &$menuList[$key];
|
|
} else {
|
|
$tree[] = &$menuList[$key];
|
|
}
|
|
}
|
|
}
|
|
|
|
return $tree;
|
|
}
|
|
|
|
/**
|
|
* 生成菜单配置内容
|
|
*
|
|
* @param array $menuTree 菜单树
|
|
* @param array $deleteMenuKeys 要删除的菜单键
|
|
*
|
|
* @return string
|
|
*/
|
|
private function generateMenuConfigContent(array $menuTree, array $deleteMenuKeys): string
|
|
{
|
|
$content = '<?php' . PHP_EOL;
|
|
$content .= 'return [' . PHP_EOL;
|
|
$content .= $this->formatArrayToPhp($menuTree);
|
|
|
|
if (!empty($deleteMenuKeys)) {
|
|
$deleteKeys = array_map(function ($key) {
|
|
return "'{$key}'";
|
|
}, $deleteMenuKeys);
|
|
$content .= " 'delete' => [" . implode(',', $deleteKeys) . "]" . PHP_EOL;
|
|
}
|
|
|
|
$content .= '];';
|
|
return $content;
|
|
}
|
|
|
|
/**
|
|
* 格式化数组为PHP代码字符串
|
|
*
|
|
* @param array $array 要格式化的数组
|
|
* @param int $level 缩进级别
|
|
*
|
|
* @return string
|
|
*/
|
|
private function formatArrayToPhp(array $array, int $level = 1): string
|
|
{
|
|
$indent = str_repeat(' ', $level);
|
|
$content = '';
|
|
|
|
foreach ($array as $key => $value) {
|
|
// 跳过不需要的字段
|
|
if (in_array($key, ['status_name', 'menu_type_name']) || ($level > 2 && $key == 'parent_key')) {
|
|
continue;
|
|
}
|
|
|
|
$content .= $indent;
|
|
if (is_string($key)) {
|
|
$content .= "'{$key}' => ";
|
|
}
|
|
|
|
if (is_array($value)) {
|
|
$content .= '[' . PHP_EOL . $this->formatArrayToPhp($value, $level + 1);
|
|
$content .= $indent . '],' . PHP_EOL;
|
|
} else {
|
|
$content .= "'{$value}'," . PHP_EOL;
|
|
}
|
|
}
|
|
|
|
return $content;
|
|
}
|
|
|
|
/**
|
|
* 打包Admin模块
|
|
*
|
|
* @return true
|
|
*/
|
|
public function packageAdmin(): true
|
|
{
|
|
$sourcePath = $this->getFrontendProjectPath('admin') . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'addon' . DIRECTORY_SEPARATOR . $this->plugin . DIRECTORY_SEPARATOR;
|
|
if (!is_dir($sourcePath)) {
|
|
return true;
|
|
}
|
|
|
|
$targetPath = $this->pluginPath . 'admin' . DIRECTORY_SEPARATOR;
|
|
$this->copyDirectoryWithCleanup($sourcePath, $targetPath);
|
|
|
|
// 打包admin icon文件
|
|
$iconSourcePath = $this->getFrontendProjectPath('admin') . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'styles' . DIRECTORY_SEPARATOR . 'icon' . DIRECTORY_SEPARATOR . 'addon' . DIRECTORY_SEPARATOR . $this->plugin;
|
|
$iconTargetPath = $targetPath . 'icon' . DIRECTORY_SEPARATOR;
|
|
|
|
if (is_dir($iconSourcePath)) {
|
|
$this->copyDirectoryWithCleanup($iconSourcePath, $iconTargetPath);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* 打包Uniapp模块
|
|
*
|
|
* @return true
|
|
*/
|
|
public function packageUniapp(): true
|
|
{
|
|
$sourcePath = $this->getFrontendProjectPath('uni-app') . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'addon' . DIRECTORY_SEPARATOR . $this->plugin . DIRECTORY_SEPARATOR;
|
|
if (!is_dir($sourcePath)) {
|
|
return true;
|
|
}
|
|
|
|
$targetPath = $this->pluginPath . 'uni-app' . DIRECTORY_SEPARATOR;
|
|
$this->copyDirectoryWithCleanup($sourcePath, $targetPath);
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* 构建uni-app页面配置
|
|
*
|
|
* @return true
|
|
* @throws \Exception
|
|
*/
|
|
public function buildUniappPagesJson(): true
|
|
{
|
|
$pagesJsonPath = $this->getFrontendProjectPath('uni-app') . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'pages.json';
|
|
if (!file_exists($pagesJsonPath)) {
|
|
return true;
|
|
}
|
|
|
|
$pagesJsonContent = file_get_contents($pagesJsonPath);
|
|
$codeBegin = strtoupper($this->plugin) . '_PAGE_BEGIN' . PHP_EOL;
|
|
$codeEnd = strtoupper($this->plugin) . '_PAGE_END' . PHP_EOL;
|
|
|
|
if (!str_contains($pagesJsonContent, $codeBegin) || !str_contains($pagesJsonContent, $codeEnd)) {
|
|
return true;
|
|
}
|
|
|
|
$pattern = "/\\/\\/\s+{$codeBegin}([\\S\\s]+)\\/\\/\s+{$codeEnd}?/";
|
|
preg_match($pattern, $pagesJsonContent, $matches);
|
|
|
|
if (empty($matches)) {
|
|
return true;
|
|
}
|
|
|
|
$addonPages = str_replace(PHP_EOL . ',' . PHP_EOL, '', $matches[1]);
|
|
$packagePath = $this->pluginPath . 'package';
|
|
|
|
if (!is_dir($packagePath)) {
|
|
create_directory($packagePath);
|
|
}
|
|
|
|
$content = '<?php' . PHP_EOL;
|
|
$content .= 'return [' . PHP_EOL . " 'pages' => <<<EOT" . PHP_EOL . ' // PAGE_BEGIN' . PHP_EOL;
|
|
$content .= $addonPages;
|
|
$content .= '// PAGE_END' . PHP_EOL . 'EOT' . PHP_EOL . '];';
|
|
|
|
file_put_contents($packagePath . DIRECTORY_SEPARATOR . 'uni-app-pages.php', $content);
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* 构建uni-app语言文件
|
|
*
|
|
* @return true
|
|
* @throws \Exception
|
|
*/
|
|
public function buildUniappLangJson(): true
|
|
{
|
|
$zhJsonPath = $this->getFrontendProjectPath('uni-app') . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'locale' . DIRECTORY_SEPARATOR . 'zh-Hans.json';
|
|
$enJsonPath = $this->getFrontendProjectPath('uni-app') . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'locale' . DIRECTORY_SEPARATOR . 'en.json';
|
|
|
|
if (!file_exists($zhJsonPath) || !file_exists($enJsonPath)) {
|
|
return true;
|
|
}
|
|
|
|
$zhJson = json_decode(file_get_contents($zhJsonPath), true);
|
|
$enJson = json_decode(file_get_contents($enJsonPath), true);
|
|
|
|
$pluginZh = [];
|
|
$pluginEn = [];
|
|
$pluginKeyPrefix = $this->plugin . '.';
|
|
|
|
foreach ($zhJson as $key => $value) {
|
|
if (str_starts_with($key, $pluginKeyPrefix)) {
|
|
$newKey = str_replace($pluginKeyPrefix, '', $key);
|
|
$pluginZh[$newKey] = $value;
|
|
}
|
|
}
|
|
|
|
foreach ($enJson as $key => $value) {
|
|
if (str_starts_with($key, $pluginKeyPrefix)) {
|
|
$newKey = str_replace($pluginKeyPrefix, '', $key);
|
|
$pluginEn[$newKey] = $value;
|
|
}
|
|
}
|
|
|
|
$langDir = $this->pluginPath . 'uni-app' . DIRECTORY_SEPARATOR . 'locale' . DIRECTORY_SEPARATOR;
|
|
if (!is_dir($langDir)) {
|
|
create_directory($langDir);
|
|
}
|
|
|
|
$this->writeJsonFile($pluginZh, $langDir . 'zh-Hans.json');
|
|
$this->writeJsonFile($pluginEn, $langDir . 'en.json');
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* 打包Web模块
|
|
*
|
|
* @return true
|
|
*/
|
|
public function packageWeb(): true
|
|
{
|
|
$sourcePath = $this->getFrontendProjectPath('web') . DIRECTORY_SEPARATOR . 'addon' . DIRECTORY_SEPARATOR . $this->plugin . DIRECTORY_SEPARATOR;
|
|
if (!is_dir($sourcePath)) {
|
|
return true;
|
|
}
|
|
|
|
$targetPath = $this->pluginPath . 'web' . DIRECTORY_SEPARATOR;
|
|
$this->copyDirectoryWithCleanup($sourcePath, $targetPath);
|
|
|
|
// 处理布局文件
|
|
$layoutSourcePath = $this->getFrontendProjectPath('web') . DIRECTORY_SEPARATOR . 'layouts' . DIRECTORY_SEPARATOR . $this->plugin;
|
|
if (is_dir($layoutSourcePath)) {
|
|
$layoutTargetPath = $targetPath . 'layouts' . DIRECTORY_SEPARATOR . $this->plugin;
|
|
$this->copyDirectoryWithCleanup($layoutSourcePath, $layoutTargetPath);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* 打包资源文件
|
|
*
|
|
* @return true
|
|
*/
|
|
public function packageResource(): true
|
|
{
|
|
$sourcePath = public_path() . 'plugin' . DIRECTORY_SEPARATOR . $this->plugin . DIRECTORY_SEPARATOR;
|
|
if (!is_dir($sourcePath)) {
|
|
return true;
|
|
}
|
|
|
|
$targetPath = $this->pluginPath . 'resource' . DIRECTORY_SEPARATOR;
|
|
$this->copyDirectoryWithCleanup($sourcePath, $targetPath);
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* 清理临时文件并准备打包
|
|
*
|
|
* @return void
|
|
* @throws \Exception
|
|
*/
|
|
private function cleanupAndPreparePackage(): void
|
|
{
|
|
$runtimePath = runtime_path() . $this->plugin . DIRECTORY_SEPARATOR;
|
|
$pluginRuntimePath = $runtimePath . $this->plugin;
|
|
|
|
// 先拷贝
|
|
copy_directory($this->pluginPath, $pluginRuntimePath);
|
|
|
|
// 清理和准备ZIP文件
|
|
$zipFile = runtime_path() . $this->plugin . '.zip';
|
|
if (file_exists($zipFile)) {
|
|
unlink($zipFile);
|
|
}
|
|
|
|
// 清理临时目录
|
|
remove_directory($runtimePath, true);
|
|
}
|
|
|
|
/**
|
|
* 复制目录并清理目标目录
|
|
*
|
|
* @param string $sourcePath 源目录路径
|
|
* @param string $targetPath 目标目录路径
|
|
*
|
|
* @return void
|
|
*/
|
|
private function copyDirectoryWithCleanup(string $sourcePath, string $targetPath): void
|
|
{
|
|
if (is_dir($targetPath)) {
|
|
remove_directory($targetPath, true);
|
|
}
|
|
copy_directory($sourcePath, $targetPath);
|
|
}
|
|
} |