This commit is contained in:
zhang zhuo 2025-10-21 18:07:36 +08:00
parent e3951a09d8
commit bf44e4abb4
17 changed files with 696 additions and 75 deletions

View File

@ -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"));
}

View File

@ -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());
}
}

View File

@ -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){}
}

View File

@ -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();
}
}

View File

@ -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()
]);
}
}

View File

@ -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;
}
}

View File

@ -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;

57
app/Model/Online.php Normal file
View File

@ -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');
}
}

View File

@ -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);
}
}

67
app/Utils/CpuHelper.php Normal file
View File

@ -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),
];
}
}

View File

@ -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]);
}
}

View File

@ -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,
];
}
}

View File

@ -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;
}
}

View File

@ -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]);
}
}

View File

@ -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
}
}

View File

@ -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.",

View File

@ -13,4 +13,5 @@ return [
Hyperf\ExceptionHandler\Listener\ErrorExceptionHandler::class,
Hyperf\Command\Listener\FailToHandleListener::class,
App\Listener\LogHandleListener::class,
App\Listener\AppBootListener::class,
];