凌晨 3 点告警群炸了,我用浏览器干了原本 XShell 才能干的事

凌晨 3 点 02 分。

告警群第 14 条消息弹出来的时候,我人在郊区的民宿,手机连着房东的 WiFi,VPN 客户端右下角的小图标在转着圈,已经转了 40 秒。

跳板机的二次验证码 30 秒一换,等我点开 Authenticator,VPN 终于连上,但 SSH 还是没握上手。这种时候哪怕你是 10 年老 Java,也只能干瞪眼。

我打开浏览器,登进我们自己那套基于 erupt 写的后台管理系统------它本来就是要 7×24 开着的,HTTPS、域名、SSO 全配齐了。在菜单栏点了一下"Terminal",新开的一个浏览器 Tab 里,光标在闪。

lua 复制代码
Last login: Wed May 27 03:02:14

 ▶ ERUPT  v1.14.3  Low-Code · Zero-Frontend · Open Source
 Host my-server  OS Linux 5.15.0 amd64  Java 17.0.9

[root@my-server ~]$ tail -f /var/log/app/error.log

10 分钟后我躺回床上。


一、为什么"在浏览器里跑终端"这件事,今年突然变得重要

过去十年我们都习惯了一套工作流:服务器在内网 → 跳板机做 ACL → 本地装个 XShell / iTerm / Termius / Tabby → SSH 上去敲命令。

但 2026 年的几个新现实,正在把这套链路一点点压垮:

第一,越来越多团队没有"固定研发机" 。客户实施同学、PM、运维、SaaS 上的二级管理员------他们要看日志、跑临时脚本、改个 nginx 配置,但只有后台账号,没有 SSH 权限。每次出问题就排队找研发,研发要一遍遍 paste 命令,效率低还容易出事。

第二,跳板机/堡垒机过门越来越烦。VPN → 跳板机 → 二次验证 → 选目标机 → 跳上去------4 道门,每道都可能掉链子。手机端尤其灾难,外网应急的时候让人想砸键盘。

第三,合规要求"所有命令操作必须留痕"。SSH 客户端是装在个人电脑上的,命令历史散落各处,做安全审计基本拿不到全量记录。后台管理系统反而是天然有审计能力的入口。

第四,AI 时代的运维范式在变 。让 AI Agent 去跑命令、看日志、改配置,它显然没法手动开 XShell。它需要一个有鉴权、有边界、可被程序调用的 shell 入口。

社区其实早就嗅到这个方向------阿里云开发者社区那篇《再见 xShell,自己用 Java 撸一个 Web 版》两年多还在被转发,V2EX 上 Termix(自托管 SSH 管理工具)的发布帖去年九月顶到了热门,xtermjs-spring-boot-starter 也在 Maven 中央仓静静躺了好几年。这不是新需求,但一直没有"开箱即用 + 接生产权限"的成品

直到这个月发布的 erupt-terminal


二、一行依赖,菜单自动出现

很多"Web SSH"项目,README 第一句话就是"先 npm install xterm 然后启动 node-pty 服务再 fork 一个进程......"。erupt-terminal 不一样,它假定你已经在用 erupt 写后台------所有该有的东西(登录、菜单、角色、HTTPS)已经在那儿了。

步骤一

xml 复制代码
<dependency>
    <groupId>xyz.erupt</groupId>
    <artifactId>erupt-terminal</artifactId>
    <version>1.14.3</version>
</dependency>

步骤二:......没有步骤二了。

mvn install 完,重启服务,左侧菜单自动多出一个图标是 fa fa-terminalTerminal 入口。点进去就是终端。

为什么能做到?因为 erupt 的每个模块都是一个 EruptModule

java 复制代码
@Configuration @EruptScan @EnableWebSocket
public class EruptTerminalAutoConfiguration implements EruptModule {
    public static final String TERMINAL_KEY = "erupt-terminal.html";

    @Override
    public List<MetaMenu> initMenus() {
        MetaMenu m = MetaMenu.createSimpleMenu(
            "terminal", "Terminal", TERMINAL_KEY, null, 120, EruptTplService.TPL);
        m.setIcon("fa fa-terminal");
        return List.of(m);
    }
}

启动时框架扫到这个模块,把菜单写进 e_upms_menu 表,前端读菜单自动渲染------RBAC、i18n、图标、权限分配全部自动接上。这就是 erupt 的模块化设计在这种场景下的红利:你不是"集成"了一个 web terminal,你是给后台系统装了一个内置功能


三、能力盘点:为什么这个不是又一个 Demo

我把市面上几个流行的 web terminal 方案放在一起对比过。先说结论:erupt-terminal 是少数直接可以接进生产环境的一个。逐条说原因。

1. 真 PTY,不是 ProcessBuilder

很多 Web Shell 项目用 Java 的 Runtime.execProcessBuilder 起进程,结果就是 vim 进去界面错乱、top 不刷新、htop 直接报错、Ctrl-C 杀不掉、Tab 补全没反应。

erupt-terminal 的内核是 JetBrains 开源的 pty4j(IntelliJ 系列 IDE 内置终端用的就是它)。它在 Unix 上调 forkpty(3),在 Windows 上对接 winpty/ConPTY给到的是一个真正的伪终端 。设个 TERM=xterm-256color,再开个 COLORTERM=truecolor,颜色、光标、字符宽度计算都对了。

2. 多标签 + Alt+T 新开

一个 Tab 跑日志,一个 Tab 跑 kubectl,一个 Tab 跑临时 SQL------三个 PtyProcess 互不干扰,状态完全隔离。Alt+T 直接开新页。代码就在模板里 22 行 JS:

javascript 复制代码
document.addEventListener('keydown', function (e) {
  if (e.altKey && e.key === 't') { e.preventDefault(); createTab(); }
});

3. 自动重连 + 心跳 + 空闲超时

WebSocket 最怕"信号断了你不知道"。erupt-terminal 在这一块写得比预期细致:

  • 服务端每 30 秒 ping 一次所有活跃 session,断连立刻可感知
  • 客户端最多重试 8 次,1s → 指数退避 ,重连成功后会打印一行 ── reconnected ──
  • 30 分钟无操作自动断开,释放服务器资源(这条避免了上线后被滥用变堡垒机)
java 复制代码
// TerminalEndpoint.java(节选)
private static final long IDLE_TIMEOUT_MS = 30 * 60 * 1000L;
idleChecker.scheduleAtFixedRate(TerminalEndpoint::checkIdle,
    1, 1, TimeUnit.MINUTES);
idleChecker.scheduleAtFixedRate(TerminalEndpoint::pingAll,
    30, 30, TimeUnit.SECONDS);

4. Token + 菜单权限双重校验

打开 WebSocket 连接的第一件事,不是 fork shell,而是验证:

java 复制代码
List<String> tokens = session.getRequestParameterMap()
    .get(EruptMutualConst.TOKEN);
if (tokens == null || !tokenService.tokenExist(tokens.get(0))) {
    session.close(new CloseReason(PROTOCOL_ERROR, "Unauthorized"));
    return;
}
if (userService.getEruptMenuByValue(TERMINAL_KEY, token) == null) {
    session.close(new CloseReason(VIOLATED_POLICY, "Forbidden"));
    return;
}

有效的 erupt-token + 用户角色绑定了 Terminal 菜单------两条都满足才放行。这意味着:

  • 给客户实施的角色,可以只授权"日志查看"菜单 + Terminal,他能看日志但碰不到用户表
  • 给运维的角色,授权 Terminal + 部分配置类菜单
  • 普通业务用户角色,根本看不到 Terminal 入口

不用再为"web shell 不安全"焦虑,因为它复用了你已经审过、跑了几年的 RBAC。

5. 跨平台

Linux/macOS 起 $SHELL(兜底 /bin/bash),Windows 起 cmd.exe。开发机 Mac、测试机 Linux、客户现场 Windows------同一份 jar 跑下来都是一样的体验。

6. 优雅的视觉细节

JetBrains Mono 字体内置在 jar 里,连接成功后的横幅是手写的 ANSI 序列:

css 复制代码
 ▶ ERUPT  v1.14.3   Low-Code · Zero-Frontend · Open Source
 Host my-server     OS Linux 5.15.0 amd64   Java 17.0.9

紫色品牌色 + 灰色辅助文字 + 黄色高亮主机信息。这一段是设计同学单独抠过的------一个产品级 web terminal 该有的样子。


四、给"AI 时代"留了一道门

注意,erupt 1.14.3 同期还更新了另一个模块:erupt-ai-claw 新增了 enableExecShell 配置项。

这两件事放在一起看就有意思了------你既给"人"准备了一个浏览器终端,也给"AI Agent"准备了一个可控的 Shell 调用入口,两者共享同一套 RBAC

意思是:未来你可以让一个 AI Agent 只在"运维角色"下被允许调用 Shell,并且每次调用都可以在审计日志里留痕。这是单纯一个 web ssh starter 给不了的整体设计。


五、什么场景下你会真的用上它

挑几个真实场景照镜子:

  • 2B SaaS 平台:客户内部部署,运维不会用 SSH,但能登后台------给运维角色加 Terminal,客户工程师 0 培训成本
  • 多租户管理后台:每个租户的二级管理员,按角色精细控制能不能开终端
  • 客户现场实施 :堡垒机审批要 2 小时,浏览器后台 30 秒就能 tail -f
  • 手机应急:iPhone 自带的 Safari 也能跑(xterm.js 支持触屏),可以临时救火
  • K8s 容器内自管理 :把 erupt 应用本身打进容器,浏览器开终端就等于 kubectl exec 进了这个 pod
  • 教学 / 演示场景:开个公网 demo 站点,让学生在浏览器里练命令,省掉每人开 EC2

六、用之前,请把这两件事记在脑子里

1. 终端进程权限 = 运行 Java 的系统用户权限。

如果你用 root 跑 Spring Boot,那 web 终端就是 root shell------这跟你给那个用户开 SSH 是一样的安全模型。生产环境强烈建议用专用 OS 用户跑 Java,并把 Terminal 菜单只授权给受信任的管理员角色

2. 生产必须 HTTPS / WSS。

WebSocket over HTTP 等于明文传命令。这件事框架不会替你做,是基础设施侧的责任。配 nginx / ingress 时把 UpgradeConnection 头透传好。

文档里也明明白白写了这条警告,不是我吓唬人。


七、写在最后

erupt 这两年最让我意外的,是它从一个"@Erupt 注解生成增删改查 UI"的低代码框架,正在变成一个完整的 Java 后台管理操作系统

  • 菜单、用户、角色、权限------erupt-upms
  • 工作流------erupt-flow
  • BI 分析------erupt-cube
  • AI 集成 + Agent 协作 + 跨会话记忆------erupt-ai / erupt-ai-claw
  • 现在再加一个浏览器终端------erupt-terminal

每个模块都是一行 Maven 依赖,自动注册菜单,自动接入鉴权。这种"乐高式"的扩展能力,在 Spring Boot 生态里其实非常少见。

如果你今晚刚好在线,0:00 到 1:00,正好可以把 erupt-terminal 加进你那个用了 erupt 的内部系统试一下,亲手摸一次"在浏览器里 tail -f 生产日志"是什么手感

🌟 项目地址:https://github.com/erupts/erupt --- 顺手点个 Star,这是开源作者最大的动力来源

相关推荐
染翰1 小时前
Nacos 切换 Namespace 后配置不生效、占位符报错终极复盘
java·后端·spring·nacos
阿正的梦工坊2 小时前
【Rust】19-FFI、ABI 与跨语言边界设计
开发语言·后端·rust
fox_lht2 小时前
第十五章 函数式语言:迭代器和闭包
开发语言·后端·学习·算法·rust
码不停蹄的玄黓2 小时前
Spring Boot 实现过滤器(Filter)三种常用方式
java·spring boot·后端
QN1幻化引擎2 小时前
自注意力机制 20 年了,我们终于让它学会"压缩记忆"
github
悟空瞎说2 小时前
PM2 最全常用命令详解
后端
长栎2 小时前
你每次 git commit 都在用设计模式,但你可能一个都没认出来
后端
长栎2 小时前
HikariCP 源码里的设计模式,比连接池本身更值得学
后端
Java编程爱好者2 小时前
从 B+ 树到应用层分表:MySQL 海量数据架构解析
后端