Skip to content

Java 扩展组件 (IScriptExtension) 开发指南

1. 概述

本指南面向需要在 lovrabet-runtime 独立部署环境中,通过 Java 代码向平台脚本引擎暴露自定义能力的开发者。

平台通过定义一个标准的 IScriptExtension 服务提供者接口(SPI),允许您将任何 Java 逻辑(如操作 Redis、调用内部微服务、执行特定算法等)封装成一个可供 JS 脚本调用的“组件”,而无需关心脚本引擎的内部实现。

2. 核心接口

您需要实现的核心接口是 com.lovrabet.runtime.core.sdk.IScriptExtension

Java
public interface IScriptExtension {
    /**
     * 定义您的组件类型名称。
     * 这个名称将作为 JS 脚本中 context.client.extension.execute 的第一个参数,是组件的唯一标识符。
     * @return 组件类型名 (e.g., "redis", "myCustomLogic")
     */
    String getComponentType();
    
    /**
     * 执行组件的具体动作。
     * @param action JS 调用时传入的“动作”名,用于区分同一组件下的不同操作。
     * @param params JS 调用时传入的参数对象,平台会自动将其转换为一个 Map。
     * @return 执行结果。任何可序列化的 Java 对象都将被自动转为 JS 对象。
     */
    Object invoke(String action, Map<String, Object> params);
}

3. 开发步骤

步骤 1: 创建实现类

在您的 Spring Boot 项目中(通常是宿主应用或一个自定义的 starter 包),创建一个新的 Java 类并实现 IScriptExtension 接口。
必须将该类声明为一个 Spring Bean,最简单的方式是使用 @Component 注解。

Java
import org.springframework.stereotype.Component;
import java.util.Map;

@Component // 关键:确保 Spring 能够扫描并注册这个 Bean
public class MyCustomComponent implements IScriptExtension {
    
    @Override
    public String getComponentType() {
        // ... 见步骤 2
        return null; 
    }

    @Override
    public Object invoke(String action, Map<String, Object> params) {
        // ... 见步骤 3
        return null;
    }
}

步骤 2: 定义组件类型

实现 getComponentType() 方法,返回一个全局唯一的字符串,用于在 JS 中标识您的组件。命名应清晰、简洁,并使用驼峰式命名法(如 myCustomLogic)。

Java
@Override
public String getComponentType() {
    return "myCustomLogic"; // JS 中将通过 "myCustomLogic" 调用到这里
}

步骤 3: 实现核心逻辑

invoke 方法中,根据传入的 actionparams 参数,编写您的核心业务逻辑。

Java
@Override
public Object invoke(String action, Map<String, Object> params) {
    // 使用 action 参数来分发不同的子任务
    if ("sayHello".equals(action)) {
        // 从 params Map 中安全地获取参数
        String name = (String) params.getOrDefault("name", "World");
        return "Hello, " + name + "!";
    }
    
    if ("calculate".equals(action)) {
        Number a = (Number) params.get("a");
        Number b = (Number) params.get("b");
        if (a == null || b == null) {
            throw new IllegalArgumentException("参数 'a' 和 'b' 不能为空");
        }
        return a.doubleValue() + b.doubleValue();
    }

    // 如果 action 不被支持,抛出异常
    throw new UnsupportedOperationException("不支持的动作: " + action);
}

JS 侧调用示例:

JavaScript
// 调用 sayHello
const message = context.client.extension.execute("myCustomLogic", "sayHello", { name: "开发者" });
// message -> "Hello, 开发者!"

// 调用 calculate
const sum = context.client.extension.execute("myCustomLogic", "calculate", { a: 10, b: 20 });
// sum -> 30.0

4. 关键原则与最佳实践

4.1 无状态与线程安全

由于 @Component 默认创建的是单例 (Singleton) Bean,您的 IScriptExtension 实现类将被多个脚本执行线程并发访问。因此,实现必须是无状态和线程安全的严禁在类中使用实例变量(成员变量)来存储单个请求的状态。

Java
@Component
public class BadExampleComponent implements IScriptExtension {

    private int count = 0; // 错误!这是一个非线程安全的实例变量

    public Object invoke(String action, Map<String, Object> params) {
        this.count++; // 在并发下会导致数据错乱
        return this.count;
    }
    // ...
}

4.2 依赖注入

您可以自由地使用 @Autowired 来注入项目中的任何其他 Spring Bean,如 Service、Repository、RestTemplateRedisTemplate

Java
@Component
public class UserComponent implements IScriptExtension {

    @Autowired
    private UserService userService; // 正确:注入其他 Spring Bean

    @Override
    public String getComponentType() { return "user"; }

    @Override
    public Object invoke(String action, Map<String, Object> params) {
        if ("findById".equals(action)) {
            Long userId = ((Number) params.get("id")).longValue();
            return userService.findById(userId); // 调用已有业务逻辑
        }
        // ...
    }
}

4.3 异常处理

您在 invoke 方法中抛出的任何 Java 异常(无论是 RuntimeException 还是受检异常),都会被平台的核心执行引擎捕获。异常的 message 会被提取出来,并作为错误信息在 JS 侧的 catch 块中抛出。

我们建议您抛出带有清晰业务含义的异常,例如 IllegalArgumentException 或自定义的业务异常。

4.4 关于性能

execute 方法会被一个隔离的、有超时限制的线程池调用。如果您的代码执行时间过长(默认超过3秒),平台会强制中断并向 JS 抛出超时异常。因此,请确保您的实现是高效的,避免长时间的阻塞操作。

5. 部署

您无需进行任何特殊的部署配置。只要您将实现了 IScriptExtension 接口并带有 @Component 注解的类打包到您最终运行的 Spring Boot 应用的 classpath 中(例如,作为一个普通的业务类或包含在某个依赖 JAR 包里),平台的 IScriptExtension 扫描机制就会在启动时自动发现并注册它。

基于飞书知识库同步生成,内容以飞书源文档为准