Skip to main content

One post tagged with "manipulate"

View All Tags

· 11 min read
百岁

本文面向刚加入 TIS 团队的开发工程师。读完本文,你将理解"伴生操作类"是什么、为什么需要它、以及如何从零开发一个完整的 Manipulate 插件。


一、背景:什么是"伴生操作类"?

TIS 的核心实体(如数据管道 DataXProcessor、本体域 OntologyDomain)本身只负责描述配置,不直接处理用户在界面上触发的"操作"——比如克隆、开启某个功能、触发推断任务等。

这些"操作"由伴生操作类(Manipulate)承担。每个核心实体可以挂载若干个 Manipulate 插件,每个插件对应一种用户可触发的操作。

核心实体(如 OntologyDomain)
└── 伴生操作类 1:EnableChatBI(开启智能问数)
└── 伴生操作类 2:InferOntologyFromLLM(LLM 推断本体关系)
└── 伴生操作类 N:...

用户在界面上点击某个操作按钮时,TIS 框架会找到对应的 Manipulate 实例,调用其 manipuldateProcess 方法。


二、类体系总览

BasicManipuldateProcessor<T>          ← 基类,封装持久化通用逻辑(本文重点)
├── DefaultDataXProcessorManipulate ← DataX 管道的伴生操作基类
│ ├── BatchJobCrontab ← 定时任务配置
│ ├── AddMonitorForEvents ← 告警监控配置
│ └── ExportTISPipelineToDolphinscheduler ← 导出到调度系统
└── OntologyDomainManipulate ← 本体域的伴生操作基类
├── EnableChatBI ← 开启智能问数(需持久化)
└── InferOntologyFromLLM ← LLM 推断本体关系(不持久化)

关键包路径:

路径
BasicManipuldateProcessortis-plugin/.../plugin/manipulate/
DefaultDataXProcessorManipulatetis-plugin/.../datax/
OntologyDomainManipulatetis-plugin/.../plugin/ontology/
EnableChatBI / InferOntologyFromLLMtis-ontology-plugin/.../plugin/ontology/

三、核心接口与基类详解

3.1 IPluginStore.ManipuldateProcessor

这是所有 Manipulate 类必须实现的顶层接口,只有一个方法:

interface ManipuldateProcessor {
void manipuldateProcess(IPluginContext pluginContext,
UploadPluginMeta pluginMeta,
Optional<Context> context);
}

你不需要直接实现这个接口。继承 BasicManipuldateProcessor 后,框架已经帮你实现了。


3.2 BasicManipuldateProcessor<T>

这是所有 Manipulate 插件的基类,封装了完整的持久化流程:

用户触发操作

manipuldateProcess() ← final,不可覆盖

ManipuldateUtils.instance() ← 解析请求上下文(是新增/更新/删除?)

loadPluginStore() ← 抽象方法,子类实现,返回对应的 IPluginStore

[删除] deleteFromStore()
[新增/更新] 判重 → replaceInStore()

afterManipuldateProcess() ← 钩子,子类按需覆盖(如触发同步任务)

你需要实现的方法只有两个:

loadPluginStore()(必须实现)

告诉基类"把数据存到哪里"。从 itemsProcessor.getPluginMeta() 中提取上下文(如 domain 名、pipeline 名),返回对应的 IPluginStore

@Override
protected IPluginStore<MyManipulate> loadPluginStore(
IPluginContext pluginContext,
ManipulateItemsProcessor itemsProcessor) {
// 从 pluginMeta 中取出业务上下文
String domain = itemsProcessor.getPluginMeta()
.getExtraParam(OntologyDomain.NAME_ONTOLOGY_DOMAIN);
KeyedPluginStore.Key<MyManipulate> key =
OntologyDomain.getStoreKey(domain, MyManipulate.class);
return TIS.getPluginStore(key);
}

afterManipuldateProcess()(按需覆盖)

持久化完成后的后续操作。默认是空实现,如果你的操作需要在保存后触发额外逻辑(如同步到 Neo4j、发送通知),在这里写。

@Override
protected void afterManipuldateProcess(
IPluginContext pluginContext,
Optional<Context> context,
ManipulateItemsProcessor itemsProcessor) {
if (itemsProcessor.isDeleteProcess()) {
return; // 删除时通常不需要后续操作
}
// 触发你的业务逻辑
MyService.getInstance().doSomething();
}

3.3 BasicDesc(内部 Descriptor 基类)

每个 Manipulate 插件都需要一个内部静态类 DftDesc(或其他名字),继承对应的 BasicDesc,并加上 @TISExtension 注解。

最关键的方法:isManipulateStorable()

@TISExtension
public static final class DftDesc extends OntologyDomainManipulate.BasicDesc {
@Override
public boolean isManipulateStorable() {
return true; // true = 需要持久化;false = 只执行逻辑,不存储
}
}
isManipulateStorable() 返回值行为
true基类自动完成判重、持久化(replaceInStore/deleteFromStore),然后调用 afterManipuldateProcess
false跳过持久化,直接调用 afterManipuldateProcess

四、开发流程:手把手写一个 Manipulate 插件

以"为本体域开启某个新功能"为例,完整步骤如下。

Step 1:确定你的操作属于哪个核心实体

  • 操作针对 DataX 数据管道 → 继承 DefaultDataXProcessorManipulate
  • 操作针对 本体域 → 继承 OntologyDomainManipulate
  • 操作针对其他实体 → 继承对应的 BasicManipuldateProcessor 子类

Step 2:创建操作类

package com.qlangtech.tis.plugin.ontology;

import com.qlangtech.tis.extension.TISExtension;
import com.qlangtech.tis.plugin.annotation.FormField;
import com.qlangtech.tis.plugin.annotation.FormFieldType;
import com.qlangtech.tis.plugin.annotation.Validator;
import com.qlangtech.tis.plugin.ds.manipulate.ManipulateItemsProcessor;
import com.qlangtech.tis.runtime.module.misc.IControlMsgHandler;
import com.qlangtech.tis.util.IPluginContext;

import java.util.Optional;
import com.alibaba.citrus.turbine.Context;

public class MyNewFeature extends OntologyDomainManipulate {

// 在界面上展示的表单字段
@FormField(ordinal = 1, type = FormFieldType.INPUTTEXT,
validate = {Validator.require})
public String someConfig;

@Override
protected void afterManipuldateProcess(
IPluginContext pluginContext,
Optional<Context> context,
ManipulateItemsProcessor itemsProcessor) {
if (itemsProcessor.isDeleteProcess()) {
return;
}
// 持久化完成后,触发你的业务逻辑
String domain = itemsProcessor.getPluginMeta()
.getExtraParam(OntologyDomain.NAME_ONTOLOGY_DOMAIN);
MyService.activate(domain, this.someConfig);
}

@TISExtension
public static final class DftDesc extends OntologyDomainManipulate.BasicDesc {
public DftDesc() {
super();
}

@Override
public boolean isManipulateStorable() {
return true; // 需要持久化
}

@Override
public String getDisplayName() {
return "My New Feature";
}
}
}

Step 3:判断是否需要持久化

问自己一个问题:用户下次打开界面,需要看到这个操作的配置吗?

  • 需要(如 EnableChatBI 的 LLM 选择)→ isManipulateStorable() 返回 true
  • 不需要(如 InferOntologyFromLLM 只是触发一次推断)→ isManipulateStorable() 返回 false

Step 4:处理删除逻辑

afterManipuldateProcess 在删除操作时也会被调用。通过 itemsProcessor.isDeleteProcess() 判断:

@Override
protected void afterManipuldateProcess(...) {
if (itemsProcessor.isDeleteProcess()) {
// 清理资源,如关闭连接、删除远端配置
MyService.deactivate(domain);
return;
}
// 新增/更新逻辑
MyService.activate(domain, this.someConfig);
}

五、两个典型案例对比

案例 A:EnableChatBI(需持久化 + 有后续操作)

public class EnableChatBI extends OntologyDomainManipulate implements IManipulateStatus {

@FormField(type = FormFieldType.SELECTABLE, ordinal = 1,
validate = {Validator.identity, Validator.require})
public String llm; // 用户选择的大模型

@Override
protected void afterManipuldateProcess(IPluginContext pluginContext,
Optional<Context> context, ManipulateItemsProcessor itemsProcessor) {
if (itemsProcessor.isDeleteProcess()) return;
// 持久化完成后,触发 Neo4j 全量重建
String domainName = itemsProcessor.getPluginMeta()
.getExtraParam(OntologyDomain.NAME_ONTOLOGY_DOMAIN);
OntologySyncQueue.enqueue(
() -> OntologyNeo4jSyncService.getInstance().fullRebuild(domainName));
}

@TISExtension
public static final class DftDesc extends OntologyDomainManipulate.BasicDesc {
@Override
public boolean isManipulateStorable() {
return true; // llm 字段需要持久化,下次打开界面能看到
}
}
}

要点:

  • isManipulateStorable() = truellm 字段会被保存到 XML 配置文件
  • afterManipuldateProcess 里触发异步 Neo4j 同步
  • 实现了 IManipulateStatus 接口,可以在界面上展示当前状态

案例 B:InferOntologyFromLLM(不持久化,只执行一次性任务)

public class InferOntologyFromLLM extends OntologyDomainManipulate {

@FormField(type = FormFieldType.SELECTABLE, ordinal = 1)
public String llm;

@Override
protected void afterManipuldateProcess(IPluginContext pluginContext,
Optional<Context> context, ManipulateItemsProcessor itemsProcessor) {
// 调用 LLM 推断本体关系,结果直接写入本体存储
OntologyPluginMeta ometa = getOntologyPluginMeta(pluginContext, context);
List<OntologyObjectType> objectTypes =
OntologyObjectType.loadAll(ometa.getDomain());
// ... 调用 LLM,解析结果,写入本体 ...
}

@TISExtension
public static final class DftDesc extends OntologyDomainManipulate.BasicDesc {
@Override
public boolean isManipulateStorable() {
return false; // 不需要持久化,每次触发都是全新推断
}
}
}

要点:

  • isManipulateStorable() = false:基类跳过持久化,直接调用 afterManipuldateProcess
  • 所有业务逻辑都在 afterManipuldateProcess

六、常见错误与注意事项

❌ 错误 1:覆盖 manipuldateProcess

// 错误!manipuldateProcess 是 final 的,不能覆盖
@Override
public void manipuldateProcess(...) { ... }

正确做法:把逻辑放到 afterManipuldateProcess


❌ 错误 2:DftDesc 忘记加 @TISExtension

// 错误!没有 @TISExtension,TIS 框架找不到这个 Descriptor
public static final class DftDesc extends OntologyDomainManipulate.BasicDesc { ... }

正确做法:必须加 @TISExtension


❌ 错误 3:DftDesc 没有继承正确的 BasicDesc

// 错误!继承了错误的基类
public static final class DftDesc extends Descriptor<MyFeature> { ... }

正确做法:必须继承对应的 BasicDescOntologyDomainManipulate.BasicDescDefaultDataXProcessorManipulate.BasicDesc)。


❌ 错误 4:在 afterManipuldateProcess 里忘记处理删除分支

// 危险!删除时也会触发 afterManipuldateProcess
@Override
protected void afterManipuldateProcess(...) {
// 如果是删除操作,这里会报错(domain 已不存在)
MyService.activate(domain);
}

正确做法:先判断 itemsProcessor.isDeleteProcess()


⚠️ 注意:loadPluginStore 中的上下文提取

itemsProcessor.getPluginMeta().getExtraParam(key) 是从请求的 pluginMeta 中提取参数。不同的核心实体,参数 key 不同:

核心实体上下文 key获取方式
本体域OntologyDomain.NAME_ONTOLOGY_DOMAINpluginMeta.getExtraParam(...)
DataX 管道DataXNameitemsProcessor.getOriginIdentityId()

七、开发检查清单

开发完成后,对照以下清单自查:

  • 操作类继承了正确的基类(OntologyDomainManipulateDefaultDataXProcessorManipulate
  • 实现了 loadPluginStore()(如果直接继承 BasicManipuldateProcessor
  • 内部类 DftDesc 加了 @TISExtension
  • DftDesc 继承了正确的 BasicDesc
  • isManipulateStorable() 返回值与业务需求一致
  • afterManipuldateProcess 里处理了删除分支
  • 编译通过:mvn compile -q

八、总结

场景做法
需要持久化配置isManipulateStorable() 返回 true,业务逻辑写在 afterManipuldateProcess
只执行一次性任务isManipulateStorable() 返回 false,逻辑全写在 afterManipuldateProcess
持久化后需要触发额外操作覆盖 afterManipuldateProcess,在里面调用业务服务
克隆/重命名场景覆盖 getNewIdentityName() 返回新名称

整个体系的设计思路是模板方法模式:基类 BasicManipuldateProcessor 定义了持久化的完整流程,子类只需填充"存到哪里"(loadPluginStore)和"存完之后做什么"(afterManipuldateProcess)两个空白,其余的校验、判重、存储、删除逻辑由框架统一处理。

有问题随时找团队的同学,欢迎加入 TIS!