mirror of
https://github.com/kuaifan/dootask.git
synced 2025-12-17 06:32:51 +00:00
no message
This commit is contained in:
parent
428db42140
commit
18ffad5de5
@ -306,12 +306,15 @@ class AppsController extends AbstractController
|
|||||||
*
|
*
|
||||||
* @apiParam {String} app_name 应用名称
|
* @apiParam {String} app_name 应用名称
|
||||||
* @apiParam {Number} [lines=50] 获取日志行数,默认50行
|
* @apiParam {Number} [lines=50] 获取日志行数,默认50行
|
||||||
|
* @apiParam {Boolean} [simple=false] 是否只返回日志,默认false
|
||||||
*
|
*
|
||||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||||
* @apiSuccess {String} msg 返回信息(错误描述)
|
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||||
* @apiSuccess {Object} data 返回数据
|
* @apiSuccess {Object} data 返回数据
|
||||||
* @apiSuccess {String} data.name 应用名称
|
* @apiSuccess {String} data.name 应用名称
|
||||||
* @apiSuccess {Object} data.config 应用配置信息
|
* @apiSuccess {Object} data.config 应用配置信息
|
||||||
|
* @apiSuccess {Object} data.versions 可用版本列表
|
||||||
|
* @apiSuccess {Boolean} data.upgradeable 是否可升级
|
||||||
* @apiSuccess {String} data.log 日志内容
|
* @apiSuccess {String} data.log 日志内容
|
||||||
*/
|
*/
|
||||||
public function logs()
|
public function logs()
|
||||||
@ -320,13 +323,22 @@ class AppsController extends AbstractController
|
|||||||
//
|
//
|
||||||
$appName = Request::input('app_name');
|
$appName = Request::input('app_name');
|
||||||
$lines = intval(Request::input('lines', 50));
|
$lines = intval(Request::input('lines', 50));
|
||||||
|
$simple = Request::input('simple', false);
|
||||||
|
|
||||||
$logContent = implode("\n", Apps::getAppLog($appName, $lines));
|
$logContent = implode("\n", Apps::getAppLog($appName, $lines));
|
||||||
|
|
||||||
return Base::retSuccess('success', [
|
$data = [
|
||||||
'name' => $appName,
|
'name' => $appName,
|
||||||
'config' => Apps::getAppConfig($appName),
|
|
||||||
'log' => trim($logContent)
|
'log' => trim($logContent)
|
||||||
]);
|
];
|
||||||
|
if (!$simple) {
|
||||||
|
$config = Apps::getAppConfig($appName);
|
||||||
|
$versions = Apps::getAvailableVersions($appName);
|
||||||
|
$data['config'] = $config;
|
||||||
|
$data['versions'] = $versions;
|
||||||
|
$data['upgradeable'] = Apps::isUpgradeable($config, $versions);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Base::retSuccess('success', $data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,8 +8,11 @@ use Symfony\Component\Yaml\Yaml;
|
|||||||
|
|
||||||
class Apps
|
class Apps
|
||||||
{
|
{
|
||||||
|
// 软件源列表URL
|
||||||
|
const SOURCES_URL = 'https://appstore.dootask.com/';
|
||||||
|
|
||||||
// 受保护的服务名称列表
|
// 受保护的服务名称列表
|
||||||
protected static array $protectedServiceNames = [
|
const PROTECTED_NAMES = [
|
||||||
'php',
|
'php',
|
||||||
'nginx',
|
'nginx',
|
||||||
'redis',
|
'redis',
|
||||||
@ -18,13 +21,11 @@ class Apps
|
|||||||
'appstore',
|
'appstore',
|
||||||
];
|
];
|
||||||
|
|
||||||
// 软件源列表URL
|
|
||||||
protected static string $sourcesUrl = 'https://appstore.dootask.com/sources.list';
|
|
||||||
|
|
||||||
// 缓存键集合
|
// 缓存键集合
|
||||||
protected static array $cacheKeys = [
|
const CACHE_KEYS = [
|
||||||
'app_list' => 'appstore_app_list',
|
'list' => 'appstore_app_list', // 应用列表缓存
|
||||||
'menu_items' => 'appstore_menu_items',
|
'menu' => 'appstore_menu_items', // 应用菜单缓存
|
||||||
|
'installed' => 'appstore_installed', // 已安装应用缓存
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -36,22 +37,25 @@ class Apps
|
|||||||
public static function appList(bool $cache = true): array
|
public static function appList(bool $cache = true): array
|
||||||
{
|
{
|
||||||
if ($cache === false) {
|
if ($cache === false) {
|
||||||
Cache::forget(self::$cacheKeys['app_list']);
|
Cache::forget(self::CACHE_KEYS['list']);
|
||||||
}
|
}
|
||||||
$apps = Cache::remember(self::$cacheKeys['app_list'], now()->addHour(), function () {
|
$apps = Cache::remember(self::CACHE_KEYS['list'], now()->addHour(), function () {
|
||||||
$apps = [];
|
$apps = [];
|
||||||
$baseDir = base_path('docker/appstore/apps');
|
$baseDir = base_path('docker/appstore/apps');
|
||||||
$dirs = scandir($baseDir);
|
$dirs = scandir($baseDir);
|
||||||
foreach ($dirs as $dir) {
|
foreach ($dirs as $appName) {
|
||||||
// 跳过当前目录、父目录和隐藏文件
|
if ($appName === '.' || $appName === '..' || str_starts_with($appName, '.')) {
|
||||||
if ($dir === '.' || $dir === '..' || str_starts_with($dir, '.')) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
$info = self::getAppInfo($appName);
|
||||||
|
$config = self::getAppConfig($appName);
|
||||||
|
$versions = self::getAvailableVersions($appName);
|
||||||
$apps[] = [
|
$apps[] = [
|
||||||
'name' => $dir,
|
'name' => $appName,
|
||||||
'info' => self::getAppInfo($dir),
|
'info' => $info,
|
||||||
'config' => self::getAppConfig($dir),
|
'config' => $config,
|
||||||
'versions' => self::getAvailableVersions($dir),
|
'versions' => $versions,
|
||||||
|
'upgradeable' => self::isUpgradeable($config, $versions),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
return $apps;
|
return $apps;
|
||||||
@ -67,11 +71,15 @@ class Apps
|
|||||||
*/
|
*/
|
||||||
public static function appInfo(string $appName): array
|
public static function appInfo(string $appName): array
|
||||||
{
|
{
|
||||||
|
$info = self::getAppInfo($appName);
|
||||||
|
$config = self::getAppConfig($appName);
|
||||||
|
$versions = self::getAvailableVersions($appName);
|
||||||
return Base::retSuccess("success", [
|
return Base::retSuccess("success", [
|
||||||
'name' => $appName,
|
'name' => $appName,
|
||||||
'info' => self::getAppInfo($appName),
|
'info' => $info,
|
||||||
'config' => self::getAppConfig($appName),
|
'config' => $config,
|
||||||
'versions' => self::getAvailableVersions($appName),
|
'versions' => $versions,
|
||||||
|
'upgradeable' => self::isUpgradeable($config, $versions),
|
||||||
'document' => self::getAppDocument($appName),
|
'document' => self::getAppDocument($appName),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@ -185,11 +193,6 @@ class Apps
|
|||||||
*/
|
*/
|
||||||
public static function dockerComposeFinalize(string $appName, string $status): array
|
public static function dockerComposeFinalize(string $appName, string $status): array
|
||||||
{
|
{
|
||||||
// 清理缓存
|
|
||||||
foreach (self::$cacheKeys as $key) {
|
|
||||||
Cache::forget($key);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取当前应用信息
|
// 获取当前应用信息
|
||||||
$appInfo = self::getAppConfig($appName);
|
$appInfo = self::getAppConfig($appName);
|
||||||
|
|
||||||
@ -362,13 +365,13 @@ class Apps
|
|||||||
public static function getAppMenuItems(bool $cache = true): array
|
public static function getAppMenuItems(bool $cache = true): array
|
||||||
{
|
{
|
||||||
if ($cache === false) {
|
if ($cache === false) {
|
||||||
Cache::forget(self::$cacheKeys['menu_items']);
|
Cache::forget(self::CACHE_KEYS['menu']);
|
||||||
}
|
}
|
||||||
$res = Cache::remember(self::$cacheKeys['menu_items'], now()->addHour(), function () {
|
$res = Cache::remember(self::CACHE_KEYS['menu'], now()->addHour(), function () {
|
||||||
return self::menuGetAll();
|
return self::menuGetAll();
|
||||||
});
|
});
|
||||||
if (Base::isError($res)) {
|
if (Base::isError($res)) {
|
||||||
Cache::forget(self::$cacheKeys['menu_items']);
|
Cache::forget(self::CACHE_KEYS['menu']);
|
||||||
}
|
}
|
||||||
return $res;
|
return $res;
|
||||||
}
|
}
|
||||||
@ -419,17 +422,30 @@ class Apps
|
|||||||
return Base::retSuccess("success", $allMenuItems);
|
return Base::retSuccess("success", $allMenuItems);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取所有已安装的应用配置
|
||||||
|
$installedApps = [];
|
||||||
$dirs = scandir($baseDir);
|
$dirs = scandir($baseDir);
|
||||||
foreach ($dirs as $dir) {
|
foreach ($dirs as $appName) {
|
||||||
if ($dir === '.' || $dir === '..' || str_starts_with($dir, '.')) {
|
if ($appName === '.' || $appName === '..' || str_starts_with($appName, '.')) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!self::isInstalled($dir)) {
|
$appConfig = self::getAppConfig($appName);
|
||||||
|
if ($appConfig['status'] !== 'installed') {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$appMenuItems = self::menuGetSingle($dir);
|
$installedApps[$appName] = strtotime($appConfig['install_at'] ?? '');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按安装时间排序应用
|
||||||
|
uasort($installedApps, function ($timeA, $timeB) {
|
||||||
|
return $timeB <=> $timeA; // 降序排列
|
||||||
|
});
|
||||||
|
|
||||||
|
// 按排序后的顺序获取菜单
|
||||||
|
foreach ($installedApps as $appName => $installAt) {
|
||||||
|
$appMenuItems = self::menuGetSingle($appName);
|
||||||
if (Base::isSuccess($appMenuItems)) {
|
if (Base::isSuccess($appMenuItems)) {
|
||||||
$allMenuItems = array_merge($allMenuItems, $appMenuItems['data']);
|
$allMenuItems = array_merge($allMenuItems, $appMenuItems['data']);
|
||||||
}
|
}
|
||||||
@ -570,6 +586,9 @@ class Apps
|
|||||||
mkdir($baseDir, 0755, true);
|
mkdir($baseDir, 0755, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 清理缓存
|
||||||
|
self::clearCache();
|
||||||
|
|
||||||
// 写入文件
|
// 写入文件
|
||||||
return (bool)file_put_contents(
|
return (bool)file_put_contents(
|
||||||
$configFile,
|
$configFile,
|
||||||
@ -618,8 +637,16 @@ class Apps
|
|||||||
*/
|
*/
|
||||||
public static function isInstalled(string $appName): bool
|
public static function isInstalled(string $appName): bool
|
||||||
{
|
{
|
||||||
|
$array = Base::json2array(Cache::get(self::CACHE_KEYS['installed'], []));
|
||||||
|
if (isset($array[$appName])) {
|
||||||
|
return $array[$appName];
|
||||||
|
}
|
||||||
|
|
||||||
$appConfig = self::getAppConfig($appName);
|
$appConfig = self::getAppConfig($appName);
|
||||||
return $appConfig['status'] === 'installed';
|
$array[$appName] = $appConfig['status'] === 'installed';
|
||||||
|
Cache::put(self::CACHE_KEYS['installed'], Base::array2json($array));
|
||||||
|
|
||||||
|
return $array[$appName];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -630,21 +657,22 @@ class Apps
|
|||||||
public static function appListUpdate(): array
|
public static function appListUpdate(): array
|
||||||
{
|
{
|
||||||
// 检查是否正在更新
|
// 检查是否正在更新
|
||||||
$cacheKey = 'appstore_update_running';
|
$cacheTmp = 'appstore_update_running';
|
||||||
if (Cache::has($cacheKey)) {
|
if (Cache::has($cacheTmp)) {
|
||||||
return Base::retError('应用列表正在更新中,请稍后再试');
|
return Base::retError('应用列表正在更新中,请稍后再试');
|
||||||
}
|
}
|
||||||
$onFailure = function (string $message) use ($cacheKey) {
|
$onFailure = function (string $message) use ($cacheTmp) {
|
||||||
Cache::forget($cacheKey);
|
Cache::forget($cacheTmp);
|
||||||
return Base::retError($message);
|
return Base::retError($message);
|
||||||
};
|
};
|
||||||
$onSuccess = function (string $message, array $data = []) use ($cacheKey) {
|
$onSuccess = function (string $message, array $data = []) use ($cacheTmp) {
|
||||||
Cache::forget($cacheKey);
|
self::clearCache();
|
||||||
|
Cache::forget($cacheTmp);
|
||||||
return Base::retSuccess($message, $data);
|
return Base::retSuccess($message, $data);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 设置更新状态
|
// 设置更新状态
|
||||||
Cache::put($cacheKey, true, 180); // 3分钟有效期
|
Cache::put($cacheTmp, true, 180); // 3分钟有效期
|
||||||
|
|
||||||
// 临时目录
|
// 临时目录
|
||||||
$tempDir = base_path('docker/appstore/temp/sources');
|
$tempDir = base_path('docker/appstore/temp/sources');
|
||||||
@ -659,7 +687,7 @@ class Apps
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// 下载源列表
|
// 下载源列表
|
||||||
$res = Ihttp::ihttp_request(self::$sourcesUrl);
|
$res = Ihttp::ihttp_request(self::SOURCES_URL);
|
||||||
if (Base::isError($res)) {
|
if (Base::isError($res)) {
|
||||||
return $onFailure('下载源列表失败');
|
return $onFailure('下载源列表失败');
|
||||||
}
|
}
|
||||||
@ -758,21 +786,22 @@ class Apps
|
|||||||
public static function downloadApp(string $url): array
|
public static function downloadApp(string $url): array
|
||||||
{
|
{
|
||||||
// 检查是否正在下载
|
// 检查是否正在下载
|
||||||
$cacheKey = 'appstore_download_running';
|
$cacheTmp = 'appstore_download_running';
|
||||||
if (Cache::has($cacheKey)) {
|
if (Cache::has($cacheTmp)) {
|
||||||
return Base::retError('应用正在下载中,请稍后再试');
|
return Base::retError('应用正在下载中,请稍后再试');
|
||||||
}
|
}
|
||||||
$onFailure = function (string $message) use ($cacheKey) {
|
$onFailure = function (string $message) use ($cacheTmp) {
|
||||||
Cache::forget($cacheKey);
|
Cache::forget($cacheTmp);
|
||||||
return Base::retError($message);
|
return Base::retError($message);
|
||||||
};
|
};
|
||||||
$onSuccess = function (string $message, array $data = []) use ($cacheKey) {
|
$onSuccess = function (string $message, array $data = []) use ($cacheTmp) {
|
||||||
Cache::forget($cacheKey);
|
self::clearCache();
|
||||||
|
Cache::forget($cacheTmp);
|
||||||
return Base::retSuccess($message, $data);
|
return Base::retSuccess($message, $data);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 设置下载状态
|
// 设置下载状态
|
||||||
Cache::put($cacheKey, true, 180); // 3分钟有效期
|
Cache::put($cacheTmp, true, 180); // 3分钟有效期
|
||||||
|
|
||||||
// 验证URL格式
|
// 验证URL格式
|
||||||
if (!filter_var($url, FILTER_VALIDATE_URL)) {
|
if (!filter_var($url, FILTER_VALIDATE_URL)) {
|
||||||
@ -845,7 +874,7 @@ class Apps
|
|||||||
|
|
||||||
// 处理应用名称
|
// 处理应用名称
|
||||||
$appName = Base::camel2snake(Base::cn2pinyin($configData['name'], '_'));
|
$appName = Base::camel2snake(Base::cn2pinyin($configData['name'], '_'));
|
||||||
if (in_array($appName, self::$protectedServiceNames)) {
|
if (in_array($appName, self::PROTECTED_NAMES)) {
|
||||||
return Base::retError('服务名称 "' . $appName . '" 被保护,不能使用');
|
return Base::retError('服务名称 "' . $appName . '" 被保护,不能使用');
|
||||||
}
|
}
|
||||||
$targetDir = base_path('docker/appstore/apps/' . $appName);
|
$targetDir = base_path('docker/appstore/apps/' . $appName);
|
||||||
@ -920,23 +949,6 @@ class Apps
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查文件名是否匹配 README 模式
|
|
||||||
*
|
|
||||||
* @param string $fileName 文件名
|
|
||||||
* @param array $patterns 正则模式数组
|
|
||||||
* @return bool 是否匹配
|
|
||||||
*/
|
|
||||||
private static function matchReadmePattern(string $fileName, array $patterns): bool
|
|
||||||
{
|
|
||||||
foreach ($patterns as $pattern) {
|
|
||||||
if (preg_match($pattern, $fileName)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 生成docker-compose.yml文件配置
|
* 生成docker-compose.yml文件配置
|
||||||
*
|
*
|
||||||
@ -989,7 +1001,7 @@ class Apps
|
|||||||
|
|
||||||
// 检查服务名称是否被保护
|
// 检查服务名称是否被保护
|
||||||
foreach ($content['services'] as $name => $service) {
|
foreach ($content['services'] as $name => $service) {
|
||||||
if (in_array($name, self::$protectedServiceNames)) {
|
if (in_array($name, self::PROTECTED_NAMES)) {
|
||||||
return Base::retError('服务名称 "' . $name . '" 被保护,不能使用');
|
return Base::retError('服务名称 "' . $name . '" 被保护,不能使用');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1164,6 +1176,27 @@ class Apps
|
|||||||
return $field[$lang] ?? $field[array_key_first($field)];
|
return $field[$lang] ?? $field[array_key_first($field)];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查应用是否可升级
|
||||||
|
*
|
||||||
|
* @param array $config 应用配置
|
||||||
|
* @param array $versions 可用版本列表
|
||||||
|
* @return bool 如果可升级返回 true,否则返回 false
|
||||||
|
*/
|
||||||
|
public static function isUpgradeable(array $config, array $versions): bool
|
||||||
|
{
|
||||||
|
$upgradeable = false;
|
||||||
|
if ($config['status'] === 'installed' && !empty($versions)) {
|
||||||
|
foreach ($versions as $version) {
|
||||||
|
if (version_compare($config['install_version'], $version['version'], '<')) {
|
||||||
|
$upgradeable = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $upgradeable;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 处理应用图标
|
* 处理应用图标
|
||||||
*
|
*
|
||||||
@ -1216,7 +1249,7 @@ class Apps
|
|||||||
* @param string $appName 应用名称
|
* @param string $appName 应用名称
|
||||||
* @return array 按语义化版本排序(从新到旧)的版本号数组
|
* @return array 按语义化版本排序(从新到旧)的版本号数组
|
||||||
*/
|
*/
|
||||||
private static function getAvailableVersions(string $appName): array
|
public static function getAvailableVersions(string $appName): array
|
||||||
{
|
{
|
||||||
$baseDir = base_path('docker/appstore/apps/' . $appName);
|
$baseDir = base_path('docker/appstore/apps/' . $appName);
|
||||||
$versions = [];
|
$versions = [];
|
||||||
@ -1347,6 +1380,18 @@ class Apps
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清除缓存
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private static function clearCache(): void
|
||||||
|
{
|
||||||
|
foreach (self::CACHE_KEYS as $key) {
|
||||||
|
Cache::forget($key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 执行curl请求
|
* 执行curl请求
|
||||||
*
|
*
|
||||||
|
|||||||
@ -36,7 +36,7 @@ services:
|
|||||||
image: "nginx:alpine"
|
image: "nginx:alpine"
|
||||||
ports:
|
ports:
|
||||||
- "${APP_PORT}:80"
|
- "${APP_PORT}:80"
|
||||||
- "${APP_SSL_PORT:-}:443"
|
- "${APP_SSL_PORT:-0}:443"
|
||||||
volumes:
|
volumes:
|
||||||
- ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf
|
- ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf
|
||||||
- ./:/var/www
|
- ./:/var/www
|
||||||
@ -100,7 +100,7 @@ services:
|
|||||||
appstore:
|
appstore:
|
||||||
container_name: "dootask-appstore-${APP_ID}"
|
container_name: "dootask-appstore-${APP_ID}"
|
||||||
privileged: true
|
privileged: true
|
||||||
image: "kuaifan/dootask-appstore:0.0.2"
|
image: "kuaifan/dootask-appstore:0.0.3"
|
||||||
volumes:
|
volumes:
|
||||||
- shared_data:/usr/share/dootask
|
- shared_data:/usr/share/dootask
|
||||||
- /var/run/docker.sock:/var/run/docker.sock
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user