首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >Ooder A2UI 核心架构深度解析:WEB 拦截层的设计与实现

Ooder A2UI 核心架构深度解析:WEB 拦截层的设计与实现

原创
作者头像
OneCode
发布2026-04-30 09:49:06
发布2026-04-30 09:49:06
360
举报
文章被收录于专栏:ooderAgentooderAgent

从 CLI 到增强型 UI:A2UI 构建自主 UI 体系与 CLI 的 Bridger 层核心逻辑

0. A2UI 的诞生背景与意义

0.1 从 CLI 到 A2UI:人机交互的演进

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

  • 学习曲线陡峭:需要记忆大量命令和参数
  • 可视化能力弱:难以直观展示复杂的数据结构和关系
  • 操作效率受限:对于非技术用户,CLI 几乎是不可逾越的鸿沟

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

0.2 为什么在 AICoding 时代仍需要 A2UI?⭐ 核心技术洞察

在 AICoding(AI 辅助编程)技术日趋成熟的今天,特别是在前端领域,AI 已经能够根据需求自动生成高质量的全栈代码。这引发了一个核心问题:为什么还需要引入 A2UI 这样的技术体系?

0.2.1 AICoding 的局限性

虽然 AICoding 在代码生成方面表现出色,但在实际企业级应用场景中,它面临着以下挑战:

1. 生成成本高昂

  • 每次生成都需要调用 AI 模型,消耗大量计算资源
  • 对于高频、重复的 UI 需求,成本不可控
  • 生成代码的质量参差不齐,需要人工审核和调整

2. 代码维护困难

  • AI 生成的代码风格不统一,难以维护
  • 业务逻辑与 UI 代码耦合度高,修改成本大
  • 缺乏统一的架构约束,容易产生技术债

3. 无法实时响应上下文变化

  • 用户上下文(User Context)、模块上下文(Module Context)、数据模块上下文(Data Module Context)的变化需要重新生成
  • 无法根据运行时环境动态调整 UI 展现形式
  • 缺乏对实时数据的响应能力
0.2.2 A2UI 的核心技术难点

A2UI 的设计面临着独特的技术挑战,这些挑战是 AICoding 难以直接解决的:

核心矛盾:CLI 数据的固定性 vs 前端展现的动态性

图 0-3:CLI 数据固定性与前端展现动态性的矛盾

0.2.3 动态拦截机制:A2UI 的技术核心

为了解决上述矛盾,A2UI 引入了动态拦截机制,这是本文的技术重点:

为什么选择动态拦截而非 AICoding?

对比维度

AICoding 方案

A2UI 动态拦截机制

响应速度

秒级(需调用 AI 模型)

毫秒级(本地计算)

成本控制

每次生成都消耗资源

一次性开发,无限次使用

上下文感知

需要重新生成

实时动态调整

代码质量

参差不齐,需审核

统一标准,可控可测

维护成本

高(每次修改需重新生成)

低(修改配置即可)

扩展性

依赖 AI 模型能力

基于 SPI 机制,无限扩展

动态拦截机制的核心价值:

1. 实时上下文感知

代码语言:javascript
复制
// 根据用户上下文动态选择展现方式
if (userContext.hasPermission("admin")) {
    skill = new DetailedViewSkill();  // 管理员看详细视图
} else {
    skill = new SimpleViewSkill();    // 普通用户看简洁视图
}

2. 智能 UI 组件选择

代码语言:javascript
复制
// 根据数据量自动选择展现形式
if (dataCount > 1000) {
    componentType = ComponentType.PAGINATED_GRID;  // 大数据量用分页表格
} else if (hasHierarchy(data)) {
    componentType = ComponentType.TREE_GRID;       // 层级数据用树形表格
} else {
    componentType = ComponentType.SIMPLE_GRID;     // 简单数据用普通表格
}

3. 动态字段配置

代码语言:javascript
复制
// 根据模块上下文动态配置表单字段
List<Field> fields = new ArrayList<>();
if (moduleContext.isCreateMode()) {
    fields.addAll(getRequiredFields());  // 创建模式显示必填字段
} else if (moduleContext.isViewMode()) {
    fields.addAll(getAllFields());       // 查看模式显示所有字段
}

4. 运行时决策引擎

图 0-4:A2UI 动态决策引擎架构

0.2.4 A2UI 与 AICoding 的协同关系

A2UI 并不排斥 AICoding,而是与之形成互补:

图 0-5:A2UI 与 AICoding 的协同关系

总结:

A2UI 的引入不是为了替代 AICoding,而是为了解决 AICoding 在运行时动态性方面的不足。通过动态拦截机制,A2UI 实现了:

  1. 实时响应上下文变化:无需重新生成代码,即可根据用户、模块、数据上下文动态调整 UI
  2. 成本可控:一次性开发技能,无限次复用,避免频繁调用 AI 模型的高昂成本
  3. 质量可控:统一的架构约束和代码规范,确保生成 UI 的一致性和可维护性
  4. 扩展性强:基于 SPI 机制,可以灵活扩展新的 UI 组件和交互方式

这正是本文要深入探讨的技术重点:如何设计和实现一个高效、灵活、可扩展的动态拦截机制

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

Ooder A2UI 的设计理念可以概括为以下三个核心价值:

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

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

  • 零外部依赖:核心组件完全自主研发
  • 高度可定制:每个组件都可以根据业务需求深度定制
  • 版本控制友好:组件与业务逻辑解耦,升级无负担
2. CLI 与 UI 的无缝桥接

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

图 0-1:CLI ↔ A2UI Bridge 层架构 - 双向同步的智能桥接机制

这种设计使得:

  • 开发者可以继续使用熟悉的 CLI 工作流,同时享受 UI 的可视化优势
  • 运维人员可以通过 UI 界面操作,系统自动生成对应的 CLI 脚本供审计和复用
  • 产品经理业务人员可以通过直观的 UI 界面参与系统配置,降低沟通成本
3. Web 技术的前沿探索

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

  • 组件化架构:借鉴 React、Vue 等现代框架的设计理念
  • 声明式 UI:通过注解和配置定义 UI,而非命令式编程
  • 响应式设计:一套代码适配多种设备和屏幕尺寸
  • 渐进式增强:从基础功能到高级特性,按需加载

0.4 Web 拦截层:A2UI 的核心枢纽

本文聚焦于 Ooder A2UI 架构中最关键的一环 —— Web 拦截层的设计与实现。作为连接用户请求与后端服务的桥梁,Web 拦截层承担着以下核心职责:

  1. 请求路由与分发:智能识别请求类型,分发到对应的处理器
  2. 技能驱动:基于 Skills 架构,实现组件的动态加载和生成
  3. SPI 扩展:通过 Java SPI 机制,支持第三方技能的无缝集成
  4. 性能优化:缓存、懒加载、并发控制等多重优化策略

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

  • 如何从传统的硬编码拦截器演进到 Skills + SPI 架构
  • 注解驱动开发如何简化组件定义和注册
  • SPI 机制如何实现技能的热插拔和动态加载
  • 服务层如何协作完成复杂的业务逻辑
  • 如何设计和实现 RESTful API 供前端调用

0.5 适用读者

本文适合以下读者:

  • 架构师:了解现代 Web 应用的架构设计思路
  • 后端开发者:学习 Java 注解、SPI、拦截器等高级特性
  • 前端开发者:理解后端 API 设计,更好地进行前后端协作
  • 技术管理者:评估技术方案的可行性和扩展性

1. 架构演进背景

1.1 传统拦截机制的痛点

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

图 1-1:传统拦截架构 - 多个独立拦截器导致代码重复和维护困难

核心问题

  • 耦合度高:拦截逻辑与具体的组件类型硬编码绑定
  • 扩展困难:新增组件类型需要修改拦截器核心代码
  • 维护成本高:多个拦截器类职责不清晰,代码重复
  • 缺乏统一管理:组件注册和发现机制分散

1.2 Skills + SPI 架构的优势

为了解决上述问题,我们引入了 Skills + SPI 的架构设计:

图 1-2:Skills + SPI 拦截架构 - 统一入口和可扩展的技能注册机制

核心优势

  • 解耦:拦截逻辑与具体组件实现完全分离
  • 可扩展:通过 SPI 机制动态加载新技能
  • 统一管理:所有技能通过注册中心集中管理
  • 注解驱动:使用注解声明式定义技能元数据

2. 核心架构设计

2.1 整体架构图

图 2-1:Ooder A2UI 六层架构设计 - 从 Web 层到注解层的完整体系

2.2 数据流图

图 2-2:请求处理数据流 - 从 HTTP 请求到 JSON 响应的完整链路

2.3 技能生命周期管理

图 2-3:Skill 生命周期 - 从定义、注册、发现到调用、发布、桥接、管理的完整流程


3. 注解层设计

3.1 @A2uiSkill 注解

注解是整个技能系统的元数据基础,用于声明式地定义技能的核心属性。

代码语言:javascript
复制
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface A2uiSkill {
    String id();                          // 技能唯一标识
    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;           // 优先级
}

设计要点

  • id 是必填项:确保每个技能有唯一标识
  • 默认值策略:减少配置负担,常用字段提供合理默认值
  • 类型安全:使用枚举类型(ModuleViewType、ComponentType)避免字符串错误
  • 可扩展性:capabilities 数组支持声明技能的特殊能力

3.2 A2uiSkillBehavior 接口

行为接口定义了技能必须实现的核心方法:

代码语言:javascript
复制
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
}

方法职责

  • buildGenJson(): 核心方法,根据参数生成模块的 JSON 表示
  • buildFromNaturalLanguage(): 支持自然语言输入,智能解析意图
  • toCardJson(): 生成用于前端展示的卡片元数据

4. SPI 扩展机制

4.1 A2uiSkillRegistry 接口

SPI 接口定义了技能注册表的契约:

代码语言:javascript
复制
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);// 反向查找
}

4.2 A2uiSkillRegistrySPI 实现

SPI 加载器使用 Java 原生的 ServiceLoader 机制:

代码语言:javascript
复制
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<>();

    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));
    }
}

设计亮点

  • 多重缓存:classNameCache、classCache、componentTypeToSkillIdCache 三层缓存
  • 优先级排序:支持多个注册表,按优先级加载
  • 线程安全:使用 volatile 和 ConcurrentHashMap 保证并发安全
  • 热加载支持:提供 reload() 和 clearCache() 方法

4.3 SPI 配置文件

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

代码语言:javascript
复制
# 文件路径:src/main/resources/META-INF/services/net.ooder.annotation.spi.A2uiSkillRegistry
net.ooder.a2ui.nlp.skill.A2uiSkillRegistryImpl

5. 技能实现层

5.1 AbstractA2uiSkill 抽象基类

抽象基类使用模板方法模式提供通用实现:

代码语言:javascript
复制
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();
    }

    // 模板方法:子类重写以提供具体组件的生成逻辑
    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 实现示例

具体技能实现展示如何扩展抽象基类:

代码语言:javascript
复制
@Component
@A2uiSkill(
    id = "treegrid",
    name = "树形表格",
    description = "用于展示树形结构数据的表格组件",
    category = "data-display",
    moduleViewType = ModuleViewType.GRIDCONFIG,
    componentType = ComponentType.TREEGRID,
    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", "name");
        properties.put("showTreeLines", true);
        properties.put("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(), "[]");
    }
}

5.3 内置技能列表

Skill ID

Component

Category

treegrid

TREEGRID

data-display

form

FORMLAYOUT

data-input

tree

TREEVIEW

data-display

gallery

GALLERY

data-display

chart

ECHARTS

data-visualization

navtree

TREEBAR

navigation

navigallery

GALLERY

navigation

navtabs

TABS

navigation

navmenubar

BAR

navigation

tabscontainer

TABS

layout

layout

LAYOUT

layout


6. 拦截器重构

6.1 SkillDrivenInterceptor 核心逻辑

统一拦截器替代了原有的多个硬编码拦截器:

代码语言:javascript
复制
@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);

        // 1. 无后缀请求:数据请求处理
        if (suffix == SuffixType.NONE) {
            return handleDataRequest(handler, request, response);
        }

        // 2. 剥离后缀获取类名
        String className = SuffixType.stripSuffix(url);

        // 3. 根据后缀类型分发到不同处理器
        switch (suffix) {
            case JSX:  return handleJsx(className, request, response);
            case CLS:  return handleCls(className, request, response);
            case DYN:  return handleDyn(handler, request, response);
            case VIEW: 
            case JSA:
            case JSAA: return handleView(className, request, response);
            default:   return true;
        }
    }

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

        // 2. 解析组件类型
        String componentType = resolveComponentType(className);

        // 3. 查找对应技能
        A2uiSkillBehavior skill = findSkill(componentType);

        if (skill != null) {
            try {
                // 4. 使用技能构建模块
                UIModule uiModule = buildModuleFromSkill(skill, className, request);
                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);
                    sendJSON(response, json.toString());
                    return false;
                }
            } catch (Exception e) {
                log.error("Skill-driven module build failed", e);
            }
        }

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

    // 技能查找:优先从缓存,其次从 SPI
    private A2uiSkillBehavior findSkill(String componentType) {
        if (componentType == null) return null;

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

        // 2. 从 SPI 查找
        A2uiSkillRegistrySPI skillSPI = A2uiSkillRegistrySPI.getInstance();
        String skillId = skillSPI.findSkillIdByComponentType(componentType);
        if (skillId != null) {
            try {
                Class<?> skillClass = skillSPI.getSkillClass(skillId);
                if (skillClass != null && 
                    A2uiSkillBehavior.class.isAssignableFrom(skillClass)) {
                    return (A2uiSkillBehavior) skillClass
                        .getDeclaredConstructor().newInstance();
                }
            } catch (Exception e) {
                log.error("Skill SPI lookup failed", e);
            }
        }
        return null;
    }
}

6.2 拦截器对比

图 6-1:拦截器架构对比 - 传统多拦截器 vs 统一技能驱动拦截器


7. 服务层架构

7.1 SkillManagementService

技能管理的核心服务,采用单例模式:

代码语言:javascript
复制
public class SkillManagementService {
    private static volatile SkillManagementService instance;
    private final Map<String, SkillModuleConfig> skillModuleCache = new ConcurrentHashMap<>();

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

    // 核心方法:通过技能创建模块
    public UIModule createModuleFromSkill(String skillId, String moduleName,
                                          String projectName, String caption,
                                          List<String> fields, 
                                          Map<String, Object> options) {
        // 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");
        }
        return uiModule;
    }

    // 获取所有技能卡片
    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) {
                cards.add(skill.toCardJson());
            }
        }
        result.put("skills", cards);
        result.put("total", cards.size());
        return result;
    }
}

7.2 SkillPublishService

技能发布服务,负责生成可发布的技能包:

代码语言:javascript
复制
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);

        // 4. 返回发布结果
        SkillPublishResult result = SkillPublishResult.success(skillId);
        result.setJavaSource(javaSource);
        result.setPublishedAt(new Date().toInstant().toString());
        return result;
    }
}

7.3 服务层协作关系

图 7-1:服务层协作关系 - SkillController 调用三个核心服务


8. 项目整合桥接

8.1 SkillProjectBridge

桥接服务负责技能与项目之间的关联管理:

代码语言:javascript
复制
public class SkillProjectBridge {

    // 添加技能到项目
    public SkillProjectBridgeResult addSkillToProject(
            String projectName, String skillId,
            String moduleName, String caption,
            List<String> fields) {

        // 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<>());

        // 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());
        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());
                }
                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;
    }
}

8.2 项目与技能的关系

图 8-1:项目 - 技能关系 - 一对多的技能依赖管理


9. RESTful API 设计

9.1 SkillController API 列表

代码语言:javascript
复制
@RestController
@RequestMapping("/api/skill")
public class SkillController {

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

    // 2. 通过技能构建模块
    @PostMapping("/build")
    public ResponseEntity<Map<String, Object>> buildFromSkill(
            @RequestBody JSONObject request) {
        // POST /api/skill/build
        // Body: { "skillId": "treegrid", "moduleName": "MyModule", 
        //         "caption": "测试模块", "fields": ["id", "name"], 
        //         "options": {...} }
    }

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

    // 4. 添加技能到项目
    @PostMapping("/project/add-skill")
    public ResponseEntity<Map<String, Object>> addSkillToProject(
            @RequestBody JSONObject request) {
        // POST /api/skill/project/add-skill
    }

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

    // 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
    }

    // 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
    }

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

9.2 API 响应示例

获取技能列表

代码语言:javascript
复制
{
  "skills": [
    {
      "skillId": "treegrid",
      "name": "树形表格",
      "description": "用于展示树形结构数据的表格组件",
      "category": "data-display",
      "componentType": "TREEGRID",
      "moduleViewType": "GRIDCONFIG",
      "version": "1.0.0",
      "keywords": ["树形表格", "tree", "grid", "层级数据"]
    },
    {
      "skillId": "form",
      "name": "表单",
      "description": "数据录入表单组件",
      "category": "data-input",
      "componentType": "FORMLAYOUT",
      "moduleViewType": "FORMCONFIG",
      "version": "1.0.0",
      "keywords": ["表单", "form", "数据录入"]
    }
  ],
  "total": 11
}

添加技能到项目

代码语言:javascript
复制
{
  "success": true,
  "message": "Skill added successfully",
  "skillId": "treegrid",
  "moduleName": "MyTreeGrid",
  "componentType": "TREEGRID",
  "projectName": "ProjectA"
}

10. 最佳实践

10.1 开发自定义技能

步骤 1:创建技能类

代码语言:javascript
复制
@Component
@A2uiSkill(
    id = "mycustom",
    name = "我的自定义组件",
    description = "用于特定业务场景的自定义组件",
    category = "custom",
    componentType = ComponentType.CUSTOM,
    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(), "[]");
    }
}

步骤 2:注册到 SPI

代码语言:javascript
复制
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 配置文件

代码语言:javascript
复制
# src/main/resources/META-INF/services/net.ooder.annotation.spi.A2uiSkillRegistry
com.example.MyCustomSkillRegistry

10.2 技能设计原则

  1. 单一职责:每个技能只负责一种组件类型的生成
  2. 无状态设计:技能类应该是无状态的,支持并发调用
  3. 模板复用:使用 FreeMarker 模板提高代码复用性
  4. 错误处理:在 buildGenJson 中做好参数校验和异常处理
  5. 关键词优化:提供丰富的关键词以支持 NLP 意图识别

10.3 性能优化建议

图 10-1:性能优化四大策略 - 缓存、懒加载、并发控制、模板预热

10.4 调试技巧

启用详细日志

代码语言:javascript
复制
# application.properties
logging.level.net.ooder.engine.core.skill=DEBUG
logging.level.net.ooder.annotation.spi=DEBUG
logging.level.net.ooder.web.interceptor=DEBUG

查看已注册技能

代码语言:javascript
复制
curl http://localhost:8080/api/skill/health

测试技能构建

代码语言:javascript
复制
curl -X POST http://localhost:8080/api/skill/build \
  -H "Content-Type: application/json" \
  -d '{
    "skillId": "treegrid",
    "moduleName": "TestModule",
    "caption": "测试模块",
    "fields": ["id", "name", "status"]
  }'

总结

Ooder A2UI 的 Skills 架构设计通过以下几个关键点实现了框架的现代化重构:

  1. 注解驱动:使用 @A2uiSkill 注解声明式定义技能元数据,简化配置
  2. SPI 扩展:通过 Java SPI 机制实现技能的动态加载和热插拔
  3. 统一拦截SkillDrivenInterceptor 替代多个硬编码拦截器,降低耦合
  4. 服务分层:清晰的服务层职责划分(管理、发布、桥接)
  5. 模板方法AbstractA2uiSkill 提供通用实现,子类专注业务逻辑
  6. 项目桥接SkillProjectBridge 实现技能与项目的松耦合关联

这套架构不仅解决了传统拦截机制的痛点,更为未来的扩展提供了坚实的基础。通过 SPI 机制,第三方开发者可以轻松开发自定义技能,丰富 A2UI 的组件生态。

© 2026 Ooder 架构团队 | 保留所有权利

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 0. A2UI 的诞生背景与意义
    • 0.1 从 CLI 到 A2UI:人机交互的演进
    • 0.2 为什么在 AICoding 时代仍需要 A2UI?⭐ 核心技术洞察
      • 0.2.1 AICoding 的局限性
      • 0.2.2 A2UI 的核心技术难点
      • 0.2.3 动态拦截机制:A2UI 的技术核心
      • 0.2.4 A2UI 与 AICoding 的协同关系
    • 0.3 A2UI 的核心意义:WEB 前端技术的演进方向
      • 1. 完全自主的 UI 基础设施
      • 2. CLI 与 UI 的无缝桥接
      • 3. Web 技术的前沿探索
    • 0.4 Web 拦截层:A2UI 的核心枢纽
    • 0.5 适用读者
  • 1. 架构演进背景
    • 1.1 传统拦截机制的痛点
    • 1.2 Skills + SPI 架构的优势
  • 2. 核心架构设计
    • 2.1 整体架构图
    • 图 2-1:Ooder A2UI 六层架构设计 - 从 Web 层到注解层的完整体系
    • 2.2 数据流图
    • 图 2-2:请求处理数据流 - 从 HTTP 请求到 JSON 响应的完整链路
    • 2.3 技能生命周期管理
    • 图 2-3:Skill 生命周期 - 从定义、注册、发现到调用、发布、桥接、管理的完整流程
  • 3. 注解层设计
    • 3.1 @A2uiSkill 注解
    • 3.2 A2uiSkillBehavior 接口
  • 4. SPI 扩展机制
    • 4.1 A2uiSkillRegistry 接口
    • 4.2 A2uiSkillRegistrySPI 实现
    • 4.3 SPI 配置文件
  • 5. 技能实现层
    • 5.1 AbstractA2uiSkill 抽象基类
    • 5.2 TreeGridSkill 实现示例
    • 5.3 内置技能列表
  • 6. 拦截器重构
    • 6.1 SkillDrivenInterceptor 核心逻辑
    • 6.2 拦截器对比
  • 7. 服务层架构
    • 7.1 SkillManagementService
    • 7.2 SkillPublishService
    • 7.3 服务层协作关系
  • 8. 项目整合桥接
    • 8.1 SkillProjectBridge
    • 8.2 项目与技能的关系
  • 9. RESTful API 设计
    • 9.1 SkillController API 列表
    • 9.2 API 响应示例
  • 10. 最佳实践
    • 10.1 开发自定义技能
    • 10.2 技能设计原则
    • 10.3 性能优化建议
    • 10.4 调试技巧
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档