脚本管理
脚本管理功能支持创建、编辑和编译 C# 表达式脚本,用于扩展网关的业务逻辑,可在变量读写、数据转换等环节使用。
功能入口
在工程开发页面,点击左侧菜单"脚本"进入管理页面。

图1:脚本管理页面
脚本列表
| 列 | 说明 |
|---|---|
| 名称 | 脚本的标识名称 |
| 分类 | 脚本所属分类 |
| 类型 | 脚本类型 |
| 类名 | 脚本类名 |
| 更新时间 | 最后修改时间 |
列表功能
| 功能 | 说明 |
|---|---|
| 分页 | 支持分页显示(20/50/100条/页) |
| 多选 | 支持复选框多选,用于批量操作 |
| 分类筛选 | 支持按分类筛选脚本列表 |
脚本类型
| 类型 | 说明 |
|---|---|
| 数据转换 | 变量读取后的数据转换处理 |
| 内存变量 | 内存变量的数据转换处理 |
| 动态模型-变量数据 | 动态模型变量数据处理 |
| 动态模型-设备数据 | 动态模型设备数据处理 |
| 动态模型-告警变量 | 动态模型告警变量处理 |
| 动态模型-插件事件 | 动态模型插件事件处理 |
| 动态SQL-变量数据 | 动态SQL变量数据处理 |
| 动态SQL-设备数据 | 动态SQL设备数据处理 |
| 动态SQL-告警变量 | 动态SQL告警变量处理 |
| 动态SQL-插件事件 | 动态SQL插件事件处理 |
| MQTT RPC | MQTT RPC 脚本 |
脚本示例
数据转换
用于变量读取后的数据转换处理,如单位转换、数值计算等。
基类: ExpressionDatatrans
示例:温度单位转换(开尔文转摄氏度)
using ThingsGatewayRuntime.Application;
using ThingsGatewayRuntime.Plugin;
public class KelvinToCelsius : ExpressionDatatrans
{
public override string Name => "KelvinToCelsius";
public override object Execute(object raw, Logger? logger)
{
if (raw == null) return 0;
double kelvin = Convert.ToDouble(raw);
double celsius = kelvin - 273.15;
return Math.Round(celsius, 2);
}
}
示例:数值范围映射
using ThingsGatewayRuntime.Application;
using ThingsGatewayRuntime.Plugin;
public class RangeMapping : ExpressionDatatrans
{
public override string Name => "RangeMapping";
// 输入参数:原始范围最小值
public double InputMin { get; set; } = 0;
// 输入参数:原始范围最大值
public double InputMax { get; set; } = 100;
// 输入参数:目标范围最小值
public double OutputMin { get; set; } = 0;
// 输入参数:目标范围最大值
public double OutputMax { get; set; } = 10;
public override object Execute(object raw, Logger? logger)
{
if (raw == null) return 0;
double value = Convert.ToDouble(raw);
// 线性映射公式
double result = OutputMin + (value - InputMin) * (OutputMax - OutputMin) / (InputMax - InputMin);
return Math.Round(result, 2);
}
}
内存变量
用于内存变量的数据转换处理,可引用其他变量的值进行计算。
基类: MemoryVariableExpressionDatatrans
示例:多变量求和
using ThingsGatewayRuntime.Application;
using ThingsGatewayRuntime.Plugin;
public class SumVariables : MemoryVariableExpressionDatatrans
{
public override string Name => "SumVariables";
public override object Execute(object raw, Logger? logger)
{
// 引用其他变量的值
var var1 = Tag("Device1", "Temperature1");
var var2 = Tag("Device1", "Temperature2");
var var3 = Tag("Device1", "Temperature3");
double sum = 0;
if (var1.Value != null) sum += Convert.ToDouble(var1.Value);
if (var2.Value != null) sum += Convert.ToDouble(var2.Value);
if (var3.Value != null) sum += Convert.ToDouble(var3.Value);
return sum;
}
}
示例:平均值计算
using ThingsGatewayRuntime.Application;
using ThingsGatewayRuntime.Plugin;
public class AverageValue : MemoryVariableExpressionDatatrans
{
public override string Name => "AverageValue";
public override object Execute(object raw, Logger? logger)
{
var var1 = Tag("Device1", "Sensor1");
var var2 = Tag("Device1", "Sensor2");
var var3 = Tag("Device1", "Sensor3");
double sum = 0;
int count = 0;
if (var1.IsOnline && var1.Value != null) { sum += Convert.ToDouble(var1.Value); count++; }
if (var2.IsOnline && var2.Value != null) { sum += Convert.ToDouble(var2.Value); count++; }
if (var3.IsOnline && var3.Value != null) { sum += Convert.ToDouble(var3.Value); count++; }
return count > 0 ? Math.Round(sum / count, 2) : 0;
}
}
动态模型-变量数据
用于将变量数据转换为自定义模型结构,常用于数据上报格式转换。
基类: DynamicModelBase<VariableBasicData>
示例:简化变量模型
using ThingsGatewayRuntime.Application;
using ThingsGatewayRuntime.Plugin;
using System.Collections.Generic;
public class SimpleVariableModel : DynamicModelBase<VariableBasicData>
{
public override string Name => "SimpleVariableModel";
public override IEnumerable<object> GetList(IEnumerable<VariableBasicData> datas, Logger? logger)
{
foreach (var data in datas)
{
yield return new
{
Name = data.Name,
Value = data.Value,
Time = data.CollectTime.ToString("yyyy-MM-dd HH:mm:ss.fff"),
Online = data.IsOnline ? 1 : 0
};
}
}
}
动态模型-设备数据
用于将设备数据转换为自定义模型结构。
基类: DynamicModelBase<DeviceBasicData>
示例:设备状态汇总
using ThingsGatewayRuntime.Application;
using ThingsGatewayRuntime.Plugin;
using System.Collections.Generic;
public class DeviceStatusModel : DynamicModelBase<DeviceBasicData>
{
public override string Name => "DeviceStatusModel";
public override IEnumerable<object> GetList(IEnumerable<DeviceBasicData> datas, Logger? logger)
{
foreach (var device in datas)
{
yield return new
{
DeviceName = device.Name,
Status = device.DeviceStatus.ToString(),
ActiveTime = device.ActiveTime.ToString("yyyy-MM-dd HH:mm:ss"),
PluginName = device.PluginName,
LastError = device.LastErrorMessage
};
}
}
}
动态模型-告警变量
用于将告警数据转换为自定义模型结构。
基类: DynamicModelBase<AlarmVariable>
示例:告警汇总模型
using ThingsGatewayRuntime.Application;
using ThingsGatewayRuntime.Plugin;
using System.Collections.Generic;
public class AlarmSummaryModel : DynamicModelBase<AlarmVariable>
{
public override string Name => "AlarmSummaryModel";
public override IEnumerable<object> GetList(IEnumerable<AlarmVariable> datas, Logger? logger)
{
foreach (var alarm in datas)
{
yield return new
{
AlarmId = alarm.VariableId,
VariableName = alarm.Name,
DeviceName = alarm.DeviceName,
AlarmLevel = alarm.AlarmLevel,
AlarmText = alarm.AlarmText ?? alarm.AlarmCode,
AlarmTime = alarm.AlarmTime.ToString("yyyy-MM-dd HH:mm:ss"),
EventType = alarm.EventType.ToString(),
IsRecovered = alarm.FinishTime != default,
RecoveryTime = alarm.FinishTime != default
? alarm.FinishTime.ToString("yyyy-MM-dd HH:mm:ss")
: null
};
}
}
}
动态模型-插件事件
用于将插件事件数据转换为自定义模型结构。
基类: DynamicModelBase<PluginEventData>
示例:插件事件模型
using ThingsGatewayRuntime.Application;
using ThingsGatewayRuntime.Plugin;
using System.Collections.Generic;
public class PluginEventModel : DynamicModelBase<PluginEventData>
{
public override string Name => "PluginEventModel";
public override IEnumerable<object> GetList(IEnumerable<PluginEventData> datas, Logger? logger)
{
foreach (var eventData in datas)
{
yield return new
{
DeviceName = eventData.DeviceName,
ValueType = eventData.ValueType,
Value = eventData.ObjectValue?.ToString(),
EventTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")
};
}
}
}
动态SQL-变量数据
用于将变量数据保存到数据库,支持自定义表结构和存储逻辑。
基类: DynamicSQLBase<VariableBasicData>
示例:变量数据存储(列转行)
将多行变量数据转换为单行存储,变量名作为列名。
原始数据格式:
| Name | Value | CollectTime |
|---|---|---|
| Temperature | 25.5 | 2024-01-01 10:00:00 |
| Humidity | 60 | 2024-01-01 10:00:00 |
数据库表结构:
| CollectTime | Temperature | Humidity |
|---|---|---|
| 2024-01-01 10:00:00 | 25.5 | 60 |
using ThingsGatewayRuntime.Application;
using ThingsGatewayRuntime.Plugin;
using TORM;
using System.Collections.Generic;
public class VariableSqlPivot : DynamicSQLBase<VariableBasicData>
{
public override string Name => "VariableSqlPivot";
private static readonly Dictionary<string, string> ColumnMapping = new()
{
["Temperature"] = "Temperature",
["Humidity"] = "Humidity",
["Pressure"] = "Pressure"
};
public override async Task DBInit(OrmClient db, Logger? logger, CancellationToken cancellationToken)
{
var sql = @"
CREATE TABLE IF NOT EXISTS VariableDataPivot (
Id BIGINT PRIMARY KEY AUTO_INCREMENT,
CollectTime DATETIME NOT NULL,
Temperature DOUBLE,
Humidity DOUBLE,
Pressure DOUBLE,
INDEX IX_CollectTime (CollectTime)
)";
await db.Ado.ExecuteNonQueryAsync(sql, null, cancellationToken);
}
public override async Task<int> DBDeleteable(OrmClient db, int days, Logger? logger, CancellationToken cancellationToken)
{
var cutoffDate = DateTime.UtcNow.AddDays(-days);
var param = db.Ado.CreateParameter("cutoffDate", cutoffDate);
return await db.Ado.ExecuteNonQueryAsync(
"DELETE FROM VariableDataPivot WHERE CollectTime < @cutoffDate",
[param], cancellationToken);
}
public override async Task DBSaveable(OrmClient db, IEnumerable<VariableBasicData> datas, Logger? logger, CancellationToken cancellationToken)
{
var grouped = datas.GroupBy(d => d.CollectTime);
foreach (var group in grouped)
{
var collectTime = group.Key;
var row = new Dictionary<string, object?>
{
["CollectTime"] = collectTime
};
foreach (var variable in group)
{
if (ColumnMapping.TryGetValue(variable.Name, out var columnName))
{
row[columnName] = variable.Value != null ? Convert.ToDouble(variable.Value) : null;
}
}
var parameters = new[]
{
db.Ado.CreateParameter("CollectTime", row["CollectTime"]!),
db.Ado.CreateParameter("Temperature", row["Temperature"] ?? DBNull.Value),
db.Ado.CreateParameter("Humidity", row["Humidity"] ?? DBNull.Value),
db.Ado.CreateParameter("Pressure", row["Pressure"] ?? DBNull.Value)
};
await db.Ado.ExecuteNonQueryAsync(
"INSERT INTO VariableDataPivot (CollectTime, Temperature, Humidity, Pressure) VALUES (@CollectTime, @Temperature, @Humidity, @Pressure)",
parameters, cancellationToken);
}
}
}
MQTT RPC
用于处理MQTT RPC请求,实现自定义的RPC逻辑。
基类: MQTTDynamicRPCBase
示例:自定义RPC处理
using ThingsGatewayRuntime.Application;
using ThingsGatewayRuntime.Plugin;
using TUtility;
using TUtility.Json.Extension;
using TouchSocket.Mqtt;
using System.Text.Json;
public class CustomMqttRpc : MQTTDynamicRPCBase
{
public override string Name => "CustomMqttRpc";
public override async Task RPCInvokeAsync(
MqttArrivedMessage message,
Func<TopicArray, CancellationToken, Task> publish,
Func<Dictionary<string, Dictionary<string, JsonElement>>, ValueTask<Dictionary<string, Dictionary<string, IOperResult>>>> getRpcResult,
Logger? logger = null,
CancellationToken cancellationToken = default)
{
TopicArray topicArray = new();
try
{
var request = message.Payload.FromSystemTextJsonString<Dictionary<string, JsonElement>>();
if (request == null) return;
logger?.LogInformation($"收到RPC请求");
var rpcRequest = new Dictionary<string, Dictionary<string, JsonElement>>
{
["Device1"] = new Dictionary<string, JsonElement>
{
["Temperature"] = request["value"]
}
};
var result = await getRpcResult(rpcRequest);
var response = new Dictionary<string, object?>
{
["requestId"] = request.TryGetValue("requestId", out var reqId) ? reqId.ToString() : null,
["success"] = result.All(r => r.Value.All(v => v.Value.IsSuccess)),
["message"] = "执行完成",
["timestamp"] = DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss.fff")
};
topicArray.Topic = "gateway/rpc/write/Response";
topicArray.Payload = response.ToSystemTextJsonUtf8Bytes();
await publish(topicArray, cancellationToken);
}
catch (Exception ex)
{
logger?.LogError($"RPC处理失败: {ex.Message}");
var errorResponse = new Dictionary<string, object?>
{
["success"] = false,
["error"] = ex.Message,
["timestamp"] = DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss.fff")
};
topicArray.Topic = "gateway/rpc/write/Response";
topicArray.Payload = errorResponse.ToSystemTextJsonUtf8Bytes();
await publish(topicArray, cancellationToken);
}
}
}
新建脚本
点击"新建脚本"按钮,填写脚本名称、脚本类型、描述(可选)。

图2:新建脚本对话框
新建脚本字段
| 字段 | 说明 | 校验规则 |
|---|---|---|
| 脚本名称 | 脚本标识名称 | 必填 |
| 脚本类型 | 选择脚本类型 | 必填,下拉选择 |
| 分类 | 脚本所属分类 | 可选,用于组织和筛选脚本 |
| 描述 | 脚本功能说明 | 可选 |
基类说明
选择脚本类型后,对话框底部会动态显示对应的基类说明,帮助开发者了解脚本编写要求:
| 脚本类型 | 基类说明 |
|---|---|
| 数据转换 | 继承 ExpressionDatatrans,实现 Execute 方法 |
| 内存变量 | 继承 MemoryVariable,实现 Execute 方法 |
| 动态模型-变量数据 | 继承 DynamicModelVariable,实现 Execute 方法 |
| 动态模型-设备数据 | 继承 DynamicModelDevice,实现 Execute 方法 |
| 动态模型-告警变量 | 继承 DynamicModelAlarmVariable,实现 Execute 方法 |
| 动态模型-插件事件 | 继承 DynamicModelPluginEvent,实现 Execute 方法 |
| 动态SQL-变量数据 | 继承 DynamicSqlVariable,实现 Execute 方法 |
| 动态SQL-设备数据 | 继承 DynamicSqlDevice,实现 Execute 方法 |
| 动态SQL-告警变量 | 继承 DynamicSqlAlarmVariable,实现 Execute 方法 |
| 动态SQL-插件事件 | 继承 DynamicSqlPluginEvent,实现 Execute 方法 |
| MQTT RPC | 继承 MqttRpcScript,实现 Execute 方法 |
基类说明区域会根据选择的脚本类型自动更新,请参考基类说明编写脚本代码。
编辑脚本
点击"编辑"按钮进入脚本编辑器:

图3:脚本编辑器页面
编辑器功能
| 功能 | 说明 |
|---|---|
| 代码编辑 | 使用 Monaco 编辑器编写 C# 代码 |
| 参数配置 | 配置脚本的输入参数(仅数据转换和内存变量类型支持) |
| 编译 | 编译脚本代码 |
| 保存 | 保存脚本文件 |
代码编辑器
使用 Monaco 编辑器编写 C# 代码,支持以下功能:
| 功能 | 说明 | 快捷键 |
|---|---|---|
| 语法高亮 | C# 语法高亮显示 | - |
| 智能提示 | 代码自动补全,输入 . 或空格触发 | - |
| 语法检查 | 实时语法检查,错误和警告在编辑器中波浪线标注 | - |
| 悬浮提示 | 鼠标悬浮显示类型信息和文档 | - |
| 签名帮助 | 输入方法括号时显示参数签名 | - |
| 快速修复 | 触发代码修复建议 | Ctrl+. |
| 撤销/重做 | 撤销和重做操作 | Ctrl+Z / Ctrl+Y |
| 注释切换 | 注释/取消注释当前行 | Ctrl+/ |
| 保存 | 保存脚本 | Ctrl+S |
| 格式化 | 格式化代码 | Ctrl+K, Ctrl+F |
| 复制行 | 向下复制当前行 | Ctrl+D |
| 删除行 | 删除当前行 | Ctrl+Shift+K |
| 转到定义 | 跳转到符号定义 | F12 |
输入参数配置
仅数据转换和内存变量类型的脚本支持自定义输入参数:
| 属性 | 说明 |
|---|---|
| 参数名 | 参数的标识名称 |
| 数据类型 | 参数的数据类型 |
| 初始值 | 参数的默认值 |
| 描述 | 参数的功能说明 |
编译日志
编译日志面板位于编辑器右侧,显示编译过程的详细信息和结果。

图4:脚本编辑器-编译日志面板
日志头部
日志面板顶部显示统计信息:
| 信息 | 说明 |
|---|---|
| 错误数 | 红色显示,表示编译错误数量 |
| 警告数 | 橙色显示,表示编译警告数量 |
日志条目类型
| 类型 | 图标 | 背景色 | 说明 |
|---|---|---|---|
| 错误 | 红色叉号 | 红色背景 | 编译错误,必须修复才能编译成功 |
| 警告 | 橙色感叹号 | 橙色背景 | 编译警告,建议修复但不阻止编译 |
| 成功 | 绿色勾号 | 绿色背景 | 编译成功信息 |
| 信息 | 灰色 i | 无背景 | 编译过程信息(如编译进度、脚本类型等) |
编译流程日志
编译时按顺序显示以下日志信息:
- 开始编译 -
开始编译脚本: {脚本名称} - 脚本类型 -
脚本类型: {类型名称} - 编译结果 - 编译成功/失败
- 输出路径 - 编译成功时显示 DLL 路径
- 诊断信息 - 编译器返回的错误和警告详情(含行号和列号)
点击跳转
点击编译日志中的错误或警告条目,编辑器会自动跳转到对应的代码行,并高亮选中出错的位置,方便快速定位和修复问题。
实时语法检查
除了编译时的日志,编辑器还会在代码修改时自动进行实时语法检查(约500ms延迟),检查结果会:
- 在编辑器中以波浪线标注错误和警告位置
- 同步显示在编译日志面板中
- 支持点击跳转到对应代码行
操作按钮
| 操作 | 说明 |
|---|---|
| 编辑 | 打开脚本编辑器 |
| 编译 | 编译单个脚本 |
| 删除 | 删除脚本 |
| 批量编译 | 编译选中的多个脚本 |
| 批量删除 | 删除选中的多个脚本 |