no message

This commit is contained in:
kuaifan 2025-05-08 19:33:43 +08:00
parent 4424e4f9be
commit cb17110562

View File

@ -26,6 +26,30 @@ class Apps
'search', 'search',
]; ];
/**
* 获取应用列表
* @return array
*/
public static function appList(): array
{
$apps = [];
$baseDir = base_path('docker/apps');
$dirs = scandir($baseDir);
foreach ($dirs as $dir) {
// 跳过当前目录、父目录和隐藏文件
if ($dir === '.' || $dir === '..' || str_starts_with($dir, '.')) {
continue;
}
$apps[] = [
'name' => $dir,
'info' => self::getAppInfo($dir),
'local' => self::getAppLocalInfo($dir),
'versions' => self::getAvailableVersions($dir),
];
}
return Base::retSuccess("success", $apps);
}
/** /**
* 执行docker-compose up命令 * 执行docker-compose up命令
* @param string $appName * @param string $appName
@ -52,13 +76,15 @@ class Apps
return Base::retError("没有找到版本 {$version}"); return Base::retError("没有找到版本 {$version}");
} }
// 保存版本信息 // 保存版本信息到.applocal文件
file_put_contents($versionInfo['base_dir'] . '/latest', $versionInfo['version']); self::saveAppLocalInfo($appName, [
'installed_version' => $versionInfo['version'],
'installed_at' => date('Y-m-d H:i:s')
]);
$params = self::getAppLocalInfo($appName)['params'] ?? [];
// 生成docker-compose.yml文件 // 生成docker-compose.yml文件
$res = self::generateDockerComposeYml($versionInfo['compose_file'], [ $res = self::generateDockerComposeYml($versionInfo['compose_file'], $params);
'PROXY_PORT' => '33062', // todo 参数自定义
]);
if (Base::isError($res)) { if (Base::isError($res)) {
return $res; return $res;
} }
@ -104,6 +130,181 @@ class Apps
return self::dockerComposeUp($appName, $version, 'down'); return self::dockerComposeUp($appName, $version, 'down');
} }
/**
* 获取应用信息
* @param string $appName 应用名称
* @return array
*/
public static function getAppInfo(string $appName): array
{
$baseDir = base_path('docker/apps/' . $appName);
$info = [
'name' => $appName,
'description' => '',
'author' => '',
'website' => '',
'github' => '',
'document' => '',
'fields' => [],
];
if (file_exists($baseDir . '/config.yml')) {
$configData = Yaml::parseFile($baseDir . '/config.yml');
// 处理基础字段
if (isset($configData['name'])) {
$info['name'] = $configData['name'];
}
// 处理描述(支持多语言)
if (isset($configData['description'])) {
$info['description'] = self::formatMultiLanguageField($configData['description']);
}
// 处理字段
if (isset($configData['fields']) && is_array($configData['fields'])) {
$fields = [];
foreach ($configData['fields'] as $field) {
// 检查必需的name字段及其格式
if (!isset($field['name'])) {
continue;
}
// 验证字段名格式,必须以字母或下划线开头,只允许大小写字母、数字和下划线
if (!preg_match('/^[A-Za-z_][A-Za-z0-9_]*$/', $field['name'])) {
continue;
}
// 标准化字段结构
$normalizedField = [
'name' => $field['name'],
'type' => $field['type'] ?? 'text',
'default' => $field['default'] ?? '',
'label' => [],
'placeholder' => [],
];
// 处理label支持多语言
if (isset($field['label'])) {
$normalizedField['label'] = self::formatMultiLanguageField($field['label']);
}
// 处理placeholder支持多语言
if (isset($field['placeholder'])) {
$normalizedField['placeholder'] = self::formatMultiLanguageField($field['placeholder']);
}
// 处理其他属性
foreach ($field as $key => $value) {
if (!in_array($key, ['name', 'type', 'default', 'label', 'placeholder'])) {
$normalizedField[$key] = $value;
}
}
$fields[] = $normalizedField;
}
$info['fields'] = $fields;
}
// 处理其他标准字段
foreach (['author', 'website', 'github', 'document'] as $field) {
if (isset($configData[$field])) {
$info[$field] = $configData[$field];
}
}
}
return $info;
}
/**
* 获取应用的本地安装信息
*
* @param string $appName 应用名称
* @return array 应用的本地安装信息
*/
public static function getAppLocalInfo(string $appName): array
{
$baseDir = base_path('docker/apps/' . $appName);
$appLocalFile = $baseDir . '/.applocal';
$defaultInfo = [
'created_at' => '', // 应用首次添加到系统的时间
'installed_at' => '', // 最近一次安装/更新的时间
'installed_version' => '', // 最近一次安装/更新的版本
'status' => 'not_installed', // 应用状态: installed, not_installed, error
'params' => [], // 用户自定义参数值
'resources' => [
'cpu_limit' => '', // CPU限制例如 '0.5' 或 '2'
'memory_limit' => '' // 内存限制,例如 '512M' 或 '2G'
],
];
if (file_exists($appLocalFile)) {
$localInfo = json_decode(file_get_contents($appLocalFile), true);
if (json_last_error() === JSON_ERROR_NONE && is_array($localInfo)) {
$defaultInfo = array_merge($defaultInfo, $localInfo);
}
}
// 确保 params 是数组
if (!is_array($defaultInfo['params'])) {
$defaultInfo['params'] = [];
}
// 添加 DooTask 版本信息
$defaultInfo['params']['DOOTASK_VERSION'] = Base::getVersion();
return $defaultInfo;
}
/**
* 保存应用的本地配置信息
*
* @param string $appName 应用名称
* @param array $data 要更新的数据
* @param bool $merge 是否与现有配置合并默认true
* @return bool 保存是否成功
*/
public static function saveAppLocalInfo(string $appName, array $data, bool $merge = true): bool
{
$baseDir = base_path('docker/apps/' . $appName);
$appLocalFile = $baseDir . '/.applocal';
// 初始化数据
$localInfo = [];
// 如果需要合并,先读取现有配置
if ($merge && file_exists($appLocalFile)) {
$existingData = json_decode(file_get_contents($appLocalFile), true);
if (json_last_error() === JSON_ERROR_NONE && is_array($existingData)) {
$localInfo = $existingData;
}
}
// 更新数据
foreach ($data as $key => $value) {
if (is_array($value) && isset($localInfo[$key]) && is_array($localInfo[$key])) {
// 如果是嵌套数组,进行深度合并
$localInfo[$key] = array_replace_recursive($localInfo[$key], $value);
} else {
// 普通值直接覆盖
$localInfo[$key] = $value;
}
}
// 确保目录存在
if (!is_dir($baseDir)) {
mkdir($baseDir, 0755, true);
}
// 写入文件
return (bool)file_put_contents(
$appLocalFile,
json_encode($localInfo, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)
);
}
/** /**
* 生成docker-compose.yml文件配置 * 生成docker-compose.yml文件配置
* @param string $filePath docker-compose.yml文件路径 * @param string $filePath docker-compose.yml文件路径
@ -125,7 +326,7 @@ class Apps
$hostPwd = '${HOST_PWD}/docker/apps/' . $appName . '/' . basename(dirname($filePath)); $hostPwd = '${HOST_PWD}/docker/apps/' . $appName . '/' . basename(dirname($filePath));
// 保存路径 // 保存路径
$savePath = dirname($filePath) . '/docker-compose.doo.yml'; $savePath = dirname($filePath) . '/.docker-compose.local.yml';
try { try {
// 解析YAML文件 // 解析YAML文件
@ -166,7 +367,7 @@ class Apps
$yamlContent = Yaml::dump($content, 4, 2); $yamlContent = Yaml::dump($content, 4, 2);
// 替换${XXX}格式变量 // 替换${XXX}格式变量
$yamlContent = preg_replace_callback('/\$\{(.*?)\}/', function ($matches) use ($params) { $yamlContent = preg_replace_callback('/\$\{(.*?)}/', function ($matches) use ($params) {
return $params[$matches[1]] ?? $matches[0]; return $params[$matches[1]] ?? $matches[0];
}, $yamlContent); }, $yamlContent);
@ -185,6 +386,26 @@ class Apps
} }
} }
/**
* 格式化多语言字段
*
* @param mixed $field 要处理的字段值
* @return array 格式化后的多语言数组
*/
private static function formatMultiLanguageField(mixed $field): array
{
if (is_array($field)) {
// 多语言字段
$result = $field;
$firstLang = array_key_first($result);
$result['default'] = $result[$firstLang];
return $result;
} else {
// 单语言字段
return ['default' => $field];
}
}
/** /**
* 获取应用的可用版本列表 * 获取应用的可用版本列表
* *