This commit is contained in:
parent
e3951a09d8
commit
bf44e4abb4
|
|
@ -82,12 +82,15 @@ class Login extends Base
|
|||
$tData['belong_id'] = $account['belong_id'];
|
||||
$tData['username'] = $account['username'];
|
||||
$tData['master_flag'] = $account['master_flag'];
|
||||
$token = Token::buildToken($tData, 72 * 60 * 60);
|
||||
|
||||
$uuid = Str::uuid();
|
||||
$token = Token::buildToken(['uuid' => $uuid, 'time' => time() + config("jwt.ttl")], config("jwt.ttl"));
|
||||
// 记录登录日志
|
||||
$this->eventDispatcher->dispatch(new LogEvent($tData, $param, compact("token")));
|
||||
$this->eventDispatcher->dispatch(new LogEvent($tData, $uuid));
|
||||
// 根据账号所属角色缓存相应的权限数据
|
||||
$auths = Account::getAuths($account['account_id'], $account['account_type'], $account['master_flag']);
|
||||
$redis->set("AUTH:" . $account['account_id'], json_encode($auths), 72 * 60 * 60);
|
||||
$redis->set("AUTH:" . $account['account_id'], json_encode($auths));
|
||||
$redis->set("USER:" . $uuid, json_encode($tData), config("jwt.ttl"));
|
||||
// 生成token
|
||||
return $this->success(compact("token"));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,8 +8,16 @@ namespace App\Controller\Admin;
|
|||
use App\Annotation\Auth;
|
||||
use App\Model\Menu as mModel;
|
||||
use App\Model\Dept as dModel;
|
||||
use App\Model\Online as oModel;
|
||||
use App\Model\Role as rModel;
|
||||
use App\Utils\AppInfoHelper;
|
||||
use App\Utils\CpuHelper;
|
||||
use App\Utils\DiskInfoHelper;
|
||||
use App\Utils\Ip;
|
||||
use App\Utils\MemoryHelper;
|
||||
use App\Utils\Param;
|
||||
use App\Utils\RedisInfoHelper;
|
||||
use App\Utils\SystemHelper;
|
||||
use Hyperf\HttpServer\Annotation\Controller;
|
||||
use Hyperf\HttpServer\Annotation\DeleteMapping;
|
||||
use Hyperf\HttpServer\Annotation\GetMapping;
|
||||
|
|
@ -296,4 +304,65 @@ class System extends Base
|
|||
$res = aModel::del($id);
|
||||
return $res ? $this->success("操作成功") : $this->error("操作失败");
|
||||
}
|
||||
|
||||
// 系统监控
|
||||
#[GetMapping(path: "monitor/server")]
|
||||
#[Auth(auth: "monitor:server")]
|
||||
public function monitorServer()
|
||||
{
|
||||
$cores = CpuHelper::getCpuCores();
|
||||
$usage = CpuHelper::getCpuUsage(1);
|
||||
$data['cpu'] = [
|
||||
'cpu_cores' => $cores,
|
||||
'user_usage' => $usage['user'] . '%',
|
||||
'system_usage' => $usage['system'] . '%',
|
||||
'idle' => $usage['idle'] . '%'
|
||||
];
|
||||
|
||||
$mem = MemoryHelper::getMemoryInfo();
|
||||
$data['mem'] = [
|
||||
'total' => $mem['total'] . "M",
|
||||
'used' => $mem['used'] . "M",
|
||||
'free' => $mem['free'] . "M",
|
||||
'usage' => $mem['usage'] . '%'
|
||||
];
|
||||
|
||||
$sys = SystemHelper::getServerInfo();
|
||||
$data['sys'] = [
|
||||
'server_name' => $sys['server_name'],
|
||||
'server_ip' => $sys['server_ip'],
|
||||
'os' => $sys['os'],
|
||||
'architecture' => $sys['architecture'],
|
||||
'php_version' => $sys['php_version']
|
||||
];
|
||||
|
||||
$app = AppInfoHelper::getInfo();
|
||||
$data['app'] = [
|
||||
'swoole_version' => $app['swoole_version'],
|
||||
'hyperf_version' => $app['hyperf_version'],
|
||||
'project_path' => $app['project_path'],
|
||||
'start_time' => $app['start_time'],
|
||||
'uptime' => $app['uptime']
|
||||
];
|
||||
|
||||
$disks = DiskInfoHelper::getAllDisks();
|
||||
$data['disks'] = $disks;
|
||||
|
||||
$redis = RedisInfoHelper::getInfo();
|
||||
$data['redis'] = $redis;
|
||||
return $this->success($data);
|
||||
}
|
||||
|
||||
#[GetMapping(path: "online/list")]
|
||||
#[Auth(auth: "online:list")]
|
||||
public function onlineList()
|
||||
{
|
||||
$param = Param::only(['limit' => 10, 'username']);
|
||||
$paginate = oModel::list($param);
|
||||
foreach ($paginate->items() as &$item) {
|
||||
$item['os'] = Ip::getBrowserAndOS($item['ua']);
|
||||
$item['location'] = ip2region($item['ip']);
|
||||
}
|
||||
return $this->success('在线用户', $paginate->items(), $paginate->total());
|
||||
}
|
||||
}
|
||||
|
|
@ -7,5 +7,5 @@ namespace App\Event;
|
|||
|
||||
class LogEvent
|
||||
{
|
||||
public function __construct(public array $data, public array $request, public array $response){}
|
||||
public function __construct(public array $data, public string $uuid){}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
|
||||
namespace App\Listener;
|
||||
|
||||
use App\Utils\AppInfoHelper;
|
||||
use Hyperf\Event\Contract\ListenerInterface;
|
||||
use Hyperf\Framework\Event\BootApplication;
|
||||
|
||||
class AppBootListener implements ListenerInterface
|
||||
{
|
||||
public function listen(): array
|
||||
{
|
||||
return [BootApplication::class];
|
||||
}
|
||||
|
||||
public function process(object $event): void
|
||||
{
|
||||
// 在框架启动时初始化启动时间
|
||||
AppInfoHelper::initStartTime();
|
||||
}
|
||||
}
|
||||
|
|
@ -6,10 +6,7 @@
|
|||
namespace App\Listener;
|
||||
|
||||
use App\Event\LogEvent;
|
||||
use App\Model\Account;
|
||||
use App\Model\AccountLog;
|
||||
use App\Model\Menu;
|
||||
use App\Service\Com;
|
||||
use App\Model\Online;
|
||||
use App\Utils\Ip;
|
||||
use Hyperf\Event\Contract\ListenerInterface;
|
||||
|
||||
|
|
@ -24,20 +21,15 @@ class LogHandleListener implements ListenerInterface
|
|||
|
||||
public function process(object $event): void
|
||||
{
|
||||
AccountLog::loginRecord([
|
||||
Online::insert([
|
||||
'session_id' => $event->uuid,
|
||||
'account_type' => $event->data['account_type'],
|
||||
'belong_id' => $event->data['belong_id'],
|
||||
'account_id' => $event->data['account_id'],
|
||||
'username' => $event->data['username'],
|
||||
'create_time' => date("Y-m-d H:i:s"),
|
||||
'online_time' => date("Y-m-d H:i:s"),
|
||||
'ua' => Ip::ua(),
|
||||
'ip' => Ip::ip(),
|
||||
'flag' => "login",
|
||||
'title' => "账号登录",
|
||||
'method' => "POST",
|
||||
'code' => 0,
|
||||
'request' => json_encode($event->request, JSON_UNESCAPED_UNICODE),
|
||||
'response' => json_encode(['code' => 0, 'msg' => 'success', 'data' => $event->response], JSON_UNESCAPED_UNICODE)
|
||||
'ip' => Ip::ip()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
@ -5,12 +5,14 @@ declare(strict_types=1);
|
|||
namespace App\Middleware;
|
||||
|
||||
use App\Utils\Token;
|
||||
use Hyperf\Context\ApplicationContext;
|
||||
use Hyperf\Context\Context;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Server\MiddlewareInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use function Hyperf\Config\config;
|
||||
|
||||
class JWTMiddleware implements MiddlewareInterface
|
||||
{
|
||||
|
|
@ -32,47 +34,36 @@ class JWTMiddleware implements MiddlewareInterface
|
|||
*/
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
||||
{
|
||||
$isMatched = preg_match('/\/user\//', $request->getUri()->getPath());
|
||||
if ($isMatched) {
|
||||
// 用户端
|
||||
$request = $request->withAttribute("isLogin", false);
|
||||
$request = $request->withAttribute("user", []);
|
||||
$request = $request->withAttribute("merchant_user_id", 0);
|
||||
$request = $request->withAttribute("merchant_id", 0);
|
||||
try {
|
||||
$token = $request->getHeaderLine("Authorization", "");
|
||||
$user = Token::parseToken(str_replace("Bearer ", "", $token));
|
||||
if (!empty($user)) {
|
||||
$request = $request->withAttribute("isLogin", true);
|
||||
// 基础信息
|
||||
$request = $request->withAttribute("user", $user);
|
||||
// 账号ID
|
||||
$request = $request->withAttribute("merchant_user_id", $user['merchant_user_id']);
|
||||
// 商户ID
|
||||
$request = $request->withAttribute("merchant_id", $user['merchant_id']);
|
||||
}
|
||||
} catch (\Exception $exception) {
|
||||
}
|
||||
} else {
|
||||
// 管理端 和 商户端
|
||||
$request = $request->withAttribute("isLogin", false);
|
||||
$request = $request->withAttribute("account", []);
|
||||
$request = $request->withAttribute("account_id", 0);
|
||||
try {
|
||||
$token = $request->getHeaderLine("Authorization", "");
|
||||
$account = Token::parseToken(str_replace("Bearer ", "", $token));
|
||||
if (!empty($account)) {
|
||||
// 是否登录
|
||||
$request = $request->withAttribute("isLogin", true);
|
||||
// 账号ID
|
||||
$request = $request->withAttribute("account_id", $account['account_id']);
|
||||
// 基础信息
|
||||
$request = $request->withAttribute("account", $account);
|
||||
}
|
||||
} catch (\Exception $exception) {
|
||||
$container = ApplicationContext::getContainer();
|
||||
$redis = $container->get(\Hyperf\Redis\Redis::class);
|
||||
// 管理端 和 商户端
|
||||
$request = $request->withAttribute("isLogin", false);
|
||||
$request = $request->withAttribute("account", []);
|
||||
$request = $request->withAttribute("account_id", 0);
|
||||
try {
|
||||
$token = $request->getHeaderLine("Authorization", "");
|
||||
$result = Token::parseToken(str_replace("Bearer ", "", $token));
|
||||
$account = !empty($result) ? json_decode($redis->get("USER:" . $result['uuid']), true) : null;
|
||||
if (!empty($account)) {
|
||||
// 是否登录
|
||||
$request = $request->withAttribute("isLogin", true);
|
||||
$account = json_decode($redis->get("USER:" . $result['uuid']), true);
|
||||
// 账号ID
|
||||
$request = $request->withAttribute("account_id", $account['account_id']);
|
||||
// 基础信息
|
||||
$request = $request->withAttribute("account", $account);
|
||||
}
|
||||
} catch (\Exception $exception) {
|
||||
}
|
||||
Context::set(ServerRequestInterface::class, $request);
|
||||
return $handler->handle($request);
|
||||
$response = $handler->handle($request);
|
||||
// 续签逻辑
|
||||
if (!empty($result) && $result['time'] - time() < 600 && !empty($account)) {
|
||||
$newToken = Token::buildToken(['uuid' => $result['uuid'], 'time' => time() + config("jwt.ttl")], config("jwt.ttl"));
|
||||
$response = $response->withHeader('X-Token-Refresh', $newToken);
|
||||
$response = $response->withHeader('X-Token-Expire', config("jwt.ttl"));
|
||||
$redis->set("USER:" . $result['uuid'], json_encode($account), config("jwt.ttl"));
|
||||
}
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,17 +41,6 @@ class AccountLog extends Model
|
|||
*/
|
||||
protected array $casts = ['log_id' => 'integer', 'account_type' => 'integer', 'belong_id' => 'integer', 'account_id' => 'integer', 'code' => 'integer'];
|
||||
|
||||
/**
|
||||
* 登录记录日志
|
||||
* Author: cfn <cfn@leapy.cn>
|
||||
* @param array $data
|
||||
* @return bool
|
||||
*/
|
||||
public static function loginRecord(array $data): bool
|
||||
{
|
||||
return self::insert($data);
|
||||
}
|
||||
|
||||
public static function recordLog(RequestInterface $request, mixed $admin, string $flag, mixed $response): bool
|
||||
{
|
||||
$code = 0;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,57 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Model;
|
||||
|
||||
/**
|
||||
* @property int $online_id
|
||||
* @property string $session_id
|
||||
* @property int $account_type
|
||||
* @property int $belong_id
|
||||
* @property int $account_id
|
||||
* @property int $fd
|
||||
* @property int $status
|
||||
* @property string $ip
|
||||
* @property string $ua
|
||||
* @property string $online_time
|
||||
* @property string $offline_time
|
||||
*/
|
||||
class Online extends Model
|
||||
{
|
||||
/**
|
||||
* The table associated with the model.
|
||||
*/
|
||||
protected ?string $table = 'online';
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*/
|
||||
protected array $fillable = [];
|
||||
|
||||
/**
|
||||
* The attributes that should be cast to native types.
|
||||
*/
|
||||
protected array $casts = ['online_id' => 'integer', 'account_type' => 'integer', 'belong_id' => 'integer', 'account_id' => 'integer', 'fd' => 'integer', 'status' => 'integer'];
|
||||
|
||||
public static function list(array $param)
|
||||
{
|
||||
return self::where("status", 1)
|
||||
->with(["account" => function ($query) {
|
||||
$query->select(["account_id", "username", 'dept_id']);
|
||||
}, "account.dept" => function ($query) {
|
||||
$query->select(["dept_id", "dept_name"]);
|
||||
}])
|
||||
->when(isset($param['username']) && $param['username'] != '', function ($q) use ($param) {
|
||||
$q->where('username', 'like', "%{$param['username']}%");
|
||||
})
|
||||
->orderByDesc("online_id")
|
||||
->select(['session_id', 'username', 'ip', 'ua', 'online_time', 'account_id'])
|
||||
->paginate((int)$param['limit']);
|
||||
}
|
||||
|
||||
public function account()
|
||||
{
|
||||
return $this->hasOne(Account::class, 'account_id', 'account_id');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
<?php
|
||||
|
||||
namespace App\Utils;
|
||||
|
||||
use Hyperf\Context\ApplicationContext;
|
||||
use Swoole\Server;
|
||||
use function Hyperf\Collection\value;
|
||||
|
||||
class AppInfoHelper
|
||||
{
|
||||
private static float $startTime = 0;
|
||||
|
||||
public static function initStartTime(): void
|
||||
{
|
||||
// 避免重复初始化
|
||||
if (self::$startTime == 0) {
|
||||
self::$startTime = microtime(true);
|
||||
}
|
||||
}
|
||||
|
||||
public static function getInfo(): array
|
||||
{
|
||||
return [
|
||||
'hyperf_version' => self::getHyperfVersion(),
|
||||
'swoole_version' => defined('SWOOLE_VERSION') ? SWOOLE_VERSION : 'N/A',
|
||||
'project_path' => BASE_PATH,
|
||||
'start_time' => self::getStartTime(),
|
||||
'uptime' => self::getUptime(),
|
||||
];
|
||||
}
|
||||
|
||||
private static function getHyperfVersion(): string
|
||||
{
|
||||
$composerFile = BASE_PATH . '/composer.lock';
|
||||
if (file_exists($composerFile)) {
|
||||
$composer = json_decode(file_get_contents($composerFile), true);
|
||||
foreach ($composer['packages'] ?? [] as $pkg) {
|
||||
if ($pkg['name'] === 'hyperf/framework') {
|
||||
return $pkg['version'] ?? 'unknown';
|
||||
}
|
||||
}
|
||||
}
|
||||
return 'unknown';
|
||||
}
|
||||
|
||||
private static function getStartTime(): string
|
||||
{
|
||||
$start = self::getServerStartTimestamp() ?: self::$startTime;
|
||||
return $start ? date('Y-m-d H:i:s', (int)$start) : 'unknown';
|
||||
}
|
||||
|
||||
private static function getUptime(): string
|
||||
{
|
||||
$start = self::getServerStartTimestamp() ?: self::$startTime;
|
||||
if (!$start) {
|
||||
return 'unknown';
|
||||
}
|
||||
$seconds = (int)(microtime(true) - $start);
|
||||
return self::formatDuration($seconds);
|
||||
}
|
||||
|
||||
private static function getServerStartTimestamp(): ?float
|
||||
{
|
||||
try {
|
||||
$container = ApplicationContext::getContainer();
|
||||
if ($container->has(Server::class)) {
|
||||
$server = $container->get(Server::class);
|
||||
return $server->start_time ?? null;
|
||||
}
|
||||
} catch (\Throwable) {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static function formatDuration(int $seconds): string
|
||||
{
|
||||
$h = floor($seconds / 3600);
|
||||
$m = floor(($seconds % 3600) / 60);
|
||||
$s = $seconds % 60;
|
||||
return sprintf('%2d天%2d小时%2d分钟', $h, $m, $s);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
<?php
|
||||
|
||||
namespace App\Utils;
|
||||
|
||||
class CpuHelper
|
||||
{
|
||||
/**
|
||||
* 获取 CPU 核心数
|
||||
*/
|
||||
public static function getCpuCores(): int
|
||||
{
|
||||
return (int) shell_exec('nproc');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 CPU 使用情况(用户、系统、空闲百分比)
|
||||
*/
|
||||
public static function getCpuUsage(int $interval = 1): array
|
||||
{
|
||||
$stat1 = self::readStat();
|
||||
sleep($interval);
|
||||
$stat2 = self::readStat();
|
||||
|
||||
$diff = [];
|
||||
foreach ($stat1 as $key => $value) {
|
||||
$diff[$key] = $stat2[$key] - $value;
|
||||
}
|
||||
|
||||
$total = array_sum($diff);
|
||||
if ($total == 0) {
|
||||
return [
|
||||
'user' => 0,
|
||||
'system' => 0,
|
||||
'idle' => 100,
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'user' => round(($diff['user'] + $diff['nice']) / $total * 100, 2),
|
||||
'system' => round($diff['system'] / $total * 100, 2),
|
||||
'idle' => round($diff['idle'] / $total * 100, 2),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取 /proc/stat 文件中的 CPU 信息
|
||||
*/
|
||||
private static function readStat(): array
|
||||
{
|
||||
$fh = fopen('/proc/stat', 'r');
|
||||
$line = fgets($fh);
|
||||
fclose($fh);
|
||||
|
||||
$parts = preg_split('/\s+/', trim($line));
|
||||
// /proc/stat 第一行示例:
|
||||
// cpu 3357 0 4313 1362393 0 0 0
|
||||
return [
|
||||
'user' => (int) $parts[1],
|
||||
'nice' => (int) $parts[2],
|
||||
'system' => (int) $parts[3],
|
||||
'idle' => (int) $parts[4],
|
||||
'iowait' => (int) ($parts[5] ?? 0),
|
||||
'irq' => (int) ($parts[6] ?? 0),
|
||||
'softirq'=> (int) ($parts[7] ?? 0),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,128 @@
|
|||
<?php
|
||||
|
||||
namespace App\Utils;
|
||||
|
||||
class DiskInfoHelper
|
||||
{
|
||||
/**
|
||||
* 获取所有磁盘信息
|
||||
*/
|
||||
public static function getAllDisks(): array
|
||||
{
|
||||
if (PHP_OS_FAMILY === 'Windows') {
|
||||
return self::getWindowsDisks();
|
||||
}
|
||||
|
||||
return self::getLinuxDisks();
|
||||
}
|
||||
|
||||
/**
|
||||
* Linux 系统:通过 df -T 获取磁盘信息
|
||||
*/
|
||||
protected static function getLinuxDisks(): array
|
||||
{
|
||||
$output = [];
|
||||
@exec('df -T -B1 2>/dev/null', $output);
|
||||
|
||||
$disks = [];
|
||||
$ignoreFs = ['tmpfs', 'devtmpfs', 'overlay', 'squashfs', 'proc', 'sysfs', 'cgroup'];
|
||||
|
||||
foreach ($output as $line) {
|
||||
if (str_starts_with($line, 'Filesystem') || empty(trim($line))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
[$filesystem, $type, $size, $used, $avail, $usePercent, $mount] = preg_split('/\s+/', $line);
|
||||
|
||||
if (in_array($type, $ignoreFs)) {
|
||||
continue; // 过滤掉虚拟文件系统
|
||||
}
|
||||
|
||||
$size = (int)$size;
|
||||
$used = (int)$used;
|
||||
$avail = (int)$avail;
|
||||
$usePercent = rtrim($usePercent, '%');
|
||||
|
||||
$disks[] = [
|
||||
'path' => $mount,
|
||||
'filesystem' => $filesystem,
|
||||
'type' => $type,
|
||||
'total' => self::formatBytes($size),
|
||||
'used' => self::formatBytes($used),
|
||||
'available' => self::formatBytes($avail),
|
||||
'used_percent' => (float)$usePercent . '%',
|
||||
];
|
||||
}
|
||||
|
||||
return $disks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Windows 系统:通过 wmic 获取磁盘信息
|
||||
*/
|
||||
protected static function getWindowsDisks(): array
|
||||
{
|
||||
$output = [];
|
||||
@exec('wmic logicaldisk get Caption,FileSystem,Size,FreeSpace,Description /format:list', $output);
|
||||
|
||||
$disks = [];
|
||||
$current = [];
|
||||
|
||||
foreach ($output as $line) {
|
||||
$line = trim($line);
|
||||
if ($line === '') {
|
||||
if (!empty($current)) {
|
||||
$disks[] = self::formatWindowsDisk($current);
|
||||
$current = [];
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
[$key, $value] = array_map('trim', explode('=', $line, 2) + [null, null]);
|
||||
if ($key) {
|
||||
$current[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($current)) {
|
||||
$disks[] = self::formatWindowsDisk($current);
|
||||
}
|
||||
|
||||
return $disks;
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化 Windows 磁盘数据
|
||||
*/
|
||||
protected static function formatWindowsDisk(array $data): array
|
||||
{
|
||||
$size = isset($data['Size']) ? (int)$data['Size'] : 0;
|
||||
$free = isset($data['FreeSpace']) ? (int)$data['FreeSpace'] : 0;
|
||||
$used = $size - $free;
|
||||
$usedPercent = $size > 0 ? round($used / $size * 100, 2) : 0;
|
||||
|
||||
return [
|
||||
'path' => $data['Caption'] ?? '',
|
||||
'filesystem' => $data['FileSystem'] ?? '',
|
||||
'type' => $data['Description'] ?? '',
|
||||
'total' => self::formatBytes($size),
|
||||
'used' => self::formatBytes($used),
|
||||
'available' => self::formatBytes($free),
|
||||
'used_percent' => $usedPercent . "%",
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化字节为易读形式
|
||||
*/
|
||||
protected static function formatBytes(int $bytes): string
|
||||
{
|
||||
if ($bytes <= 0) {
|
||||
return '0 B';
|
||||
}
|
||||
|
||||
$units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];
|
||||
$i = (int)floor(log($bytes, 1024));
|
||||
return sprintf('%.2f %s', $bytes / pow(1024, $i), $units[$i]);
|
||||
}
|
||||
}
|
||||
|
|
@ -7,8 +7,6 @@ namespace App\Utils;
|
|||
|
||||
use Hyperf\Context\ApplicationContext;
|
||||
use Hyperf\HttpServer\Contract\RequestInterface;
|
||||
use Psr\Container\ContainerExceptionInterface;
|
||||
use Psr\Container\NotFoundExceptionInterface;
|
||||
|
||||
class Ip
|
||||
{
|
||||
|
|
@ -43,4 +41,42 @@ class Ip
|
|||
$request = $container->get(RequestInterface::class);
|
||||
return $request->header("user-agent", "unknown");
|
||||
}
|
||||
|
||||
public static function getBrowserAndOS(string $userAgent): array
|
||||
{
|
||||
$browser = 'Unknown';
|
||||
$os = 'Unknown';
|
||||
// 操作系统识别
|
||||
if (preg_match('/windows/i', $userAgent)) {
|
||||
$os = 'Windows';
|
||||
} elseif (preg_match('/macintosh|mac os x/i', $userAgent)) {
|
||||
$os = 'Mac OS';
|
||||
} elseif (preg_match('/linux/i', $userAgent)) {
|
||||
$os = 'Linux';
|
||||
} elseif (preg_match('/iphone/i', $userAgent)) {
|
||||
$os = 'iPhone';
|
||||
} elseif (preg_match('/android/i', $userAgent)) {
|
||||
$os = 'Android';
|
||||
}
|
||||
|
||||
// 浏览器识别
|
||||
if (preg_match('/MSIE|Trident/i', $userAgent)) {
|
||||
$browser = 'Internet Explorer';
|
||||
} elseif (preg_match('/Edge/i', $userAgent)) {
|
||||
$browser = 'Microsoft Edge';
|
||||
} elseif (preg_match('/Firefox/i', $userAgent)) {
|
||||
$browser = 'Mozilla Firefox';
|
||||
} elseif (preg_match('/Chrome/i', $userAgent)) {
|
||||
$browser = 'Google Chrome';
|
||||
} elseif (preg_match('/Safari/i', $userAgent)) {
|
||||
$browser = 'Safari';
|
||||
} elseif (preg_match('/Opera|OPR/i', $userAgent)) {
|
||||
$browser = 'Opera';
|
||||
}
|
||||
|
||||
return [
|
||||
'browser' => $browser,
|
||||
'os' => $os,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
|
||||
namespace App\Utils;
|
||||
|
||||
class MemoryHelper
|
||||
{
|
||||
/**
|
||||
* 获取系统内存使用情况
|
||||
*
|
||||
* @return array [
|
||||
* 'total' => 总内存(MB)
|
||||
* 'used' => 已用内存(MB)
|
||||
* 'free' => 剩余内存(MB)
|
||||
* 'usage' => 使用率(百分比)
|
||||
* ]
|
||||
*/
|
||||
public static function getMemoryInfo(): array
|
||||
{
|
||||
$meminfo = self::readMeminfo();
|
||||
|
||||
$total = $meminfo['MemTotal'] ?? 0;
|
||||
$free = ($meminfo['MemFree'] ?? 0)
|
||||
+ ($meminfo['Buffers'] ?? 0)
|
||||
+ ($meminfo['Cached'] ?? 0)
|
||||
+ ($meminfo['SReclaimable'] ?? 0);
|
||||
|
||||
$used = $total - $free;
|
||||
$usage = $total > 0 ? round($used / $total * 100, 2) : 0;
|
||||
|
||||
return [
|
||||
'total' => round($total / 1024, 2), // 转 MB
|
||||
'used' => round($used / 1024, 2),
|
||||
'free' => round($free / 1024, 2),
|
||||
'usage' => $usage,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取 /proc/meminfo 并返回数组
|
||||
*/
|
||||
private static function readMeminfo(): array
|
||||
{
|
||||
$data = [];
|
||||
foreach (file('/proc/meminfo') as $line) {
|
||||
if (preg_match('/^(\S+):\s+(\d+)/', $line, $matches)) {
|
||||
$data[$matches[1]] = (int) $matches[2]; // 单位 kB
|
||||
}
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
<?php
|
||||
|
||||
namespace App\Utils;
|
||||
|
||||
use Hyperf\Redis\RedisFactory;
|
||||
use Hyperf\Redis\RedisProxy;
|
||||
use function Hyperf\Support\make;
|
||||
|
||||
class RedisInfoHelper
|
||||
{
|
||||
protected static RedisProxy|null $redis = null;
|
||||
|
||||
/**
|
||||
* 初始化 Redis 实例
|
||||
*/
|
||||
protected static function getRedis(): RedisProxy|null
|
||||
{
|
||||
if (!self::$redis) {
|
||||
$factory = make(RedisFactory::class);
|
||||
// 默认使用 default pool
|
||||
self::$redis = $factory->get('default');
|
||||
}
|
||||
return self::$redis;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 Redis 运行信息
|
||||
*/
|
||||
public static function getInfo(): array
|
||||
{
|
||||
$redis = self::getRedis();
|
||||
$info = $redis->info(); // 返回数组形式
|
||||
|
||||
// 解析需要的字段
|
||||
$used_memory = isset($info['used_memory']) ? self::formatBytes($info['used_memory']) : '0 B';
|
||||
$used_cpu = isset($info['used_cpu_sys']) && isset($info['used_cpu_user'])
|
||||
? round($info['used_cpu_sys'] + $info['used_cpu_user'], 2)
|
||||
: 0;
|
||||
|
||||
return [
|
||||
'version' => $info['redis_version'] ?? 'unknown',
|
||||
'mode' => $info['redis_mode'] ?? 'unknown',
|
||||
'port' => $info['tcp_port'] ?? 0,
|
||||
'clients' => $info['connected_clients'] ?? 0,
|
||||
'uptime_days' => $info['uptime_in_days'] ?? 0,
|
||||
'used_memory' => $used_memory,
|
||||
'used_cpu' => $used_cpu . " 秒",
|
||||
'maxmemory' => isset($info['maxmemory']) ? self::formatBytes($info['maxmemory']) : 'unlimited',
|
||||
'aof_enabled' => isset($info['aof_enabled']) && $info['aof_enabled'] == 1,
|
||||
'rdb_last_bgsave_status' => $info['rdb_last_bgsave_status'] ?? 'unknown',
|
||||
'keys' => $info['db0'] ?? '0',
|
||||
'net_input_bytes' => isset($info['total_net_input_bytes']) ? self::formatBytes($info['total_net_input_bytes']) : '0 B',
|
||||
'net_output_bytes' => isset($info['total_net_output_bytes']) ? self::formatBytes($info['total_net_output_bytes']) : '0 B',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化字节为易读形式
|
||||
*/
|
||||
protected static function formatBytes(int $bytes): string
|
||||
{
|
||||
if ($bytes <= 0) {
|
||||
return '0 B';
|
||||
}
|
||||
|
||||
$units = ['B', 'KB', 'MB', 'GB', 'TB'];
|
||||
$i = (int) floor(log($bytes, 1024));
|
||||
return sprintf('%.2f %s', $bytes / pow(1024, $i), $units[$i]);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
<?php
|
||||
|
||||
namespace App\Utils;
|
||||
|
||||
class SystemHelper
|
||||
{
|
||||
/**
|
||||
* 获取服务器信息
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function getServerInfo(): array
|
||||
{
|
||||
return [
|
||||
'php_version' => PHP_VERSION,
|
||||
'server_name' => self::getServerName(),
|
||||
'server_ip' => self::getServerIp(),
|
||||
'os' => self::getOs(),
|
||||
'architecture'=> self::getArchitecture(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取服务器主机名
|
||||
*/
|
||||
public static function getServerName(): string
|
||||
{
|
||||
return gethostname() ?: php_uname('n');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取服务器 IP
|
||||
*/
|
||||
public static function getServerIp(): string
|
||||
{
|
||||
// 尝试解析本机 IP(排除 127.0.0.1)
|
||||
$hostname = gethostname();
|
||||
$ip = gethostbyname($hostname);
|
||||
if ($ip && $ip !== $hostname && $ip !== '127.0.0.1') {
|
||||
return $ip;
|
||||
}
|
||||
|
||||
// 如果在容器环境,可能要用命令获取真实 IP
|
||||
$ip = shell_exec("hostname -I 2>/dev/null | awk '{print $1}'");
|
||||
return trim($ip) ?: '127.0.0.1';
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取操作系统信息
|
||||
*/
|
||||
public static function getOs(): string
|
||||
{
|
||||
return php_uname('s') . ' ' . php_uname('r') . ' (' . php_uname('v') . ')';
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取系统架构
|
||||
*/
|
||||
public static function getArchitecture(): string
|
||||
{
|
||||
return php_uname('m'); // 常见值: x86_64, aarch64
|
||||
}
|
||||
}
|
||||
|
|
@ -19,7 +19,7 @@
|
|||
"hyperf/crontab": "^3.1",
|
||||
"hyperf/database": "~3.1.0",
|
||||
"hyperf/db-connection": "~3.1.0",
|
||||
"hyperf/engine": "^2.10",
|
||||
"hyperf/engine": "^2.14",
|
||||
"hyperf/filesystem": "^3.1",
|
||||
"hyperf/framework": "~3.1.0",
|
||||
"hyperf/guzzle": "~3.1.0",
|
||||
|
|
@ -33,16 +33,17 @@
|
|||
"hyperf/redis": "~3.1.0",
|
||||
"hyperf/validation": "^3.1",
|
||||
"hyperf/websocket-server": "^3.1",
|
||||
"phpoffice/phpspreadsheet": "^4.3"
|
||||
"phpoffice/phpspreadsheet": "^4.5",
|
||||
"zoujingli/ip2region": "^3.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"friendsofphp/php-cs-fixer": "^3.0",
|
||||
"friendsofphp/php-cs-fixer": "^3.89",
|
||||
"hyperf/devtool": "~3.1.0",
|
||||
"hyperf/testing": "~3.1.0",
|
||||
"hyperf/watcher": "^3.1",
|
||||
"mockery/mockery": "^1.0",
|
||||
"phpstan/phpstan": "^1.0",
|
||||
"swoole/ide-helper": "^5.0"
|
||||
"mockery/mockery": "^1.6",
|
||||
"phpstan/phpstan": "^1.12",
|
||||
"swoole/ide-helper": "^5.1"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-openssl": "Required to use HTTPS.",
|
||||
|
|
|
|||
|
|
@ -13,4 +13,5 @@ return [
|
|||
Hyperf\ExceptionHandler\Listener\ErrorExceptionHandler::class,
|
||||
Hyperf\Command\Listener\FailToHandleListener::class,
|
||||
App\Listener\LogHandleListener::class,
|
||||
App\Listener\AppBootListener::class,
|
||||
];
|
||||
|
|
|
|||
Loading…
Reference in New Issue