Spring Boot 打造通用CLI命令系统

一、背景

在日常开发中,某些情况下可能需要为服务提供一个命令行工具(CLI),方便运维、调试或者远程调用业务接口。

假设我们有一个 Spring Boot 服务,提供多个接口,例如:

  • 获取用户列表:/users?type=admin
  • 获取角色列表:/roles?level=manager
  • 获取系统状态:/system/status
  • 批量导入数据:/data/import
  • 生成报表:/report/generate

传统做法:

  • 每个接口在 CLI 客户端写一条命令
  • CLI 方法直接调用服务端 REST 接口
  • 硬编码的服务地址和参数格式

问题

  • 接口多时,CLI 方法数量激增,代码冗余严重
  • 每次新增接口都需要修改客户端,发布新版本
  • 维护成本高,不同环境的配置分散
  • 缺乏统一的认证、授权和日志机制
  • 开发效率低下,重复劳动多

解决方案通用命令 + 动态分发

  • CLI 只维护一条通用命令 exec
  • 根据参数动态路由到服务端对应的 Service Bean
  • 服务端统一管理,支持动态扩展
  • 一次开发,多处复用

二、方案设计

1. 核心架构

我们设计了一套基于 Spring Boot + Spring Shell 的通用CLI系统,采用分层架构设计:

scss 复制代码
客户端(Spring Shell)  <--HTTP-->  服务端(Spring Boot)
       |                              |
    通用命令exec                    统一控制器(/cli)
       |                              |
    动态参数                      动态Bean分发
       |                              |
  单一入口命令                  多个CommandHandler
       |                              |
    REST通信                    业务逻辑处理

设计原则

单一职责 :客户端只负责命令解析和HTTP通信,服务端只负责业务逻辑 开闭原则 :对扩展开放(新增服务),对修改关闭(不需改客户端) 依赖倒置 :依赖抽象的CommandHandler接口,而非具体实现 最小知识:客户端无需知道服务端的具体实现细节

2. 客户端设计

在 CLI 客户端定义一条通用命令 exec

java 复制代码
@ShellComponent
public class ExecCommand {

    @ShellMethod(key = "exec", value = "执行远程服务命令")
    public String executeCommand(
            @ShellOption(value = {"", "service"}, help = "服务名称") String serviceName,
            @ShellOption(value = "--args", help = "命令参数", arity = 100) String[] args) {

        // 构建请求并发送到服务端
        CommandRequest request = new CommandRequest(serviceName, Arrays.asList(args));
        return httpClient.post("/cli", request);
    }
}

使用示例

shell 复制代码
> exec userService --args list
user1, user2, user3

> exec roleService --args users admin
role1, role2

> exec systemService --args status
系统正常运行

3. 服务端设计

服务端提供统一接口 /cli,根据服务名动态分发:

java 复制代码
@RestController
@RequestMapping("/cli")
public class CliController {

    @Autowired
    private ApplicationContext applicationContext;

    @PostMapping
    public String execute(@RequestBody CommandRequest request) {
        String serviceName = request.getService();
        String[] args = request.getArgs().toArray(new String[0]);

        // 动态获取 Service Bean
        Object serviceBean = applicationContext.getBean(serviceName);

        // 执行命令
        if (serviceBean instanceof CommandHandler handler) {
            return handler.handle(args);
        }

        return "服务未找到";
    }
}

4. 统一接口规范

所有需要通过CLI调用的服务都必须实现 CommandHandler 接口:

java 复制代码
public interface CommandHandler {
    String handle(String[] args);
    default String getDescription() { return "命令描述"; }
    default String getUsage() { return "使用说明"; }
}

示例服务实现:

java 复制代码
@Service("userService")
public class UserService implements CommandHandler {

    @Override
    public String handle(String[] args) {
        if (args.length == 0) return getUsage();

        switch (args[0]) {
            case "list":
                return listUsers(args.length > 1 ? args[1] : null);
            case "get":
                return getUser(args[1]);
            default:
                return "未知命令: " + args[0];
        }
    }

    private String listUsers(String type) {
        // 实现获取用户列表逻辑
        return "用户列表...";
    }
}

三、方案优势

1. 客户端统一命令

  • Shell 只需维护一条 exec 命令
  • 新增服务无需修改客户端代码

2. 服务端动态分发

  • 新增接口无需修改 CLI
  • 统一接口入口便于权限控制与日志审计

3. 易扩展

  • 支持任意参数数量、类型
  • 可结合 OpenAPI 自动生成命令提示与帮助信息

4. 逻辑解耦

  • CLI 仅做命令解析和 HTTP 调用
  • 业务逻辑完全在服务端

四、安全控制

1. 服务白名单

通过配置文件限制可访问的服务:

yaml 复制代码
cli:
  allowed-services:
    - userService
    - roleService
    - systemService

2. 参数验证

使用 Spring Validation 进行请求参数校验,防止恶意输入。

3. 访问日志

记录所有CLI调用,便于审计和问题追踪:

java 复制代码
logger.info("CLI请求 - 服务: {}, 参数: {}, 来源: {}",
    serviceName, Arrays.toString(args), httpRequest.getRemoteAddr());

五、实际应用场景

1. 运维场景

shell 复制代码
# 查看系统状态
exec systemService --args status

# 重启服务
exec serviceManager --args restart userService

# 查看日志
exec logService --args tail 100 error

2. 调试场景

shell 复制代码
# 查看用户详情
exec userService --args get 123

# 测试接口
exec testService --args simulate /api/orders

# 清理缓存
exec cacheService --args clear all

3. 批量操作

shell 复制代码
# 批量导入用户
exec userService --args import users.csv

# 批量更新角色
exec roleService --args batchUpdate role-mapping.json

六、扩展功能

1. 交互增强

  • Tab 补全:自动补全服务名和参数
  • 命令历史:保存执行历史,支持上下键浏览
  • 颜色输出:不同类型信息使用不同颜色显示

2. 结果格式化

java 复制代码
private String formatResponse(String data) {
    try {
        Object json = objectMapper.readValue(data, Object.class);
        return objectMapper.writerWithDefaultPrettyPrinter()
                          .writeValueAsString(json);
    } catch (Exception e) {
        return data;
    }
}

3. 脚本模式

支持从文件执行命令序列:

shell 复制代码
exec script --args commands.txt

七、总结

本文介绍的"通用命令+动态分发"方案,通过Spring Boot + Spring Shell构建,使用单一 exec 命令实现多服务动态调用,大幅简化了CLI系统的维护复杂度。

ruby 复制代码
https://github.com/yuboon/java-examples/tree/master/springboot-cli
相关推荐
想用offer打牌10 小时前
RocketMQ如何防止消息丢失?
java·后端·架构·开源·rocketmq
源码获取_wx:Fegn089511 小时前
基于springboot + vue健身房管理系统
java·开发语言·前端·vue.js·spring boot·后端·spring
码事漫谈12 小时前
Vibe Coding时代:人人都是开发者
后端
2501_9167665412 小时前
【Spring框架】SpringJDBC
java·后端·spring
+VX:Fegn089512 小时前
计算机毕业设计|基于springboot + vue图书管理系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·课程设计
AntBlack12 小时前
忍不住推荐 : AI 时代 ,桌面端真的可以考虑一下Go+Wails 的组合
后端·go·ai编程
码事漫谈12 小时前
C++20协程如何撕开异步编程的牢笼
后端
DevYK13 小时前
Coze Studio 二次开发(二)支持 MCP Server 动态配置
后端·agent·coze
掘金码甲哥13 小时前
在调度的花园里面挖呀挖
后端