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} |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ | 数据库类型 |
+ {$databaseInfo.type} |
+
+
+ | 连接状态 |
+
+ {if $databaseInfo.status == 'connected'}
+ 正常
+ {elseif $databaseInfo.status == 'error'}
+ 异常
+ {else/}
+ 未知
+ {/if}
+ |
+
+
+ | 数据库版本 |
+ {$databaseInfo.version} |
+
+
+ | 数据库名称 |
+ {$databaseInfo.database} |
+
+
+ | 表前缀 |
+ {$databaseInfo.prefix} |
+
+
+ | 数据表数量 |
+ {$databaseInfo.table_count} |
+
+ {if $databaseInfo.status == 'error'}
+
+ | 错误信息 |
+ {$databaseInfo.error} |
+
+ {/if}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ | 根目录 |
+
+ {$storageInfo.root_path}
+ |
+
+
+ | 总容量 |
+ {$storageInfo.total} |
+
+
+ | 已用空间 |
+ {$storageInfo.used} |
+
+
+ | 可用空间 |
+ {$storageInfo.free} |
+
+
+ | 使用率 |
+
+
+ |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ | 默认驱动 |
+ {$cacheInfo.default_driver} |
+
+
+ | 驱动类型 |
+ {$cacheInfo.type} |
+
+
+ | 缓存路径 |
+ {$cacheInfo.path} |
+
+
+ | 缓存前缀 |
+ {$cacheInfo.prefix} |
+
+
+ | 有效期 |
+ {$cacheInfo.expire == 0 ? '永久' : $cacheInfo.expire . '秒'} |
+
+
+ | 状态 |
+
+ {if $cacheInfo.status == 'available'}
+ 可用
+ {elseif $cacheInfo.status == 'error'}
+ 异常
+ {else/}
+ 未知
+ {/if}
+ |
+
+ {if $cacheInfo.status == 'error'}
+
+ | 错误信息 |
+ {$cacheInfo.error} |
+
+ {/if}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ | 上传文件限制 |
+ {$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();
+});