diff --git a/app/Controller/Admin/Login.php b/app/Controller/Admin/Login.php index 71ed947..698fc61 100644 --- a/app/Controller/Admin/Login.php +++ b/app/Controller/Admin/Login.php @@ -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")); } diff --git a/app/Controller/Admin/System.php b/app/Controller/Admin/System.php index b1e9e67..eda03c3 100644 --- a/app/Controller/Admin/System.php +++ b/app/Controller/Admin/System.php @@ -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()); + } } \ No newline at end of file diff --git a/app/Event/LogEvent.php b/app/Event/LogEvent.php index 962f52a..3065114 100644 --- a/app/Event/LogEvent.php +++ b/app/Event/LogEvent.php @@ -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){} } \ No newline at end of file diff --git a/app/Listener/AppBootListener.php b/app/Listener/AppBootListener.php new file mode 100644 index 0000000..45ecb44 --- /dev/null +++ b/app/Listener/AppBootListener.php @@ -0,0 +1,21 @@ + $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() ]); } } \ No newline at end of file diff --git a/app/Middleware/JWTMiddleware.php b/app/Middleware/JWTMiddleware.php index af4cd4a..8d25eb5 100644 --- a/app/Middleware/JWTMiddleware.php +++ b/app/Middleware/JWTMiddleware.php @@ -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; } } diff --git a/app/Model/AccountLog.php b/app/Model/AccountLog.php index d46788d..191e638 100644 --- a/app/Model/AccountLog.php +++ b/app/Model/AccountLog.php @@ -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 - * @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; diff --git a/app/Model/Online.php b/app/Model/Online.php new file mode 100644 index 0000000..b55bcb5 --- /dev/null +++ b/app/Model/Online.php @@ -0,0 +1,57 @@ + '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'); + } +} diff --git a/app/Utils/AppInfoHelper.php b/app/Utils/AppInfoHelper.php new file mode 100644 index 0000000..0f163cb --- /dev/null +++ b/app/Utils/AppInfoHelper.php @@ -0,0 +1,82 @@ + 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); + } +} \ No newline at end of file diff --git a/app/Utils/CpuHelper.php b/app/Utils/CpuHelper.php new file mode 100644 index 0000000..b6b48ba --- /dev/null +++ b/app/Utils/CpuHelper.php @@ -0,0 +1,67 @@ + $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), + ]; + } +} diff --git a/app/Utils/DiskInfoHelper.php b/app/Utils/DiskInfoHelper.php new file mode 100644 index 0000000..b98e185 --- /dev/null +++ b/app/Utils/DiskInfoHelper.php @@ -0,0 +1,128 @@ +/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]); + } +} diff --git a/app/Utils/Ip.php b/app/Utils/Ip.php index 322bd50..ea4ec20 100644 --- a/app/Utils/Ip.php +++ b/app/Utils/Ip.php @@ -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, + ]; + } } \ No newline at end of file diff --git a/app/Utils/MemoryHelper.php b/app/Utils/MemoryHelper.php new file mode 100644 index 0000000..b1fca59 --- /dev/null +++ b/app/Utils/MemoryHelper.php @@ -0,0 +1,51 @@ + 总内存(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; + } +} diff --git a/app/Utils/RedisInfoHelper.php b/app/Utils/RedisInfoHelper.php new file mode 100644 index 0000000..6a2a80b --- /dev/null +++ b/app/Utils/RedisInfoHelper.php @@ -0,0 +1,70 @@ +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]); + } +} diff --git a/app/Utils/SystemHelper.php b/app/Utils/SystemHelper.php new file mode 100644 index 0000000..30a24b6 --- /dev/null +++ b/app/Utils/SystemHelper.php @@ -0,0 +1,63 @@ + 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 + } +} diff --git a/composer.json b/composer.json index 79781d7..8468f87 100644 --- a/composer.json +++ b/composer.json @@ -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.", diff --git a/config/autoload/listeners.php b/config/autoload/listeners.php index 67f3bac..6f3cb55 100644 --- a/config/autoload/listeners.php +++ b/config/autoload/listeners.php @@ -13,4 +13,5 @@ return [ Hyperf\ExceptionHandler\Listener\ErrorExceptionHandler::class, Hyperf\Command\Listener\FailToHandleListener::class, App\Listener\LogHandleListener::class, + App\Listener\AppBootListener::class, ];