CTimet_bukii

导航

一个简单,轻量,开箱即用,注解驱动,常驻交互的Java终端库:Simple-Terminal

注解驱动、开箱即用、常驻交互。不是再写一个参数解析器,而是给你的程序装上一整套 REPL——还能接网络流做远程终端。

先说痛点:这些场景,你还在自己造轮子吗?

如果你

程序跑起来了,想随时敲命令干预它 游戏服务器、机器人、守护进程……它们都是常驻运行的。你想查个在线人数、踢个人、热重载配置,结果发现现有的命令行库解析完 main(String[] args) 就退出了——它们是为「敲一下就跑」的一次性工具设计的,不是为常驻控制台设计的。想做 REPL?那得自己写 while 循环、自己拆分输入、自己分发命令、自己写 help。你烦了,希望有一个简单,易用,开箱即用,交互式常驻的库。

想做一个分步交互的命令 比如「先输入消息内容,再问发给谁,再问优先级」。这种多轮对话,如果自己维护一个状态机一写就是一大坨 if-else。你希望有一个库能帮你解决这些难点

想远程连上去敲命令 程序部署在服务器上,你不想为了改个配置就开 HTTP 接口、写鉴权、写文档。你想像 SSH 那样,开个 netcat 连上去就能敲命令。可现有的方案要么只读一行不管后续,要么输入输出流写死了 System.out,接不到网络上。你希望这个库能够自行设置输入输出流。

参数校验写到吐 端口是不是数字?用户名符不符合规范?日期格式对不对?每个命令都要写一堆 if 判断,还要 try-catch 转类型。一个命令十个参数,校验代码比业务代码还长。你希望这个库能支持正则表达式,提前为你检查参数格式是否正确。

退出时希望优雅地关闭资源 输入stop命令,你想在退出前做点收尾,希望有一个统一的钩子。

免费开源商用 你想白嫖,库最好是 MIT 开源的。

这些痛点,Simple-Terminal 全部解决了。下面一个个说。

Simple-Terminal 是什么

Simple-Terminal 是一个轻量的 Java 终端库。最少4行代码启动一个常驻控制台,自带 helpstop;用注解注册命令,自动转换参数类型,用正则校验参数,支持子命令、多轮对话式命令、上下文跳转、国际化、异步执行,还能接入网络流做远程终端。

Terminal terminal = TerminalBuilder.builder()
        .setAnchorPackageClassHelper(YourCommandProcessor.class)
        .build();
terminal.run();

跑起来就是这样一个交互界面:

> info CTimet
Hello, CTimet!
> stop
...

突出优势

1. 常驻交互,开箱即用

这是 Simple-Terminal 最根本的定位。它天生就是常驻的——你不用写循环,不用管分发,run() 一调用就是一个完整的 REPL。自带 help(列出所有命令、查看用法)和 stop(退出进程)。 help 国际化也不需要自己写,你只需要提供命令解释的国际化资源,Simple-Terminal的help命令就能自动根据当前语言区域,自动读取对应的国际化properties文件。

适合场景:任何需要常驻运行、随时接受指令的程序——游戏服务器、聊天机器人、定时任务守护进程、IoT 设备网关、开发调试控制台。

2. 多轮对话 + 上下文跳转

想象一个 send 命令:先问发什么,再问发给谁,再问感受评分。用 Simple-Terminal 只需给每个方法标上 stage

@CommandProcessor(value = "send", hasContext = true)
public class SendContextCommandProcessor {
    @InjectOutPrintStream
    private PrintStream out;

    @CommandExecutor
    public void send(String content) {
        out.println("Send '" + content + "' to whom?");
    }

    @ContextCommandExecutor(stage = 1, paraJoin = true)
    public void acceptUser(String user) {
        out.println("Send to '" + user + "' done. Your feeling rate:");
    }

    @ContextCommandExecutor(stage = 2)
    public void acceptUserFeelingRate(int rate) {
        out.println("Rate = " + rate);
    }
    // ...还能继续 stage 3、4
}

交互过程:

> send hello
Send 'hello' to whom?
> CTimet bukii
Send to 'CTimet bukii' done. Your feeling rate:
> 8
Rate = 8

更强大的是上下文跳转——让方法返回一个 int,下一轮输入就会跳到对应的 stage。「有密码就要求输入,没密码就跳过」这种分支流程,一行 return 搞定:

@ContextCommandExecutor(stage = 2, patterns = "^[1-4]\\d*$")
public int jumpTo(int jumpTo) {
    out.println("Jump to " + jumpTo + ".");
    return jumpTo;  // 下一轮输入进入 jumpTo 这个 stage
}

还有两个配套控制:

  • 重复当前轮:抛 NeedRepeatException,用户重新输入这一轮(密码错了重来)
  • 提前终止:抛 ContextInterruptException,命令直接退出(密码错了不想继续后面步骤)

适合场景:安装向导、配置流程、数据库迁移确认、多步表单、交互式问答、任何需要分步收集信息或分支处理的命令。以前这些要写一坨状态机,现在几个注解就搞定。

3. 接入网络流,把终端变成远程控制台

这是 Simple-Terminal 最被低估的能力。输入输出流都可以替换——这意味着你可以把终端接到 Socket 上,做一个远程控制台

Socket socket = server.accept();
Terminal terminal = TerminalBuilder.builder()
        .setAnchorPackageClassHelper(App.class)
        .setTerminalInputStream(socket.getInputStream())
        .setTerminalOutputStream(new PrintStream(socket.getOutputStream()))
        .runAsync()  // 异步监听,主线程继续接受下一个连接
        .build();
terminal.run();

配合 @InjectOutPrintStream 注入输出流,处理器里写 out.println(...) 就会发到网络另一端,不用写死 System.out。运维同学开个 netcat 连上来就能敲命令,比开 HTTP 接口轻量得多。

适合场景:远程运维控制台、游戏服务器后台管理、嵌入式设备调试、分布式节点控制、多客户端同时连接的交互式服务。

4. 正则校验 + 自动重载分发,把 90% 的 if 交给框架

写命令行最烦的是校验参数。Simple-Terminal 用 patterns 正则数组帮你校验,更妙的是同一个命令名可以注册多个重载方法,框架会根据用户输入的类型自动匹配:

@CommandProcessor("hello")
public class HelloCommandProcessor {
    @InjectOutPrintStream
    PrintStream out;

    @CommandExecutor
    public void hello() { out.println("hello"); }              // hello

    @CommandExecutor
    public void helloUser(String user) { out.println("hello " + user); }  // hello a

    @CommandExecutor
    public void helloId(int id) { out.println(id); }           // hello 1

    @CommandExecutor(patterns = {"[0-9]", "[0-9]"})
    public void helloId(int id1, int id2) { out.println(id1); out.println(id2); }  // hello 1 2
}

输入 hello 走无参版,hello a 走 String 版,hello 1 走 int 版,hello 1 2 走双 int 版。参数类型和正则都由框架比对,匹配不上就自动走 @ArgsIllegalExecutor 兜底。你只管写业务逻辑,校验和分发交给 Simple-Terminal。

适合场景:任何需要严格参数校验的命令——端口配置、日期输入、用户名规范、ID 校验、多参数组合命令。

5. 四种参数转换策略 + 自定义布尔标记

底层用 ElegantConvertChecker 把字符串转成方法参数类型,提供四种策略:

策略 行为
CONVERT 严格转换,"abc" 喂给 int 直接拒绝
COERCE 强制转换,"1.1111111111111111" 喂给 float 会变成 1.1112
INTERPRET 语义解释后转换
COERCE_WITH_INTERPRET 默认策略,先解释后强制

更实用的是自定义布尔标记:Y/y/T/t/1/是/yes,任何你想要的字符串都能被转成 true。做确认类命令(delete -confirm yes)时特别顺手,不用自己写 if (input.equals("yes") || input.equals("y"))

适合场景:确认类命令、容忍宽松输入的配置命令、需要精确控制「什么算合法」的严格命令。

6. 优雅退出 + 定时关停

退出时 Simple-Terminal 会先调用所有 ClosableFlag 处理器的 closeResources(),再执行你配置的 stopRunnable,最后才 System.exit。数据库连接、文件句柄、网络 socket——都能被妥善关闭:

@CommandProcessor("db")
public class DbCommandProcessor implements ClosableFlag {
    private Connection conn;
    @Override
    public void closeResources() throws Exception {
        if (conn != null) conn.close();
    }
}

而且 stop 命令自带一整套定时退出:

stop -ms 200          # 200 毫秒后
stop -s 2             # 2 秒后
stop -min 2           # 2 分钟后
stop -h 2             # 2 小时后
stop -r 2030 2 17 13 14 15 888   # 定时到 2030-02-17 13:14:15.888

适合场景:凌晨维护、定时重启、预约关停、任何需要在退出前释放资源的常驻服务。

还有这些顺手的设计

  • 国际化:根据系统语言自动加载 descriptions_zh.properties / descriptions.properties,help 文案中英文自动切换。descriptionKey 机制让你只管提供资源文件,help 命令自动查表展示。
  • 子命令@SubcommandExecutor("at") 注册子命令,支持 alias 别名和 matchAllCases 大小写策略。子命令不必须以 - 开头,任何不含空格的字符串都行。
  • 命令别名alias = {"hi", "hello"},想怎么叫就怎么叫。
  • 字段注入@InjectOutPrintStream@InjectErrPrintStream@InjectTerminalOption@InjectCommandDescriptionProperties——运行时对象自动注入,不用层层传参,也避免写死 System.out
  • 异步执行runAsync() 让终端在独立线程监听,主线程继续干别的事;也可以塞进自定义线程池。
  • Java 8 兼容:核心模块编译目标 1.8,老项目也能用。

使用场景速查

你的需求 用 Simple-Terminal 的什么
给常驻服务加运维控制台 Terminal.run() 常驻 REPL + 内置 help/stop
远程连上去敲命令 setTerminalInputStream/OutputStream 接网络流
分步交互的向导命令 @ContextCommandExecutor + stage
分支式多轮流程 上下文跳转(返回 int)
密码错了重来 NeedRepeatException
中途放弃命令 ContextInterruptException
端口/日期等参数校验 patterns 正则
同命令不同参数自动分发 方法重载 + 自动匹配
确认类命令(yes/no) 自定义布尔标记
退出前释放资源 ClosableFlag
定时/预约关停 stop -s/-min/-h/-r
中英文 help 文档 i18n properties + descriptionKey
一个命令多个名字 alias

上手

Maven:

<dependency>
    <groupId>io.github.ctimetbukii</groupId>
    <artifactId>simple-terminal</artifactId>
    <version>1.0.0</version>
</dependency>
Terminal terminal = TerminalBuilder.builder()
        .setAnchorPackageClassHelper(App.class)
        .build();
terminal.run();

打上 @CommandProcessor,写个 @CommandExecutor 方法,你的命令就注册好了。完整文档见 项目README

写在最后

Java 生态里,「一次性参数解析」已经很成熟了,但「常驻交互式终端」这一层长期空白——要么自己拿底层库糊一个,要么拿参数解析器套层循环。Simple-Terminal 想把这件事变得无聊地简单:注解一打,终端就有了;正则一写,校验就做了;流一接,远程控制台就成了

1.0.0 是第一个正式版本,API 已经稳定。项目基于 MIT 协议开源,欢迎 Star、Issue、PR。如果你正在维护一个需要运维控制台的 Java 服务,不妨试试,两行代码就能跑起来。

适用边界

Simple-Terminal 专注的是「常驻交互式终端」,不是所有命令行场景都适合。以下情况,传统的参数解析器依然是更合适的选择:

  • 一次性命令行工具:像 gitmvn 这种「敲一下就跑、跑完就退」的工具。这类工具不需要常驻 REPL,用传统的一次性参数解析器更轻量、更对口。
  • 复杂的 POSIX 风格选项组合:如果你需要 --flag=value-abc(等价于 -a -b -c)、-- 分隔符、选项互斥、必选选项校验这类复杂的 POSIX/GNU 风格选项解析,传统参数解析器在这块做得更成熟、更规范。
  • 命令行自动补全脚本生成:如果你需要生成 bash/zsh 的自动补全脚本,这是传统参数解析器的强项,Simple-Terminal 目前不提供这个能力。

需要常驻交互、多轮对话、远程控制台——用 Simple-Terminal;需要一次性执行、复杂选项解析、补全脚本——用传统参数解析器。 两者各有所长,按需选择就好。

求 Star 喵

如果你觉得 Simple-Terminal 好用/易用/解决了你的痛点/需求,不仿给个Star吧!求求你了喵⭐

项目地址:https://github.com/CTimetbukii/Simple-Termina

MIT 开源

真的不给个 Star⭐ 吗喵?>︿<

posted on 2026-07-14 14:24  CTimet_bukii  阅读(1)  评论(0)    收藏  举报