基于SpringBoot打造一个通用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系统,采用分层架构设计:
客户端(Spring Shell) <--HTTP--> 服务端(Spring Boot)
| |
通用命令exec 统一控制器(/cli)
| |
动态参数 动态Bean分发
| |
单一入口命令 多个CommandHandler
| |
REST通信 业务逻辑处理
设计原则
单一职责:客户端只负责命令解析和HTTP通信,服务端只负责业务逻辑 开闭原则:对扩展开放(新增服务),对修改关闭(不需改客户端) 依赖倒置:依赖抽象的CommandHandler接口,而非具体实现 最小知识:客户端无需知道服务端的具体实现细节
2. 客户端设计
在 CLI 客户端定义一条通用命令 exec:
@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);
}
}
使用示例:
> exec userService --args list user1, user2, user3 > exec roleService --args users admin role1, role2 > exec systemService --args status 系统正常运行
3. 服务端设计
服务端提供统一接口 /cli,根据服务名动态分发:
@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 接口:
public interface CommandHandler {
String handle(String[] args);
default String getDescription() { return "命令描述"; }
default String getUsage() { return "使用说明"; }
}
示例服务实现:
@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. 服务白名单
通过配置文件限制可访问的服务:
cli:
allowed-services:
- userService
- roleService
- systemService
2. 参数验证
使用 Spring Validation 进行请求参数校验,防止恶意输入。
3. 访问日志
记录所有CLI调用,便于审计和问题追踪:
logger.info("CLI请求 - 服务: {}, 参数: {}, 来源: {}",
serviceName, Arrays.toString(args), httpRequest.getRemoteAddr());
五、实际应用场景
1. 运维场景
# 查看系统状态 exec systemService --args status # 重启服务 exec serviceManager --args restart userService # 查看日志 exec logService --args tail 100 error
2. 调试场景
# 查看用户详情 exec userService --args get 123 # 测试接口 exec testService --args simulate /api/orders # 清理缓存 exec cacheService --args clear all
3. 批量操作
# 批量导入用户 exec userService --args import users.csv # 批量更新角色 exec roleService --args batchUpdate role-mapping.json
六、扩展功能
1. 交互增强
- Tab 补全:自动补全服务名和参数
- 命令历史:保存执行历史,支持上下键浏览
- 颜色输出:不同类型信息使用不同颜色显示
2. 结果格式化
private String formatResponse(String data) {
try {
Object json = objectMapper.readValue(data, Object.class);
return objectMapper.writerWithDefaultPrettyPrinter()
.writeValueAsString(json);
} catch (Exception e) {
return data;
}
}
3. 脚本模式
支持从文件执行命令序列:
exec script --args commands.txt
七、总结
本文介绍的"通用命令+动态分发"方案,通过Spring Boot + Spring Shell构建,使用单一 exec 命令实现多服务动态调用,大幅简化了CLI系统的维护复杂度。
到此这篇关于基于SpringBoot打造一个通用CLI命令系统的文章就介绍到这了,更多相关SpringBoot CLI命令系统内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
相关文章
浅谈Java异常的Exception e中的egetMessage()和toString()方法的区别
下面小编就为大家带来一篇浅谈Java异常的Exception e中的egetMessage()和toString()方法的区别。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧2017-07-07
Mybatis配置之<properties>属性配置元素解析
这篇文章主要介绍了Mybatis配置之<properties>属性配置元素解析,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教2021-07-07
Spring的@CrossOrigin注解使用与CrossFilter对象自定义详解
这篇文章主要介绍了Spring的@CrossOrigin注解使用与CrossFilter对象自定义详解,跨域,指的是浏览器不能执行其他网站的脚本,它是由浏览器的同源策略造成的,是浏览器施加的安全限制,所谓同源是指,域名,协议,端口均相同,需要的朋友可以参考下2023-12-12


最新评论