import type { RuleSceneApi } from '#/api/iot/rule/scene';
|
|
import {
|
IotRuleSceneActionTypeEnum,
|
IotRuleSceneTriggerConditionTypeEnum,
|
IotRuleSceneTriggerTimeOperatorEnum,
|
IotRuleSceneTriggerTypeEnum,
|
isDeviceTrigger,
|
} from '..\..\..\packages\constants\src';
|
import { CronUtils, isEmptyVal, isObject } from '..\..\..\packages\utils\src';
|
|
/**
|
* 判断普通 ID 选择值是否缺失。
|
*
|
* 产品、告警配置等普通业务 ID 应为正数;`0` 不代表有效业务数据。
|
*
|
* @param value 普通业务 ID
|
* @returns 是否缺失
|
*/
|
function isRequiredIdMissing(value: unknown): boolean {
|
return !value;
|
}
|
|
/**
|
* 判断设备 ID 选择值是否缺失。
|
*
|
* 场景联动的设备选择器中,`0` 表示「全部设备」,是合法值;因此这里只能把
|
* `undefined`、`null`、空字符串视为未选择,不能使用普通 falsy 判断。
|
*
|
* @param value 设备 ID
|
* @returns 是否缺失
|
*/
|
function isDeviceIdMissing(value: unknown): boolean {
|
return isEmptyVal(value);
|
}
|
|
/**
|
* 判断执行器参数是否为空。
|
*
|
* 参数配置当前以 JSON 字符串为主,同时兼容历史对象值:
|
* - 空字符串、空白字符串视为空;
|
* - 空对象 `{}` 视为空;
|
* - 非空 JSON 对象视为已配置;
|
* - 非法 JSON 不在这里判空,交给 JSON 格式校验返回更准确的错误。
|
*
|
* @param params 执行器参数
|
* @returns 是否为空
|
*/
|
export function isActionParamsEmpty(params?: unknown): boolean {
|
if (isEmptyVal(params)) {
|
return true;
|
}
|
|
if (typeof params === 'string') {
|
if (!params.trim()) {
|
return true;
|
}
|
try {
|
const parsed = JSON.parse(params);
|
if (isObject(parsed) && !Array.isArray(parsed)) {
|
return Object.keys(parsed).length === 0;
|
}
|
} catch {
|
return false;
|
}
|
return false;
|
}
|
|
if (isObject(params) && !Array.isArray(params)) {
|
return Object.keys(params).length === 0;
|
}
|
|
return false;
|
}
|
|
/**
|
* 判断执行器参数是否为合法 JSON。
|
*
|
* 参数编辑组件正常会保存 JSON 字符串;编辑旧数据时可能仍然是对象,对象可直接视为合法。
|
*
|
* @param params 执行器参数
|
* @returns 是否合法
|
*/
|
function isActionParamsJsonValid(params?: unknown): boolean {
|
if (isObject(params)) {
|
return true;
|
}
|
try {
|
JSON.parse(String(params));
|
return true;
|
} catch {
|
return false;
|
}
|
}
|
|
/**
|
* 校验单个附加子条件。
|
*
|
* 该方法用于提交前兜底校验,错误提示会带上 path,方便定位到第几个触发器、
|
* 第几个条件组和第几个条件。
|
*
|
* @param condition 子条件配置
|
* @param path 错误提示前缀
|
* @returns 错误信息,通过则返回 null
|
*/
|
export function validateTriggerCondition(
|
condition: RuleSceneApi.TriggerCondition,
|
path: string,
|
): null | string {
|
if (!condition.type) {
|
return `${path}:条件类型不能为空`;
|
}
|
|
const isDeviceStatus =
|
condition.type === IotRuleSceneTriggerConditionTypeEnum.DEVICE_STATUS;
|
const isDeviceProperty =
|
condition.type === IotRuleSceneTriggerConditionTypeEnum.DEVICE_PROPERTY;
|
|
// 设备状态和设备属性都必须先选择产品、设备;deviceId = 0 表示全部设备。
|
if (isDeviceStatus || isDeviceProperty) {
|
if (isRequiredIdMissing(condition.productId)) {
|
return `${path}:产品不能为空`;
|
}
|
if (isDeviceIdMissing(condition.deviceId)) {
|
return `${path}:设备不能为空`;
|
}
|
}
|
|
// 设备状态只校验操作符和状态枚举值。
|
if (isDeviceStatus) {
|
if (!condition.operator) {
|
return `${path}:操作符不能为空`;
|
}
|
if (isEmptyVal(condition.param)) {
|
return `${path}:设备状态不能为空`;
|
}
|
return null;
|
}
|
|
// 设备属性需要校验物模型标识符、操作符和比较值。
|
if (isDeviceProperty) {
|
if (!condition.identifier) {
|
return `${path}:监控项不能为空`;
|
}
|
if (!condition.operator) {
|
return `${path}:操作符不能为空`;
|
}
|
if (isEmptyVal(condition.param)) {
|
return `${path}:比较值不能为空`;
|
}
|
return null;
|
}
|
|
// 当前时间按操作符动态判断 param 是否需要填写。
|
if (condition.type === IotRuleSceneTriggerConditionTypeEnum.CURRENT_TIME) {
|
if (!condition.operator) {
|
return `${path}:时间条件不能为空`;
|
}
|
|
if (
|
condition.operator === IotRuleSceneTriggerTimeOperatorEnum.TODAY.value
|
) {
|
return null;
|
}
|
|
if (isEmptyVal(condition.param)) {
|
return `${path}:时间值不能为空`;
|
}
|
|
if (
|
condition.operator ===
|
IotRuleSceneTriggerTimeOperatorEnum.BETWEEN_TIME.value
|
) {
|
const parts = String(condition.param).split(',');
|
if (!parts[0]?.trim() || !parts[1]?.trim()) {
|
return `${path}:开始和结束时间不能为空`;
|
}
|
}
|
}
|
|
return null;
|
}
|
|
/**
|
* 校验触发器的附加条件组。
|
*
|
* 条件组结构是二维数组:外层是 OR 关系的条件组,内层是 AND 关系的条件列表。
|
* 这里逐组、逐条件返回第一条错误,避免一次性弹出过多提示。
|
*
|
* @param groups 附加条件组
|
* @param triggerIndex 触发器在列表中的下标,用于生成可定位的错误提示
|
* @returns 错误信息,通过则返回 null
|
*/
|
export function validateTriggerConditionGroups(
|
groups: RuleSceneApi.TriggerCondition[][] | undefined,
|
triggerIndex: number,
|
): null | string {
|
if (!groups?.length) {
|
return null;
|
}
|
|
for (const [groupIndex, group] of groups.entries()) {
|
// 空条件组没有实际过滤条件,提交后语义不明确,需要拦截。
|
if (!Array.isArray(group) || group.length === 0) {
|
return `触发器 ${triggerIndex + 1}:条件组 ${groupIndex + 1} 不能为空`;
|
}
|
|
for (const [conditionIndex, condition] of group.entries()) {
|
const error = validateTriggerCondition(
|
condition,
|
`触发器 ${triggerIndex + 1} 条件组 ${groupIndex + 1} 条件 ${
|
conditionIndex + 1
|
}`,
|
);
|
if (error) {
|
return error;
|
}
|
}
|
}
|
|
return null;
|
}
|
|
/**
|
* 校验单个触发器配置。
|
*
|
* 该方法用于场景联动表单提交前兜底校验,避免触发器配置没有独立表单项 prop 时漏掉必填项。
|
* 校验逻辑需要和触发器主条件 UI 保持一致,同时继续校验附加条件组。
|
*
|
* @param trigger 触发器配置
|
* @param index 触发器在列表中的下标,用于生成可定位的错误提示
|
* @returns 错误信息,通过则返回 null
|
*/
|
export function validateTriggerItem(
|
trigger: RuleSceneApi.Trigger,
|
index: number,
|
): null | string {
|
const prefix = `触发器 ${index + 1}`;
|
|
if (!trigger.type) {
|
return `${prefix}:触发器类型不能为空`;
|
}
|
|
// 设备类触发器都有产品、设备两个基础字段;deviceId = 0 表示全部设备。
|
if (isDeviceTrigger(trigger.type)) {
|
if (isRequiredIdMissing(trigger.productId)) {
|
return `${prefix}:产品不能为空`;
|
}
|
if (isDeviceIdMissing(trigger.deviceId)) {
|
return `${prefix}:设备不能为空`;
|
}
|
|
// 设备状态变化不依赖物模型标识符,只校验操作符和状态值。
|
if (trigger.type === IotRuleSceneTriggerTypeEnum.DEVICE_STATE_UPDATE) {
|
if (!trigger.operator) {
|
return `${prefix}:操作符不能为空`;
|
}
|
if (isEmptyVal(trigger.value)) {
|
return `${prefix}:设备状态不能为空`;
|
}
|
} else {
|
if (!trigger.identifier) {
|
return `${prefix}:监控项不能为空`;
|
}
|
|
// 事件上报和服务调用只监听是否发生,不需要额外的操作符和比较值。
|
const isEventOrService =
|
trigger.type === IotRuleSceneTriggerTypeEnum.DEVICE_EVENT_POST ||
|
trigger.type === IotRuleSceneTriggerTypeEnum.DEVICE_SERVICE_INVOKE;
|
if (!isEventOrService) {
|
if (!trigger.operator) {
|
return `${prefix}:操作符不能为空`;
|
}
|
if (isEmptyVal(trigger.value)) {
|
return `${prefix}:参数值不能为空`;
|
}
|
}
|
}
|
}
|
|
// 定时触发器需要 CRON 表达式,并继续校验 CRON 格式。
|
if (trigger.type === IotRuleSceneTriggerTypeEnum.TIMER) {
|
if (!trigger.cronExpression) {
|
return `${prefix}:CRON 表达式不能为空`;
|
}
|
if (!CronUtils.validate(trigger.cronExpression)) {
|
return `${prefix}:CRON 表达式格式不正确`;
|
}
|
}
|
|
return validateTriggerConditionGroups(trigger.conditionGroups, index);
|
}
|
|
/**
|
* 校验触发器列表。
|
*
|
* 场景联动至少需要一个触发器;列表内逐条返回第一条错误,避免一次提交出现多条提示。
|
*
|
* @param triggers 触发器列表
|
* @returns 错误信息,通过则返回 null
|
*/
|
export function validateSceneRuleTriggers(
|
triggers?: RuleSceneApi.Trigger[],
|
): null | string {
|
if (!triggers?.length) {
|
return '至少需要一个触发器';
|
}
|
|
for (const [index, trigger] of triggers.entries()) {
|
const error = validateTriggerItem(trigger, index);
|
if (error) {
|
return error;
|
}
|
}
|
|
return null;
|
}
|
|
/**
|
* 校验单个执行器配置。
|
*
|
* 该方法用于场景联动表单提交前兜底校验,避免执行器配置没有独立表单项 prop 时漏掉必填项。
|
* 校验逻辑需要和执行器 UI 保持一致。
|
*
|
* @param action 执行器配置
|
* @param index 执行器在列表中的下标,用于生成可定位的错误提示
|
* @returns 错误信息,通过则返回 null
|
*/
|
export function validateActionItem(
|
action: RuleSceneApi.Action,
|
index: number,
|
): null | string {
|
const prefix = `执行器 ${index + 1}`;
|
|
if (!action.type) {
|
return `${prefix}:执行器类型不能为空`;
|
}
|
|
// 设备属性设置和设备服务调用都需要指定设备,并填写物模型参数。
|
if (
|
action.type === IotRuleSceneActionTypeEnum.DEVICE_PROPERTY_SET ||
|
action.type === IotRuleSceneActionTypeEnum.DEVICE_SERVICE_INVOKE
|
) {
|
if (isRequiredIdMissing(action.productId)) {
|
return `${prefix}:产品不能为空`;
|
}
|
if (isDeviceIdMissing(action.deviceId)) {
|
return `${prefix}:设备不能为空`;
|
}
|
if (
|
action.type === IotRuleSceneActionTypeEnum.DEVICE_SERVICE_INVOKE &&
|
!action.identifier
|
) {
|
return `${prefix}:服务不能为空`;
|
}
|
|
if (isActionParamsEmpty(action.params)) {
|
return `${prefix}:参数配置不能为空`;
|
}
|
if (!isActionParamsJsonValid(action.params)) {
|
return `${prefix}:参数格式须为合法 JSON`;
|
}
|
|
return null;
|
}
|
|
// 告警恢复执行器需要绑定具体告警配置;触发告警不需要预选告警配置。
|
if (
|
action.type === IotRuleSceneActionTypeEnum.ALERT_RECOVER &&
|
!action.alertConfigId
|
) {
|
return `${prefix}:告警配置不能为空`;
|
}
|
|
return null;
|
}
|
|
/**
|
* 校验执行器列表。
|
*
|
* 场景联动至少需要一个执行器;列表内逐条返回第一条错误,避免一次提交出现多条提示。
|
*
|
* @param actions 执行器列表
|
* @returns 错误信息,通过则返回 null
|
*/
|
export function validateSceneRuleActions(
|
actions?: RuleSceneApi.Action[],
|
): null | string {
|
if (!actions?.length) {
|
return '至少需要一个执行器';
|
}
|
|
for (const [index, action] of actions.entries()) {
|
const error = validateActionItem(action, index);
|
if (error) {
|
return error;
|
}
|
}
|
|
return null;
|
}
|