Ooder A2UI 核心架构深度解析:WEB 拦截层的设计与实现(CLI 与前端融合的自主 UI 桥接层)

Ooder A2UI 核心架构深度解析:WEB 拦截层的设计与实现(CLI 与前端融合的自主 UI 桥接层)

副标题:从 CLI 到增强型 UI:A2UI 构建自主 UI 体系与 CLI 的 Bridger 层核心逻辑
作者: Ooder 架构团队
发布日期: 2026 年 4 月 30 日
技术栈: Java + Spring + SPI + 注解驱动

目录

  1. 开篇:A2UI 的价值定位与设计背景
  2. 架构演进背景
  3. 核心架构设计
  4. 注解层设计
  5. SPI 扩展机制
  6. 技能实现层
  7. 拦截器重构
  8. 服务层架构
  9. 项目整合桥接
  10. RESTful API 设计
  11. 最佳实践
    附录. 关键术语表

0. 开篇:A2UI 的价值定位与设计背景

随着 CLI 命令行技术在研发、运维场景中的深度应用,单纯的命令行交互已无法满足复杂场景下的可视化、易用性需求 —— 这也催生了对「增强型 UI 体系」的核心诉求。A2UI(Augmented & Autonomous UI)的诞生,正是为了填补这一空白:它既是 CLI 命令行效率的延伸,也是 WEB 前端技术的重要发展方向 —— 前端技术正从单纯的页面渲染,走向「命令行级效率 + 可视化交互 + 自主化能力」的融合形态。

Ooder A2UI 的核心目标,是建立一套完全基于自主 UI 技术体系、能够与 CLI 层无缝衔接的 Bridger(桥接)层,打通命令行操作的高效性与 UI 交互的易用性之间的壁垒。本文聚焦于 Ooder A2UI 核心架构中 WEB 拦截层的设计实现,这是整个 A2UI 体系中承接 CLI 桥接能力、驱动 UI 技能扩展的核心环节,也是实现「CLI 指令 - UI 交互 - 自主化处理」闭环的关键载体。

0.1 CLI 命令行的局限性与增强型 UI 的刚需

在软件开发的历史长河中,命令行界面(CLI)曾是开发者与系统交互的唯一方式。虽然 CLI 以其高效、灵活的特点深受技术人员的喜爱,但随着软件系统复杂度的指数级增长,纯文本的交互方式逐渐显露出其局限性:

  • 学习曲线陡峭:需要记忆大量命令和参数,新手上手成本高
  • 可视化能力弱:难以直观展示复杂的数据结构和关系,调试困难
  • 操作效率受限:对于非技术用户,CLI 几乎是不可逾越的鸿沟
  • 协作成本高:团队间难以共享操作流程,知识传承困难
  • 实时反馈差:无法实时预览操作结果,错误发现滞后

正是在这样的背景下,A2UI(Augmented & Autonomous UI,增强型自主 UI) 应运而生。A2UI 不仅仅是传统 GUI 的升级版,它代表了人机交互的下一个范式:在保持 CLI 高效性的同时,通过智能化的 UI 组件提供更直观、更强大的交互体验

0.2 A2UI 的核心意义:WEB 前端技术的演进方向

Ooder A2UI 的设计理念代表了 WEB 前端技术的重要演进方向,其核心意义体现在三个层面:

1. 完全自主的 UI 基础设施

传统的 UI 框架往往依赖于第三方组件库,导致定制化困难、版本依赖复杂。Ooder A2UI 从底层开始构建,实现了:

  • 零外部依赖:核心组件完全自主研发,不受第三方库制约
  • 高度可定制:每个组件都可以根据业务需求深度定制
  • 版本控制友好:组件与业务逻辑解耦,升级无负担
  • 技术自主可控:掌握核心技术栈,应对技术变革更有底气

2. CLI 与 UI 的无缝桥接 :star: 核心创新

这是 Ooder A2UI 最具创新性的设计目标。通过建立一层智能的 Bridger 层,实现:

┌─────────────────────────────────────────────────────────────┐
│                    CLI ↔ A2UI Bridger 层                    │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  CLI 命令 ────▶ Bridger 解析 ────▶ UI 组件自动生成         │
│                                                             │
│  UI 交互 ────▶ Bridger 转换 ────▶ CLI 命令执行             │
│                                                             │
│  双向同步:CLI 操作实时反映到 UI,UI 操作可回溯为 CLI       │
│                                                             │
└─────────────────────────────────────────────────────────────┘

图 0-1:CLI ↔ A2UI Bridger 层架构

这种设计使得:

  • 开发者可以继续使用熟悉的 CLI 工作流,同时享受 UI 的可视化优势
  • 运维人员可以通过 UI 界面操作,系统自动生成对应的 CLI 脚本供审计和复用
  • 产品经理业务人员可以通过直观的 UI 界面参与系统配置,降低沟通成本
  • 团队协作更加顺畅,CLI 操作与 UI 操作可以无缝切换和追溯

3. WEB 技术的前沿探索

Ooder A2UI 紧跟现代 WEB 技术的发展趋势,融合了多项前沿技术:

  • 组件化架构:借鉴 React、Vue 等现代框架的设计理念,实现高内聚低耦合
  • 声明式 UI:通过注解和配置定义 UI,而非命令式编程,提高开发效率
  • 响应式设计:一套代码适配多种设备和屏幕尺寸,降低维护成本
  • 渐进式增强:从基础功能到高级特性,按需加载,优化性能
  • 技能驱动:基于 SPI 机制,将 CLI 指令/UI 操作转化为可扩展技能

0.3 Ooder A2UI 的核心目标:自主 UI 与 CLI 的 Bridger 层构建

Ooder A2UI 的核心目标是建立一套完全自主的 UI 技术体系,并通过 Bridger(桥接)层实现与 CLI 的无缝衔接。这个目标包含三个关键维度:

维度一:自主 UI 技术体系

  • 底层自主:从组件定义、渲染引擎到状态管理,完全自主研发
  • 技能扩展:通过 Skills 架构,支持 UI 组件的动态扩展和组合
  • 注解驱动:使用 Java 注解声明式定义 UI 元素和行为
  • SPI 扩展:通过 Java SPI 机制,支持第三方技能的无缝集成

维度二:CLI 桥接能力

  • 指令映射:将 CLI 指令映射为 UI 操作,实现可视化执行
  • 双向转换:UI 操作可转换为 CLI 脚本,CLI 命令可生成 UI 界面
  • 状态同步:CLI 状态与 UI 状态实时同步,保持一致性
  • 历史追溯:所有操作可追溯,支持回滚和审计

维度三:WEB 拦截层核心

WEB 拦截层是 Ooder A2UI 架构中最关键的一环,承担着以下核心职责:

  1. 请求路由与分发:智能识别 CLI 指令和 HTTP 请求,分发到对应的处理器
  2. 技能驱动:基于 Skills 架构,实现 UI 组件的动态加载和生成
  3. SPI 扩展:通过 Java SPI 机制,支持第三方技能的无缝集成
  4. 性能优化:缓存、懒加载、并发控制等多重优化策略
  5. CLI/UI 双端适配:统一处理 CLI 和 UI 的请求,提供一致的交互体验

0.4 本文核心:WEB 拦截层在 A2UI 架构中的定位与价值

本文聚焦于 Ooder A2UI 架构中最关键的一环 —— WEB 拦截层的设计与实现。作为连接 CLI 指令、HTTP 请求与后端服务的桥梁,WEB 拦截层在整个 A2UI 体系中具有核心地位:

在架构中的定位

┌─────────────────────────────────────────────────────────────┐
│                    Ooder A2UI 整体架构                       │
├─────────────────────────────────────────────────────────────┤
│  CLI 层 ────▶ Bridger 层 ────▶ WEB 拦截层 ────▶ 服务层     │
│                              ↑                               │
│                         【本文核心】                          │
│                              ↓                               │
│                        技能驱动层                             │
└─────────────────────────────────────────────────────────────┘

图 0-2:WEB 拦截层在 A2UI 架构中的定位

核心价值

  1. 承接 CLI 桥接能力:将 CLI 指令转换为 HTTP 请求,实现 CLI 与 WEB 的统一处理
  2. 驱动 UI 技能扩展:基于 Skills 架构,动态加载和生成 UI 组件
  3. 实现请求智能路由:根据请求类型(CLI/UI/API)智能分发到不同处理器
  4. 保障系统性能:通过缓存、懒加载等策略,确保高并发场景下的系统稳定性
  5. 支持技术演进:基于 SPI 机制,支持第三方技能的无缝集成,保持架构的可扩展性

本文将深入解析

通过本文的深度解析,您将全面了解:

  • 架构演进:如何从传统的硬编码拦截器演进到 Skills + SPI 架构
  • 注解驱动:注解驱动开发如何简化组件定义和 CLI 指令映射
  • SPI 扩展:SPI 机制如何实现技能的热插拔和 CLI 适配
  • 拦截器重构:如何设计支持 CLI/UI 双端请求的统一拦截器
  • 服务层协作:服务层如何协作完成 CLI 指令到 UI 生成的完整流程
  • API 设计:如何设计和实现支持 CLI 调用的 RESTful API
  • 最佳实践:开发自定义技能、性能优化、调试技巧等实战经验

0.5 适用读者

本文适合以下读者:

  • 架构师:了解现代 WEB 应用的架构设计思路,特别是 CLI 与 UI 融合的架构模式
  • 后端开发者:学习 Java 注解、SPI、拦截器等高级特性,以及 CLI 适配技巧
  • 前端开发者:理解后端 API 设计,更好地进行前后端协作,实现 CLI 驱动的 UI
  • 运维工程师:了解如何通过 UI 操作自动生成 CLI 脚本,提高运维效率
  • 技术管理者:评估技术方案的可行性和扩展性,把握前端技术演进方向

1. 架构演进背景

1.1 传统拦截机制的痛点(CLI/UI 割裂问题)

在 Ooder 框架的早期版本中,我们采用了基于硬编码的拦截器模式来处理特定后缀的请求(如 .jsx.cls.dyn 等)。这种设计存在以下问题,特别是在 CLI 与 UI 的协作方面:

┌─────────────────────────────────────────────────────────┐
│                    传统拦截架构                          │
├─────────────────────────────────────────────────────────┤
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐     │
│  │ JSXInterceptor│  │CLSInterceptor│  │DYNInterceptor│     │
│  └──────┬──────┘  └──────┬──────┘  └──────┬──────┘     │
│         │                │                │             │
│         └────────────────┼────────────────┘             │
│                          │                              │
│                 ┌────────▼────────┐                     │
│                 │ BaseInterceptor │                     │
│                 └─────────────────┘                     │
│                          │                              │
│         ┌────────────────┼────────────────┐             │
│         │                │                │             │
│  ┌──────▼──────┐  ┌──────▼──────┐  ┌──────▼──────┐     │
│  │ Module 转换   │  │ 反射执行    │  │ 模板渲染    │     │
│  └─────────────┘  └─────────────┘  └─────────────┘     │
│                                                         │
│  【问题】CLI 指令与 UI 请求被割裂处理,无法统一桥接     │
└─────────────────────────────────────────────────────────┘

核心问题

  • 耦合度高:拦截逻辑与具体的组件类型硬编码绑定,难以扩展
  • 扩展困难:新增组件类型需要修改拦截器核心代码,违反开闭原则
  • 维护成本高:多个拦截器类职责不清晰,代码重复严重
  • 缺乏统一管理:组件注册和发现机制分散,难以统一维护
  • CLI/UI 割裂:star: 关键问题:CLI 指令和 UI 请求被分别处理,无法实现双向桥接
    • CLI 命令无法自动生成对应的 UI 界面
    • UI 操作无法回溯为 CLI 脚本
    • 两套独立的处理逻辑,维护成本翻倍

1.2 Skills + SPI 架构的优势(桥接层能力支撑)

为了解决上述问题,特别是 CLI/UI 割裂的问题,我们引入了 Skills + SPI 的架构设计:

┌─────────────────────────────────────────────────────────┐
│               Skills + SPI 拦截架构                      │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  ┌──────────────────────────────────────────────────┐  │
│  │          SkillDrivenInterceptor (统一入口)        │  │
│  │     【支持 CLI 指令 + UI 请求的统一处理】          │  │
│  └────────────────────┬─────────────────────────────┘  │
│                       │                                 │
│         ┌─────────────┴─────────────┐                   │
│         │                           │                   │
│  ┌──────▼─────────┐         ┌──────▼─────────┐         │
│  │ A2uiSkillRegistrySPI │         │ResourceResolver│     │
│  │ (技能注册中心)    │         │ (资源解析器)   │         │
│  │ 【CLI 指令映射】  │         │【CLI/UI 双端】 │         │
│  └──────┬─────────┘         └────────────────┘         │
│         │                                               │
│  ┌──────▼─────────────────────────────────┐            │
│  │         Skill 实现层                    │            │
│  │  ┌──────────┐ ┌──────────┐ ┌────────┐ │            │
│  │  │TreeSkill │ │ FormSkill│ │Chart...│ │            │
│  │  └──────────┘ └──────────┘ └────────┘ │            │
│  │  【每个技能支持 CLI 指令和 UI 操作】    │            │
│  └─────────────────────────────────────────┘            │
│                                                         │
│  【优势】通过 Bridger 层实现 CLI ↔ UI 的双向桥接        │
└─────────────────────────────────────────────────────────┘

核心优势

  • 解耦:拦截逻辑与具体组件实现完全分离,易于维护
  • 可扩展:通过 SPI 机制动态加载新技能,无需修改核心代码
  • 统一管理:所有技能通过注册中心集中管理,便于监控和维护
  • 注解驱动:使用注解声明式定义技能元数据,简化开发
  • CLI/UI 统一:star: 核心优势:实现 CLI 指令与 UI 操作的统一处理
    • CLI 命令自动映射为技能调用,生成对应 UI 界面
    • UI 操作可转换为 CLI 脚本,支持审计和复用
    • 一套技能逻辑,同时支持 CLI 和 UI 两种交互方式
    • Bridger 层负责双向转换,业务层无需关心交互方式

2. 核心架构设计

2.1 整体架构图(六层架构,标注 Bridger 层位置)

┌─────────────────────────────────────────────────────────────────┐
│                    Ooder A2UI 整体架构                           │
│                  (标注 Bridger 层核心位置)                      │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  ┌─────────────────────────────────────────────────────────┐   │
│  │                    Web 层 (Controller)                    │   │
│  │  ┌──────────────────────────────────────────────────┐   │   │
│  │  │ SkillController - RESTful API 入口                │   │   │
│  │  │ 【支持 CLI 调用和 UI 请求】                        │   │   │
│  │  └──────────────────────────────────────────────────┘   │   │
│  └─────────────────────────────────────────────────────────┘   │
│                              │                                  │
│  ┌───────────────────────────▼───────────────────────────────┐ │
│  │              拦截层 (Interceptor)                          │ │
│  │  ┌────────────────────────────────────────────────────┐  │ │
│  │  │ SkillDrivenInterceptor - 技能驱动的请求分发          │  │ │
│  │  │ 【Bridger 层核心:CLI/UI 双端统一处理】              │  │ │
│  │  └────────────────────────────────────────────────────┘  │ │
│  └───────────────────────────────────────────────────────────┘ │
│                              │                                  │
│  ┌───────────────────────────▼───────────────────────────────┐ │
│  │               服务层 (Service Layer)                       │ │
│  │  ┌──────────────┐ ┌──────────────┐ ┌─────────────────┐   │ │
│  │  │SkillManagement│ │SkillPublish  │ │SkillProjectBridge│  │ │
│  │  │   Service    │ │   Service    │ │     Service     │   │ │
│  │  │【CLI 状态管理】│ │【CLI 脚本生成】│ │【CLI/UI 桥接】  │   │ │
│  │  └──────────────┘ └──────────────┘ └─────────────────┘   │ │
│  └───────────────────────────────────────────────────────────┘ │
│                              │                                  │
│  ┌───────────────────────────▼───────────────────────────────┐ │
│  │              SPI 层 (Service Provider Interface)           │ │
│  │  ┌────────────────────────────────────────────────────┐  │ │
│  │  │ A2uiSkillRegistrySPI - 技能注册与发现的统一接口      │  │ │
│  │  │ 【支持 CLI 指令映射的技能注册】                      │  │ │
│  │  └────────────────────────────────────────────────────┘  │ │
│  └───────────────────────────────────────────────────────────┘ │
│                              │                                  │
│  ┌───────────────────────────▼───────────────────────────────┐ │
│  │              技能实现层 (Skill Implementation)             │ │
│  │  ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐    │ │
│  │  │ TreeSkill│ │ FormSkill│ │ChartSkill│ │   ...    │    │ │
│  │  │【CLI 指令】│ │【CLI 指令】│ │【CLI 指令】│ │          │    │ │
│  │  └──────────┘ └──────────┘ └──────────┘ └──────────┘    │ │
│  │  ┌──────────────────────────────────────────────────┐   │ │
│  │  │ AbstractA2uiSkill - 通用基类 (模板方法模式)        │   │ │
│  │  │ 【提供 CLI 指令解析和 UI 生成的通用方法】          │   │ │
│  │  └──────────────────────────────────────────────────┘   │ │
│  └───────────────────────────────────────────────────────────┘ │
│                              │                                  │
│  ┌───────────────────────────▼───────────────────────────────┐ │
│  │              注解层 (Annotation Layer)                     │ │
│  │  ┌──────────────┐ ┌────────────────────────────────┐     │ │
│  │  │ @A2uiSkill   │ │ A2uiSkillBehavior (接口)       │     │ │
│  │  │【CLI 指令映射】│ │【CLI 指令处理规范】            │     │ │
│  │  └──────────────┘ └────────────────────────────────┘     │ │
│  └───────────────────────────────────────────────────────────┘ │
│                                                                 │
│  ════════════════════════════════════════════════════════════  │
│  【Bridger 层】贯穿拦截层、服务层、技能实现层,实现 CLI ↔ UI   │
│  ════════════════════════════════════════════════════════════  │
└─────────────────────────────────────────────────────────────────┘

2.2 数据流图(补充 CLI 指令到 UI 交互的数据流链路)

┌──────────────────────────────────────────────────────────────────┐
│              请求处理数据流(CLI + UI 双端支持)                  │
├──────────────────────────────────────────────────────────────────┤
│                                                                  │
│  1. 请求来源(CLI 或 UI)                                        │
│         │                                                        │
│         ├──── CLI 命令 ────┐                                     │
│         │                  │                                     │
│         └──── UI 请求 ────┤                                     │
│                            │                                     │
│                            ▼                                     │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │ Bridger 层:请求识别与转换                               │    │
│  │  - CLI 命令 → 转换为 HTTP 请求                          │    │
│  │  - UI 请求 → 直接传递                                   │    │
│  └────────┬────────────────────────────────────────────────┘    │
│           │                                                      │
│           ▼                                                      │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │ 2. SkillDrivenInterceptor.preHandle()                   │    │
│  │  【统一处理 CLI 和 UI 请求】                             │    │
│  └────────┬────────────────────────────────────────────────┘    │
│           │                                                      │
│           ▼                                                      │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │ 3. SkillManagementService.findSkill()                   │    │
│  │  - 通过 SPI 查找对应的技能                              │    │
│  │  - 支持 CLI 指令名称映射                                │    │
│  └────────┬────────────────────────────────────────────────┘    │
│           │                                                      │
│           ▼                                                      │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │ 4. A2uiSkillBehavior.buildGenJson()                     │    │
│  │  【生成 UI 组件的 JSON 表示】                            │    │
│  │  - CLI 指令参数 → UI 组件配置                           │    │
│  │  - UI 操作参数 → UI 组件配置                            │    │
│  └────────┬────────────────────────────────────────────────┘    │
│           │                                                      │
│           ▼                                                      │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │ 5. 响应生成                                             │    │
│  │  - CLI 调用:返回 JSON + 生成 CLI 脚本                  │    │
│  │  - UI 调用:返回 JSON 渲染 UI                           │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                  │
│  【双向桥接】                                                    │
│  CLI 命令 ────▶ Bridger 层 ────▶ UI 组件生成                   │
│  UI 操作  ────▶ Bridger 层 ────▶ CLI 脚本生成                  │
│                                                                  │
└──────────────────────────────────────────────────────────────────┘

2.3 技能生命周期管理(新增桥接层技能的生命周期说明)

┌─────────────────────────────────────────────────────────────────┐
│                Skill 生命周期(支持 CLI 桥接)                   │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  ┌──────────┐    ┌──────────┐    ┌──────────┐    ┌──────────┐ │
│  │  定义    │───▶│  注册    │───▶│  发现    │───▶│  调用    │ │
│  └──────────┘    └──────────┘    └──────────┘    └──────────┘ │
│       │               │               │               │        │
│       ▼               ▼               ▼               ▼        │
│  @A2uiSkill      SPI 加载      拦截器解析     buildGenJson()  │
│  注解声明        ServiceLoader   组件类型映射   模板渲染        │
│  【CLI 指令映射】  【CLI 适配】   【CLI/UI 双端】 【CLI 参数解析】│
│                                                                 │
│  ┌──────────┐    ┌──────────┐    ┌──────────┐                 │
│  │  发布    │───▶│  桥接    │───▶│  管理    │                 │
│  └──────────┘    └──────────┘    └──────────┘                 │
│       │               │               │                        │
│       ▼               ▼               ▼                        │
│  生成 Java 源码    添加到项目    增删改查操作                    │
│  编译打包        技能依赖管理    CLI 脚本管理                    │
│  【CLI 文档生成】  【CLI 命令注册】                              │
│                                                                 │
│  ════════════════════════════════════════════════════════════  │
│  【Bridger 层支持】                                             │
│  - CLI 指令自动映射为技能 ID                                   │
│  - 技能调用记录可追溯,生成 CLI 脚本                           │
│  - UI 操作与 CLI 命令双向同步                                  │
│  ════════════════════════════════════════════════════════════  │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

3. 注解层设计

3.1 @A2uiSkill 注解(新增 CLI 指令映射扩展说明)

注解是整个技能系统的元数据基础,用于声明式地定义技能的核心属性,包括 CLI 指令的映射关系

@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface A2uiSkill {
    String id();                          // 技能唯一标识(CLI 指令名称)
    String name() default "";             // 技能显示名称
    String description() default "";      // 技能描述
    String version() default "1.0.0";     // 版本号
    String category() default "data-display"; // 分类
    String[] capabilities() default {};   // 能力列表
    ModuleViewType moduleViewType() default ModuleViewType.NONE;
    ComponentType componentType() default ComponentType.MODULE;
    int priority() default 100;           // 优先级
    
    // ⭐ CLI 桥接扩展
    String[] cliCommands() default {};    // CLI 命令别名列表
    String cliHelp() default "";          // CLI 帮助文档
}

设计要点

  • id 是必填项:确保每个技能有唯一标识,同时作为 CLI 指令的默认名称
  • CLI 指令映射:通过 cliCommands 字段,支持一个技能对应多个 CLI 命令别名
  • CLI 帮助文档:通过 cliHelp 字段,为 CLI 用户提供详细的命令说明
  • 默认值策略:减少配置负担,常用字段提供合理默认值
  • 类型安全:使用枚举类型(ModuleViewType、ComponentType)避免字符串错误
  • 可扩展性:capabilities 数组支持声明技能的特殊能力

CLI 指令映射示例

@Component
@A2uiSkill(
    id = "treegrid",
    name = "树形表格",
    description = "用于展示树形结构数据的表格组件",
    category = "data-display",
    moduleViewType = ModuleViewType.GRIDCONFIG,
    componentType = ComponentType.TREEGRID,
    cliCommands = {"treegrid", "tg", "tree-grid"},  // CLI 命令别名
    cliHelp = "Usage: a2ui treegrid <module_name> [options]\n" +
              "Options:\n" +
              "  --caption <text>    Set module caption\n" +
              "  --fields <list>     Set field list (comma-separated)",
    priority = 10
)
public class TreeGridSkill extends AbstractA2uiSkill {
    // 技能实现...
}

3.2 A2uiSkillBehavior 接口(补充 CLI 指令处理规范)

行为接口定义了技能必须实现的核心方法,包括 CLI 指令的处理规范

public interface A2uiSkillBehavior {
    String getSkillId();                                    // 获取技能 ID
    String getComponentType();                              // 获取组件类型
    ModuleViewType getModuleViewType();                     // 获取模块视图类型
    String getCategory();                                   // 获取分类
    String buildGenJson(String moduleName, String caption,  // 构建生成 JSON
                        List<String> fields, 
                        Map<String, Object> options);
    Map<String, Object> buildFromNaturalLanguage(String query); // NLP 构建
    List<String> getKeywords();                             // 关键词列表
    JSONObject toCardJson();                                // 卡片 JSON
    
    // ⭐ CLI 桥接方法
    String[] getCliCommands();                              // 获取 CLI 命令别名
    String getCliHelp();                                    // 获取 CLI 帮助文档
    Map<String, Object> parseCliArgs(String[] args);        // 解析 CLI 参数
    String generateCliScript(Map<String, Object> params);   // 生成 CLI 脚本
}

方法职责

  • buildGenJson(): 核心方法,根据参数生成模块的 JSON 表示,支持 CLI 参数和 UI 参数
  • buildFromNaturalLanguage(): 支持自然语言输入,智能解析意图,可用于 CLI 交互式命令
  • toCardJson(): 生成用于前端展示的卡片元数据
  • parseCliArgs(): :star: 新增:解析 CLI 命令行参数,转换为技能调用参数
  • generateCliScript(): :star: 新增:根据参数生成可执行的 CLI 脚本

4. SPI 扩展机制

4.1 A2uiSkillRegistry 接口(补充 CLI 适配的 SPI 扩展)

SPI 接口定义了技能注册表的契约,支持 CLI 指令的注册和查询

public interface A2uiSkillRegistry {
    int getPriority();                                      // 优先级
    String getSkillClassName(String skillId);               // 获取类名
    Class<?> getSkillClass(String skillId);                 // 获取 Class
    Set<String> getSkillIds();                              // 所有技能 ID
    String getCategory(String skillId);                     // 获取分类
    ModuleViewType getModuleViewType(String skillId);       // 获取视图类型
    ComponentType getComponentType(String skillId);         // 获取组件类型
    String findSkillIdByComponentType(String componentType);// 反向查找
    
    // ⭐ CLI 桥接扩展
    String findSkillIdByCliCommand(String cliCommand);      // 通过 CLI 命令查找技能
    Set<String> getAllCliCommands();                        // 获取所有 CLI 命令
    String getCliHelp(String skillId);                      // 获取 CLI 帮助文档
}

4.2 A2uiSkillRegistrySPI 实现(CLI 命令缓存)

SPI 加载器使用 Java 原生的 ServiceLoader 机制,并增加 CLI 命令的缓存

public class A2uiSkillRegistrySPI {
    private static volatile A2uiSkillRegistrySPI instance;
    private final List<A2uiSkillRegistry> registries = new ArrayList<>();
    private final Map<String, String> classNameCache = new ConcurrentHashMap<>();
    private final Map<String, Class<?>> classCache = new ConcurrentHashMap<>();
    private final Map<String, String> componentTypeToSkillIdCache = new ConcurrentHashMap<>();
    
    // ⭐ CLI 命令缓存
    private final Map<String, String> cliCommandToSkillIdCache = new ConcurrentHashMap<>();

    private void loadRegistries() {
        ServiceLoader<A2uiSkillRegistry> loader = ServiceLoader.load(
            A2uiSkillRegistry.class, 
            Thread.currentThread().getContextClassLoader());
        
        for (A2uiSkillRegistry registry : loader) {
            registries.add(registry);
        }
        
        // 按优先级排序
        registries.sort(Comparator.comparingInt(A2uiSkillRegistry::getPriority));
        
        // ⭐ 初始化 CLI 命令缓存
        initializeCliCommandCache();
    }
    
    private void initializeCliCommandCache() {
        for (A2uiSkillRegistry registry : registries) {
            Set<String> cliCommands = registry.getAllCliCommands();
            for (String cliCommand : cliCommands) {
                String skillId = registry.findSkillIdByCliCommand(cliCommand);
                if (skillId != null) {
                    cliCommandToSkillIdCache.put(cliCommand.toLowerCase(), skillId);
                }
            }
        }
    }
    
    // ⭐ 通过 CLI 命令查找技能
    public String findSkillIdByCliCommand(String cliCommand) {
        return cliCommandToSkillIdCache.get(cliCommand.toLowerCase());
    }
}

设计亮点

  • 多重缓存:classNameCache、classCache、componentTypeToSkillIdCache、cliCommandToSkillIdCache 四层缓存
  • CLI 命令映射:支持 CLI 命令别名到技能 ID 的快速查找
  • 优先级排序:支持多个注册表,按优先级加载
  • 线程安全:使用 volatile 和 ConcurrentHashMap 保证并发安全
  • 热加载支持:提供 reload() 和 clearCache() 方法

4.3 SPI 配置文件

META-INF/services 下创建配置文件:

# 文件路径:src/main/resources/META-INF/services/net.ooder.annotation.spi.A2uiSkillRegistry
net.ooder.a2ui.nlp.skill.A2uiSkillRegistryImpl

5. 技能实现层

5.1 AbstractA2uiSkill 抽象基类(提供 CLI 指令解析通用方法)

抽象基类使用模板方法模式提供通用实现,包括 CLI 指令解析和脚本生成的通用方法

public abstract class AbstractA2uiSkill implements A2uiSkillBehavior {
    protected Configuration freemarkerCfg;  // FreeMarker 模板引擎

    public AbstractA2uiSkill() {
        // 初始化 FreeMarker 配置
        freemarkerCfg = new Configuration(Configuration.VERSION_2_3_31);
        freemarkerCfg.setClassForTemplateLoading(this.getClass(), "/");
        freemarkerCfg.setDefaultEncoding("UTF-8");
    }

    // 默认实现:通过注解获取元数据
    @Override
    public String getSkillId() {
        A2uiSkill anno = getClass().getAnnotation(A2uiSkill.class);
        return anno != null ? anno.id() : getClass().getSimpleName().toLowerCase();
    }
    
    // ⭐ CLI 桥接:获取 CLI 命令别名
    @Override
    public String[] getCliCommands() {
        A2uiSkill anno = getClass().getAnnotation(A2uiSkill.class);
        return anno != null ? anno.cliCommands() : new String[]{getSkillId()};
    }
    
    // ⭐ CLI 桥接:获取 CLI 帮助文档
    @Override
    public String getCliHelp() {
        A2uiSkill anno = getClass().getAnnotation(A2uiSkill.class);
        return anno != null ? anno.cliHelp() : "No help available for " + getSkillId();
    }
    
    // ⭐ CLI 桥接:解析 CLI 参数(默认实现,子类可重写)
    @Override
    public Map<String, Object> parseCliArgs(String[] args) {
        Map<String, Object> params = new LinkedHashMap<>();
        for (int i = 0; i < args.length; i++) {
            if (args[i].startsWith("--")) {
                String key = args[i].substring(2);
                if (i + 1 < args.length && !args[i + 1].startsWith("--")) {
                    params.put(key, args[i + 1]);
                    i++;
                } else {
                    params.put(key, true);
                }
            }
        }
        return params;
    }
    
    // ⭐ CLI 桥接:生成 CLI 脚本(默认实现)
    @Override
    public String generateCliScript(Map<String, Object> params) {
        StringBuilder script = new StringBuilder();
        script.append("a2ui ").append(getSkillId());
        
        if (params.containsKey("moduleName")) {
            script.append(" ").append(params.get("moduleName"));
        }
        
        for (Map.Entry<String, Object> entry : params.entrySet()) {
            if (!"moduleName".equals(entry.getKey())) {
                script.append(" --").append(entry.getKey());
                if (!(entry.getValue() instanceof Boolean)) {
                    script.append(" ").append(entry.getValue());
                }
            }
        }
        
        return script.toString();
    }

    // 模板方法:子类重写以提供具体组件的生成逻辑
    protected abstract String getModuleTemplate();
    
    // 通用方法:构建模块 JSON
    protected String buildModuleFromTemplate(String className, String propertiesJson, 
                                             String childrenJson) {
        String template = getModuleTemplate();
        Template tpl = new Template("module", template, freemarkerCfg);
        // ... 模板渲染逻辑
    }
}

5.2 TreeGridSkill 实现示例(展示 CLI 指令支持)

具体技能实现展示如何扩展抽象基类,并支持 CLI 指令

@Component
@A2uiSkill(
    id = "treegrid",
    name = "树形表格",
    description = "用于展示树形结构数据的表格组件",
    category = "data-display",
    moduleViewType = ModuleViewType.GRIDCONFIG,
    componentType = ComponentType.TREEGRID,
    cliCommands = {"treegrid", "tg", "tree-grid"},
    cliHelp = "Usage: a2ui treegrid <module_name> [options]\n" +
              "  --caption <text>    Set module caption\n" +
              "  --fields <list>     Set field list (comma-separated)\n" +
              "  --tree-field <name> Set tree field name",
    priority = 10
)
public class TreeGridSkill extends AbstractA2uiSkill {
    
    @Override
    public List<String> getKeywords() {
        return Arrays.asList("树形表格", "tree", "grid", "层级数据", "树状结构");
    }

    @Override
    public String buildGenJson(String moduleName, String caption, 
                               List<String> fields, Map<String, Object> options) {
        // 1. 创建基础属性
        JSONObject properties = createBaseProperties(caption, moduleName);
        
        // 2. 添加 TreeGrid 特有配置
        properties.put("treeField", options.getOrDefault("treeField", "name"));
        properties.put("showTreeLines", options.getOrDefault("showTreeLines", true));
        properties.put("expandColumn", options.getOrDefault("expandColumn", 0));
        
        // 3. 构建字段配置
        JSONArray columns = new JSONArray();
        for (int i = 0; i < fields.size(); i++) {
            JSONObject col = new JSONObject();
            col.put("field", fields.get(i));
            col.put("caption", fields.get(i));
            col.put("width", 100);
            columns.add(col);
        }
        properties.put("columns", columns);
        
        // 4. 使用模板生成最终 JSON
        return buildModuleFromTemplate(moduleName, properties.toJSONString(), "[]");
    }
    
    // ⭐ CLI 桥接:重写 CLI 参数解析
    @Override
    public Map<String, Object> parseCliArgs(String[] args) {
        Map<String, Object> params = super.parseCliArgs(args);
        
        // 解析 fields 参数(逗号分隔)
        if (params.containsKey("fields")) {
            String fieldsStr = (String) params.get("fields");
            params.put("fields", Arrays.asList(fieldsStr.split(",")));
        }
        
        return params;
    }
}

5.3 内置技能列表

Skill ID Component Category CLI Commands Description
treegrid TREEGRID data-display treegrid, tg, tree-grid 树形表格组件
form FORMLAYOUT data-input form, f 数据录入表单
tree TREEVIEW data-display tree, t 树形视图组件
gallery GALLERY data-display gallery, gal 图片画廊组件
chart ECHARTS data-visualization chart, c ECharts 图表组件
navtree TREEBAR navigation navtree, nt 导航树组件
navigallery GALLERY navigation navigallery, ngal 导航画廊组件
navtabs TABS navigation navtabs, nt 导航标签组件
navmenubar BAR navigation navmenubar, nmb 导航菜单栏组件
tabscontainer TABS layout tabs, tc 标签容器组件
layout LAYOUT layout layout, l 布局组件

6. 拦截器重构

6.1 SkillDrivenInterceptor 核心逻辑(强化 CLI/UI 双端请求统一处理)

统一拦截器替代了原有的多个硬编码拦截器,并支持 CLI 和 UI 双端请求的统一处理

@Component
public class SkillDrivenInterceptor extends BaseInterceptor {

    @Autowired
    private List<ResourceResolver> resourceResolvers;

    @Override
    public boolean preHandle(HttpServletRequest request, 
                            HttpServletResponse response, 
                            Object handler) {
        String url = request.getRequestURI();
        SuffixType suffix = SuffixType.fromUrl(url);

        // ⭐ Bridger 层:识别请求来源(CLI 或 UI)
        boolean isCliRequest = isCliRequest(request);
        
        // 1. 无后缀请求:数据请求处理
        if (suffix == SuffixType.NONE) {
            return handleDataRequest(handler, request, response, isCliRequest);
        }

        // 2. 剥离后缀获取类名
        String className = SuffixType.stripSuffix(url);
        
        // 3. 根据后缀类型分发到不同处理器
        switch (suffix) {
            case JSX:  return handleJsx(className, request, response, isCliRequest);
            case CLS:  return handleCls(className, request, response, isCliRequest);
            case DYN:  return handleDyn(handler, request, response, isCliRequest);
            case VIEW: 
            case JSA:
            case JSAA: return handleView(className, request, response, isCliRequest);
            default:   return true;
        }
    }
    
    // ⭐ Bridger 层:判断是否为 CLI 请求
    private boolean isCliRequest(HttpServletRequest request) {
        String userAgent = request.getHeader("User-Agent");
        String cliFlag = request.getHeader("X-A2UI-CLI");
        return "true".equals(cliFlag) || 
               (userAgent != null && userAgent.startsWith("A2UI-CLI/"));
    }

    // JSX 处理:技能驱动的模块构建(支持 CLI 和 UI)
    private boolean handleJsx(String className, HttpServletRequest request, 
                             HttpServletResponse response, boolean isCliRequest) {
        // 1. 尝试静态资源解析
        if (tryStaticResource(className, request, response)) {
            return false;
        }

        // 2. 解析组件类型
        String componentType = resolveComponentType(className);
        
        // 3. ⭐ Bridger 层:查找对应技能(支持 CLI 命令映射)
        A2uiSkillBehavior skill = findSkill(componentType, request, isCliRequest);

        if (skill != null) {
            try {
                // 4. 使用技能构建模块
                UIModule uiModule = buildModuleFromSkill(skill, className, request, isCliRequest);
                if (uiModule != null) {
                    // 5. 填充数据并返回 JSON
                    MethodConfig methodConfig = getCurrMethodConfig(request);
                    if (methodConfig != null && uiModule.getComponent() != null) {
                        fillDataFromMethod(methodConfig, uiModule, request);
                    }
                    String json = EngineFactory.getAdminESDClient()
                        .genJSON(uiModule, null, true);
                    
                    // ⭐ Bridger 层:CLI 请求额外生成 CLI 脚本
                    if (isCliRequest) {
                        String cliScript = generateCliScript(skill, request);
                        response.setHeader("X-A2UI-CLI-Script", cliScript);
                    }
                    
                    sendJSON(response, json.toString());
                    return false;
                }
            } catch (Exception e) {
                log.error("Skill-driven module build failed", e);
            }
        }

        // 6. 降级到传统模块处理
        return handleLegacyModule(className, request, response);
    }

    // ⭐ Bridger 层:技能查找(支持 CLI 命令映射)
    private A2uiSkillBehavior findSkill(String componentType, 
                                        HttpServletRequest request,
                                        boolean isCliRequest) {
        if (componentType == null) return null;

        // 1. 从配置工厂查找
        A2uiSkillBehavior skill = C2UConfigFactory.getInstance()
            .findSkillByComponentType(componentType);
        if (skill != null) return skill;

        // 2. 从 SPI 查找
        A2uiSkillRegistrySPI skillSPI = A2uiSkillRegistrySPI.getInstance();
        
        // ⭐ CLI 请求:尝试通过 CLI 命令查找
        if (isCliRequest) {
            String cliCommand = request.getHeader("X-A2UI-CLI-Command");
            if (cliCommand != null) {
                String skillId = skillSPI.findSkillIdByCliCommand(cliCommand);
                if (skillId != null) {
                    return loadSkillInstance(skillSPI, skillId);
                }
            }
        }
        
        // 3. 通过组件类型查找
        String skillId = skillSPI.findSkillIdByComponentType(componentType);
        if (skillId != null) {
            return loadSkillInstance(skillSPI, skillId);
        }
        
        return null;
    }
    
    // ⭐ Bridger 层:生成 CLI 脚本
    private String generateCliScript(A2uiSkillBehavior skill, 
                                     HttpServletRequest request) {
        Map<String, Object> params = extractParamsFromRequest(request);
        return skill.generateCliScript(params);
    }
}

6.2 拦截器对比(突出 CLI/UI 双端支持)

┌─────────────────────────────────────────────────────────────────┐
│                    拦截器架构对比                               │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  传统架构:                                                     │
│  ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐              │
│  │JSXInter.│ │CLSInter.│ │DYNInter.│ │  ...    │              │
│  │(仅 UI)  │ │(仅 UI)  │ │(仅 UI)  │ │         │              │
│  └────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘              │
│       │           │           │           │                    │
│       └───────────┴───────────┴───────────┘                    │
│                           │                                    │
│                  ┌────────▼────────┐                           │
│                  │BaseInterceptor  │                           │
│                  │(无 CLI 支持)    │                           │
│                  └─────────────────┘                           │
│                                                                 │
│  新架构(Bridger 层):                                         │
│  ┌────────────────────────────────────────────────────────┐   │
│  │        SkillDrivenInterceptor (统一入口)                │   │
│  │        【支持 CLI + UI 双端请求】                        │   │
│  │        ⭐ Bridger 层核心                                 │   │
│  └────────────────────┬───────────────────────────────────┘   │
│                       │                                       │
│         ┌─────────────┴─────────────┐                         │
│         │                           │                         │
│  ┌──────▼─────────┐         ┌──────▼─────────┐               │
│  │SkillRegistrySPI│         │ResourceResolver│               │
│  │(技能注册中心)  │         │(资源解析策略)  │               │
│  │【CLI 命令映射】│         │【CLI/UI 双端】 │               │
│  └────────────────┘         └────────────────┘               │
│                                                                 │
│  【核心改进】                                                   │
│  - CLI 指令自动映射为技能调用                                  │
│  - UI 操作可生成 CLI 脚本                                     │
│  - 一套技能逻辑,双端复用                                      │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

7. 服务层架构

7.1 SkillManagementService(CLI 状态管理)

技能管理的核心服务,采用单例模式,支持 CLI 状态管理

public class SkillManagementService {
    private static volatile SkillManagementService instance;
    private final Map<String, SkillModuleConfig> skillModuleCache = new ConcurrentHashMap<>();
    
    // ⭐ CLI 桥接:CLI 操作历史记录
    private final Map<String, List<CliOperationRecord>> cliOperationHistory = new ConcurrentHashMap<>();

    public static SkillManagementService getInstance() {
        if (instance == null) {
            synchronized (SkillManagementService.class) {
                if (instance == null) {
                    instance = new SkillManagementService();
                }
            }
        }
        return instance;
    }

    // 核心方法:通过技能创建模块(支持 CLI 和 UI)
    public UIModule createModuleFromSkill(String skillId, String moduleName,
                                          String projectName, String caption,
                                          List<String> fields, 
                                          Map<String, Object> options,
                                          boolean isCliRequest) {
        // 1. 查找技能
        A2uiSkillBehavior skill = findSkill(skillId);
        if (skill == null) {
            logger.error("Skill not found: " + skillId);
            return null;
        }

        // 2. 生成模块配置
        SkillModuleConfig config = SkillModuleConfig.fromSkill(
            skill, moduleName, caption, fields, options);
        skillModuleCache.put(moduleName, config);

        // 3. 创建物理模块
        ProjectCacheManager pcm = getProjectCacheManager(projectName);
        INProject project = pcm.getProjectByName(projectName);
        UIModule uiModule = pcm.createModule(version, moduleName);

        // 4. 保存生成的 JSON 到文件系统
        if (uiModule != null && config.getGenJson() != null) {
            String physicalPath = version.getPath() + "/" + 
                moduleName.replace(".", "/") + ".cls";
            CtVfsFactory.getCtVfsService().saveFileAsContent(
                physicalPath, config.getGenJson(), "UTF-8");
        }
        
        // ⭐ CLI 桥接:记录 CLI 操作历史
        if (isCliRequest) {
            recordCliOperation(skillId, moduleName, options);
        }
        
        return uiModule;
    }
    
    // ⭐ CLI 桥接:记录 CLI 操作
    private void recordCliOperation(String skillId, String moduleName, 
                                    Map<String, Object> options) {
        CliOperationRecord record = new CliOperationRecord();
        record.setSkillId(skillId);
        record.setModuleName(moduleName);
        record.setOptions(options);
        record.setTimestamp(new Date());
        
        cliOperationHistory.computeIfAbsent(skillId, k -> new ArrayList<>())
            .add(record);
    }
    
    // ⭐ CLI 桥接:获取 CLI 操作历史
    public List<CliOperationRecord> getCliOperationHistory(String skillId) {
        return cliOperationHistory.getOrDefault(skillId, Collections.emptyList());
    }
    
    // ⭐ CLI 桥接:生成 CLI 脚本
    public String generateCliScript(String skillId, Map<String, Object> params) {
        A2uiSkillBehavior skill = findSkill(skillId);
        if (skill != null) {
            return skill.generateCliScript(params);
        }
        return null;
    }

    // 获取所有技能卡片
    public Map<String, Object> getSkillCards() {
        Map<String, Object> result = new LinkedHashMap<>();
        A2uiSkillRegistrySPI skillSPI = A2uiSkillRegistrySPI.getInstance();
        Set<String> skillIds = skillSPI.getSkillIds();

        List<JSONObject> cards = new ArrayList<>();
        for (String skillId : skillIds) {
            A2uiSkillBehavior skill = findSkill(skillId);
            if (skill != null) {
                JSONObject card = skill.toCardJson();
                // ⭐ CLI 桥接:添加 CLI 命令信息
                card.put("cliCommands", skill.getCliCommands());
                card.put("cliHelp", skill.getCliHelp());
                cards.add(card);
            }
        }
        result.put("skills", cards);
        result.put("total", cards.size());
        return result;
    }
}

7.2 SkillPublishService(CLI 脚本生成)

技能发布服务,负责生成可发布的技能包,包括 CLI 脚本和文档

public class SkillPublishService {
    public SkillPublishResult publish(A2uiSkillBehavior skill, String outputDir) {
        String skillId = skill.getSkillId();
        
        // 1. 创建技能目录结构
        Path skillDir = Paths.get(outputDir, skillId + "-skill");
        Files.createDirectories(skillDir);
        
        // 2. 生成 Java 源码
        Path javaDir = skillDir.resolve("src/main/java")
            .resolve(packageName.replace(".", "/"));
        Files.createDirectories(javaDir);
        
        String javaSource = generateSkillJavaSource(skill, packageName);
        Path javaFile = javaDir.resolve(skill.getClass().getSimpleName() + ".java");
        Files.writeString(javaFile, javaSource);
        
        // 3. 生成 SPI 配置文件
        Path metaInfDir = skillDir.resolve("src/main/resources/META-INF/services");
        Files.createDirectories(metaInfDir);
        
        String spiContent = packageName + "." + 
            skill.getClass().getSimpleName() + "\n";
        Path spiFile = metaInfDir.resolve("net.ooder.annotation.spi.A2uiSkillRegistry");
        Files.writeString(spiFile, spiContent);
        
        // ⭐ CLI 桥接:生成 CLI 脚本模板
        Path cliDir = skillDir.resolve("cli");
        Files.createDirectories(cliDir);
        String cliScript = generateCliScriptTemplate(skill);
        Path cliFile = cliDir.resolve(skillId + ".sh");
        Files.writeString(cliFile, cliScript);
        
        // ⭐ CLI 桥接:生成 CLI 文档
        Path docsDir = skillDir.resolve("docs");
        Files.createDirectories(docsDir);
        String cliDoc = generateCliDocumentation(skill);
        Path docFile = docsDir.resolve("CLI.md");
        Files.writeString(docFile, cliDoc);
        
        // 4. 返回发布结果
        SkillPublishResult result = SkillPublishResult.success(skillId);
        result.setJavaSource(javaSource);
        result.setCliScript(cliScript);
        result.setPublishedAt(new Date().toInstant().toString());
        return result;
    }
    
    // ⭐ CLI 桥接:生成 CLI 脚本模板
    private String generateCliScriptTemplate(A2uiSkillBehavior skill) {
        StringBuilder script = new StringBuilder();
        script.append("#!/bin/bash\n\n");
        script.append("# A2UI CLI Script for ").append(skill.getSkillId()).append("\n");
        script.append("# Generated by Ooder A2UI SkillPublishService\n\n");
        script.append("SKILL_ID=\"").append(skill.getSkillId()).append("\"\n");
        script.append("MODULE_NAME=\"\"\n");
        script.append("CAPTION=\"\"\n");
        script.append("FIELDS=\"\"\n\n");
        script.append("# Parse arguments\n");
        script.append("while [[ $# -gt 0 ]]; do\n");
        script.append("    case $1 in\n");
        script.append("        --module) MODULE_NAME=\"$2\"; shift 2 ;;\n");
        script.append("        --caption) CAPTION=\"$2\"; shift 2 ;;\n");
        script.append("        --fields) FIELDS=\"$2\"; shift 2 ;;\n");
        script.append("        *) echo \"Unknown option: $1\"; exit 1 ;;\n");
        script.append("    esac\n");
        script.append("done\n\n");
        script.append("# Call A2UI API\n");
        script.append("curl -X POST \"http://localhost:8080/api/skill/build\" \\\n");
        script.append("    -H \"Content-Type: application/json\" \\\n");
        script.append("    -H \"X-A2UI-CLI: true\" \\\n");
        script.append("    -H \"X-A2UI-CLI-Command: $SKILL_ID\" \\\n");
        script.append("    -d \"{\\\"skillId\\\":\\\"$SKILL_ID\\\",\\\"moduleName\\\":\\\"$MODULE_NAME\\\",\\\"caption\\\":\\\"$CAPTION\\\",\\\"fields\\\":\\\"$FIELDS\\\"}\"\n");
        
        return script.toString();
    }
    
    // ⭐ CLI 桥接:生成 CLI 文档
    private String generateCliDocumentation(A2uiSkillBehavior skill) {
        StringBuilder doc = new StringBuilder();
        doc.append("# ").append(skill.getSkillId()).append(" - CLI Usage\n\n");
        doc.append("## Description\n");
        doc.append(skill.getCliHelp()).append("\n\n");
        doc.append("## CLI Commands\n");
        for (String cmd : skill.getCliCommands()) {
            doc.append("- `a2ui ").append(cmd).append("`\n");
        }
        doc.append("\n## Examples\n");
        doc.append("```bash\n");
        doc.append("# Create a tree grid module\n");
        doc.append("a2ui treegrid MyModule --caption \"My Tree\" --fields \"id,name,status\"\n");
        doc.append("```\n");
        
        return doc.toString();
    }
}

7.3 服务层协作关系(CLI/UI 桥接)

┌─────────────────────────────────────────────────────────────────┐
│                      服务层协作图                               │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  ┌──────────────────────┐                                       │
│  │ SkillController      │                                       │
│  │ (REST API 入口)       │                                       │
│  │ 【支持 CLI 调用】     │                                       │
│  └──────┬───────────────┘                                       │
│         │                                                       │
│         │ 调用                                                   │
│         ▼                                                       │
│  ┌─────────────────────────────────────────────────────────┐   │
│  │ SkillManagementService                                  │   │
│  │ - 技能生命周期管理                                       │   │
│  │ - 模块创建与删除                                         │   │
│  │ - 技能卡片生成                                           │   │
│  │ ⭐ CLI 操作历史记录                                      │   │
│  │ ⭐ CLI 脚本生成                                          │   │
│  └──────┬──────────────────────┬──────────────────────────┘   │
│         │                       │                              │
│         │ 调用                │ 调用                           │
│         ▼                       ▼                              │
│  ┌─────────────────┐   ┌─────────────────┐                    │
│  │SkillPublishSvc  │   │SkillProjectBridge                    │
│  │- 生成 Java 源码   │   │- 项目技能关联    │                    │
│  │- 创建 SPI 配置    │   │- 技能依赖管理    │                    │
│  │- 打包发布        │   │- 项目技能概览    │                    │
│  │⭐ CLI 脚本生成   │   │⭐ CLI/UI 桥接    │                    │
│  │⭐ CLI 文档生成   │   │⭐ CLI 脚本管理   │                    │
│  └─────────────────┘   └─────────────────┘                    │
│                                                                 │
│  【Bridger 层支持】                                             │
│  - 所有服务层组件支持 CLI 桥接                                 │
│  - CLI 操作可追溯、可回滚                                      │
│  - UI 操作自动生成 CLI 脚本                                    │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

8. 项目整合桥接

8.1 SkillProjectBridge(CLI/UI 桥接服务)

桥接服务负责技能与项目之间的关联管理,并实现 CLI 与 UI 的双向桥接

public class SkillProjectBridge {
    
    // 添加技能到项目(支持 CLI 和 UI)
    public SkillProjectBridgeResult addSkillToProject(
            String projectName, String skillId,
            String moduleName, String caption,
            List<String> fields,
            boolean isCliRequest) {
        
        // 1. 查找技能
        SkillManagementService skillMgmt = SkillManagementService.getInstance();
        A2uiSkillBehavior skill = skillMgmt.findSkill(skillId);
        if (skill == null) {
            return SkillProjectBridgeResult.fail("Skill not found: " + skillId);
        }

        // 2. 获取项目管理器
        ProjectCacheManager pcm = getProjectCacheManager(projectName);
        if (pcm == null) {
            return SkillProjectBridgeResult.fail("Project not found: " + projectName);
        }

        // 3. 创建模块
        UIModule module = pcm.createModuleFromSkill(projectName, moduleName, 
            skillId, caption, fields, new HashMap<>(), isCliRequest);

        // 4. 记录技能依赖
        ProjectConfig config = project.getConfig();
        config.addSkillDependency(skillId);
        pcm.updateProjectConfig(projectName, config);

        // 5. 返回结果
        SkillProjectBridgeResult result = SkillProjectBridgeResult.success();
        result.setSkillId(skillId);
        result.setModuleName(moduleName);
        result.setProjectName(projectName);
        result.setComponentType(skill.getComponentType());
        
        // ⭐ CLI 桥接:生成 CLI 脚本
        if (isCliRequest) {
            Map<String, Object> params = new LinkedHashMap<>();
            params.put("moduleName", moduleName);
            params.put("caption", caption);
            params.put("fields", String.join(",", fields));
            result.setCliScript(skill.generateCliScript(params));
        }
        
        return result;
    }

    // 列出项目的所有技能
    public List<Map<String, String>> listProjectSkills(String projectName) {
        List<Map<String, String>> skills = new ArrayList<>();
        ProjectCacheManager pcm = getProjectCacheManager(projectName);
        if (pcm != null) {
            // 从项目配置中读取技能依赖
            List<String> skillDeps = pcm.getSkillDependencies(projectName);
            for (String skillId : skillDeps) {
                Map<String, String> info = new LinkedHashMap<>();
                info.put("skillId", skillId);
                A2uiSkillBehavior skill = SkillManagementService
                    .getInstance().findSkill(skillId);
                if (skill != null) {
                    info.put("name", skill.toCardJson().getString("name"));
                    info.put("componentType", skill.getComponentType());
                    info.put("category", skill.getCategory());
                    // ⭐ CLI 桥接:添加 CLI 命令信息
                    info.put("cliCommands", String.join(",", skill.getCliCommands()));
                }
                skills.add(info);
            }
        }
        return skills;
    }

    // 从项目移除技能
    public boolean removeSkillFromProject(String projectName, String skillId) {
        ProjectCacheManager pcm = getProjectCacheManager(projectName);
        if (pcm == null) return false;

        INProject project = pcm.getProjectByName(projectName);
        if (project != null && project.getConfig() != null) {
            ProjectConfig config = project.getConfig();
            // 移除技能依赖
            config.removeSkillDependency(skillId);
            pcm.updateProjectConfig(projectName, config);
            return true;
        }
        return false;
    }
    
    // ⭐ CLI 桥接:获取项目的 CLI 操作历史
    public List<CliOperationRecord> getProjectCliHistory(String projectName) {
        ProjectCacheManager pcm = getProjectCacheManager(projectName);
        if (pcm != null) {
            INProject project = pcm.getProjectByName(projectName);
            if (project != null) {
                return SkillManagementService.getInstance()
                    .getCliOperationHistory(project.getSkillDependencies());
            }
        }
        return Collections.emptyList();
    }
}

8.2 项目与技能的关系(CLI 桥接视角)

┌─────────────────────────────────────────────────────────────────┐
│                    项目 - 技能关系图(CLI 桥接视角)            │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  ┌─────────────────────────────────────────────────────────┐   │
│  │                    Project A                             │   │
│  │  ┌───────────────────────────────────────────────────┐  │   │
│  │  │ ProjectConfig                                      │  │   │
│  │  │  skillDependencies: ["treegrid", "form", "chart"] │  │   │
│  │  │  cliOperationHistory: [...]                       │  │   │
│  │  └───────────────────────────────────────────────────┘  │   │
│  │                                                          │   │
│  │  ┌──────────┐  ┌──────────┐  ┌──────────┐              │   │
│  │  │ Module 1 │  │ Module 2 │  │ Module 3 │  ...         │   │
│  │  │ (treegrid)│ │ (form)   │  │ (chart)  │              │   │
│  │  │ CLI: tg  │  │ CLI: f   │  │ CLI: c   │              │   │
│  │  └──────────┘  └──────────┘  └──────────┘              │   │
│  └─────────────────────────────────────────────────────────┘   │
│                                                                 │
│  ┌─────────────────────────────────────────────────────────┐   │
│  │              Skill Registry (全局注册表)                 │   │
│  │  ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐  │   │
│  │  │treegrid  │ │ form     │ │ chart    │ │   ...    │  │   │
│  │  │CLI: tg   │ │CLI: f    │ │CLI: c    │ │          │  │   │
│  │  └──────────┘ └──────────┘ └──────────┘ └──────────┘  │   │
│  └─────────────────────────────────────────────────────────┘   │
│                                                                 │
│  【CLI 桥接关系】                                               │
│  - 一个项目可以依赖多个技能 (1:N)                               │
│  - 一个技能可以被多个项目复用 (N:M)                             │
│  - 技能是全局共享的,项目通过依赖引用                            │
│  - ⭐ CLI 命令映射到技能,实现 CLI ↔ UI 双向桥接               │
│  - ⭐ 所有 CLI 操作可追溯,支持审计和回滚                       │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

9. RESTful API 设计

9.1 SkillController API 列表(支持 CLI 调用)

@RestController
@RequestMapping("/api/skill")
public class SkillController {

    // 1. 获取所有技能列表
    @GetMapping("/list")
    public ResponseEntity<Map<String, Object>> listSkills() {
        // GET /api/skill/list
        // Response: { "skills": [...], "total": 11 }
        // ⭐ CLI: a2ui list
    }

    // 2. 通过技能构建模块(支持 CLI 调用)
    @PostMapping("/build")
    public ResponseEntity<Map<String, Object>> buildFromSkill(
            @RequestBody JSONObject request,
            @RequestHeader(value = "X-A2UI-CLI", required = false) String cliFlag,
            @RequestHeader(value = "X-A2UI-CLI-Command", required = false) String cliCommand) {
        // POST /api/skill/build
        // Body: { "skillId": "treegrid", "moduleName": "MyModule", 
        //         "caption": "测试模块", "fields": ["id", "name"], 
        //         "options": {...} }
        // ⭐ CLI: a2ui treegrid MyModule --caption "测试模块" --fields "id,name"
        
        boolean isCliRequest = "true".equals(cliFlag);
        // ... 处理逻辑
    }

    // 3. 自然语言构建模块
    @PostMapping("/nlp-build")
    public ResponseEntity<Map<String, Object>> buildFromNaturalLanguage(
            @RequestBody JSONObject request) {
        // POST /api/skill/nlp-build
        // Body: { "query": "创建一个包含 ID 和名称的树形表格" }
        // ⭐ CLI: a2ui "创建一个包含 ID 和名称的树形表格"
    }

    // 4. 添加技能到项目
    @PostMapping("/project/add-skill")
    public ResponseEntity<Map<String, Object>> addSkillToProject(
            @RequestBody JSONObject request,
            @RequestHeader(value = "X-A2UI-CLI", required = false) String cliFlag) {
        // POST /api/skill/project/add-skill
        // ⭐ CLI: a2ui project add-skill ProjectA treegrid --module MyTree
    }

    // 5. 列出项目的技能
    @GetMapping("/project/{projectName}/skills")
    public ResponseEntity<Map<String, Object>> listProjectSkills(
            @PathVariable String projectName) {
        // GET /api/skill/project/ProjectA/skills
        // ⭐ CLI: a2ui project list ProjectA
    }

    // 6. 从项目移除技能
    @DeleteMapping("/project/{projectName}/skill/{skillId}")
    public ResponseEntity<Map<String, Object>> removeSkillFromProject(
            @PathVariable String projectName, 
            @PathVariable String skillId) {
        // DELETE /api/skill/project/ProjectA/skill/treegrid
        // ⭐ CLI: a2ui project remove ProjectA treegrid
    }

    // 7. 发布技能
    @PostMapping("/publish/{skillId}")
    public ResponseEntity<Map<String, Object>> publishSkill(
            @PathVariable String skillId,
            @RequestParam(defaultValue = "./published-skills") String outputDir) {
        // POST /api/skill/publish/treegrid?outputDir=./published
        // ⭐ CLI: a2ui publish treegrid --output ./published
    }
    
    // ⭐ CLI 桥接:获取 CLI 操作历史
    @GetMapping("/project/{projectName}/cli-history")
    public ResponseEntity<List<CliOperationRecord>> getCliHistory(
            @PathVariable String projectName) {
        // GET /api/skill/project/ProjectA/cli-history
        // ⭐ CLI: a2ui project history ProjectA
    }
    
    // ⭐ CLI 桥接:生成 CLI 脚本
    @PostMapping("/generate-cli-script")
    public ResponseEntity<Map<String, String>> generateCliScript(
            @RequestBody JSONObject request) {
        // POST /api/skill/generate-cli-script
        // Body: { "skillId": "treegrid", "params": {...} }
        // ⭐ 用于 UI 操作生成 CLI 脚本
    }

    // 8. 健康检查
    @GetMapping("/health")
    public ResponseEntity<Map<String, Object>> health() {
        // GET /api/skill/health
        // Response: { "status": "UP", "registeredSkills": 11, ... }
    }
}

9.2 API 响应示例(包含 CLI 信息)

获取技能列表

{
  "skills": [
    {
      "skillId": "treegrid",
      "name": "树形表格",
      "description": "用于展示树形结构数据的表格组件",
      "category": "data-display",
      "componentType": "TREEGRID",
      "moduleViewType": "GRIDCONFIG",
      "version": "1.0.0",
      "keywords": ["树形表格", "tree", "grid", "层级数据"],
      "cliCommands": ["treegrid", "tg", "tree-grid"],
      "cliHelp": "Usage: a2ui treegrid <module_name> [options]..."
    }
  ],
  "total": 11
}

添加技能到项目(CLI 调用)

{
  "success": true,
  "message": "Skill added successfully",
  "skillId": "treegrid",
  "moduleName": "MyTreeGrid",
  "componentType": "TREEGRID",
  "projectName": "ProjectA",
  "cliScript": "a2ui treegrid MyTreeGrid --caption \"My Tree\" --fields \"id,name,status\""
}

10. 最佳实践

10.1 开发自定义技能(支持 CLI 指令)

步骤 1:创建技能类

@Component
@A2uiSkill(
    id = "mycustom",
    name = "我的自定义组件",
    description = "用于特定业务场景的自定义组件",
    category = "custom",
    componentType = ComponentType.CUSTOM,
    cliCommands = {"mycustom", "mc", "my-custom"},  // ⭐ CLI 命令别名
    cliHelp = "Usage: a2ui mycustom <module_name> [options]\n" +
              "  --caption <text>    Set module caption\n" +
              "  --custom-prop <value> Set custom property",
    priority = 50
)
public class MyCustomSkill extends AbstractA2uiSkill {
    
    @Override
    public List<String> getKeywords() {
        return Arrays.asList("自定义", "custom", "业务组件");
    }

    @Override
    public String buildGenJson(String moduleName, String caption, 
                               List<String> fields, 
                               Map<String, Object> options) {
        // 1. 创建基础属性
        JSONObject properties = createBaseProperties(caption, moduleName);
        
        // 2. 添加自定义配置
        properties.put("customProp1", options.getOrDefault("prop1", "default"));
        properties.put("customProp2", options.get("prop2"));
        
        // 3. 使用模板生成
        return buildModuleFromTemplate(moduleName, 
            properties.toJSONString(), "[]");
    }
    
    // ⭐ CLI 桥接:重写 CLI 参数解析
    @Override
    public Map<String, Object> parseCliArgs(String[] args) {
        Map<String, Object> params = super.parseCliArgs(args);
        
        // 自定义参数解析逻辑
        if (params.containsKey("custom-prop")) {
            params.put("customProp", params.get("custom-prop"));
        }
        
        return params;
    }
}

步骤 2:注册到 SPI

public class MyCustomSkillRegistry implements A2uiSkillRegistry {
    private static final Map<String, SkillEntry> SKILL_MAP = new HashMap<>();
    
    static {
        register("mycustom", "com.example.MyCustomSkill", 
            "custom", ModuleViewType.CUSTOMCONFIG, ComponentType.CUSTOM);
    }
    
    // 实现接口方法...
}

步骤 3:创建 SPI 配置文件

# src/main/resources/META-INF/services/net.ooder.annotation.spi.A2uiSkillRegistry
com.example.MyCustomSkillRegistry

10.2 技能设计原则(CLI 桥接视角)

  1. 单一职责:每个技能只负责一种组件类型的生成
  2. 无状态设计:技能类应该是无状态的,支持并发调用
  3. 模板复用:使用 FreeMarker 模板提高代码复用性
  4. 错误处理:在 buildGenJson 中做好参数校验和异常处理
  5. 关键词优化:提供丰富的关键词以支持 NLP 意图识别
  6. :star: CLI 友好:提供清晰的 CLI 命令别名和帮助文档
  7. :star: 参数映射:CLI 参数名与 UI 参数名保持一致或提供映射
  8. :star: 脚本生成:实现 generateCliScript 方法,支持 UI 操作回溯

10.3 性能优化建议

┌─────────────────────────────────────────────────────────────────┐
│                      性能优化策略                               │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  1. 缓存策略                                                    │
│     ┌─────────────────────────────────────────────────────┐    │
│     │ - classNameCache: 技能 ID → 类名                     │    │
│     │ - classCache: 技能 ID → Class 对象                   │    │
│     │ - componentTypeToSkillIdCache: 组件类型 → 技能 ID    │    │
│     │ ⭐ cliCommandToSkillIdCache: CLI 命令 → 技能 ID      │    │
│     └─────────────────────────────────────────────────────┘    │
│                                                                 │
│  2. 懒加载                                                      │
│     - 技能实例在首次使用时创建                                  │
│     - 避免启动时加载所有技能                                    │
│                                                                 │
│  3. 并发控制                                                    │
│     - 使用 ConcurrentHashMap 保证线程安全                       │
│     - 单例模式使用双重检查锁定 (DCL)                            │
│                                                                 │
│  4. 模板预热                                                    │
│     - 应用启动时预加载常用 FreeMarker 模板                       │
│     - 避免首次请求时模板编译延迟                                │
│                                                                 │
│  5. ⭐ CLI 操作历史限制                                         │
│     - 限制历史记录数量,避免内存溢出                            │
│     - 定期清理过期的历史记录                                    │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

10.4 调试技巧(CLI 调试)

启用详细日志

# application.properties
logging.level.net.ooder.engine.core.skill=DEBUG
logging.level.net.ooder.annotation.spi=DEBUG
logging.level.net.ooder.web.interceptor=DEBUG

查看已注册技能(包含 CLI 命令)

curl http://localhost:8080/api/skill/health

测试 CLI 调用

# 使用 curl 模拟 CLI 调用
curl -X POST http://localhost:8080/api/skill/build \
  -H "Content-Type: application/json" \
  -H "X-A2UI-CLI: true" \
  -H "X-A2UI-CLI-Command: treegrid" \
  -d '{
    "skillId": "treegrid",
    "moduleName": "TestModule",
    "caption": "测试模块",
    "fields": ["id", "name", "status"]
  }'

查看 CLI 操作历史

curl http://localhost:8080/api/skill/project/ProjectA/cli-history

总结

Ooder A2UI 的 Skills 架构设计通过以下几个关键点实现了框架的现代化重构,并实现了 CLI 与 UI 的无缝桥接

  1. 注解驱动:使用 @A2uiSkill 注解声明式定义技能元数据,包括 CLI 指令映射
  2. SPI 扩展:通过 Java SPI 机制实现技能的动态加载和热插拔,支持 CLI 命令注册
  3. 统一拦截SkillDrivenInterceptor 替代多个硬编码拦截器,支持 CLI/UI 双端请求
  4. 服务分层:清晰的服务层职责划分(管理、发布、桥接),包含 CLI 脚本生成
  5. 模板方法AbstractA2uiSkill 提供通用实现,包括 CLI 参数解析和脚本生成
  6. 项目桥接SkillProjectBridge 实现技能与项目的松耦合关联,支持 CLI 操作追溯
  7. :star: Bridger 层:贯穿整个架构,实现 CLI ↔ UI 的双向桥接

这套架构不仅解决了传统拦截机制的痛点,更为未来的扩展提供了坚实的基础。通过 Bridger 层的设计,实现了:

  • CLI 命令自动映射为技能调用,生成对应 UI 界面
  • UI 操作可转换为 CLI 脚本,支持审计和复用
  • 一套技能逻辑,双端复用,降低维护成本
  • 操作历史可追溯,支持回滚和审计

通过 SPI 机制,第三方开发者可以轻松开发自定义技能,丰富 A2UI 的组件生态,同时自动获得 CLI 支持


附录 关键术语表

术语定义

术语 英文 定义
A2UI Augmented & Autonomous UI 增强型自主 UI,CLI 的可视化增强层,代表 WEB 前端技术的演进方向
Bridger 层 Bridger Layer Ooder A2UI 中连接 CLI 命令行与自主 UI 体系的核心桥接层,实现双向同步
WEB 拦截层 WEB Interceptor Layer A2UI 架构中承接 CLI/HTTP 请求,驱动技能扩展的核心拦截逻辑层
技能驱动 Skill-Driven 基于 SPI 机制,将 CLI 指令/UI 操作转化为可扩展技能的核心设计思想
Skills Skills 可复用的 UI 组件生成单元,支持 CLI 指令和 UI 操作
SPI Service Provider Interface Java 服务提供者接口,用于技能的动态加载和热插拔
CLI 指令映射 CLI Command Mapping 将 CLI 命令名称映射到技能 ID 的机制
CLI 脚本生成 CLI Script Generation 根据 UI 操作参数生成可执行的 CLI 脚本
CLI 操作历史 CLI Operation History 记录所有 CLI 调用的历史,支持审计和回滚
注解驱动 Annotation-Driven 使用 Java 注解声明式定义技能元数据和行为

架构层次术语

层次 职责 CLI 桥接支持
Web 层 RESTful API 入口 支持 CLI 调用
拦截层 请求路由与分发 Bridger 层核心,CLI/UI 双端统一处理
服务层 业务逻辑处理 CLI 状态管理、脚本生成
SPI 层 技能注册与发现 CLI 命令注册与查询
技能实现层 具体技能实现 CLI 参数解析、脚本生成
注解层 元数据定义 CLI 指令映射声明

CLI 命令格式

# 基本格式
a2ui <skill_id|cli_command> [module_name] [options]

# 示例
a2ui treegrid MyModule --caption "My Tree" --fields "id,name,status"
a2ui tg MyModule --caption "My Tree" --fields "id,name,status"  # 使用别名
a2ui form MyForm --caption "My Form" --fields "name,email,phone"
a2ui chart MyChart --type "line" --title "Sales Data"

# 项目管理
a2ui project add-skill ProjectA treegrid --module MyTree
a2ui project list ProjectA
a2ui project history ProjectA

# 技能管理
a2ui list
a2ui publish treegrid --output ./published
1 个赞