Merge branch 'feature-transform-node-data'

This commit is contained in:
2026-04-14 17:30:54 +08:00
24 changed files with 2406 additions and 37 deletions

View File

@@ -6,7 +6,10 @@ import com.solution.common.core.domain.AjaxResult;
import com.solution.common.core.page.TableDataInfo;
import com.solution.common.enums.BusinessType;
import com.solution.rule.domain.Rule;
import com.solution.rule.domain.config.RuleConfig;
import com.solution.rule.domain.config.RuleConfigQuery;
import com.solution.rule.service.IRuleService;
import com.solution.rule.service.IRuleConfigService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
@@ -22,6 +25,8 @@ public class RuleController extends BaseController {
@Autowired
private IRuleService ruleService;
@Autowired
private IRuleConfigService ruleConfigService;
@PreAuthorize("@ss.hasPermi('system:rule:list')")
@GetMapping("/list")
@@ -62,4 +67,50 @@ public class RuleController extends BaseController {
public AjaxResult remove(@PathVariable Integer[] ids) {
return toAjax(ruleService.deleteRuleByIds(ids));
}
@PreAuthorize("@ss.hasPermi('system:rule:list')")
@GetMapping("/config/list")
@ApiOperation("查询规则聚合列表")
public TableDataInfo configList(RuleConfigQuery query) {
startPage();
return getDataTable(ruleConfigService.selectRuleConfigList(query));
}
@PreAuthorize("@ss.hasPermi('system:rule:query')")
@GetMapping("/config/{ruleCode}")
@ApiOperation("查询规则聚合详情")
public AjaxResult configInfo(@PathVariable String ruleCode) {
return success(ruleConfigService.selectRuleConfigByCode(ruleCode));
}
@PreAuthorize("@ss.hasPermi('system:rule:add')")
@Log(title = "规则聚合管理", businessType = BusinessType.INSERT)
@PostMapping("/config")
@ApiOperation("新增规则聚合")
public AjaxResult addConfig(@RequestBody RuleConfig ruleConfig) {
return toAjax(ruleConfigService.insertRuleConfig(ruleConfig));
}
@PreAuthorize("@ss.hasPermi('system:rule:edit')")
@Log(title = "规则聚合管理", businessType = BusinessType.UPDATE)
@PutMapping("/config")
@ApiOperation("修改规则聚合")
public AjaxResult editConfig(@RequestBody RuleConfig ruleConfig) {
return toAjax(ruleConfigService.updateRuleConfig(ruleConfig));
}
@PreAuthorize("@ss.hasPermi('system:rule:remove')")
@Log(title = "规则聚合管理", businessType = BusinessType.DELETE)
@DeleteMapping("/config/{ruleCodes}")
@ApiOperation("删除规则聚合")
public AjaxResult removeConfig(@PathVariable String[] ruleCodes) {
return toAjax(ruleConfigService.deleteRuleConfigByCodes(ruleCodes));
}
@PreAuthorize("@ss.hasPermi('system:rule:query')")
@GetMapping("/config/dict/{dictType}")
@ApiOperation("按类型查询规则字典")
public AjaxResult dict(@PathVariable String dictType) {
return success(ruleConfigService.selectDictByType(dictType));
}
}

View File

@@ -64,4 +64,15 @@ public class SceneController extends BaseController {
public AjaxResult getAllTree(@PathVariable Integer id){
return success(sceneService.getAllTree(id));
}
/**
* 根据场景id获取场景下所有关系
* @param id
* @return
*/
@GetMapping("/getAllRelation/{id}")
@ApiOperation("根据场景id获取场景下所有关系")
public AjaxResult getAllRelation(@PathVariable Integer id){
return success(sceneService.getAllRelation(id));
}
}

View File

@@ -6,11 +6,9 @@ spring:
druid:
# 主库数据源
master:
# url: jdbc:mysql://192.168.166.71:3306/behaviortreedb?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
url: jdbc:mysql://localhost:3306/autosolution_db?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
url: jdbc:mysql://127.0.0.1:3306/autosolution_db?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root
# password: 123456
password: 1234
password: 123456
# 从库数据源
slave:
# 从数据源开关/默认关闭

View File

@@ -0,0 +1,61 @@
package com.solution.rule.domain.config;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.Date;
import java.util.List;
@Data
@ApiModel("规则聚合对象")
public class RuleConfig {
@ApiModelProperty("主键ID")
private Long id;
@ApiModelProperty("规则编码")
private String ruleCode;
@ApiModelProperty("规则名称")
private String ruleName;
@ApiModelProperty("层级编码(task/action/platform)")
private String levelCode;
@ApiModelProperty("种类编码(select/assign/deploy/config/mode/spacetime/relation/limit)")
private String kindCode;
@ApiModelProperty("模块编码(equipment/target/position/track/group)")
private String moduleCode;
@ApiModelProperty("优先级(数字越小越先执行)")
private Integer priorityNo;
@ApiModelProperty("条件表达式")
private String conditionExpr;
@ApiModelProperty("动作表达式")
private String actionExpr;
@ApiModelProperty("版本号")
private Integer versionNo;
@ApiModelProperty("是否启用(1是0否)")
private Integer enabled;
@ApiModelProperty("备注")
private String remark;
@ApiModelProperty("创建时间")
private Date createdAt;
@ApiModelProperty("更新时间")
private Date updatedAt;
@ApiModelProperty("参数列表")
private List<RuleConfigParam> params;
@ApiModelProperty("适用任务类型编码列表")
private List<String> taskTypes;
}

View File

@@ -0,0 +1,34 @@
package com.solution.rule.domain.config;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
@ApiModel("规则参数对象")
public class RuleConfigParam {
@ApiModelProperty("规则编码")
private String ruleCode;
@ApiModelProperty("参数键")
private String paramKey;
@ApiModelProperty("参数值")
private String paramVal;
@ApiModelProperty("值类型(string/number/bool/json)")
private String valType;
@ApiModelProperty("参数名称")
private String paramName;
@ApiModelProperty("排序号")
private Integer sortNo;
@ApiModelProperty("是否启用(1是0否)")
private Integer enabled;
@ApiModelProperty("备注")
private String remark;
}

View File

@@ -0,0 +1,28 @@
package com.solution.rule.domain.config;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
@ApiModel("规则配置查询对象")
public class RuleConfigQuery {
@ApiModelProperty("规则编码")
private String ruleCode;
@ApiModelProperty("规则名称")
private String ruleName;
@ApiModelProperty("层级编码(task/action/platform)")
private String levelCode;
@ApiModelProperty("种类编码(select/assign/deploy/config/mode/spacetime/relation/limit)")
private String kindCode;
@ApiModelProperty("模块编码(equipment/target/position/track/group)")
private String moduleCode;
@ApiModelProperty("是否启用(1是0否)")
private Integer enabled;
}

View File

@@ -0,0 +1,28 @@
package com.solution.rule.domain.config;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
@ApiModel("规则字典项")
public class RuleDictItem {
@ApiModelProperty("字典类型")
private String dictType;
@ApiModelProperty("字典编码")
private String dictCode;
@ApiModelProperty("字典名称")
private String dictName;
@ApiModelProperty("排序号")
private Integer sortNo;
@ApiModelProperty("是否启用(1是0否)")
private Integer enabled;
@ApiModelProperty("备注")
private String remark;
}

View File

@@ -0,0 +1,38 @@
package com.solution.rule.mapper;
import com.solution.rule.domain.config.RuleConfig;
import com.solution.rule.domain.config.RuleConfigParam;
import com.solution.rule.domain.config.RuleConfigQuery;
import com.solution.rule.domain.config.RuleDictItem;
import org.apache.ibatis.annotations.Param;
import java.util.List;
public interface RuleConfigMapper {
List<RuleConfig> selectRuleConfigList(RuleConfigQuery query);
RuleConfig selectRuleConfigByCode(@Param("ruleCode") String ruleCode);
int countByRuleCode(@Param("ruleCode") String ruleCode);
int insertRuleConfig(RuleConfig ruleConfig);
int updateRuleConfig(RuleConfig ruleConfig);
int deleteRuleConfigByCodes(@Param("ruleCodes") String[] ruleCodes);
List<RuleConfigParam> selectParamsByRuleCode(@Param("ruleCode") String ruleCode);
int deleteParamsByRuleCodes(@Param("ruleCodes") String[] ruleCodes);
int insertParamsBatch(@Param("params") List<RuleConfigParam> params);
List<String> selectTaskTypesByRuleCode(@Param("ruleCode") String ruleCode);
int deleteTaskTypesByRuleCodes(@Param("ruleCodes") String[] ruleCodes);
int insertTaskTypesBatch(@Param("ruleCode") String ruleCode, @Param("taskTypes") List<String> taskTypes);
List<RuleDictItem> selectDictByType(@Param("dictType") String dictType);
}

View File

@@ -0,0 +1,22 @@
package com.solution.rule.service;
import com.solution.rule.domain.config.RuleConfig;
import com.solution.rule.domain.config.RuleConfigQuery;
import com.solution.rule.domain.config.RuleDictItem;
import java.util.List;
public interface IRuleConfigService {
List<RuleConfig> selectRuleConfigList(RuleConfigQuery query);
RuleConfig selectRuleConfigByCode(String ruleCode);
int insertRuleConfig(RuleConfig ruleConfig);
int updateRuleConfig(RuleConfig ruleConfig);
int deleteRuleConfigByCodes(String[] ruleCodes);
List<RuleDictItem> selectDictByType(String dictType);
}

View File

@@ -0,0 +1,139 @@
package com.solution.rule.service.impl;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import com.solution.common.constant.ExceptionConstants;
import com.solution.rule.domain.config.RuleConfig;
import com.solution.rule.domain.config.RuleConfigParam;
import com.solution.rule.domain.config.RuleConfigQuery;
import com.solution.rule.domain.config.RuleDictItem;
import com.solution.rule.mapper.RuleConfigMapper;
import com.solution.rule.service.IRuleConfigService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@Service
public class RuleConfigServiceImpl implements IRuleConfigService {
@Autowired
private RuleConfigMapper ruleConfigMapper;
@Override
public List<RuleConfig> selectRuleConfigList(RuleConfigQuery query) {
return ruleConfigMapper.selectRuleConfigList(query);
}
@Override
public RuleConfig selectRuleConfigByCode(String ruleCode) {
RuleConfig config = ruleConfigMapper.selectRuleConfigByCode(ruleCode);
if (config == null) {
return null;
}
config.setParams(ruleConfigMapper.selectParamsByRuleCode(ruleCode));
config.setTaskTypes(ruleConfigMapper.selectTaskTypesByRuleCode(ruleCode));
return config;
}
@Override
@Transactional(rollbackFor = Exception.class)
public int insertRuleConfig(RuleConfig ruleConfig) {
validateBase(ruleConfig);
if (ruleConfigMapper.countByRuleCode(ruleConfig.getRuleCode()) > 0) {
throw new RuntimeException("规则编码已存在");
}
int rows = ruleConfigMapper.insertRuleConfig(fillDefault(ruleConfig));
saveChildren(ruleConfig);
return rows;
}
@Override
@Transactional(rollbackFor = Exception.class)
public int updateRuleConfig(RuleConfig ruleConfig) {
validateBase(ruleConfig);
if (ruleConfigMapper.countByRuleCode(ruleConfig.getRuleCode()) <= 0) {
throw new RuntimeException("规则编码不存在");
}
int rows = ruleConfigMapper.updateRuleConfig(fillDefault(ruleConfig));
String[] ruleCodes = {ruleConfig.getRuleCode()};
ruleConfigMapper.deleteParamsByRuleCodes(ruleCodes);
ruleConfigMapper.deleteTaskTypesByRuleCodes(ruleCodes);
saveChildren(ruleConfig);
return rows;
}
@Override
@Transactional(rollbackFor = Exception.class)
public int deleteRuleConfigByCodes(String[] ruleCodes) {
if (ruleCodes == null || ruleCodes.length == 0) {
return 0;
}
ruleConfigMapper.deleteParamsByRuleCodes(ruleCodes);
ruleConfigMapper.deleteTaskTypesByRuleCodes(ruleCodes);
return ruleConfigMapper.deleteRuleConfigByCodes(ruleCodes);
}
@Override
public List<RuleDictItem> selectDictByType(String dictType) {
if (ObjectUtil.isEmpty(dictType)) {
throw new RuntimeException(ExceptionConstants.PARAMETER_EXCEPTION);
}
return ruleConfigMapper.selectDictByType(dictType);
}
private void saveChildren(RuleConfig ruleConfig) {
if (CollUtil.isNotEmpty(ruleConfig.getParams())) {
Set<String> keys = new HashSet<>();
for (RuleConfigParam param : ruleConfig.getParams()) {
if (param == null || ObjectUtil.isEmpty(param.getParamKey())) {
throw new RuntimeException("参数键不能为空");
}
if (!keys.add(param.getParamKey())) {
throw new RuntimeException("参数键重复: " + param.getParamKey());
}
param.setRuleCode(ruleConfig.getRuleCode());
if (param.getSortNo() == null) {
param.setSortNo(0);
}
if (param.getEnabled() == null) {
param.setEnabled(1);
}
if (ObjectUtil.isEmpty(param.getValType())) {
param.setValType("string");
}
}
ruleConfigMapper.insertParamsBatch(ruleConfig.getParams());
}
if (CollUtil.isNotEmpty(ruleConfig.getTaskTypes())) {
ruleConfigMapper.insertTaskTypesBatch(ruleConfig.getRuleCode(), ruleConfig.getTaskTypes());
}
}
private RuleConfig fillDefault(RuleConfig ruleConfig) {
if (ruleConfig.getPriorityNo() == null) {
ruleConfig.setPriorityNo(100);
}
if (ruleConfig.getVersionNo() == null) {
ruleConfig.setVersionNo(1);
}
if (ruleConfig.getEnabled() == null) {
ruleConfig.setEnabled(1);
}
return ruleConfig;
}
private void validateBase(RuleConfig ruleConfig) {
if (ruleConfig == null
|| ObjectUtil.isEmpty(ruleConfig.getRuleCode())
|| ObjectUtil.isEmpty(ruleConfig.getRuleName())
|| ObjectUtil.isEmpty(ruleConfig.getLevelCode())
|| ObjectUtil.isEmpty(ruleConfig.getKindCode())
|| ObjectUtil.isEmpty(ruleConfig.getModuleCode())) {
throw new RuntimeException(ExceptionConstants.PARAMETER_EXCEPTION);
}
}
}

View File

@@ -82,7 +82,7 @@
</resultMap>
<select id="findComponentsByPlatformId" resultMap="VPlatformComponentMap">
SELECT * FROM platform_component
WHERE platform_id=#{platformId}
WHERE platform_id=#{platformId} AND platform_component.type = "comm"
</select>
<resultMap id="VPBasicPlatformMap" type="com.solution.rule.domain.BasicPlatform">
@@ -112,13 +112,21 @@
FROM platform p
LEFT JOIN platform_component pc ON p.id = pc.platform_id
WHERE pc.type = 'comm'
AND p.scenario_id = #{scenarioId}
AND p.scenario_id = #{scenarioId}
ORDER BY p.name,pc.name
</select>
<!-- SELECT
p.id,
p.name,
p.description
FROM platform p
LEFT JOIN platform_component pc ON p.id = pc.platform_id
WHERE pc.type = 'comm'
AND p.scenario_id = #{scenarioId}
ORDER BY p.name,pc.name
</select>
<select id="findAllPlatformComponents" resultMap="VPlatformMap">
SELECT *
FROM platform
<select id="findAllPlatformComponents" resultMap="VPlatformMap">
SELECT *
FROM platform-->
</select>
</mapper>
</mapper>

View File

@@ -0,0 +1,174 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.solution.rule.mapper.RuleConfigMapper">
<resultMap id="RuleConfigMap" type="com.solution.rule.domain.config.RuleConfig">
<id property="id" column="id"/>
<result property="ruleCode" column="rule_code"/>
<result property="ruleName" column="rule_name"/>
<result property="levelCode" column="level_code"/>
<result property="kindCode" column="kind_code"/>
<result property="moduleCode" column="module_code"/>
<result property="priorityNo" column="priority_no"/>
<result property="conditionExpr" column="condition_expr"/>
<result property="actionExpr" column="action_expr"/>
<result property="versionNo" column="version_no"/>
<result property="enabled" column="enabled"/>
<result property="remark" column="remark"/>
<result property="createdAt" column="created_at"/>
<result property="updatedAt" column="updated_at"/>
</resultMap>
<resultMap id="RuleConfigParamMap" type="com.solution.rule.domain.config.RuleConfigParam">
<result property="ruleCode" column="rule_code"/>
<result property="paramKey" column="param_key"/>
<result property="paramVal" column="param_val"/>
<result property="valType" column="val_type"/>
<result property="paramName" column="param_name"/>
<result property="sortNo" column="sort_no"/>
<result property="enabled" column="enabled"/>
<result property="remark" column="remark"/>
</resultMap>
<resultMap id="RuleDictItemMap" type="com.solution.rule.domain.config.RuleDictItem">
<result property="dictType" column="dict_type"/>
<result property="dictCode" column="dict_code"/>
<result property="dictName" column="dict_name"/>
<result property="sortNo" column="sort_no"/>
<result property="enabled" column="enabled"/>
<result property="remark" column="remark"/>
</resultMap>
<select id="selectRuleConfigList" resultMap="RuleConfigMap">
SELECT id, rule_code, rule_name, level_code, kind_code, module_code, priority_no,
condition_expr, action_expr, version_no, enabled, remark, created_at, updated_at
FROM rule_item
<where>
<if test="ruleCode != null and ruleCode != ''">
AND rule_code = #{ruleCode}
</if>
<if test="ruleName != null and ruleName != ''">
AND rule_name LIKE CONCAT('%', #{ruleName}, '%')
</if>
<if test="levelCode != null and levelCode != ''">
AND level_code = #{levelCode}
</if>
<if test="kindCode != null and kindCode != ''">
AND kind_code = #{kindCode}
</if>
<if test="moduleCode != null and moduleCode != ''">
AND module_code = #{moduleCode}
</if>
<if test="enabled != null">
AND enabled = #{enabled}
</if>
</where>
ORDER BY priority_no ASC, updated_at DESC
</select>
<select id="selectRuleConfigByCode" resultMap="RuleConfigMap">
SELECT id, rule_code, rule_name, level_code, kind_code, module_code, priority_no,
condition_expr, action_expr, version_no, enabled, remark, created_at, updated_at
FROM rule_item
WHERE rule_code = #{ruleCode}
</select>
<select id="countByRuleCode" resultType="int">
SELECT COUNT(1)
FROM rule_item
WHERE rule_code = #{ruleCode}
</select>
<insert id="insertRuleConfig" parameterType="com.solution.rule.domain.config.RuleConfig">
INSERT INTO rule_item
(rule_code, rule_name, level_code, kind_code, module_code, priority_no, condition_expr,
action_expr, version_no, enabled, remark, created_at, updated_at)
VALUES
(#{ruleCode}, #{ruleName}, #{levelCode}, #{kindCode}, #{moduleCode}, #{priorityNo}, #{conditionExpr},
#{actionExpr}, #{versionNo}, #{enabled}, #{remark}, NOW(), NOW())
</insert>
<update id="updateRuleConfig" parameterType="com.solution.rule.domain.config.RuleConfig">
UPDATE rule_item
<set>
<if test="ruleName != null">rule_name = #{ruleName},</if>
<if test="levelCode != null">level_code = #{levelCode},</if>
<if test="kindCode != null">kind_code = #{kindCode},</if>
<if test="moduleCode != null">module_code = #{moduleCode},</if>
<if test="priorityNo != null">priority_no = #{priorityNo},</if>
<if test="conditionExpr != null">condition_expr = #{conditionExpr},</if>
<if test="actionExpr != null">action_expr = #{actionExpr},</if>
<if test="versionNo != null">version_no = #{versionNo},</if>
<if test="enabled != null">enabled = #{enabled},</if>
<if test="remark != null">remark = #{remark},</if>
updated_at = NOW()
</set>
WHERE rule_code = #{ruleCode}
</update>
<delete id="deleteRuleConfigByCodes">
DELETE FROM rule_item
WHERE rule_code IN
<foreach item="code" collection="ruleCodes" open="(" separator="," close=")">
#{code}
</foreach>
</delete>
<select id="selectParamsByRuleCode" resultMap="RuleConfigParamMap">
SELECT rule_code, param_key, param_val, val_type, param_name, sort_no, enabled, remark
FROM rule_item_param
WHERE rule_code = #{ruleCode}
ORDER BY sort_no ASC, id ASC
</select>
<delete id="deleteParamsByRuleCodes">
DELETE FROM rule_item_param
WHERE rule_code IN
<foreach item="code" collection="ruleCodes" open="(" separator="," close=")">
#{code}
</foreach>
</delete>
<insert id="insertParamsBatch">
INSERT INTO rule_item_param
(rule_code, param_key, param_val, val_type, param_name, sort_no, enabled, remark, created_at, updated_at)
VALUES
<foreach item="item" collection="params" separator=",">
(#{item.ruleCode}, #{item.paramKey}, #{item.paramVal}, #{item.valType}, #{item.paramName},
#{item.sortNo}, #{item.enabled}, #{item.remark}, NOW(), NOW())
</foreach>
</insert>
<select id="selectTaskTypesByRuleCode" resultType="string">
SELECT task_type_code
FROM rule_item_task_type
WHERE rule_code = #{ruleCode}
ORDER BY id ASC
</select>
<delete id="deleteTaskTypesByRuleCodes">
DELETE FROM rule_item_task_type
WHERE rule_code IN
<foreach item="code" collection="ruleCodes" open="(" separator="," close=")">
#{code}
</foreach>
</delete>
<insert id="insertTaskTypesBatch">
INSERT INTO rule_item_task_type (rule_code, task_type_code, created_at)
VALUES
<foreach item="taskType" collection="taskTypes" separator=",">
(#{ruleCode}, #{taskType}, NOW())
</foreach>
</insert>
<select id="selectDictByType" resultMap="RuleDictItemMap">
SELECT dict_type, dict_code, dict_name, sort_no, enabled, remark
FROM rule_dict
WHERE dict_type = #{dictType}
ORDER BY sort_no ASC, id ASC
</select>
</mapper>

View File

@@ -0,0 +1,67 @@
-- 规则主数据表结构MySQL 8+
-- 说明:用于前端按“层级->种类->规则项”进行展示与增删改查。
CREATE TABLE IF NOT EXISTS `rule_dict` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`dict_type` VARCHAR(32) NOT NULL COMMENT '字典类型(level/kind/task_type/status)',
`dict_code` VARCHAR(64) NOT NULL COMMENT '字典编码',
`dict_name` VARCHAR(64) NOT NULL COMMENT '字典名称',
`sort_no` INT NOT NULL DEFAULT 0 COMMENT '排序号',
`enabled` TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否启用(1是0否)',
`remark` VARCHAR(255) DEFAULT NULL COMMENT '备注',
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_rule_dict_type_code` (`dict_type`, `dict_code`),
KEY `idx_rule_dict_type_enabled` (`dict_type`, `enabled`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='规则字典表';
CREATE TABLE IF NOT EXISTS `rule_item` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`rule_code` VARCHAR(64) NOT NULL COMMENT '规则编码',
`rule_name` VARCHAR(128) NOT NULL COMMENT '规则名称',
`level_code` VARCHAR(32) NOT NULL COMMENT '规则层级(task/action/platform)',
`kind_code` VARCHAR(32) NOT NULL COMMENT '规则种类(select/assign/deploy/config/mode/spacetime/relation/limit)',
`module_code` VARCHAR(32) NOT NULL COMMENT '规则模块(equipment/target/position/track/group)',
`priority_no` INT NOT NULL DEFAULT 100 COMMENT '优先级(数字越小越先执行)',
`condition_expr` VARCHAR(1024) DEFAULT NULL COMMENT '条件表达式(展示用)',
`action_expr` VARCHAR(1024) DEFAULT NULL COMMENT '动作表达式(展示用)',
`version_no` INT NOT NULL DEFAULT 1 COMMENT '版本号',
`enabled` TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否启用(1是0否)',
`remark` VARCHAR(255) DEFAULT NULL COMMENT '备注',
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_rule_item_code` (`rule_code`),
KEY `idx_rule_item_level_kind` (`level_code`, `kind_code`),
KEY `idx_rule_item_module_enabled` (`module_code`, `enabled`),
KEY `idx_rule_item_priority` (`priority_no`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='规则主表';
CREATE TABLE IF NOT EXISTS `rule_item_task_type` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`rule_code` VARCHAR(64) NOT NULL COMMENT '规则编码',
`task_type_code` VARCHAR(32) NOT NULL COMMENT '任务类型编码',
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_rule_task_type` (`rule_code`, `task_type_code`),
KEY `idx_rule_task_type` (`task_type_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='规则适用任务类型关联表';
CREATE TABLE IF NOT EXISTS `rule_item_param` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`rule_code` VARCHAR(64) NOT NULL COMMENT '规则编码',
`param_key` VARCHAR(128) NOT NULL COMMENT '参数键',
`param_val` TEXT NOT NULL COMMENT '参数值(统一文本存储)',
`val_type` VARCHAR(16) NOT NULL DEFAULT 'string' COMMENT '值类型(string/number/bool/json)',
`param_name` VARCHAR(128) DEFAULT NULL COMMENT '参数名称',
`sort_no` INT NOT NULL DEFAULT 0 COMMENT '排序号',
`enabled` TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否启用(1是0否)',
`remark` VARCHAR(255) DEFAULT NULL COMMENT '备注',
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_rule_param` (`rule_code`, `param_key`),
KEY `idx_rule_param_key` (`param_key`),
KEY `idx_rule_param_enabled` (`enabled`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='规则参数表';

View File

@@ -0,0 +1,211 @@
-- 从 rules/rule.drl 提取的初始化数据
-- 说明:本文件提供字典、规则主数据、规则参数与任务类型关联的初始记录。
-- 1) 字典数据
INSERT INTO `rule_dict` (`dict_type`, `dict_code`, `dict_name`, `sort_no`, `enabled`, `remark`) VALUES
('level', 'task', '任务级', 10, 1, '任务层面的选择/分配/限制'),
('level', 'action', '行动级', 20, 1, '行动层面的航迹/编组/模式'),
('level', 'platform', '平台级', 30, 1, '平台层面的部署/时空约束'),
('kind', 'select', '选择', 10, 1, '对象选择与评分'),
('kind', 'assign', '分配', 20, 1, '目标分配'),
('kind', 'deploy', '部署', 30, 1, '阵位与部署'),
('kind', 'config', '配置', 40, 1, '通用配置参数'),
('kind', 'mode', '工作模式', 50, 1, '算法与模式选择'),
('kind', 'spacetime', '时空约束', 60, 1, '空间和时间相关约束'),
('kind', 'relation', '关联关系', 70, 1, '蓝红关键词关联'),
('kind', 'limit', '限制条件', 80, 1, '阈值和边界'),
('task_type', 'strike', '打击任务', 10, 1, '典型任务类型'),
('task_type', 'recon', '侦察任务', 20, 1, '典型任务类型'),
('task_type', 'intercept', '拦截任务', 30, 1, '典型任务类型'),
('task_type', 'support', '支援任务', 40, 1, '典型任务类型'),
('task_type', 'jamming', '干扰任务', 50, 1, '典型任务类型'),
('status', 'enabled', '启用', 10, 1, '通用状态'),
('status', 'disabled', '停用', 20, 1, '通用状态')
ON DUPLICATE KEY UPDATE
`dict_name` = VALUES(`dict_name`),
`sort_no` = VALUES(`sort_no`),
`enabled` = VALUES(`enabled`),
`remark` = VALUES(`remark`);
-- 2) 规则主数据(层级->种类->规则项)
INSERT INTO `rule_item`
(`rule_code`, `rule_name`, `level_code`, `kind_code`, `module_code`, `priority_no`, `condition_expr`, `action_expr`, `version_no`, `enabled`, `remark`)
VALUES
('R_TASK_SELECT_BASE', '任务级-装备选择基础规则', 'task', 'select', 'equipment', 100, 'task!=null', 'equipmentRule(fact, params)', 1, 1, '来自装备匹配主流程'),
('R_TASK_SELECT_SLOT_1', '任务级-规则槽1', 'task', 'relation', 'equipment', 101, 'containsAny(blue,slot1Blue)&&containsAny(red,slot1Red)', 'score += ruleScore_1*weight', 1, 1, '蓝红关键词槽位匹配'),
('R_TASK_SELECT_SLOT_2', '任务级-规则槽2', 'task', 'relation', 'equipment', 102, 'containsAny(blue,slot2Blue)&&containsAny(red,slot2Red)', 'score += ruleScore_2*weight', 1, 1, '蓝红关键词槽位匹配'),
('R_TASK_SELECT_SLOT_3', '任务级-规则槽3', 'task', 'relation', 'equipment', 103, 'containsAny(blue,slot3Blue)&&containsAny(red,slot3Red)', 'score += ruleScore_3*weight', 1, 1, '蓝红关键词槽位匹配'),
('R_TASK_REL_AIR_PLATFORM', '任务级-关联关系-空中平台', 'task', 'relation', 'equipment', 104, 'bluePlatformKeywords_air && redPreferredWhenBlueAir', 'score += airScore*weight', 1, 1, '兼容层空中平台关联'),
('R_TASK_REL_AIR_TASK', '任务级-关联关系-空中任务', 'task', 'relation', 'equipment', 105, 'airTaskKeywords && redPreferredWhenBlueAir', 'score += airTaskScore*weight', 1, 1, '兼容层空中任务关联'),
('R_TASK_REL_GROUND_TASK', '任务级-关联关系-地面任务', 'task', 'relation', 'equipment', 106, 'groundTaskKeywords && redPreferredWhenGround', 'score += groundScore*weight', 1, 1, '兼容层地面任务关联'),
('R_TASK_REL_TANK', '任务级-关联关系-坦克装甲', 'task', 'relation', 'equipment', 107, 'tankKeywords && redMatchKeywords_tank', 'score += tankScore*weight', 1, 1, '兼容层坦克关联'),
('R_TASK_REL_MISSILE', '任务级-关联关系-导弹火箭', 'task', 'relation', 'equipment', 108, 'missileKeywords && redMatchKeywords_missile', 'score += missileScore*weight', 1, 1, '兼容层导弹关联'),
('R_TASK_ASSIGN_TARGET', '任务级-目标分配规则', 'task', 'assign', 'target', 90, 'task!=null', 'target(fact, params)', 1, 1, '目标分配与execute填充'),
('R_TASK_LIMIT_SUPPLEMENT', '任务级-低命中率补拿限制', 'task', 'limit', 'target', 89, 'hitRate<threshold', '补拿装备并更新任务', 1, 1, '补拿轮次和数量限制'),
('R_PLATFORM_DEPLOY', '平台级-阵位部署规则', 'platform', 'deploy', 'position', 80, 'positionRuleEnabled=true', 'position(fact, params)', 1, 1, '平台部署与编队参数'),
('R_PLATFORM_SPACETIME', '平台级-时空约束规则', 'platform', 'spacetime', 'position', 79, 'enableWarZoneClamp=true', '平台点位约束到作战区', 1, 1, '阵位空间约束'),
('R_ACTION_TRACK_ROUTE', '行动级-航迹生成规则', 'action', 'mode', 'track', 70, 'trackRuleEnabled=true', 'trackRoute(fact, params)', 1, 1, '航迹算法与路由绑定'),
('R_ACTION_TRACK_SPACETIME', '行动级-航迹时空约束', 'action', 'spacetime', 'track', 69, 'enableTrackWarZoneClamp=true', '航迹点约束到作战区', 1, 1, '航迹空间约束'),
('R_ACTION_GROUP_FORMATION', '行动级-编组规则', 'action', 'mode', 'group', 60, 'groupRuleEnabled=true', 'groupFormation(fact, params)', 1, 1, '编组与wingman规则')
ON DUPLICATE KEY UPDATE
`rule_name` = VALUES(`rule_name`),
`level_code` = VALUES(`level_code`),
`kind_code` = VALUES(`kind_code`),
`module_code` = VALUES(`module_code`),
`priority_no` = VALUES(`priority_no`),
`condition_expr` = VALUES(`condition_expr`),
`action_expr` = VALUES(`action_expr`),
`version_no` = VALUES(`version_no`),
`enabled` = VALUES(`enabled`),
`remark` = VALUES(`remark`);
-- 3) 规则参数(来自 rule.drl 的 buildParam
INSERT INTO `rule_item_param`
(`rule_code`, `param_key`, `param_val`, `val_type`, `param_name`, `sort_no`, `enabled`, `remark`)
VALUES
-- R_TASK_SELECT_BASE
('R_TASK_SELECT_BASE','weight','1','number','全局权重',10,1,'评分乘数'),
('R_TASK_SELECT_BASE','minSelectedScore','1','number','最小选中分',20,1,'低于该分不选中'),
('R_TASK_SELECT_BASE','tieBreak','equipmentId','string','并列决策方式',30,1,'equipmentId字典序'),
('R_TASK_SELECT_BASE','outputDrawNameSuffix','打击任务','string','输出任务后缀',40,1,'匹配成功后缀'),
('R_TASK_SELECT_BASE','ruleSlotCount','3','number','规则槽数量',50,1,'槽位匹配条数'),
-- R_TASK_SELECT_SLOT_1~3
('R_TASK_SELECT_SLOT_1','blueRuleKeywords_1','F-16,F-35','string','蓝方关键词1',10,1,'规则槽1'),
('R_TASK_SELECT_SLOT_1','redRuleKeywords_1','防空,导弹,无人机','string','红方关键词1',20,1,'规则槽1'),
('R_TASK_SELECT_SLOT_1','ruleScore_1','5','number','规则槽1分值',30,1,'规则槽1'),
('R_TASK_SELECT_SLOT_2','blueRuleKeywords_2','坦克,装甲','string','蓝方关键词2',10,1,'规则槽2'),
('R_TASK_SELECT_SLOT_2','redRuleKeywords_2','反坦克','string','红方关键词2',20,1,'规则槽2'),
('R_TASK_SELECT_SLOT_2','ruleScore_2','4','number','规则槽2分值',30,1,'规则槽2'),
('R_TASK_SELECT_SLOT_3','blueRuleKeywords_3','地面,突击','string','蓝方关键词3',10,1,'规则槽3'),
('R_TASK_SELECT_SLOT_3','redRuleKeywords_3','远火,榴弹,炮','string','红方关键词3',20,1,'规则槽3'),
('R_TASK_SELECT_SLOT_3','ruleScore_3','2','number','规则槽3分值',30,1,'规则槽3'),
-- R_TASK_REL_AIR_PLATFORM
('R_TASK_REL_AIR_PLATFORM','bluePlatformKeywords_air','F-16,J-10,F-35','string','蓝方空中平台关键词',10,1,'兼容层'),
('R_TASK_REL_AIR_PLATFORM','redPreferredWhenBlueAir','防空,导弹,无人机,直升机,空空','string','红方空中偏好关键词',20,1,'兼容层'),
('R_TASK_REL_AIR_PLATFORM','airScore','2','number','空中平台分值',30,1,'兼容层'),
-- R_TASK_REL_AIR_TASK
('R_TASK_REL_AIR_TASK','airTaskKeywords','空中,制空,拦截,空战','string','空中任务关键词',10,1,'兼容层'),
('R_TASK_REL_AIR_TASK','airTaskScore','10','number','空中任务分值',20,1,'兼容层'),
-- R_TASK_REL_GROUND_TASK
('R_TASK_REL_GROUND_TASK','groundTaskKeywords','地面,突击,登陆','string','地面任务关键词',10,1,'兼容层'),
('R_TASK_REL_GROUND_TASK','redPreferredWhenGround','远火,榴弹,炮,火箭','string','红方地面偏好关键词',20,1,'兼容层'),
('R_TASK_REL_GROUND_TASK','groundScore','1','number','地面任务分值',30,1,'兼容层'),
-- R_TASK_REL_TANK
('R_TASK_REL_TANK','tankKeywords','坦克,装甲','string','坦克关键词',10,1,'兼容层'),
('R_TASK_REL_TANK','redMatchKeywords_tank','反坦克','string','红方反坦克关键词',20,1,'兼容层'),
('R_TASK_REL_TANK','tankScore','1','number','坦克分值',30,1,'兼容层'),
-- R_TASK_REL_MISSILE
('R_TASK_REL_MISSILE','missileKeywords','导弹,火箭弹,巡航','string','导弹关键词',10,1,'兼容层'),
('R_TASK_REL_MISSILE','redMatchKeywords_missile','防空,导弹,导弹发射','string','红方导弹匹配关键词',20,1,'兼容层'),
('R_TASK_REL_MISSILE','missileScore','1','number','导弹分值',30,1,'兼容层'),
-- R_TASK_ASSIGN_TARGET
('R_TASK_ASSIGN_TARGET','executeTypeDefault','assault','string','执行类型默认值',10,1,'目标分配'),
('R_TASK_ASSIGN_TARGET','targetPickMode','roundRobin','string','目标选择模式',20,1,'roundRobin/random'),
('R_TASK_ASSIGN_TARGET','minTargetsPerRed','1','number','每红装最少目标数',30,1,'目标分配'),
('R_TASK_ASSIGN_TARGET','maxTargetsPerRedCap','3','number','每红装最多目标数',40,1,'目标分配'),
('R_TASK_ASSIGN_TARGET','radToTargetsCsv','0.8:1,0.5:2,0.2:3','string','命中率映射目标数',50,1,'阈值映射'),
('R_TASK_ASSIGN_TARGET','rangeParseRegex','(\\\\d+(?:\\\\.\\\\d+)?)','string','射程提取正则',60,1,'提取首个数字'),
('R_TASK_ASSIGN_TARGET','rangeUnit','km','string','射程单位',70,1,'km/m'),
('R_TASK_ASSIGN_TARGET','minRangeToAllowAssignKm','0','number','允许分配最小射程',80,1,'射程过滤'),
-- R_TASK_LIMIT_SUPPLEMENT
('R_TASK_LIMIT_SUPPLEMENT','redHitRateThreshold','0.6','number','红装命中率阈值',10,1,'低于阈值触发补拿'),
('R_TASK_LIMIT_SUPPLEMENT','maxExtraWeaponsPerTask','2','number','每任务最大补拿装备数',20,1,'限制条件'),
('R_TASK_LIMIT_SUPPLEMENT','maxSupplementRounds','2','number','补拿最大轮次',30,1,'防止死循环'),
('R_TASK_LIMIT_SUPPLEMENT','extraPickMinScore','1','number','补拿最小匹配分',40,1,'限制条件'),
-- R_PLATFORM_DEPLOY
('R_PLATFORM_DEPLOY','positionRuleEnabled','true','bool','是否启用阵位规则',10,1,'部署开关'),
('R_PLATFORM_DEPLOY','positionAnchorMode','hybrid','string','阵位锚点模式',20,1,'当前使用hybrid'),
('R_PLATFORM_DEPLOY','trackPointDirectionMode','head2next','string','航向计算模式',30,1,'head2next/tail2prev'),
('R_PLATFORM_DEPLOY','fallbackBearingDeg','0','number','默认航向角',40,1,'无法计算时回退'),
('R_PLATFORM_DEPLOY','deployDistanceKmMin','8','number','部署距离最小值(km)',50,1,'部署约束'),
('R_PLATFORM_DEPLOY','deployDistanceKmMax','30','number','部署距离最大值(km)',60,1,'部署约束'),
('R_PLATFORM_DEPLOY','deployDistanceKmDefault','15','number','默认部署距离(km)',70,1,'部署距离'),
('R_PLATFORM_DEPLOY','distanceByPlatformCsv','','string','按平台覆盖部署距离',80,1,'关键词:距离'),
('R_PLATFORM_DEPLOY','formationType','line','string','编队样式',90,1,'line/wedge/circle'),
('R_PLATFORM_DEPLOY','formationSpacingMeters','300','number','编队间距(米)',100,1,'部署参数'),
('R_PLATFORM_DEPLOY','formationHeadingOffsetDeg','15','number','编队偏转角(度)',110,1,'部署参数'),
('R_PLATFORM_DEPLOY','defaultDeployHeight','30','number','默认部署高度(米)',120,1,'部署参数'),
('R_PLATFORM_DEPLOY','heightFollowBlueRatio','0.0','number','高度跟随比例',130,1,'部署参数'),
('R_PLATFORM_DEPLOY','warZoneClampMode','nearestInside','string','作战区约束模式',140,1,'部署约束'),
('R_PLATFORM_DEPLOY','minInterPlatformDistanceMeters','80','number','平台最小间距(米)',150,1,'部署约束'),
-- R_PLATFORM_SPACETIME
('R_PLATFORM_SPACETIME','enableWarZoneClamp','true','bool','是否启用作战区约束',10,1,'平台时空约束'),
-- R_ACTION_TRACK_ROUTE
('R_ACTION_TRACK_ROUTE','trackRuleEnabled','true','bool','是否启用航迹规则',10,1,'航迹开关'),
('R_ACTION_TRACK_ROUTE','trackRouteAlgorithm','followBlue','string','航迹算法',20,1,'followBlue/shortestPath/flank/jam'),
('R_ACTION_TRACK_ROUTE','trackRouteNameSuffix','航迹','string','航迹名称后缀',30,1,'行动模式'),
('R_ACTION_TRACK_ROUTE','trackAirDataTypeCsv','taskPlane,air,plane,flight','string','空中航迹dataType关键词',40,1,'行动模式'),
('R_ACTION_TRACK_ROUTE','trackAirKeywordsCsv','机,飞,空,J-,F-,无人机,直升机','string','空中航迹关键词',50,1,'行动模式'),
('R_ACTION_TRACK_ROUTE','trackGroundTrackType','routeLineGround','string','地面航迹类型',60,1,'行动模式'),
('R_ACTION_TRACK_ROUTE','trackFallbackBearingDeg','0','number','航迹默认回退航向角',70,1,'行动模式'),
('R_ACTION_TRACK_ROUTE','trackExtraNodesMax','0','number','航迹额外插点上限',80,1,'行动模式'),
('R_ACTION_TRACK_ROUTE','trackShortPathSegments','3','number','最短路径插值分段',90,1,'行动模式'),
('R_ACTION_TRACK_ROUTE','trackFlankOffsetMeters','800','number','flank偏移距离(米)',100,1,'行动模式'),
('R_ACTION_TRACK_ROUTE','trackFlankSideMode','alternate','string','flank侧向模式',110,1,'alternate/left/right'),
('R_ACTION_TRACK_ROUTE','trackJamWobbleMeters','400','number','jam摆动振幅(米)',120,1,'行动模式'),
('R_ACTION_TRACK_ROUTE','trackJamSegments','4','number','jam摆动周期数',130,1,'行动模式'),
-- R_ACTION_TRACK_SPACETIME
('R_ACTION_TRACK_SPACETIME','enableTrackWarZoneClamp','true','bool','是否启用航迹作战区约束',10,1,'行动时空约束'),
-- R_ACTION_GROUP_FORMATION
('R_ACTION_GROUP_FORMATION','groupRuleEnabled','true','bool','是否启用编组规则',10,1,'编组开关'),
('R_ACTION_GROUP_FORMATION','groupDrawNameSuffix','编组','string','编组名称后缀',20,1,'编组规则'),
('R_ACTION_GROUP_FORMATION','groupDrawNameWithIndex','false','bool','编组名称是否加序号',30,1,'编组规则'),
('R_ACTION_GROUP_FORMATION','groupFormationMode','onePerRed','string','编组模式',40,1,'onePerRed/clusterByCount/singleGroup'),
('R_ACTION_GROUP_FORMATION','groupClusterSize','3','number','按人数编组的每组上限',50,1,'编组规则'),
('R_ACTION_GROUP_FORMATION','groupLeaderPickMode','byHitRateThenId','string','领队选择模式',60,1,'byHitRateThenId/byId'),
('R_ACTION_GROUP_FORMATION','groupMinMembersForWingman','2','number','生成僚机的最小人数',70,1,'编组规则'),
('R_ACTION_GROUP_FORMATION','wingmanDistanceBaseMeters','100','number','僚机基础距离(米)',80,1,'编组几何'),
('R_ACTION_GROUP_FORMATION','wingmanDistanceStepMeters','50','number','僚机距离步长(米)',90,1,'编组几何'),
('R_ACTION_GROUP_FORMATION','wingmanAngleBaseDeg','50','number','僚机基础角度(度)',100,1,'编组几何'),
('R_ACTION_GROUP_FORMATION','wingmanAngleStepDeg','15','number','僚机角度步长(度)',110,1,'编组几何'),
('R_ACTION_GROUP_FORMATION','wingmanAltBaseMeters','40','number','僚机基础高度(米)',120,1,'编组几何'),
('R_ACTION_GROUP_FORMATION','wingmanAltScale','1.0','number','僚机高度缩放系数',130,1,'编组几何')
ON DUPLICATE KEY UPDATE
`param_val` = VALUES(`param_val`),
`val_type` = VALUES(`val_type`),
`param_name` = VALUES(`param_name`),
`sort_no` = VALUES(`sort_no`),
`enabled` = VALUES(`enabled`),
`remark` = VALUES(`remark`);
-- 4) 规则适用任务类型(默认全部规则覆盖五类任务,后续可在前端按需调整)
INSERT IGNORE INTO `rule_item_task_type` (`rule_code`, `task_type_code`)
SELECT r.rule_code, t.task_type_code
FROM (
SELECT 'R_TASK_SELECT_BASE' AS rule_code
UNION ALL SELECT 'R_TASK_SELECT_SLOT_1'
UNION ALL SELECT 'R_TASK_SELECT_SLOT_2'
UNION ALL SELECT 'R_TASK_SELECT_SLOT_3'
UNION ALL SELECT 'R_TASK_REL_AIR_PLATFORM'
UNION ALL SELECT 'R_TASK_REL_AIR_TASK'
UNION ALL SELECT 'R_TASK_REL_GROUND_TASK'
UNION ALL SELECT 'R_TASK_REL_TANK'
UNION ALL SELECT 'R_TASK_REL_MISSILE'
UNION ALL SELECT 'R_TASK_ASSIGN_TARGET'
UNION ALL SELECT 'R_TASK_LIMIT_SUPPLEMENT'
UNION ALL SELECT 'R_PLATFORM_DEPLOY'
UNION ALL SELECT 'R_PLATFORM_SPACETIME'
UNION ALL SELECT 'R_ACTION_TRACK_ROUTE'
UNION ALL SELECT 'R_ACTION_TRACK_SPACETIME'
UNION ALL SELECT 'R_ACTION_GROUP_FORMATION'
) r
CROSS JOIN (
SELECT 'strike' AS task_type_code
UNION ALL SELECT 'recon'
UNION ALL SELECT 'intercept'
UNION ALL SELECT 'support'
UNION ALL SELECT 'jamming'
) t;

View File

@@ -1,4 +1,4 @@
package com.solution.system.domain;
package com.solution.scene.domain;
import lombok.Data;

View File

@@ -2,6 +2,7 @@ package com.solution.scene.mapper;
import com.solution.scene.domain.AfsimScenario;
import com.solution.scene.domain.AfsimScenarioForm;
import com.solution.scene.domain.PlatformCommunication;
import com.solution.system.domain.Behaviortree;
import org.apache.ibatis.annotations.Mapper;
@@ -34,4 +35,11 @@ public interface SceneMapper {
* @return
*/
List<Behaviortree> selectAllTreeBySceneId(Integer id);
/**
* 根据场景id获取场景下所有关系
* @param id
* @return
*/
List<PlatformCommunication> selectAllRelationBySceneId(Integer id);
}

View File

@@ -2,6 +2,7 @@ package com.solution.scene.service;
import com.solution.scene.domain.AfsimScenario;
import com.solution.scene.domain.AfsimScenarioForm;
import com.solution.scene.domain.PlatformCommunication;
import com.solution.system.domain.Behaviortree;
import java.util.List;
@@ -33,4 +34,11 @@ public interface SceneService {
* @return
*/
List<Behaviortree> getAllTree(Integer id);
/**
* 根据场景id获取场景下所有关系
* @param id
* @return
*/
List<PlatformCommunication> getAllRelation(Integer id);
}

View File

@@ -4,6 +4,7 @@ import cn.hutool.core.collection.CollUtil;
import com.solution.common.constant.ExceptionConstants;
import com.solution.scene.domain.AfsimScenario;
import com.solution.scene.domain.AfsimScenarioForm;
import com.solution.scene.domain.PlatformCommunication;
import com.solution.scene.mapper.PlatFormCommunicationMapper;
import com.solution.scene.mapper.SceneMapper;
import com.solution.scene.service.SceneService;
@@ -90,4 +91,18 @@ public class SceneServiceImpl implements SceneService {
return allTree;
}
/**
* 根据场景id获取场景下所有关系
* @param id
* @return
*/
@Override
public List<PlatformCommunication> getAllRelation(Integer id) {
List<PlatformCommunication> result = sceneMapper.selectAllRelationBySceneId(id);
if(CollUtil.isEmpty( result)){
throw new RuntimeException("该场景下不存在关系");
}
return result;
}
}

View File

@@ -32,6 +32,21 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
WHERE scenario_id=#{id}
</select>
<resultMap id="PlatformCommunicationResultMap" type="com.solution.scene.domain.PlatformCommunication">
<id property="id" column="id"/>
<result property="commandPlatform" column="command_platform"/>
<result property="subordinatePlatform" column="subordinate_platform"/>
<result property="commandComm" column="command_comm"/>
<result property="subordinateComm" column="subordinate_comm"/>
<result property="scenaryId" column="scenary_id"/>
</resultMap>
<select id="selectAllRelationBySceneId" resultMap="PlatformCommunicationResultMap"
parameterType="java.lang.Integer">
SELECT id,command_platform,subordinate_platform,command_comm,subordinate_comm,scenary_id
FROM platform_communication
WHERE scenary_id = #{id}
</select>
<insert id="update" parameterType="com.solution.scene.domain.AfsimScenario">
update afsim_scenario
set name=#{name},

View File

@@ -8,7 +8,7 @@
*/
import { HttpRequestClient } from '@/utils/request';
import type { Scenario, ScenarioDetailsResponse, ScenarioPageableResponse, ScenarioRequest } from './types';
import type { Scenario, ScenarioDetailsResponse, ScenarioPageableResponse, ScenarioRequest, CommunicationRelationsResponse } from './types';
import type { PlatformWithComponentsResponse } from '../types';
import type { BasicResponse } from '@/types';
import type { BehaviorTree } from '../designer/tree';
@@ -33,6 +33,15 @@ export const findPlatformWithComponents = (id: number): Promise<PlatformWithComp
return req.get<PlatformWithComponentsResponse>(`/system/firerule/platforms/${id}`);
};
/**
* 获取场景的所有通信关系
* @param id 场景ID
* @returns 通信关系列表
*/
export const findRelations = (id: number): Promise<CommunicationRelationsResponse> => {
return req.get<CommunicationRelationsResponse>(`/system/scene/getAllRelation/${id}`);
};
export const saveScenario = (scenario: Scenario): Promise<BasicResponse> => {
return req.postJson<BasicResponse>(`/system/scene/saveSceneConfig`,scenario);
};

View File

@@ -21,6 +21,14 @@
<div class="ks-model-builder-content" style="width: calc(100% - 250px);">
<div class="ks-model-builder-actions">
<a-space>
<a-button v-if="graph && currentScenario" class="ks-model-builder-save" style="width: auto;" size="small" @click="handleGenerateRandom">
<ThunderboltOutlined />
<span>随机生成</span>
</a-button>
<a-button v-if="graph && currentScenario && currentScenario.id > 0" class="ks-model-builder-save" style="width: auto;" size="small" @click="handleLoadFromBackend">
<DatabaseOutlined />
<span>从后端加载</span>
</a-button>
<a-button v-if="graph && currentScenario" class="ks-model-builder-save" size="small" @click="handleSave">
<CheckOutlined />
<span>保存</span>
@@ -50,22 +58,24 @@ import { useRoute, useRouter } from 'vue-router';
import { message } from 'ant-design-vue';
import { getTeleport } from '@antv/x6-vue-shape';
import { Graph, Node, type NodeProperties } from '@antv/x6';
import { CheckCircleOutlined, CheckOutlined, RollbackOutlined, SaveOutlined } from '@ant-design/icons-vue';
import { CheckCircleOutlined, CheckOutlined, DatabaseOutlined, RollbackOutlined, SaveOutlined, ThunderboltOutlined } from '@ant-design/icons-vue';
import { Wrapper } from '@/components/wrapper';
import { safePreventDefault, safeStopPropagation } from '@/utils/event';
import Header from '../header.vue';
import type { Scenario } from './types';
import type { PlatformWithComponents } from '../types';
import { createLineOptions, type GraphContainer, type GraphTaskElement, resolveGraph, useGraphCanvas } from '../graph';
import { createLineOptions, type GraphContainer, type GraphEdgeElement, type GraphTaskElement, resolveGraph, useGraphCanvas } from '../graph';
import { registerScenarioElement } from './register';
import { createGraphScenarioElement, createGraphTaskElementFromScenario } from './utils';
import PlatformCard from './platform-card.vue';
import NodesCard from './nodes-card.vue';
import { findOneScenarioById, saveScenario, getAllBehaviorTreesBySceneId } from './api';
import { findOneScenarioById, saveScenario, findRelations } from './api';
import { resolveConnectionRelation } from './relation';
import { generateRandomCommunicationData } from './random-data-generator';
import { convertRecordsToGraphContainer, type CommunicationRecord } from './data-converter';
const TeleportContainer = defineComponent(getTeleport());
@@ -81,6 +91,8 @@ export default defineComponent({
CheckCircleOutlined,
CheckOutlined,
RollbackOutlined,
ThunderboltOutlined,
DatabaseOutlined,
TeleportContainer,
},
setup() {
@@ -218,36 +230,67 @@ export default defineComponent({
} catch (e: any) {
console.error('parse error,cause:', e);
}
if (!nodeGraph) {
nodeGraph = {
nodes: [],
edges: [],
};
}
// 设置当前场景
currentScenario.value = {
...scenario,
graph: nodeGraph,
graph: nodeGraph || { nodes: [], edges: [] },
relations: []
};
console.log('选中场景:', currentScenario.value);
currentScenarioEditing.value = true;
// 场景ID存储到graph对象中供子组件访问
if (graph.value) {
(graph.value as any).currentScenario = currentScenario.value;
// 加载该场景下的行为树列表
// 如果场景ID且没有已保存的图数据,尝试从后端加载通信关系
if (scenario.id > 0 && !nodeGraph) {
try {
const response = await getAllBehaviorTreesBySceneId(scenario.id);
if (response.code === 200 && response.data) {
(graph.value as any).behaviorTrees = response.data;
console.log(`加载场景${scenario.id}的行为树列表:`, response.data.length, '个');
message.loading({ content: '正在加载通信关系...', key: 'loading-relations' });
const response = await findRelations(scenario.id);
console.log('API完整响应:', response);
// 解析API响应支持多种格式
let relations: any[] = [];
if (Array.isArray(response.data)) {
relations = response.data;
} else if (response.data && Array.isArray((response.data as any).data)) {
relations = (response.data as any).data;
} else if (response.data && Array.isArray((response.data as any).rows)) {
relations = (response.data as any).rows;
} else if (response.data && Array.isArray((response.data as any).list)) {
relations = (response.data as any).list;
}
console.log('解析后的通信关系数量:', relations.length);
if (relations.length > 0) {
// 字段名标准化(驼峰转下划线)
const normalizedRelations = relations.map((item: any) => ({
id: item.id,
command_platform: item.commandPlatform || item.command_platform,
subordinate_platform: item.subordinatePlatform || item.subordinate_platform,
command_comm: item.commandComm || item.command_comm,
subordinate_comm: item.subordinateComm || item.subordinate_comm,
scenary_id: item.scenaryId || item.scenary_id,
}));
console.log('标准化后的第一条记录:', normalizedRelations[0]);
// 转换为图数据
const convertedGraph = convertRecordsToGraphContainer(normalizedRelations);
console.log('转换后的图数据:', convertedGraph);
// 更新当前场景的图数据
currentScenario.value.graph = convertedGraph;
currentScenario.value.communicationGraph = JSON.stringify(convertedGraph);
message.success({ content: `成功加载 ${normalizedRelations.length} 条通信关系`, key: 'loading-relations' });
} else {
(graph.value as any).behaviorTrees = [];
console.warn('获取行为树列表失败:', response.msg);
message.warning({ content: '该场景暂无通信关系数据', key: 'loading-relations' });
}
} catch (error) {
console.error('获取行为树列表失败:', error);
(graph.value as any).behaviorTrees = [];
console.error('从后端加载通信关系失败:', error);
message.error({ content: '加载通信关系失败,请手动点击"从后端加载"', key: 'loading-relations' });
}
}
@@ -282,6 +325,7 @@ export default defineComponent({
}, 100); // 延迟一会儿,免得连线错位
}
}
}, 100);
});
};
@@ -442,6 +486,193 @@ export default defineComponent({
});
};
// 随机生成节点流图
const handleGenerateRandom = () => {
if (!graph.value) {
message.error('画布未初始化');
return;
}
try {
// 生成随机数据
const { records, graph: randomGraph } = generateRandomCommunicationData(30);
console.log('生成的随机数据:', records);
console.log('转换后的图数据:', randomGraph);
// 清空现有内容
graph.value.clearCells();
// 设置当前场景
if (!currentScenario.value) {
currentScenario.value = {
id: 0,
name: `随机场景_${Date.now()}`,
description: '自动生成的测试场景',
communicationGraph: null,
relations: [],
graph: randomGraph,
};
} else {
currentScenario.value.graph = randomGraph;
currentScenario.value.communicationGraph = JSON.stringify(randomGraph);
}
// 渲染节点
setTimeout(() => {
if (randomGraph.nodes) {
randomGraph.nodes.forEach(ele => {
const node = createGraphScenarioElement(ele as GraphTaskElement);
graph.value?.addNode(node as Node);
});
}
// 延迟添加边,确保节点已渲染
setTimeout(() => {
if (randomGraph.edges) {
randomGraph.edges.forEach(edgeData => {
graph.value?.addEdge({
...edgeData,
...createLineOptions(),
});
});
}
// 自动适应视图
fitToScreen();
message.success(`已生成 ${randomGraph.nodes.length} 个节点和 ${randomGraph.edges.length} 条连接线`);
}, 100);
}, 50);
} catch (error) {
console.error('随机生成时出错:', error);
message.error('生成失败,请重试');
}
};
// 从后端加载平台数据并转换为通信关系图(当前使用模拟数据)
const handleLoadFromBackend = async () => {
if (!graph.value || !currentScenario.value) {
message.error('请先选择场景');
return;
}
try {
message.loading({ content: '正在加载通信关系数据...', key: 'loading' });
// 调用真实API获取通信关系
console.log(`正在从后端加载场景 ${currentScenario.value.id} 的通信关系...`);
const response = await findRelations(currentScenario.value.id);
console.log('API完整响应:', response);
console.log('response.data类型:', typeof response.data, Array.isArray(response.data) ? 'Array' : 'Object');
// API返回的是 CommunicationRelationRecord[],与 CommunicationRecord 结构兼容
// 处理可能的多种返回格式
let relations: any[] = [];
if (Array.isArray(response.data)) {
relations = response.data;
} else if (response.data && Array.isArray((response.data as any).data)) {
relations = (response.data as any).data;
} else if (response.data && Array.isArray((response.data as any).rows)) {
relations = (response.data as any).rows;
} else if (response.data && Array.isArray((response.data as any).list)) {
relations = (response.data as any).list;
}
console.log('解析后的通信关系数量:', relations.length);
if (relations.length > 0) {
console.log('第一条记录:', JSON.stringify(relations[0], null, 2));
}
// 后端返回的是驼峰命名,需要转换为下划线命名以匹配前端类型
const normalizedRelations = relations.map((item: any) => ({
id: item.id,
command_platform: item.commandPlatform || item.command_platform,
subordinate_platform: item.subordinatePlatform || item.subordinate_platform,
command_comm: item.commandComm || item.command_comm,
subordinate_comm: item.subordinateComm || item.subordinate_comm,
scenary_id: item.scenaryId || item.scenary_id,
}));
console.log('标准化后的第一条记录:', normalizedRelations[0]);
if (normalizedRelations.length === 0) {
console.warn('API未返回任何通信关系数据使用模拟数据作为fallback');
// Fallback到模拟数据保留以便测试
relations.push(
{ id: 6, command_platform: 'chief', subordinate_platform: 'task1_commander', command_comm: 'radio', subordinate_comm: 'radio', scenary_id: currentScenario.value.id },
{ id: 7, command_platform: 'chief', subordinate_platform: 'task2_commander', command_comm: 'radio', subordinate_comm: 'radio', scenary_id: currentScenario.value.id },
{ id: 8, command_platform: 'chief', subordinate_platform: 'task3_commander', command_comm: 'radio', subordinate_comm: 'radio', scenary_id: currentScenario.value.id },
{ id: 9, command_platform: 'task1_commander', subordinate_platform: 'platform1', command_comm: 'radio', subordinate_comm: 'radio', scenary_id: currentScenario.value.id },
{ id: 10, command_platform: 'task1_commander', subordinate_platform: 'platform3', command_comm: 'radio', subordinate_comm: 'radio', scenary_id: currentScenario.value.id },
{ id: 11, command_platform: 'task1_commander', subordinate_platform: 'platform4', command_comm: 'radio', subordinate_comm: 'radio', scenary_id: currentScenario.value.id },
{ id: 12, command_platform: 'task1_commander', subordinate_platform: 'platform5', command_comm: 'radio', subordinate_comm: 'radio', scenary_id: currentScenario.value.id },
{ id: 13, command_platform: 'task1_commander', subordinate_platform: 'platform6', command_comm: 'radio', subordinate_comm: 'radio', scenary_id: currentScenario.value.id },
{ id: 14, command_platform: 'task2_commander', subordinate_platform: 'platform3', command_comm: 'radio', subordinate_comm: 'radio', scenary_id: currentScenario.value.id },
{ id: 15, command_platform: 'task2_commander', subordinate_platform: 'platform5', command_comm: 'radio', subordinate_comm: 'radio', scenary_id: currentScenario.value.id },
{ id: 16, command_platform: 'task2_commander', subordinate_platform: 'platform4', command_comm: 'radio', subordinate_comm: 'radio', scenary_id: currentScenario.value.id },
{ id: 17, command_platform: 'task3_commander', subordinate_platform: 'platform6', command_comm: 'radio', subordinate_comm: 'radio', scenary_id: currentScenario.value.id },
{ id: 18, command_platform: 'task3_commander', subordinate_platform: 'platform6', command_comm: 'radio', subordinate_comm: 'radio', scenary_id: currentScenario.value.id },
{ id: 19, command_platform: 'task3_commander', subordinate_platform: 'platform7', command_comm: 'radio', subordinate_comm: 'radio', scenary_id: currentScenario.value.id },
{ id: 20, command_platform: 'task3_commander', subordinate_platform: 'platform8', command_comm: 'radio', subordinate_comm: 'radio', scenary_id: currentScenario.value.id },
);
}
console.log('最终使用的通信记录:', normalizedRelations);
// 使用数据进行转换
const convertedGraph = convertRecordsToGraphContainer(normalizedRelations);
console.log('转换后的图数据:', convertedGraph);
// 清空现有内容
graph.value.clearCells();
// 更新当前场景
currentScenario.value.graph = convertedGraph;
currentScenario.value.communicationGraph = JSON.stringify(convertedGraph);
// 渲染节点
setTimeout(() => {
if (convertedGraph.nodes) {
convertedGraph.nodes.forEach(ele => {
const node = createGraphScenarioElement(ele as GraphTaskElement);
graph.value?.addNode(node as Node);
});
}
// 延迟添加边,确保节点已渲染
setTimeout(() => {
if (convertedGraph.edges) {
convertedGraph.edges.forEach(edgeData => {
graph.value?.addEdge({
...edgeData,
...createLineOptions(),
});
});
}
// 自动适应视图
fitToScreen();
message.success({
content: `已从后端加载 ${convertedGraph.nodes.length} 个平台和 ${convertedGraph.edges.length} 条连接关系`,
key: 'loading'
});
}, 100);
}, 50);
} catch (error) {
console.error('从后端加载时出错:', error);
message.error({
content: error instanceof Error ? error.message : '加载失败,请重试',
key: 'loading'
});
}
};
// 初始化
onMounted(() => {
init();
@@ -471,6 +702,8 @@ export default defineComponent({
handleDrop,
isDraggingOver,
handleSave,
handleGenerateRandom,
handleLoadFromBackend,
handleUpdateElement,
handleSelect,
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,143 @@
/*
* This file is part of the kernelstudio package.
*
* (c) 2014-2026 zlin <admin@kernelstudio.com>
*
* For the full copyright and license information, please view the LICENSE file
* that was distributed with this source code.
*/
import type { GraphContainer } from '../graph';
import { convertRecordsToGraphContainer, type CommunicationRecord } from './data-converter';
/**
* 生成随机的通信关系数据用于测试
* @param nodeCount 节点数量默认5-8个
* @param edgeDensity 边的密度0.3-0.7之间,表示连接概率)
* @returns 随机生成的通信关系记录和对应的GraphContainer
*/
export const generateRandomCommunicationData = (
nodeCount: number = Math.floor(Math.random() * 4) + 5, // 5-8个节点
edgeDensity: number = 0.5
): { records: CommunicationRecord[]; graph: GraphContainer } => {
// 生成随机平台名称
const platformTypes = ['指挥中心', '雷达站', '导弹阵地', '预警机', '战斗机', '驱逐舰', '潜艇', '电子战飞机'];
const platforms: string[] = [];
for (let i = 0; i < nodeCount; i++) {
const baseName = platformTypes[i % platformTypes.length];
const suffix = Math.floor(i / platformTypes.length) > 0 ? `-${Math.floor(i / platformTypes.length) + 1}` : '';
platforms.push(`${baseName}${suffix}`);
}
// 生成随机通信关系 - 改进版:更符合实际指挥层级
const records: CommunicationRecord[] = [];
let recordId = 1;
// 确定根节点(指挥中心)
const rootPlatformName = platforms.find(p => p.includes('指挥')) || platforms[0] || '默认平台';
const rootIndex = platforms.indexOf(rootPlatformName);
if (rootIndex === -1) {
console.warn('未找到根节点,使用第一个平台');
return { records: [], graph: { nodes: [], edges: [] } };
}
// 第一层:指挥中心直接管理的单位(通常是主要作战单元)
// 选择2-4个作为一级下属
const firstLevelCount = Math.min(Math.max(2, Math.floor(platforms.length / 2)), 4);
const firstLevelIndices: number[] = [];
for (let i = 0; i < platforms.length && firstLevelIndices.length < firstLevelCount; i++) {
if (i !== rootIndex) {
firstLevelIndices.push(i);
}
}
// 建立第一层连接:指挥中心 -> 一级下属
firstLevelIndices.forEach(idx => {
const subordinatePlatform = platforms[idx];
if (subordinatePlatform) {
records.push({
id: recordId++,
command_platform: rootPlatformName,
subordinate_platform: subordinatePlatform,
command_comm: '加密指挥链路',
subordinate_comm: '接收端',
scenary_id: 1,
});
}
});
// 第二层:一级下属可以有二级下属
const remainingIndices = platforms.map((_, i) => i).filter(i =>
i !== rootIndex && !firstLevelIndices.includes(i)
);
remainingIndices.forEach(idx => {
// 随机选择一个一级下属作为父节点
const parentIdx = firstLevelIndices[Math.floor(Math.random() * firstLevelIndices.length)];
if (parentIdx !== undefined) {
const parentPlatform = platforms[parentIdx];
const childPlatform = platforms[idx];
if (parentPlatform && childPlatform) {
records.push({
id: recordId++,
command_platform: parentPlatform,
subordinate_platform: childPlatform,
command_comm: Math.random() > 0.5 ? '战术数据链' : '无线通信',
subordinate_comm: '双向通信',
scenary_id: 1,
});
}
}
});
// 第三层添加少量横向协同连接不超过总边数的20%
const maxCrossLinks = Math.floor(records.length * 0.2);
let crossLinkCount = 0;
if (platforms.length > 4 && crossLinkCount < maxCrossLinks) {
// 在同级之间添加协同连接
for (let i = 0; i < firstLevelIndices.length - 1 && crossLinkCount < maxCrossLinks; i++) {
if (Math.random() < 0.4) { // 40%概率
const j = i + 1 + Math.floor(Math.random() * 2);
if (j < firstLevelIndices.length) {
const idxI = firstLevelIndices[i];
const idxJ = firstLevelIndices[j];
if (idxI !== undefined && idxJ !== undefined) {
const platformI = platforms[idxI];
const platformJ = platforms[idxJ];
if (platformI && platformJ) {
const exists = records.some(r =>
r.command_platform === platformI &&
r.subordinate_platform === platformJ
);
if (!exists) {
records.push({
id: recordId++,
command_platform: platformI,
subordinate_platform: platformJ,
command_comm: '协同通信',
subordinate_comm: '双向通信',
scenary_id: 1,
});
crossLinkCount++;
}
}
}
}
}
}
}
// 转换为GraphContainer
const graph = convertRecordsToGraphContainer(records);
return { records, graph };
};

View File

@@ -48,3 +48,22 @@ export interface ScenarioDetailsResponse extends ApiDataResponse<Scenario> {
}
/**
* 通信关系记录(对应数据库表结构)
*/
export interface CommunicationRelationRecord {
id: number;
command_platform: string; // 指挥平台名称
subordinate_platform: string; // 下属平台名称
command_comm?: string; // 指挥端通信方式
subordinate_comm?: string; // 下属端通信方式
scenary_id?: number; // 场景ID
}
/**
* 获取场景所有通信关系的响应
*/
export interface CommunicationRelationsResponse extends ApiDataResponse<CommunicationRelationRecord[]> {
}