资讯动态

PHP实现Linux远程管理系统:命令执行、SSH直连与安全加固

发布时间:2026/9/14 14:31:37 来源:尧图企业网站定制
简介基于PHP的Linux远程管理系统服务器端毕业设计资料包含完整源代码与配套论文适合准备毕业设计、课程设计或初期项目实训的学习者。系统采用B/S结构管理员通过浏览器以root权限登录后可远程查看系统运行状态、管理进程与系统服务、配置网络及Apache、SSH、MySQL等应用实现对Linux服务器的集中维护并通过特定任务授权与安全访问机制防止越权操作。压缩包共43个文件以29个PHP脚本为核心的源码部分覆盖系统信息、进程、用户、网络、数据库、Web服务等模块另附论文文档、HTML页面和图片整体约471KB文件结构清晰便于按模块阅读。已有183人学习下载既能帮助读者理解PHP在系统管理领域的实际应用也可作为毕设代码框架与论文写作参考但代码需自行调试和二次开发建议具备一定PHP与Linux基础后再使用。1. 为什么用 PHP 写 Linux 远程管理服务端用 PHP 去实现 Linux 远程管理系统的服务端第一反应通常是它扛得住吗。但毕业设计要的不是高并发而是把整条链路讲透浏览器点下查看 CPU 信息PHP 服务端接收请求通过 SSH 或 agent 脚本把命令送到目标 Linux 主机再把输出结果回填页面、写入日志。这条链路里真正干活的不是 PHP而是 Linux 自带的命令工具链PHP 负责请求解析、命令拼装、结果入库这三件事性能瓶颈更多出在命令超时和权限配置上。这篇内容适合正在做服务器管理类毕设、想给实验室或内网服务器搭管理后台的读者从架构选型讲到核心代码再到答辩前的验证清单可复现的坑点一次说清。2. 管理链路拆解PHP 服务端在 Linux 远程管理系统中管什么2.1 B/S 三段式结构与命令的两条通路远程管理系统最典型的结构是 B/S 三段式浏览器是管理端PHP 服务端是控制中枢被管的 Linux 主机是执行端。用户在页面上选择主机、填写命令请求到达 PHP 后服务端把操作翻译成目标主机上可执行的 linux 常用命令收集结果后再原路返回。这里要特别注意PHP 服务端本身并不拥有被管主机的控制权它只是在两端之间做协议转换和结果归一这也是后面所有安全设计的出发点。命令通路按实现方式分为两条。第一条是 agent 通路在目标 Linux 主机上放一个常驻脚本脚本监听 HTTP 接口或消费消息队列PHP 把任务投递给它它就地执行并回传。第二条是 SSH 通路PHP 服务端直接持有目标主机的登录凭证每次请求都用 SSH 库连过去执行命令不要求在目标机上装任何东西。两条路在毕业设计里的表现力不同服务端代码建议按 controllers、services、collectors 三层拆目录控制器只做参数接收和响应输出业务规则放 services针对 Linux 的具体采集逻辑放 collectors这样论文里的架构图可以直接照抄目录结构源代码和文档对得上。2.2 agent 脚本与 SSH 直连的取舍agent 通路的好处是可控粒度细。agent 可以用独立系统账户运行只暴露白名单命令执行结果还能附带退出码、耗时、stderr 三份信息坏处是要维护一条额外的部署链目标机数量一多agent 的版本同步就成了新问题。SSH 直连的好处是零部署任何装了 openssh-server 的机器都能管坏处是权限边界取决于 SSH 账户本身一旦凭证泄露等于把整个 shell 交给攻击者。对比项agent 脚本模式SSH 直连模式被管端部署需单独下发并常驻脚本无需安装复用 sshd端口要求需要 agent 自己监听端口只用 22 端口权限精细化可在脚本内做命令白名单依赖 SSH 账户权限回传信息可自定义退出码与耗时只能拿 stdout/stderr实现复杂度中要多写一套通信协议低phpseclib 开箱即用毕业设计常见做法是先实现 SSH 直连保证能管通再补一条 agent 通路作为进阶亮点。两条通路在业务层可以抽象成同一个接口例如execute($hostId, $command): array这样演示时切换底层实现只改一处工厂方法。我一般会保留 SSH 直连作为主链路agent 作为展示多点批量执行的加分项论文里也能顺便多写一节对比分析。2.3 hosts 与 task_logs 表设计状态机决定日志价值服务端要落库的至少有四类数据主机清单、用户与凭证、执行历史、操作日志。主机表存 hostname、ip、端口、登录方式、分组 ID任务表存命令内容、状态、下发时间、回传结果。状态建议用字符串枚举pending、running、success、failed、timeout不要用 0/1 布尔值否则答辩时讲不清超时是被谁判定的。CREATE TABLE hosts ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, hostname VARCHAR(64) NOT NULL, ip VARCHAR(45) NOT NULL, ssh_port SMALLINT UNSIGNED DEFAULT 22, login_user VARCHAR(32) NOT NULL DEFAULT root, auth_type ENUM(password,key) DEFAULT password, group_id INT UNSIGNED DEFAULT 0, is_active TINYINT(1) DEFAULT 1, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE task_logs ( id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, host_id INT UNSIGNED NOT NULL, task_type VARCHAR(16) NOT NULL DEFAULT shell, command TEXT NOT NULL, status ENUM(pending,running,success,failed,timeout) DEFAULT pending, stdout MEDIUMTEXT NULL, stderr TEXT NULL, exit_code INT NULL, duration_ms INT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, finished_at DATETIME NULL, KEY idx_host_status (host_id, status), KEY idx_created (created_at) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;hosts 表把 IP、端口、登录账户拆开存是为了后期做分组和批量操作时可以用一条 SQL 捞出整组主机登录密码或私钥不建议直接放这张表单独建 credentials 表并用 openssl_encrypt 加密存储。task_logs 表把 stdout 和 stderr 分开排查问题时能直接看出命令是没执行成功还是输出被吞掉了。两个索引分别支撑按主机看历史和按时间倒序看最近任务这两个最高频查询。提示私钥落库前务必用 openssl_encrypt 加密答辩被问到安全设计时可以直接用这条作答。任务状态不能只在数据库里存一个字段还应该在代码里有一个唯一的落库出口。把 TaskService::mark() 做成单一方法内部统一处理 pending 到 running、running 到 success/failed/timeout 这些合法迁移非法迁移直接抛异常。这样论文里的状态图、数据库里的枚举、代码里的常量三者一一对应答辩讲架构时才不会出现图上画了五态、代码里只有两态的尴尬。3. 用 PHP 的 exec 与 shell_exec 实现系统信息采集和命令执行3.1 PHP 执行 linux 常用命令的五个入口与选型PHP 执行外部命令的入口有 exec、shell_exec、system、passthru、proc_open 五个毕业设计里真正需要吃透的是前三个。exec 只返回最后一行输出适合要退出码的场景shell_exec 返回完整输出但不给退出码system 和 passthru 会把原始输出直接打到响应里做 Web 接口时必须配合 ob_start 捕获反而多事。经验是要健壮就用 exec 加退出码要省事就用 shell_exec两个都不满足需求时再上 proc_open。函数返回内容退出码适用场景exec末行输出有需要判断成败的命令shell_exec全量输出无采集类命令如 df、freesystem直接输出有CLI 脚本不推荐 Web 用passthru原始字节有二进制输出如 tcpdumpproc_open管道流有需要超时控制和交互输入3.2 CPU、内存、磁盘采集的最小封装系统信息采集最好直接读 /proc 和现成命令不要自己去算。负载用 sys_getloadavg()内存解析 /proc/meminfo磁盘和 inode 用 df -P这样所有输出都是行文本正则或 explode 一下就能转成 php 接口数组对象返回给前端。?php function collectSystemInfo(): array { // 负载sys_getloadavg 直接给出三个时间窗口 [$load1, $load5, $load15] sys_getloadavg(); // 内存/proc/meminfo 里 MemTotal / MemAvailable 最可靠 $meminfo file_get_contents(/proc/meminfo); preg_match(/MemTotal:\s(\d) kB/, $meminfo, $mTotal); preg_match(/MemAvailable:\s(\d) kB/, $meminfo, $mAvail); // 磁盘df -P 保证输出不换行方便逐行解析 $disks []; exec(df -P | awk \NR1 {print $6, $2, $3, $5}\, $dfOut); foreach ($dfOut as $line) { $fields preg_split(/\s/, trim($line)); $disks[] [ mount $fields[0] ?? , size_kb (int)($fields[1] ?? 0), used_kb (int)($fields[2] ?? 0), pct $fields[3] ?? , ]; } return [ load [1m $load1, 5m $load5, 15m $load15], mem [ total_kb (int)($mTotal[1] ?? 0), available_kb (int)($mAvail[1] ?? 0), used_pct $mTotal[1] 0 ? round((1 - ($mAvail[1] ?? 0) / $mTotal[1]) * 100, 1) : 0, ], disks $disks, ]; }用 /proc/meminfo 的 MemAvailable 而不是 free 输出里的 used 列是因为 MemAvailable 是内核已经减去缓存和缓冲区之后的值直接就是还能分给新进程多少前端画饼图更准确。df -P 的 -P 是 POSIX 模式避免挂载点太长时输出换行把解析打乱awk 一步完成字段裁剪比在 PHP 里再造一层循环快。返回结构统一成数组方便后续接口直接 json_encode 输给前端。3.3 命令超时、输出截断与中文乱码的兜底处理Web 请求有默认超时但 shell_exec 不会自己停。管理后台最容易出现的翻车现场是用户在页面框里填了一条 tail -f 或 ping 目标地址PHP 进程被挂起前端转圈直到 Nginx 504。最简单的兜底是给命令套系统层 timeouttimeout 10 ping -c 5 192.168.1.1超过 10 秒就由 coreutils 直接杀掉子进程PHP 侧只负责判退出码。?php function safeExec(string $command, int $timeout 10): array { // 21 把 stderr 并回 stdoutexec 才能抓到报错文本 $full sprintf(timeout %d %s 21, $timeout, escapeshellcmd($command)); exec($full, $out, $code); $result [ output implode(\n, $out), exit $code, timeout $code 124, ]; // 编码归一目标机可能是 GBK 区域设置 if (!mb_check_encoding($result[output], UTF-8)) { $result[output] mb_convert_encoding($result[output], UTF-8, GBK); } return $result; }timeout 的退出码 124 是约定值判断超时不要去看输出里有没有timeout字样直接比较退出码最准。escapeshellcmd 和 escapeshellarg 的区别要分清前者转义所有 shell 元字符适合整个命令不可信的场景后者只给单个参数加引号用户输入拼在命令尾部时用这个更安全。编码归一放在执行层统一做后面写库和回显都只用 UTF-8避免同一个结果在列表页正常、详情页乱码这种玄学问题。还有一类乱码来自压缩包文件名。被管主机上执行unzip xxx.zip后文件名是乱码多半是压缩包在 Windows 下用 GBK 记的文件名。Linux 上换unzip -O gbk或者7z x -mcpGBK就能解出来这个命令可以直接放进管理后台的文件操作模板里答辩演示时比纯文本命令更直观。4. PHP 服务端的安全加固、SSH 直连与实时回显4.1 用 Token 做接口鉴权与越权控制远程管理系统是典型的高危接口谁都能调命令等于裸奔。毕设里最常犯的错是用 Cookie 会话一把梭CSRF 不管、越权不查。这里给出一个成本低、答辩能讲清楚的最小方案登录成功发一个随机 Token存 Redis 并绑定 uid 和过期时间每次接口请求带 X-Auth-Token服务端统一校验后把 uid 塞进请求上下文所有资源查询都强制加 host_id 与 uid 的联合条件从根上杜绝横向越权。?php function issueToken(int $uid, int $ttl 7200): string { // 用随机字节生成不可预测的 token避免可爆破 $token bin2hex(random_bytes(16)); $redis-setex(token: . $token, $ttl, (string)$uid); return $token; } function currentUid(): int { $token $_SERVER[HTTP_X_AUTH_TOKEN] ?? ; $uid $token ? $redis-get(token: . $token) : null; if ($uid null) { throw new RuntimeException(token invalid or expired, 401); } return (int)$uid; }用 random_bytes 而不是 uniqid 或 md5(time())是为了保证 token 不可预测防止答辩时被人指出可爆破。Token 放 Redis 的好处是服务端可以主动踢人删除对应 key这是 JWT 做不到的毕设里组合方案通常是 Redis 存 token 做鉴权、JWT 只做无状态请求头二选一即可别同时上搞得自己讲不清。接口统一返回[code 0, data [...]]这种数组对象结构前端不用猜字段类型。4.2 用 phpseclib 打通免装客户端的 SSH 通道如果走 SSH 直连phpseclib 是 PHP 生态里最可靠的库纯 PHP 实现不依赖 libssh2 扩展普通虚拟主机上也能跑。安装就一条命令composer require phpseclib/phpseclib。核心用法是登录后执行整段命令输出、退出码、主机公钥都能拿。?php use phpseclib3\Net\SSH2; function sshExec(array $host, string $credential, string $command, int $timeout 10): array { $ssh new SSH2($host[ip], (int)$host[ssh_port]); if (!$ssh-login($host[login_user], $credential)) { return [error login failed]; } $ssh-setTimeout($timeout); $output $ssh-exec($command); return [ output $output, exit $ssh-getExitStatus(), // 记录服务端公钥指纹防止中间人替换主机 host_key $ssh-getServerPublicHostKey(), ]; }setTimeout 的单位是秒超过后 exec 会提前返回配合 getExitStatus 判断是被超时打断还是正常退出。getServerPublicHostKey 返回 OpenSSH 格式公钥首次连接时存库之后每次比对这是很多毕设漏掉的点——远程管理系统连主机校验都不做信任就建立在一个不设防的通道上。凭证方面密码模式只适合演示正式做法是生成专用密钥对私钥用 openssl_encrypt 加密后落库调用时再解密。参数作用说明setTimeout单条命令超时单位是秒默认是无限setKeepAlive长连接保活按需开启默认关闭getExitStatus获取退出码必须在 exec 之后立刻取getServerPublicHostKey获取主机公钥首次连接后应持久化比对4.3 Redis 队列配合轮询做实时回显管理后台要给前端实时回显命令输出最省事的方案是异步加 Redis。PHP 把任务推进 task_queue 列表一个常驻的 CLI worker 用 brpop 消费并执行结果写回 task_result Hash前端轮询接口拿状态。这套结构对应到 php redis 消费组场景用 brpop 是最轻量可靠的实现不用引入 Swoole 也能讲清楚异步链路。?php // worker.php 在命令行运行php worker.php while (true) { // brpop 阻塞 2 秒取出一个任务避免空转 $task $redis-brpop(task_queue, 2); if (!$task) { continue; } [$queueName, $payload] $task; $job json_decode($payload, true); // 状态先置 running让前端停止转圈 $redis-hset(task_result: . $job[id], status, running); $result safeExec($job[command], (int)($job[timeout] ?? 10)); // 结果整体写入 Hash下一次轮询就能拿到 $redis-hset(task_result: . $job[id], status, $result[timeout] ? timeout : done); $redis-hset(task_result: . $job[id], output, $result[output]); }brpop 的第二个参数是阻塞秒数设成 2 可以在一秒内感知队列变化又不会让进程忙等。任务 ID 由 Web 侧在投递前生成保证前端轮询时 key 已经存在否则可能出现第一次请求 404第二次才有结果的尴尬。想再进阶一点可以把列表换成 Stream 的消费者组支持多个 worker 并行消费但对毕设来说 brpop 已经够用多写反而增加答辩风险。?php $jobId (int)($_GET[job_id] ?? 0); $result $redis-hgetall(task_result: . $jobId); header(Content-Type: application/json); echo json_encode([ status $result[status] ?? pending, output $result[output] ?? , ]);注意brpop 在 Redis 主从切换时会短暂抛异常毕设单机环境不用处理连不上时前端按 pending 状态展示即可。5. Linux 远程管理系统答辩前的验证清单与故障定位5.1 最小场景覆盖验证表答辩演示最怕的是现场跑一条命令半天不出结果。建议在虚拟机里准备两台干净的 Linux 发行版直接在虚拟机上装系统即可不用云服务器。下面这张清单提前全部跑绿每个用例记录预期输出演示时照着点。用例操作预期结果主机连通性添加一个 host执行uptime返回 load 且 exit0信息采集打开系统概览页CPU/内存/磁盘均有数值超时兜底执行sleep 3010 秒内返回 timeout 状态权限隔离用普通用户登录管理端看不到不属于自己的主机历史留痕执行一次ls /etctask_logs 表出现新行源代码目录建议按 controllers、services、collectors 分层答辩 PPT 里每贴一张架构图都能对应到具体文件评委问这块代码在哪时能十秒内翻到。5.2 三个高频故障的定位顺序故障一Permission denied。PHP-FPM 默认以 www-data 运行执行 systemctl restart 这类特权命令必然被拒。不要图省事把服务改成 root 跑正确做法是在 /etc/sudoers.d 里给 www-data 加白名单并用sudo -n禁掉密码交互。定位时先php -r echo exec(whoami);确认运行用户再看目标命令需要什么权限逐层补。故障二命令能找到但报 command not found。原因多半是 PATH 不一致登录 shell 加载了 /etc/profile而 PHP 的子进程只有默认 PATH。处理方法是所有内部命令一律写绝对路径/usr/bin/df、/bin/grep或者统一在命令前加export PATH/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin;。这个坑在 SSH 直连模式下特别常见phpseclib 默认走非交互 shell不加载用户 profile。顺带一提如果在虚拟机里配了 xdebug 但 IDE 断点不命中先检查 CLI 和 FPM 用的 php.ini 是不是同一份远程管理系统的 worker 跑在 CLI 下扩展没装齐就只会影响命令行这一侧。故障三磁盘空间显示与实际不符。在 WSL 里开发调试时删了文件但 df 空间没释放这是 WSL 的 ext4 虚拟磁盘只增不减的特性跟代码无关答辩说明环境时一句带过。真机排查顺序是 df -h 看容量、df -i 看 inode、lsof L1 看已删未释放的文件把这三个命令写进系统概览页的诊断按钮里比单一 df 输出专业得多。最后留一条自检命令固化到后台的链路自检页签执行ps aux | wc -l uptime确认返回里同时出现进程数、负载数值和耗时标记每次答辩前点一下五秒内看到数字就说明环境没坏。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价