# 扩展函数节点

开启【函数】组件后,自定义函数将分组显示在函数详情页左侧,分组下为函数的多个操作。上图中为内置提供的函数,若想在已有分组中增加函数操作,自定义时所属分组group一致即可。具体定义数据结果可见/schema请求,示例:

{
    "label": "函数调用",                            // 函数标题
    "name": "GeneralOperationsImpl#linkFunction",  // 函数名,唯一
    "operation": "custom:linkFunction",            // 函数表达式
    "type": "custom",                              // 函数类型,custom自定义
    "orderNo": 0,                                  // 分组内排序
    "parameters": {  // 参数组
        "function": {
            "label": "函数",            // 参数标题
            "name": "function",        // 参数名,唯一,作为参数组的key
            "type": "string",          // 参数数据类型
            "defaultValue": null,      // 参数默认值
            "description": "",         // 参数描述
            "required": true,          // 参数是否必填
            "inputType": "SelectOrch"  // 参数输入控件类型,一般可不填,自动根据参数数据类型判断
        }
    },
    "output": {              // 输出结果
        "label": "调用结果",  // 输出结果标题
        "name": "result",    // 输出结果名
        "type": "object",    // 输出结果数据类型
        "description": ""    // 输出结果描述
    },
    "group": ""  // 所属分组,为空时归属到默认分组[其他]
}

# 配置式实现

配置方式实现,主要通过定义中的operation属性配置SpEL(Spring表达式语言),通过执行表达式的方式,可自定义大部分场景的函数操作。

在api工程/resources/schema.yml文件中添加函数操作定义,会同时在/schema接口中返回给IDE使用。下列提供了执行静态方法、调用bean方法等常见示例。

# 调用静态方法

- label: 获取两数中最大值
  name: Math.min
  type: custom
  operation: T(java.lang.Math).max(intVal1, intVal2)
  orderNo: 1
  parameters: 
    - label: 数值1
      name: intVal1
      type: integer
      defaultValue: 0
      description: 参数描述
      requred: true
    - label: 数值2
      name: intVal2
      type: integer
      defaultValue: 0
      description: 参数描述
      requred: true
  output:
    label: 最大值
    name: maximum
    type: integer
    className: java.lang.Integer
    description: 输出结果描述

# 调用bean方法

- label: 调用名为bean1的bean
  name: Bean.bean1.method1
  type: custom
  operation: "@bean1.method1(str1)"
  orderNo: 1
  parameters: 
    - label: 字符串1
      name: str1
      type: string
      defaultValue: "123"
      description: 参数描述
      requred: true
  output:
    label: 最大值
    name: maximum
    type: integer
    className: java.lang.Integer
    description: 输出结果描述

operation表达式中可调用的bean需要注册并添加@ExpressionModule注解指定name:

import openfuse.serverlessworkflow.api.expression.ExpressionModule;

@Bean
@ExpressionModule("bean1")
Bean1 bean1() {
    return new Bean1();
}

更多表达式使用可见fly-framework (opens new window)以及Spring SpEL官方文档。

# 进阶实现

在特殊场景下,若想要为函数操作设计更复杂的逻辑,可用Java进阶实现。本小节将介绍如何利用函数定义注解等实现函数操作,在大部分场景下,我们只需要关注函数操作类的处理逻辑即可。

# 实现函数操作类

  • 函数操作类
import openfuse.serverlessworkflow.web.schema.anno.FunctionModule;
import openfuse.serverlessworkflow.web.schema.anno.FunctionOutput;
import openfuse.serverlessworkflow.web.schema.anno.FunctionParameter;

/**
 * 函数操作类,每个方法作为一个函数操作。
 */
public class CustomOperations {
    
    // 注意'c'不能重复,如现已有custom:linkFunction,则这里为custom时可能影响原有函数表达式。
    public static final String PROVIDER_NAME = "c";
    
    protected static final String OPERATION1 = PROVIDER_NAME + ":operation1";
    
    /**
     * 函数操作,函数定义注解说明见下文。
     */
    @FunctionModule(label = "自定义操作1", operation = OPERATION1)
    @FunctionOutput(name = "result", label = "结果", type = DataType.NUMBER)
    public int operation1(
            @FunctionParameter(label = "字符串参数", required = false, type = DataType.STRING)
            String str1) {
        // todo: 逻辑实现
    }
    
}

函数定义注解说明:

@FunctionModule - 函数操作定义

属性 说明 默认值
name 函数操作名,唯一 类名#方法名
label 函数操作标题 -
type 函数操作类型 CUSTOM
operation 必填,函数操作映射的表达式 -
orderNo 排序 0
description 描述 -
group 所属分组 -

@FunctionParameter - 函数操作参数定义

属性 说明 默认值
label 参数标题 -
type 参数数据类型 OBJECT
defaultValue 默认值 -
description 描述 -
required 是否必填 true

@FunctionOutput - 函数输出结果定义

属性 说明 默认值
name 输出结果名 -
label 输出结果标题 -
type 输出结果类型 OBJECT
description 描述 -
  • 函数实现类

实现openfuse.serverlessworkflow.api.function.Function接口:

import openfuse.serverlessworkflow.api.function.Function;

/**
 * 自定义函数,执行调用函数操作类方法。
 */
public class CustomFunction implements Function {
    
    private final String name;
    private final CustomOperations operations;
    private final Method operation;
    
    public CustomFunction(String name, CustomOperations operations, Method operation) {
        this.name = name;
        this.operations = operations;
        this.operation = operation;
    }
    
    @Override
    public String name() {
        return name;
    }
    
    @Override
    public Object apply(ExecutionContext context, Map<String, Object> arguments) {
        Object result;
        try {
            Object[] args = Arrays.stream(operation.getParameters())
                    .map(param -> arguments.get(param.getName()))
                    .toArray();

            result = operation.invoke(operations, args);
        } catch (IllegalAccessException e) {
            throw new WorkflowException(e.getMessage(), e);
        } catch (InvocationTargetException e) {
            throw new WorkflowBusinessException("INVOCATION_ERROR", e.getMessage(), e);
        }
        return result;
    }
    
    @Override
    public Object getOperation() {
        return operation;
    }
    
    @Override
    public Type getReturnType() {
        return operation.getGenericReturnType();
    }
    
}
  • 暴露函数
import openfuse.serverlessworkflow.api.function.FunctionProvider;

/**
 * 用于注册xx中定义的函数方法。
 */
public class CustomFunctionProvider implements FunctionProvider {
    
    protected CustomOperations operations;
    protected Map<String, Method> functions;
    
    public CustomFunctionProvider() {
        this.operations = new CustomOperations();
        this.functions = Collections.unmodifiableMap(Arrays.stream(CustomOperations.class.getMethods())
                .filter(method -> method.isAnnotationPresent(FunctionModule.class))
                .collect(Collectors.toMap(Method::getName, java.util.function.Function.identity())););
    }
	
    @Override
    public Function createFunction(String name, FunctionMetadata metadata) {
        Method operation = functions.get(metadata.getExpression());
        if (operation == null) {
            throw new IllegalArgumentException(CustomOperations.class.getSimpleName() + " function '"
                    + metadata.getExpression() + "' not found.");
        }
		return new CustomFunction(name, operations, operation);
    }
    
}

在Configuration配置类中注册为bean,并用@Qualifier注解指定限定符(见函数操作类):

@Configuration(proxyBeanMethods = false)
public class CustomConfiguration {
    
    @Bean
    @Qualifier(CustomOperations.PROVIDER_NAME)
    FunctionProvider customFunctionProvider() {
        return new CustomFunctionProvider();
    }
    
}

# 注册操作类定义

为将新增的函数操作返回定义给IDE界面使用,上文中函数操作类已使用@FunctionModule@FunctionOutput@FunctionParameter等注解对函数操作进行定义,可以在Configuration配置类中添加@FunctionScan注解,对指定package下的类进行扫描并生成定义添加至/schema

import openfuse.serverlessworkflow.web.schema.anno.FunctionScan;

@Configuration(proxyBeanMethods = false)
// 可指定其他package(默认为当前package),为减少不必要的扫描,建议缩小范围。
@FunctionScan(basePackages = {"xxx.xxx"})
public class CustomConfiguration {
}

# 定义属性覆盖

对于一些定义注解中缺少而IDE需要的属性如inputType等,可以在/resources/schema.yml中进行添加或覆盖,该文件属性优先级高于注解属性:

- name: "CustomOperations#operation1" # 根据name查找对应的函数操作,必填
  parameter:
    param1:
      inputType: xxx
顶部