diff --git a/CODERULE.md b/CODERULE.md index 1760f06..ea2aeac 100644 --- a/CODERULE.md +++ b/CODERULE.md @@ -159,7 +159,7 @@ Scheme 机制是 CURD 生成的前置依赖,开发流程如下: 1. **设计表结构**: - 方式 A:手动创建/修改数据库表结构。 - - 方式 B:手动编写/修改 `app/admin/scheme/` 下的 Scheme 类。 + - 方式 B:手动编写/修改 `app/admin/scheme/` 下的 Scheme 类。(推荐) 2. **同步结构(确保一致性)**: - 若采用方式 A,执行 `php think scheme:make -t {table}` 将变更同步到 Scheme。 diff --git a/app/admin/controller/Status.php b/app/admin/controller/Status.php new file mode 100644 index 0000000..839dc23 --- /dev/null +++ b/app/admin/controller/Status.php @@ -0,0 +1,9 @@ +getSystemInfo(); + $serverInfo = $this->getServerInfo(); + $databaseInfo = $this->getDatabaseInfo(); + $cacheInfo = $this->getCacheInfo(); + $storageInfo = $this->getStorageInfo(); + $phpConfigInfo = $this->getPhpConfigInfo(); + $performanceInfo = $this->getPerformanceInfo(); + + $this->assign('systemInfo', $systemInfo); + $this->assign('serverInfo', $serverInfo); + $this->assign('databaseInfo', $databaseInfo); + $this->assign('cacheInfo', $cacheInfo); + $this->assign('storageInfo', $storageInfo); + $this->assign('phpConfigInfo', $phpConfigInfo); + $this->assign('performanceInfo', $performanceInfo); + + return $this->fetch(); + } + + protected function getSystemInfo() + { + return [ + 'framework' => 'Ulthon Admin', + 'version' => Version::VERSION, + 'product_version' => Version::PRODUCT_VERSION ?: Version::VERSION, + 'layui_version' => Version::LAYUI_VERSION, + 'thinkphp_version' => app()->version(), + 'php_version' => PHP_VERSION, + 'server_time' => date('Y-m-d H:i:s'), + 'timezone' => date_default_timezone_get(), + ]; + } + + protected function getServerInfo() + { + $serverSoftware = $_SERVER['SERVER_SOFTWARE'] ?? 'Unknown'; + $os = PHP_OS; + $sapi = php_sapi_name(); + $ip = $_SERVER['SERVER_ADDR'] ?? '127.0.0.1'; + $port = $_SERVER['SERVER_PORT'] ?? '80'; + + return [ + 'server_software' => $serverSoftware, + 'os' => $os, + 'sapi' => $sapi, + 'ip' => $ip, + 'port' => $port, + ]; + } + + protected function getDatabaseInfo() + { + $connectionName = Config::get('database.default', 'mysql'); + $dbConfig = Config::get('database.connections.' . $connectionName); + $info = [ + 'type' => $dbConfig['type'] ?? 'Unknown', + 'hostname' => $dbConfig['hostname'] ?? 'Unknown', + 'database' => $dbConfig['database'] ?? 'Unknown', + 'prefix' => $dbConfig['prefix'] ?? '', + 'charset' => $dbConfig['charset'] ?? 'Unknown', + 'status' => 'unknown', + 'version' => 'Unknown', + 'table_count' => 0, + ]; + + try { + $connection = Db::connect($connectionName); + $version = $connection->query('SELECT VERSION() as version')[0]['version'] ?? 'Unknown'; + + $prefix = $dbConfig['prefix'] ?? ''; + $tableCount = 0; + if ($prefix) { + $tables = $connection->query("SHOW TABLES LIKE '{$prefix}%'"); + $tableCount = count($tables); + } else { + $tables = $connection->query('SHOW TABLES'); + $tableCount = count($tables); + } + + $info['status'] = 'connected'; + $info['version'] = $version; + $info['table_count'] = $tableCount; + } catch (\Exception $e) { + $info['status'] = 'error'; + $info['error'] = $e->getMessage(); + } + + return $info; + } + + protected function getCacheInfo() + { + $defaultDriver = Config::get('cache.default'); + $cacheConfig = Config::get('cache.stores.' . $defaultDriver); + + $info = [ + 'default_driver' => $defaultDriver, + 'type' => $cacheConfig['type'] ?? 'Unknown', + 'path' => $cacheConfig['path'] ?? '', + 'prefix' => $cacheConfig['prefix'] ?? '', + 'expire' => $cacheConfig['expire'] ?? 0, + 'status' => 'unknown', + ]; + + try { + $testKey = 'system_status_test_' . time(); + Cache::set($testKey, 'test', 10); + $testValue = Cache::get($testKey); + Cache::delete($testKey); + + if ($testValue === 'test') { + $info['status'] = 'available'; + } else { + $info['status'] = 'error'; + } + } catch (\Exception $e) { + $info['status'] = 'error'; + $info['error'] = $e->getMessage(); + } + + return $info; + } + + protected function getStorageInfo() + { + $rootPath = app()->getRootPath(); + $freeBytes = disk_free_space($rootPath); + $totalBytes = disk_total_space($rootPath); + $usedBytes = $totalBytes - $freeBytes; + $usagePercent = ($usedBytes / $totalBytes) * 100; + + return [ + 'root_path' => $rootPath, + 'total' => $this->formatBytes($totalBytes), + 'free' => $this->formatBytes($freeBytes), + 'used' => $this->formatBytes($usedBytes), + 'usage_percent' => round($usagePercent, 2), + ]; + } + + protected function getPhpConfigInfo() + { + return [ + 'upload_max_filesize' => ini_get('upload_max_filesize'), + 'post_max_size' => ini_get('post_max_size'), + 'memory_limit' => ini_get('memory_limit'), + 'max_execution_time' => ini_get('max_execution_time') . 's', + 'max_input_time' => ini_get('max_input_time') . 's', + 'safe_mode' => ini_get('safe_mode') ? 'On' : 'Off', + 'allow_url_fopen' => ini_get('allow_url_fopen') ? 'On' : 'Off', + 'file_uploads' => ini_get('file_uploads') ? 'On' : 'Off', + 'disabled_functions' => ini_get('disable_functions') ?: 'None', + ]; + } + + protected function getPerformanceInfo() + { + $memoryUsage = memory_get_usage(true); + $memoryLimit = $this->convertToBytes(ini_get('memory_limit')); + $memoryPercent = $memoryLimit > 0 ? ($memoryUsage / $memoryLimit) * 100 : 0; + + return [ + 'memory_usage' => $this->formatBytes($memoryUsage), + 'memory_limit' => $this->formatBytes($memoryLimit), + 'memory_percent' => round($memoryPercent, 2), + 'peak_memory' => $this->formatBytes(memory_get_peak_usage(true)), + 'load_average' => function_exists('sys_getloadavg') ? implode(', ', sys_getloadavg()) : 'N/A', + ]; + } + + protected function formatBytes($bytes, $precision = 2) + { + $units = ['B', 'KB', 'MB', 'GB', 'TB']; + + for ($i = 0; $bytes > 1024 && $i < count($units) - 1; $i++) { + $bytes /= 1024; + } + + return round($bytes, $precision) . ' ' . $units[$i]; + } + + protected function convertToBytes($value) + { + $value = trim($value); + $unit = strtolower($value[strlen($value) - 1]); + $value = (float) $value; + + switch ($unit) { + case 'g': + $value *= 1024; + case 'm': + $value *= 1024; + case 'k': + $value *= 1024; + } + + return $value; + } +} diff --git a/extend/base/admin/service/adminInitData/SystemMenu.php b/extend/base/admin/service/adminInitData/SystemMenu.php index 2ccf7e9..ac59932 100644 --- a/extend/base/admin/service/adminInitData/SystemMenu.php +++ b/extend/base/admin/service/adminInitData/SystemMenu.php @@ -143,6 +143,17 @@ $ul_system_menu = array( "sort" => 0, "status" => 1, ), + array( + "id" => 255, + "pid" => 228, + "title" => "系统状态", + "icon" => "fa fa-info-circle", + "href" => "status/index", + "params" => "", + "target" => "_self", + "sort" => 0, + "status" => 1, + ), array( "id" => 254, "pid" => 249, diff --git a/extend/base/admin/view/status/index.html b/extend/base/admin/view/status/index.html new file mode 100644 index 0000000..93f3964 --- /dev/null +++ b/extend/base/admin/view/status/index.html @@ -0,0 +1,323 @@ + +
+
+
+
+
+
系统概览
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
框架名称{$systemInfo.framework}框架版本{$systemInfo.version}
Layui版本{$systemInfo.layui_version}ThinkPHP版本{$systemInfo.thinkphp_version}
PHP版本{$systemInfo.php_version}服务器时间{$systemInfo.server_time}
时区{$systemInfo.timezone}
+
+
+
+ +
+
+
服务器环境
+
+ + + + + + + + + + + + + + + + + + + + + + + +
服务器软件 + {$serverInfo.server_software} +
操作系统 + {$serverInfo.os} +
运行模式{$serverInfo.sapi}
服务器IP{$serverInfo.ip}:{$serverInfo.port}
+
+
+
+ +
+
+
数据库状态
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {if $databaseInfo.status == 'error'} + + + + + {/if} + +
数据库类型{$databaseInfo.type}
连接状态 + {if $databaseInfo.status == 'connected'} + 正常 + {elseif $databaseInfo.status == 'error'} + 异常 + {else/} + 未知 + {/if} +
数据库版本{$databaseInfo.version}
数据库名称{$databaseInfo.database}
表前缀{$databaseInfo.prefix}
数据表数量{$databaseInfo.table_count}
错误信息{$databaseInfo.error}
+
+
+
+ +
+
+
存储状态
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
根目录 + {$storageInfo.root_path} +
总容量{$storageInfo.total}
已用空间{$storageInfo.used}
可用空间{$storageInfo.free}
使用率 +
+
+
+
+
+
+
+ +
+
+
缓存配置
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {if $cacheInfo.status == 'error'} + + + + + {/if} + +
默认驱动{$cacheInfo.default_driver}
驱动类型{$cacheInfo.type}
缓存路径{$cacheInfo.path}
缓存前缀{$cacheInfo.prefix}
有效期{$cacheInfo.expire == 0 ? '永久' : $cacheInfo.expire . '秒'}
状态 + {if $cacheInfo.status == 'available'} + 可用 + {elseif $cacheInfo.status == 'error'} + 异常 + {else/} + 未知 + {/if} +
错误信息{$cacheInfo.error}
+
+
+
+ +
+
+
PHP配置
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
上传文件限制{$phpConfigInfo.upload_max_filesize}
POST最大限制{$phpConfigInfo.post_max_size}
内存限制{$phpConfigInfo.memory_limit}
最大执行时间{$phpConfigInfo.max_execution_time}
最大输入时间{$phpConfigInfo.max_input_time}
文件上传{$phpConfigInfo.file_uploads}
Allow URL Fopen{$phpConfigInfo.allow_url_fopen}
禁用函数 + {php}echo mb_strlen($phpConfigInfo['disabled_functions']) > 50 ? mb_substr($phpConfigInfo['disabled_functions'], 0, 50) . '...' : $phpConfigInfo['disabled_functions'];{/php} +
+
+
+
+ +
+
+
性能指标
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
内存使用{$performanceInfo.memory_usage}
内存限制{$performanceInfo.memory_limit}
内存使用率 +
+
+
+
峰值内存{$performanceInfo.peak_memory}
系统负载{$performanceInfo.load_average}
+
+
+
+ +
+
+
diff --git a/extend/base/admin/view/status/index.js b/extend/base/admin/view/status/index.js new file mode 100644 index 0000000..e52c46b --- /dev/null +++ b/extend/base/admin/view/status/index.js @@ -0,0 +1,40 @@ +$(function () { + layui.use(['element', 'layer'], function () { + var element = layui.element; + var layer = layui.layer; + + var storageUsage = parseFloat($('div[lay-filter="storage"] .layui-progress-bar').attr('lay-percent')); + var memoryUsage = parseFloat($('div[lay-filter="memory"] .layui-progress-bar').attr('lay-percent')); + + if (storageUsage > 80) { + $('div[lay-filter="storage"] .layui-progress-bar').addClass('layui-bg-red'); + } else if (storageUsage > 60) { + $('div[lay-filter="storage"] .layui-progress-bar').addClass('layui-bg-orange'); + } + + if (memoryUsage > 80) { + $('div[lay-filter="memory"] .layui-progress-bar').addClass('layui-bg-red'); + } else if (memoryUsage > 60) { + $('div[lay-filter="memory"] .layui-progress-bar').addClass('layui-bg-orange'); + } + + if (typeof ClipboardJS !== 'undefined') { + var clipboard = new ClipboardJS('[data-toggle="copy-text"]', { + text: function (trigger) { + return $(trigger).attr('data-clipboard-text'); + } + }); + + clipboard.on('success', function (e) { + layer.msg('复制成功', {icon: 1, time: 1500}); + e.clearSelection(); + }); + + clipboard.on('error', function (e) { + layer.msg('复制失败,请手动复制', {icon: 2}); + }); + } + }); + + ua.listen(); +});