- name
- create-plugin
- description
- Create a TIS plugin conforming to TIS plugin specifications
# TIS Plugin Creator
This skill helps you create a well-structured TIS (Data Integration Service) plugin that conforms to all TIS plugin specifications.
## Core Concepts
### 顶级插件 (Top-Level Plugin)
顶级插件是独立的主体,有独立的概念意义(可类比为一辆功能正常的车)。
**特征**:
- 不被任何其他插件所聚合
- 直接或间接实现 `com.qlangtech.tis.extension.Describable` 接口
- **必须实现 `com.qlangtech.tis.plugin.IdentityName` 接口** (`tis-builder-api/.../plugin/IdentityName.java`)
- 例如:`DeepSeekProvider` (LLM 提供者)、`DataxIcebergWriter` (数据写入器)
### 聚合属性插件 (Aggregated Property Plugin)
聚合属性插件单独存在没有意义,必须聚合在其他插件中才有意义(可类比为车上的轮胎)。
**特征**:
- 作为其他插件的 `@FormField` 属性类型存在
- 直接或间接实现 `com.qlangtech.tis.extension.Describable` 接口
- 内部有 `@TISExtension` 标注的 Descriptor 类
- **不实现 `IdentityName` 接口**
- Descriptor 通常继承父类的 `BasicDescriptor`
- 例如:`TemperatureSampling` (作为 `LLMProvider.sampling` 属性)、`HadoopCatalog` (作为 `DataxIcebergWriter.catalog` 属性)
**代码对比**:
```java
// 顶级插件示例
public class DeepSeekProvider extends LLMProvider implements IdentityName {
@FormField(ordinal = 0, identity = true)
public String name; // IdentityName 要求的标识字段
@TISExtension
public static class DftDesc extends Descriptor<DeepSeekProvider> {
// 可以覆盖 httpProcess() (如果前端有需求)
}
}
// LLMProvider 父类(展示聚合属性插件如何被使用)
public abstract class LLMProvider implements Describable<LLMProvider>, IdentityName {
// ↓ 这是聚合属性字段 —— Sampling 类型是 Describable,有多个子类型实现
@FormField(ordinal = 128, validate = {Validator.require})
public Sampling sampling; // 前端可以选择 TemperatureSampling / TopPSampling / TopKSampling 等
}
// 聚合属性插件示例
public class TemperatureSampling extends Sampling {
// 注意:未实现 IdentityName 接口
@FormField(ordinal = 0, validate = {Validator.require})
public Double temperature; // 温度参数
@TISExtension
public static class DefaultDescriptor extends BasicDescriptor {
// BasicDescriptor 定义在父类 Sampling 中
// 不要覆盖 httpProcess() —— 聚合属性插件不暴露独立 HTTP 端点
@Override
public String shortComment() {
return "温度采样策略";
}
}
}
```
**关系说明**:
- `DeepSeekProvider` 是顶级插件,实现 `IdentityName`,用户创建实例时命名
- `LLMProvider.sampling` 字段类型为 `Sampling`(抽象基类)
- `TemperatureSampling` 是 `Sampling` 的一个具体实现,作为聚合属性插件嵌入在 `LLMProvider` 中
- 前端展示 `DeepSeekProvider` 表单时,`sampling` 字段会渲染为下拉选择器,用户可选 `TemperatureSampling` / `TopPSampling` 等子类型
## What This Skill Does
When invoked, this skill will:
1. Analyze the user-provided context to understand the plugin's functionality
2. Design the plugin structure with appropriate properties (general or aggregated)
3. Generate the complete plugin implementation including:
- Main plugin class implementing `Describable`
- Inner `Descriptor` class with `@TISExtension`
- Properties with proper `@FormField` annotations
- Property validation methods
- Corresponding `.json` property descriptor file
- Optional `.md` help documentation
## TIS Plugin Specifications
### Core Requirements
Every TIS plugin MUST satisfy:
1. **Implement Describable Interface**
- Plugin class must implement `com.qlangtech.tis.extension.Describable` (directly or via parent)
2. **Inner Descriptor Class**
- Must have a `public` inner class extending `com.qlangtech.tis.extension.Descriptor`
- Annotated with `@TISExtension` from `com.qlangtech.tis.extension.TISExtension`
3. **Property Requirements**
- All properties must be `public`
- Must be annotated with `@FormField`
4. **Property Descriptor Files**
- A `.json` file in the same package under `resources/` directory
- Optional `.md` file for rich markdown help content
- **IMPORTANT: parent classes also need `.json`!** Even if a class is `abstract` and has no `@TISExtension` Descriptor of its own, if it declares `@FormField` fields (public fields with `@FormField` annotation), it MUST have its own `.json` file at the matching resource path. These fields' help/placeholder/dftVal are inherited by concrete subclasses; TIS looks up the `.json` for each class in the hierarchy independently.
5. **Descriptor 命名:`getDisplayName()` 禁止返回中文(CRITICAL)**
- `Descriptor#getDisplayName()` 是插件的机器可读短名,**必须由纯字母组成**:不得包含中文、空格以及任何标点符号
- 需要表达中文功能说明时,让 Descriptor 实现 `com.qlangtech.tis.extension.DescriptorUseableShortComment` 接口
(`tis-plugin/src/main/java/com/qlangtech/tis/extension/DescriptorUseableShortComment.java`),
把原来的中文内容移到 `shortComment()` 的返回值,`getDisplayName()` 改写成对应的英文短名
- 命名规则:**下划线分隔的帕斯卡命名**(每个单词首字母大写,单词之间用 `_` 分隔);
全大写缩写保持全大写(`LLM` / `URL` / `AIP`);单个单词时无需下划线
- 示例对照:
| 类名 | ✅ `getDisplayName()` | ✅ `shortComment()` |
|---|---|---|
| `CloseOverlayConfig.DefaultDescriptor` | `Close_Overlay` | `关闭浮层` |
| `DisableAutoRefreshConfig.DefaultDescriptor` | `Disable_Auto_Refresh` | `关闭自动刷新` |
| `StreamLlmConfig.DefaultDescriptor` | `Stream_LLM` | `调用大模型` |
| `RecomputeConfig.DefaultDescriptor` | `Recompute` | `重算变量` |
| `UrlClickAction.DefaultDescriptor` | `URL` | `跳转链接` |
- ❌ 反例:`return "关闭浮层";`(中文)、`return "Close Overlay";`(含空格)、`return "closeOverlay";`(单词首字母未大写)
- 本规则对**所有** Descriptor 子类生效,顶级插件 (Top-Level Plugin) 与聚合属性插件 (Aggregated Property Plugin) 一视同仁
### Property Types
#### General Properties
Basic Java types:
- `Boolean` / `boolean`
- `Integer` / `int`
- `String`
- `Long` / `long`
- `java.util.Date`
- `com.qlangtech.tis.plugin.MemorySize`
- Duration types
#### Aggregated Properties
Properties that are themselves `Describable` plugins, providing polymorphic capability. Example: `ClusterType` in `TISFlinkCDCStreamFactory`.
**CRITICAL - @FormField for Aggregated Properties**:
- **NEVER specify `type` attribute in @FormField for aggregated properties**
- TIS automatically detects aggregated properties (properties implementing `Describable`)
- Adding `type = FormFieldType.SELECTABLE` will cause TIS runtime errors
**Correct** ✅:
```java
@FormField(ordinal = 0, validate = {Validator.require})
public OntologyActionRule sideEffectRule;
```
**Wrong** ❌:
```java
@FormField(ordinal = 0, type = FormFieldType.SELECTABLE, validate = {Validator.require})
public OntologyActionRule sideEffectRule; // This will fail at runtime!
```
**IMPORTANT**: Each concrete implementation class of a 聚合属性插件 (Aggregated Property Plugin) is a full plugin and MUST have:
- Its own Java class with `@FormField` properties
- Its own `@TISExtension` Descriptor
- **Descriptor MUST implement `DescriptorUseableShortComment` interface** (from `tis-plugin/src/main/java/com/qlangtech/tis/extension/DescriptorUseableShortComment.java`)
- Implement `shortComment()` method returning a **brief Chinese description** of the plugin's function
- **Requirements for shortComment()**:
- Use Chinese language (中文)
- **Maximum 10 characters** (10个字以内)
- **NO punctuation marks** (no periods, commas, etc.)
- Will be displayed in the frontend UI to help users understand the plugin at a glance
- Example: "Hadoop文件系统管理" or "Hive元数据存储"
- 🔴 **同一个 Descriptor 的 `getDisplayName()` 不得返回中文**:中文说明只出现在 `shortComment()`,
`getDisplayName()` 必须返回纯字母的英文短名(如 `Hadoop_Catalog`)。详见 "Core Requirements" 第 5 条
- Its own `.json` descriptor file in resources (same package path)
- Optional `.md` help file if properties are complex
**IdentityName Interface Rule (CRITICAL)**:
- **聚合属性插件 (Aggregated Property Plugin) implementation classes generally DO NOT need to implement `IdentityName` interface**
- IdentityName is typically used for 顶级插件 (Top-Level Plugin), not nested 聚合属性插件
- Reference: `tis-builder-api/src/main/java/com/qlangtech/tis/plugin/IdentityName.java`
- **If a plugin implements `IdentityName` interface, it MUST have an identity property**:
- One `@FormField` property MUST be marked with `identity = true`
- Example: `@FormField(ordinal = 0, identity = true, validate = {Validator.require, Validator.identity})`
- Without this, TIS will fail to start with an error
- The identity property acts as the unique identifier for the plugin instance
- **When to implement IdentityName**:
- 顶级插件 (Top-Level Plugin) that users create multiple instances of (data sources, writers, readers)
- Plugins that need to be referenced by name across the system
- NOT for 聚合属性插件 (Aggregated Property Plugin) embedded within other plugins
**Parent Class Requirements for 聚合属性插件 (Aggregated Property Plugin)**:
- If a 聚合属性插件 type (e.g., `IcebergCatalog`) will have multiple concrete implementations, the **parent abstract class MUST define a `BasicDescriptor`**:
- Must be a **protected abstract static** inner class
- Must extend `Descriptor<ParentClassName>`
- All concrete implementation classes' Descriptors MUST extend this `BasicDescriptor`
- Example structure in parent class:
```java
public abstract class IcebergCatalog implements Describable<IcebergCatalog> {
// properties...
protected abstract static class BasicDescriptor extends Descriptor<IcebergCatalog> {
// common descriptor logic for all implementations
}
}
```
- Example in implementation class:
```java
public class HadoopCatalog extends IcebergCatalog {
// properties...
@TISExtension()
public static class DefaultDescriptor extends BasicDescriptor
implements DescriptorUseableShortComment {
@Override
public String shortComment() {
return "基于HDFS文件系统";
}
@Override
public String getDisplayName() {
// 纯字母英文短名,下划线分隔帕斯卡命名;中文一律放 shortComment()
return "Hadoop_Catalog";
}
}
}
```
Example: If `DataxIcebergWriter` has a 聚合属性插件 (Aggregated Property Plugin) `IcebergCatalog catalog`, and `HadoopCatalog` extends `IcebergCatalog`, then you need:
- `IcebergCatalog.java` (abstract parent with protected abstract static BasicDescriptor inner class)
- `HadoopCatalog.java` (with its own @FormField properties, Descriptor extends BasicDescriptor)
- `HadoopCatalog.json` (describing HadoopCatalog's own properties)
- Descriptor implements `DescriptorUseableShortComment` with `shortComment()` returning Chinese text (≤10 chars, no punctuation)
- Optional `HadoopCatalog.md` (if properties are complex)
- **DO NOT implement IdentityName** unless this is a 顶级插件 (Top-Level Plugin) (it's not in this case)
### @FormField Annotation Attributes
- **identity**: Unique identifier for the plugin instance (acts as primary key)
- **ordinal**: Display order in UI form (lower = higher priority, semantically related fields should be adjacent)
- **advance**: Mark as advanced setting (must have default value, can be hidden)
- **validate**: Frontend validation rules (see Validator options below)
- **type**: Field type (see FormFieldType options below)
### Validator Options
Reference: `tis-plugin/src/main/java/com/qlangtech/tis/plugin/annotation/Validator.java`
- `require`: Required field
- `user_name`: Username format (letters, numbers, underscore, dot, dash)
- `email`: Email format
- `forbid_start_with_number`: Cannot start with number
- `identity`: Primary key format
- `integer`: Integer format
- `host`: Internet domain with optional port (e.g., `192.168.28.200:7070`)
- `hostWithoutPort`: Domain without port
- `url`: URL starting with http/https
- `db_col_name`: Database column name format
- `relative_path`: File system relative path
- `absolute_path`: Unix absolute path
- `none_blank`: Non-empty content
### FormFieldType Options
Reference: `tis-plugin/src/main/java/com/qlangtech/tis/plugin/annotation/FormFieldType.java`
- `MULTI_SELECTABLE`: Multi-select, property type `List<IdentityName>` or `List<String>`
- `INPUTTEXT`: Single-line text input, property type `String`
- `SELECTABLE`: Single-select dropdown (register options via `registerSelectOptions()` in Descriptor)
- `PASSWORD`: Password input
- `FILE`: File upload (only one per form, plugin must implement `ITmpFileStore`)
- `TEXTAREA`: Multi-line text input (for SQL scripts, XML, etc.)
- `DATE`: Date picker
- `JDBCColumn`: JDBC column type
- `INT_NUMBER`: Integer number input
- `ENUM`: Enumeration selection
- `DateTime`: Date-time (UTC, property type `long` or `java.util.Date`)
- `DECIMAL_NUMBER`: Decimal number
- `DURATION_OF_SECOND`: Duration in seconds
- `DURATION_OF_MINUTE`: Duration in minutes
- `DURATION_OF_HOUR`: Duration in hours
- `MEMORY_SIZE_OF_BYTE`: Memory size in bytes
- `MEMORY_SIZE_OF_KIBI`: Memory size in KB
- `MEMORY_SIZE_OF_MEGA`: Memory size in MB
### FormFieldType 选用原则
#### 原则一:禁止 JSON 字符串输入
结构化数据不得使用 `TEXTAREA` 让用户填写 JSON 字符串,必须使用 TIS 的结构化表单机制:
| 场景 | 正确做法 | 错误做法 |
|---|---|---|
| 列表化结构对象(如表格列配置) | `@SubForm(desClazz = XxxConfig.class)` + `List<XxxConfig>` | `@FormField(type = FormFieldType.TEXTAREA)` 手写 `[{...}]` |
| 单个结构对象 | 字段类型声明为 `Describable` 子类,不指定 type(TIS 自动检测) | `@FormField(type = FormFieldType.TEXTAREA)` 手写 `{...}` |
```java
// ✅ 正确:使用 @SubForm
@SubForm(desClazz = WidgetColumnConfig.class, atLeastOne = false,
idListGetScript = "return java.util.Collections.emptyList();")
public List<WidgetColumnConfig> columns;
// ❌ 错误:让用户手写 JSON
@FormField(type = FormFieldType.TEXTAREA)
public String columns;
```
#### 原则二:固定取值用 ENUM,动态选项用 SELECTABLE
- **`ENUM`**:字段的取值集合同定有限,在 Java 代码中用枚举类硬编码。如:聚合方式(sum/count/avg/min/max)、对齐方式(left/center/right)、级别(1/2/3/4)、颜色选择(20种常用颜色)。
- **`SELECTABLE`**:选项来自运行时外部数据源,通过 `registerSelectOptions()` 或 `doGetOptions()` 动态供给。如:当前 module 的变量列表、数据库表名列。
```java
// ✅ 正确:固定取值用 ENUM + Java 枚举类
@FormField(type = FormFieldType.ENUM, ordinal = 3, required = true)
public AggregationType aggregation;
public enum AggregationType {
sum("求和"), count("计数"), avg("平均值"), min("最小值"), max("最大值");
public final String label;
AggregationType(String label) { this.label = label; }
}
// ❌ 错误:固定取值用 SELECTABLE(误导为动态选项)
@FormField(type = FormFieldType.SELECTABLE, ordinal = 3)
public String aggregation;
// ❌ 错误:固定取值用 INPUTTEXT(用户不知道合法值)
@FormField(type = FormFieldType.INPUTTEXT, ordinal = 3)
public String aggregation;
```
#### 原则三:颜色类枚举附带 hex 色值
枚举字段为颜色选择时,在 `.json` resource 中为每个枚举项附带 `hex` 色值,用以前端渲染颜色预览圆点:
```json
{
"color": {
"help": "选择文本颜色",
"dftVal": "inherit",
"enum": [
{ "label": "红色", "val": "red", "hex": "#f5222d" },
{ "label": "蓝色", "val": "blue", "hex": "#1890ff" }
]
}
}
```
#### 原则四:SELECTABLE 字段必须在 Descriptor 中注册选项
Auf GitHub ansehen