设计说明
概念说明
BladeX IoT 协议转换系统提供了灵活的物联网协议转换能力,允许不同厂商的设备协议通过模板配置的方式进行相互转换。系统基于 Beetl 模板引擎实现,支持 MQTT Topic 和消息体的双向转换。
步骤说明
- 给对方开 mqtt 账号,最好是开设备产品密钥签名的账号,当然也可以开 mqtt 账号管理菜单里的账号。
- 对方按自己的协议格式上报数据。
- 在平台添加一个协议转换,例如:添加
blink,添加对方协议的topic前缀blink/# - 新建产品时选择
blink协议。 - 添加协议转换的具体规则。
一、转换流程
系统按照以下流程进行协议转换:
输入 Topic + Message
↓
1. Topic 模式匹配
↓
2. 提取 Topic 变量
↓
3. 解析消息体(JSON自动识别)
↓
4. Beetl 模板渲染
↓
5. 输出 Topic + Message二、Beetl 模板系统
1. 模板变量
在协议转换模板中,可以使用以下内置变量:
| 变量名 | 类型 | 说明 | 示例 |
|---|---|---|---|
topic | Map<String, String> | 从输入 Topic 中提取的变量 | ${topic.productKey} |
topic.* | String | 通配符匹配的多级路径 (v2.3.0+) | ${topic.path} 匹配 a/b/c |
bodyRaw | String | 原始消息体字符串 | ${bodyRaw} |
body | Object/Map/List | 解析后的消息体(JSON自动解析) | ${body.temperature} |
ext.product | ProductEntity | 产品信息(仅 OUT 流向) | ${ext.product.productName} |
ext.device | DeviceEntity | 设备信息(仅 OUT 流向) | ${ext.device.deviceName} |
ext.gateway | DeviceEntity | 网关信息(仅子设备,OUT 流向) | ${ext.gateway.deviceName} |
2. 内置函数
系统提供了多个内置函数,用于模板中的数据处理:
| 函数名 | 示例 | 作用 |
|---|---|---|
toJson | ${toJson(body.data)} | 将对象转换为 JSON 字符串,如将 {temperature: 25} 转换为 {"temperature":25} |
values | ${values(body.status)} | 为字符串添加 JSON 双引号,如 online 转为 "online",数值类型不处理 |
pid | ${pid(topic.identifier)} | 将字符串中的点号(.)替换为下划线(_),如 sys.device.info 转为 sys_device_info |
iosDateFmt | ${iosDateFmt(body.timestamp)} | 将 ISO 8601 日期时间字符串转换为标准格式,如 2024-12-24T10:30:00Z 转为 2024-12-24 10:30:00 |
getProductKey | ${getProductKey(topic.deviceName)} | 根据设备名称从缓存中获取产品标识 |
uuid | ${uuid()} | 生成随机 UUID,用于生成唯一消息 ID |
timestamp | ${timestamp()} | 获取当前时间戳(毫秒) |
三、配置示例
场景 1: 设备上报数据(IN 流向)
设备协议 → 平台协议
输入示例
Topic: custom/a1b2c3d4/device001/data
Message:
{
"temp": 25.5,
"humi": 60,
"time": "2024-12-24T10:30:00Z"
}配置规则
- 输入 Topic 正则:
/custom/${productKey}/${deviceName}/data - 输出 Topic 模板:
/sys/${topic.productKey}/${topic.deviceName}/thing/event/property/post - 输出消息体模板:
{ "id": "${uuid()}", "version": "1.0", "params": { "temperature": ${body.temp}, "humidity": ${body.humi}, "timestamp": "${iosDateFmt(body.time)}" }, "method": "thing.event.property.post" }
输出结果
Topic: /sys/a1b2c3d4/device001/thing/event/property/post
Message:
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"version": "1.0",
"params": {
"temperature": 25.5,
"humidity": 60,
"timestamp": "2024-12-24 10:30:00"
},
"method": "thing.event.property.post"
}场景 2: 平台下发指令(OUT 流向)
平台协议 → 设备协议
输入示例
Topic: /sys/a1b2c3d4/device001/thing/service/switch/invoke
Message:
{
"id": "123456",
"params": {
"switch": 1
}
}配置规则
- 输入 Topic 正则:
/sys/${productKey}/${deviceName}/thing/service/${identifier}/invoke - 输出 Topic 模板:
/custom/${topic.productKey}/${topic.deviceName}/control/${pid(topic.identifier)} - 输出消息体模板:
{ "cmd": ${values(topic.identifier)}, "value": ${body.params.switch}, "timestamp": ${timestamp()} }
输出结果
Topic: /custom/a1b2c3d4/device001/control/switch
Message:
{
"cmd": "switch",
"value": 1,
"timestamp": 1703404800000
}场景 3: 使用扩展变量(OUT 流向)
利用产品、设备信息进行协议转换
输入示例
Topic: /sys/a1b2c3d4/device001/thing/service/update/invoke
Message:
{
"params": {
"version": "1.0.0"
}
}配置规则
- 输出消息体模板:
{ "deviceName": ${values(ext.device.deviceName)}, "deviceId": ${values(ext.device.deviceId)}, "productName": ${values(ext.product.productName)}, "productKey": ${values(ext.product.productKey)}, "version": ${values(body.params.version)}, "updateTime": ${timestamp()} }
输出结果
Message:
{
"deviceName": "device001",
"deviceId": "1234567890",
"productName": "智能温湿度传感器",
"productKey": "a1b2c3d4",
"version": "1.0.0",
"updateTime": 1703404800000
}场景 4: 通配符匹配(v2.3.0+)
版本要求
BladeX-Links 2.3.0 及以上版本支持此功能。
使用通配符 ${*变量名} 匹配多级 Topic 路径,适用于动态层级结构的协议转换。
功能说明
- 通配符语法:
${*varName}可以匹配一个或多个 Topic 层级 - 贪婪匹配: 通配符会匹配尽可能多的层级,直到下一个固定部分
- 多级路径: 匹配的内容包含完整的多级路径(保留
/分隔符)
输入示例
Topic: /abc/device/sensor/temperature/room101/data
Message:
{
"value": 23.5,
"unit": "celsius"
}配置规则
- 输入 Topic 正则:
/abc/${*path} - 输出 Topic 模板:
/xyz/device/${topic.path} - 输出消息体模板:
{ "originalPath": ${values(topic.path)}, "data": { "value": ${body.value}, "unit": ${values(body.unit)} }, "timestamp": ${timestamp()} }
输出结果
Topic: /xyz/device/device/sensor/temperature/room101/data
Message:
{
"originalPath": "device/sensor/temperature/room101/data",
"data": {
"value": 23.5,
"unit": "celsius"
},
"timestamp": 1703404800000
}更多示例
| 输入模式 | 匹配 Topic | 提取变量 |
|---|---|---|
/abc/${*path} | /abc/a/b/c/d | topic.path = "a/b/c/d" |
/device/${*subPath}/status | /device/sensor/temp/room1/status | topic.subPath = "sensor/temp/room1" |
/${prefix}/${*middle}/suffix | /system/a/b/c/suffix | topic.prefix = "system"topic.middle = "a/b/c" |
使用场景
- 不确定层级深度: 设备 Topic 层级不固定,如
/device/floor1/room2/sensor3/data - 路径转发: 需要保留原始路径结构进行转发
- 灵活匹配: 多租户或多产品线的统一接入
注意事项
- 贪婪匹配: 通配符会尽可能多地匹配,请合理安排 Topic 结构
- 性能考虑: 通配符匹配略慢于精确匹配,建议优先使用精确变量
- 命名规范: 通配符变量名建议使用有意义的名称(如
path、subPath) - 边界情况: 通配符至少匹配一个层级,不能匹配空路径
四、最佳实践
1. Topic 设计
- 使用清晰的层级结构:
/系统名/${产品标识}/${设备名称}/${功能} - 变量命名规范: 使用驼峰或下划线命名法
- 避免特殊字符: 仅使用字母、数字、下划线、中划线
- 通配符使用 (v2.3.0+):
- 优先使用精确变量
${varName}匹配单个层级 - 仅在层级不确定时使用通配符
${*varName}匹配多级路径 - 通配符放在 Topic 末尾性能最优,如
/prefix/${*path}
- 优先使用精确变量
2. 模板编写
- 简化逻辑: 避免在模板中编写复杂的业务逻辑
- 错误处理: 使用条件判断处理可能为空的字段
- 格式化输出: 善用内置函数确保数据格式正确
- 性能优化: 避免在模板中进行大量循环或递归
3. 调试技巧
- 先在调试模式下验证转换效果
- 提供完整的输入示例,包含所有可能的字段
- 使用日志查看详细的转换过程
- 测试边界情况(空值、特殊字符等)
4. 性能优化
- 合理设置转换规则数量,避免过多规则影响匹配性能
- 使用精确的 Topic 正则,减少不必要的匹配尝试
- 定期清理无效或过期的转换规则
- 监控转换耗时,优化复杂模板
五、注意事项
重要提示
- JSON 格式: 输出消息体必须是有效的 JSON 格式,否则转换失败
- 变量提取: Topic 变量提取依赖于正则表达式的正确配置
- 扩展变量:
ext变量仅在 OUT 流向且能解析出设备信息时可用 - 并发安全: 服务实现了线程安全,支持高并发场景
- 分布式部署: 多实例部署时规则自动同步,无需手动干预
六、扩展开发
自定义 Beetl 函数
可以通过实现 BeetlFunction 接口来扩展自定义函数:
@Component
public class MyCustomFunction implements BeetlFunction {
@Override
public String getName() {
return "myFunc"; // 函数名
}
@Override
public Object call(Object[] paras, Context ctx) {
// 实现自定义逻辑
Object input = paras[0];
return processInput(input);
}
}后端实现
系统会自动注册所有 BeetlFunction 实现的 Bean,无需额外配置。用户可根据业务需求自行拓展增强。
beetl 模板文档地址:点击查看
