20260902:规则脚本改造,支持精确到设备(多个设备);mqtt支持通配符

This commit is contained in:
zhugongping 2026-09-02 09:27:43 +08:00
parent 3352e9ab82
commit cb00505ae7
13 changed files with 110 additions and 19 deletions

View File

@ -101,6 +101,13 @@ public class Device extends BaseEntity {
@TableField(exist = false)
private Integer deviceType;
/**
* 设备类型列表(用于多类型过滤)
*/
@ApiModelProperty("设备类型列表(用于多类型过滤,如[1,2]表示直连和网关设备)")
@TableField(exist = false)
private List<Integer> deviceTypes;
/**
* 设备状态(1-未激活,2-禁用,3-在线,4-离线)
*/

View File

@ -115,7 +115,7 @@ public class Product extends BaseEntity {
/**
* 设备类型(1-直连设备、2-网关子设备、3-网关设备)
*/
@ApiModelProperty(value = "设备类型", notes = "(1-直连设备、2-网关子设备、3-网关设备、4-网关子设备)")
@ApiModelProperty(value = "设备类型", notes = "(1-直连设备、2-网关设备、3-监控设备、4-网关子设备)")
@Excel(name = "设备类型", readConverterExp = "1=直连设备、2=网关设备、3=监控设备、4-网关子设备")
private Integer deviceType;
/**

View File

@ -96,6 +96,11 @@ public class Script extends BaseEntity {
private String productName;
/**
* 设备编号列表(逗号分隔,为空表示全部设备)
*/
private String serialNumbers;
/**
* 是否生效
*/

View File

@ -46,6 +46,11 @@ public class ScriptCondition {
*/
private Long productId;
/**
* 当前上报设备的编号(用于设备级过滤,为空不过滤)
*/
private String serialNumber;
/**
* 场景ID
*/

View File

@ -44,7 +44,7 @@ public class MqttBridge extends NodeComponent {
// 优先使用bridge.route,如果为空再使用context.topic
String publishTopic = StringUtils.isNotBlank(bridge.getRoute())
? bridge.getRoute()
? cxt.placeholders(bridge.getRoute())
: cxt.getTopic();
MqttMessage message = new MqttMessage();

View File

@ -49,6 +49,7 @@ public class RuleProcess {
.scriptPurpose(1)
.scriptEvent(event)
.productId(productCode.getProductId())
.serialNumber(serialNumber)
.build();
MsgContext context = MsgContext.builder()
.serialNumber(serialNumber)

View File

@ -443,6 +443,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="deviceName != null and deviceName != ''"> and d.device_name like concat('%', #{deviceName}, '%')</if>
<if test="productId != null "> and d.product_id = #{productId}</if>
<if test="deviceType != null "> and p.device_type = #{deviceType}</if>
<if test="deviceTypes != null and deviceTypes.size() > 0">
and p.device_type in
<foreach collection="deviceTypes" item="deviceTypeItem" open="(" separator="," close=")">
#{deviceTypeItem}
</foreach>
</if>
<if test="productName != null and productName != ''"> and d.product_name like concat('%', #{productName}, '%')</if>
<if test="serialNumber != null and serialNumber != ''"> and d.serial_number = #{serialNumber}</if>
<if test="gwDevCode != null and gwDevCode != ''"> and d.gw_dev_code = #{gwDevCode}</if>
@ -1002,6 +1008,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="deviceName != null and deviceName != ''"> and d.device_name like concat('%', #{deviceName}, '%')</if>
<if test="productId != null "> and d.product_id = #{productId}</if>
<if test="deviceType != null "> and p.device_type = #{deviceType}</if>
<if test="deviceTypes != null and deviceTypes.size() > 0">
and p.device_type in
<foreach collection="deviceTypes" item="deviceTypeItem" open="(" separator="," close=")">
#{deviceTypeItem}
</foreach>
</if>
<if test="productName != null and productName != ''"> and d.product_name like concat('%', #{productName}, '%')</if>
<if test="serialNumber != null and serialNumber != ''"> and d.serial_number = #{serialNumber}</if>
<if test="gwDevCode != null and gwDevCode != ''"> and d.gw_dev_code = #{gwDevCode}</if>

View File

@ -10,6 +10,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<result property="sceneId" column="scene_id" />
<result property="productId" column="product_id" />
<result property="productName" column="product_name" />
<result property="serialNumbers" column="serial_numbers" />
<result property="bridgeId" column="bridge_id" />
<result property="bridgeName" column="bridge_name" />
<result property="scriptEvent" column="script_event" />
@ -32,16 +33,16 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</resultMap>
<sql id="selectRuleScriptVo">
select user_id, user_name, scene_id,product_id,product_name,script_event,script_action,script_purpose,script_order,application_name, script_id, script_name, script_data, script_type, script_language,enable, del_flag, create_by, create_time, update_by, update_time, remark from iot_script
select user_id, user_name, scene_id,product_id,product_name,serial_numbers,script_event,script_action,script_purpose,script_order,application_name, script_id, script_name, script_data, script_type, script_language,enable, del_flag, create_by, create_time, update_by, update_time, remark from iot_script
</sql>
<sql id="selectRuleScriptList">
select user_id, user_name, scene_id,product_id,product_name, script_event,script_action,script_purpose,script_order,application_name, script_id, script_name, script_type, script_language,enable, create_by, create_time, update_by, update_time from iot_script
select user_id, user_name, scene_id,product_id,product_name,serial_numbers, script_event,script_action,script_purpose,script_order,application_name, script_id, script_name, script_type, script_language,enable, create_by, create_time, update_by, update_time from iot_script
</sql>
<select id="selectRuleScriptList" parameterType="com.fastbee.iot.model.ScriptCondition" resultMap="RuleScriptResult">
select s.script_event,s.script_action,s.script_purpose,s.script_order,s.application_name, s.script_name, s.script_type, s.script_language,
s.user_id, s.user_name,s.scene_id,s.product_id,s.product_name,
s.user_id, s.user_name,s.scene_id,s.product_id,s.product_name,s.serial_numbers,
s.script_id, s.enable, s.create_by, s.create_time, s.update_by, s.update_time,
b.id as bridge_id, b.name as bridge_name
from iot_script s
@ -79,6 +80,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="scriptPurpose != null and scriptPurpose != ''"> and s.script_purpose = #{scriptPurpose}</if>
<if test="scriptAction != null and scriptAction != ''"> and script_action = #{scriptAction}</if>
<if test="route != null and route != ''" > and b.route = #{route}</if>
<!-- 设备级过滤:serial_numbers 为空表示关联全部设备,否则按逗号分隔匹配 -->
<if test="serialNumber != null and serialNumber != ''">
and (s.serial_numbers is null or s.serial_numbers = ''
or FIND_IN_SET(#{serialNumber}, s.serial_numbers) &gt; 0)
</if>
</where>
order by script_order
</select>
@ -106,7 +112,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<select id="selectRuleScriptById" parameterType="String" resultMap="RuleScriptResult">
select script_event,script_action,script_purpose,script_order,application_name, script_name, script_data, script_type, script_language,
user_id, user_name,scene_id,product_id,product_name,
user_id, user_name,scene_id,product_id,product_name,serial_numbers,
s.script_id, s.enable, s.create_by, s.create_time, s.update_by, s.update_time,
b.id as bridge_id, b.name as bridge_name
from iot_script s
@ -140,6 +146,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="remark != null">remark,</if>
<if test="productId != null">product_id,</if>
<if test="productName != null">product_name,</if>
<if test="serialNumbers != null">serial_numbers,</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="userId != null and userId != ''">#{userId},</if>
@ -164,6 +171,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="remark != null">#{remark},</if>
<if test="productId != null">#{productId},</if>
<if test="productName != null">#{productName},</if>
<if test="serialNumbers != null">#{serialNumbers},</if>
</trim>
</insert>
@ -192,6 +200,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="remark != null">remark = #{remark},</if>
<if test="productId != null">product_id = #{productId},</if>
<if test="productName != null">product_name = #{productName},</if>
<if test="serialNumbers != null">serial_numbers = #{serialNumbers},</if>
</trim>
where script_id = #{scriptId}
</update>
@ -209,7 +218,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<insert id="insertRuleScriptList" parameterType="java.util.List">
insert into iot_script (user_id,user_name,scene_id,script_event, script_action, script_purpose,script_order,
application_name,script_id,script_name,script_data,script_type,script_language,enable,create_by,create_time,product_id,product_name)
application_name,script_id,script_name,script_data,script_type,script_language,enable,create_by,create_time,product_id,product_name,serial_numbers)
VALUES
<foreach collection ="list" item="ruleScript" separator =",">
(#{ruleScript.userId},
@ -229,7 +238,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
#{ruleScript.createBy},
#{ruleScript.createTime},
#{ruleScript.productId},
#{ruleScript.productName})
#{ruleScript.productName},
#{ruleScript.serialNumbers})
</foreach >
</insert>

View File

@ -906,6 +906,7 @@ CREATE TABLE `iot_script` (
`scene_id` bigint(20) NOT NULL DEFAULT '0' COMMENT '关联场景ID',
`product_id` bigint(20) DEFAULT NULL COMMENT '产品ID',
`product_name` varchar(64) CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '产品名称',
`serial_numbers` varchar(2000) DEFAULT NULL COMMENT '设备编号列表(逗号分隔,为空表示全部设备)',
`script_event` tinyint(1) NOT NULL COMMENT '脚本事件(1=设备上报,2=平台下发,3=设备上线,4=设备离线)',
`script_action` tinyint(1) NOT NULL COMMENT '脚本动作(1=消息重发,2=消息通知,3=Http推送,4=Mqtt桥接,5=数据库存储)',
`script_purpose` tinyint(1) NOT NULL COMMENT '脚本用途(1=数据流,2=触发器,3=执行动作)',

View File

@ -2,7 +2,7 @@
<el-dialog :title="$t('device.product-list.058448-0')" :visible.sync="open" width="800px">
<div style="margin-top:-55px;">
<el-divider style="margin-top:-30px;"></el-divider>
<el-form :model="queryParams" ref="queryForm" :inline="true" label-width="68px">
<el-form :model="queryParams" ref="queryForm" :inline="true" label-width="68px" @submit.native.prevent>
<el-form-item :label="$t('device.allot-record.155854-2')" prop="productName">
<el-input v-model="queryParams.productName" :placeholder="$t('device.product-list.058448-2')" clearable
size="small" @keyup.enter.native="handleQuery" />

View File

@ -78,6 +78,8 @@ export default {
productName: null,
serialNumber: null,
status: null,
// 只查询直连设备(1)和网关设备(2),排除监控设备(3)和网关子设备(4)
deviceTypes: [1, 2],
},
};
},
@ -132,15 +134,6 @@ export default {
// 存在且取消选中
this.selectDeviceNums.splice(index, 1);
}
// 筛选产品下的设备比
if (this.selectDeviceNums.length == 0) {
this.queryParams.productId = null;
this.getList();
} else if (this.selectDeviceNums.length == 1) {
this.queryParams.productId = row.productId;
this.getList();
}
},
// 全选事件处理
handleSelectionAll(selection) {

View File

@ -3,7 +3,7 @@
:close-on-click-modal="false">
<div style="margin-top: -55px">
<el-divider style="margin-top: -30px"></el-divider>
<el-form :model="queryParams" ref="queryForm" :inline="true" label-width="68px">
<el-form :model="queryParams" ref="queryForm" :inline="true" label-width="68px" @submit.native.prevent>
<el-form-item :label="$t('product.index.091251-0')" prop="productName">
<el-input v-model="queryParams.productName" :placeholder="$t('product.index.091251-1')" clearable
size="small" @keyup.enter.native="handleQuery" />

View File

@ -118,6 +118,20 @@
<el-button type="warning" v-if="form.scriptAction == 5" @click="handleSelectDbSave()" size="mini">{{ $t('script.349087-31') }}</el-button>
</el-form-item>
</el-col>
<!-- 设备选择(产品选择后显示,仅设备上报/平台下发等事件) -->
<el-col :span="12" v-if="form.productId && form.scriptEvent !== 5 && form.scriptEvent !== 6">
<el-form-item label="关联设备">
<el-input readonly v-model="deviceDisplayText" size="small" placeholder="不选择则关联全部设备" style="margin-top: 3px">
<el-button slot="append" @click="handleSelectDevice()" size="small">选择设备</el-button>
<el-button v-if="form.serialNumbers" slot="append" @click="clearDeviceSelect()" size="small" icon="el-icon-delete"></el-button>
</el-input>
<div v-if="form.serialNumbers" style="font-size: 12px; color: #909399; margin-top: 5px;">
已选 {{ selectedDeviceCount }} 个设备
</div>
</el-form-item>
</el-col>
<el-col :span="12" style="float: right"></el-col>
</el-row>
</el-form>
@ -164,6 +178,8 @@
</el-dialog>
<!-- 产品列表 -->
<productList ref="productList" @productEvent="getSelectProduct($event)"></productList>
<!-- 设备列表 -->
<deviceList ref="deviceListRef" @deviceEvent="getSelectDevice($event)"></deviceList>
<!-- 客户端列表 -->
<clientList ref="clientList" @clientEvent="getSelectClient($event)"></clientList>
<!-- 接入点列表 -->
@ -175,6 +191,7 @@
import { listScript, getScript, getScriptLog, delScript, addScript, updateScript, validateScript} from '@/api/iot/script';
import AceEditor from '@/views/components/editor/editor.vue';
import productList from './product-list';
import deviceList from './device-list';
import clientList from './client-list';
import bridgeList from '@/views/iot/scene/bridge-list.vue';
@ -185,6 +202,7 @@ export default {
bridgeList,
AceEditor,
productList,
deviceList,
clientList,
},
data() {
@ -208,6 +226,10 @@ export default {
loading: true,
// 日志遮罩层
logLoading: true,
// 设备选择显示文本
deviceDisplayText: '',
// 已选设备数量
selectedDeviceCount: 0,
// 选中数组
scriptIds: [],
// 非单个禁用
@ -283,12 +305,15 @@ export default {
reset() {
this.validateMsg = '';
this.isValidate = false;
this.deviceDisplayText = '';
this.selectedDeviceCount = 0;
this.form = {
id: null,
applicationName: 'fastbee', // 后端配置和规则/脚本需要一致
scriptId: null,
productId: null,
productName: '',
serialNumbers: '',
bridgeName: '',
scriptName: null,
scriptType: 'script',
@ -350,6 +375,12 @@ export default {
const scriptId = row.scriptId || this.scriptIds;
getScript(scriptId).then((response) => {
this.form = response.data;
// 恢复设备选择显示
if (this.form.serialNumbers) {
const devices = this.form.serialNumbers.split(',').filter(s => s);
this.selectedDeviceCount = devices.length;
this.deviceDisplayText = `已选 ${devices.length} 个设备`;
}
this.open = true;
this.title = this.$t('script.349087-26');
});
@ -387,6 +418,32 @@ export default {
getSelectProduct(data) {
this.form.productId = data.productId;
this.form.productName = data.productName;
// 产品变更时清空设备选择
this.clearDeviceSelect();
},
/**选择设备 */
handleSelectDevice() {
this.$refs.deviceListRef.queryParams.productId = this.form.productId;
this.$refs.deviceListRef.selectDeviceNums = this.form.serialNumbers ? this.form.serialNumbers.split(',') : [];
this.$refs.deviceListRef.openDeviceList = true;
this.$refs.deviceListRef.getList();
},
/**设备选择回调 */
getSelectDevice(data) {
this.form.serialNumbers = data.deviceNums.join(',');
this.selectedDeviceCount = data.deviceNums.length;
this.deviceDisplayText = `已选 ${data.deviceNums.length} 个设备`;
// 如果选择了设备,更新产品信息(确保产品ID一致)
if (data.productId && !this.form.productId) {
this.form.productId = data.productId;
this.form.productName = data.productName;
}
},
/**清空设备选择 */
clearDeviceSelect() {
this.form.serialNumbers = '';
this.selectedDeviceCount = 0;
this.deviceDisplayText = '';
},
/**选择桥接接入点 */
handleSelectBridge() {