Prechádzať zdrojové kódy

Merge remote-tracking branch 'origin/master'

Kazerin 3 týždňov pred
rodič
commit
c5318355c9

+ 1 - 0
pipe-network-service/sql/iot_equipment_management.sql

@@ -71,6 +71,7 @@ CREATE TABLE `monitor_point` (
   `point_id` VARCHAR(64) NOT NULL COMMENT '监测点ID(主键)',
   `point_code` VARCHAR(64) NOT NULL COMMENT '监测点编码',
   `point_name` VARCHAR(256) NOT NULL COMMENT '监测点名称',
+  `point_type` INT COMMENT '监测点类型(1-供水监测点 2-排水监测点 3-燃气监测点)',
   `location` VARCHAR(512) COMMENT '位置描述',
   `longitude` DECIMAL(15, 10) COMMENT '经度',
   `latitude` DECIMAL(15, 10) COMMENT '纬度',

+ 9 - 6
pipe-network-service/zksy-admin/src/main/java/com/zksy/web/controller/base/MonitorPointController.java

@@ -31,22 +31,25 @@ public class MonitorPointController {
                                    @RequestParam(defaultValue = "10") long pageSize,
                                    @RequestParam(required = false) String pointCode,
                                    @RequestParam(required = false) String pointName,
-                                   @RequestParam(required = false) String associationStatus) {
-        return monitorPointService.findByPage(pageNum, pageSize, pointCode, pointName, associationStatus);
+                                   @RequestParam(required = false) String associationStatus,
+                                   @RequestParam(required = false) Integer rootTypeId) {
+        return monitorPointService.findByPage(pageNum, pageSize, pointCode, pointName, associationStatus, rootTypeId);
     }
 
     @GetMapping("/all")
     @Anonymous
     @ApiOperation("查询全部监测点位")
-    public AjaxResult all() {
-        return AjaxResult.success(monitorPointService.list());
+    public AjaxResult all(@RequestParam(required = false) Integer rootTypeId) {
+        return AjaxResult.success(monitorPointService.lambdaQuery()
+                .eq(rootTypeId != null, MonitorPoint::getPointType, rootTypeId)
+                .list());
     }
 
     @GetMapping("/statistics")
     @Anonymous
     @ApiOperation("查询监测点位统计")
-    public AjaxResult statistics() {
-        return AjaxResult.success(monitorPointService.statistics());
+    public AjaxResult statistics(@RequestParam(required = false) Integer rootTypeId) {
+        return AjaxResult.success(monitorPointService.statistics(rootTypeId));
     }
 
     @GetMapping("/{pointId}")

+ 2 - 60
pipe-network-service/zksy-admin/src/main/java/com/zksy/web/controller/gasbasic/GasEquipmentController.java

@@ -1,10 +1,8 @@
 package com.zksy.web.controller.gasbasic;
 
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
-import com.zksy.base.domain.EquipmentPointRel;
 import com.zksy.base.domain.EquipmentStatus;
 import com.zksy.base.domain.GasPipePointDeviceRel;
-import com.zksy.base.mapper.EquipmentPointRelMapper;
 import com.zksy.base.mapper.EquipmentStatusMapper;
 import com.zksy.base.mapper.GasPipePointDeviceRelMapper;
 import com.zksy.common.annotation.Anonymous;
@@ -15,17 +13,16 @@ import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.*;
 
-import java.time.LocalDateTime;
 import java.util.List;
 
 /**
- * 燃气监测设备-测点关联与运行状态查询。
+ * 燃气监测设备运行状态与管点关联查询。
  * 设备台账的新增/修改/删除已统一收口至 EquipmentBaseController(/equipment-base)。
  */
 @Slf4j
 @RestController
 @RequestMapping("/api/equipment")
-@Api(tags = "监测设备管理-测点关联")
+@Api(tags = "燃气监测设备管理")
 public class GasEquipmentController {
 
     @Autowired
@@ -34,9 +31,6 @@ public class GasEquipmentController {
     @Autowired
     private EquipmentStatusMapper equipmentStatusMapper;
 
-    @Autowired
-    private EquipmentPointRelMapper equipmentPointRelMapper;
-
     @GetMapping("/status/{equipmentId}")
     @ApiOperation("获取设备实时运行状态")
     @Anonymous
@@ -57,58 +51,6 @@ public class GasEquipmentController {
         return AjaxResult.success(list);
     }
 
-    @GetMapping("/point-rel/{equipmentId}")
-    @ApiOperation("获取设备关联的测点")
-    @Anonymous
-    public AjaxResult getPointRelByEquipment(@PathVariable String equipmentId) {
-        LambdaQueryWrapper<EquipmentPointRel> wrapper = new LambdaQueryWrapper<>();
-        wrapper.eq(EquipmentPointRel::getEquipmentId, equipmentId);
-        List<EquipmentPointRel> list = equipmentPointRelMapper.selectList(wrapper);
-        return AjaxResult.success(list);
-    }
-
-    @PostMapping("/point-rel/save")
-    @ApiOperation("新增设备测点关联")
-    @Anonymous
-    public AjaxResult savePointRel(@RequestBody EquipmentPointRel rel) {
-        rel.setCreateTime(LocalDateTime.now());
-        boolean saved = equipmentPointRelMapper.insert(rel) > 0;
-        return saved ? AjaxResult.success("关联成功") : AjaxResult.error("关联失败");
-    }
-
-    @DeleteMapping("/point-rel/delete")
-    @ApiOperation("删除设备测点关联")
-    @Anonymous
-    public AjaxResult deletePointRel(@RequestParam String relId) {
-        boolean deleted = equipmentPointRelMapper.deleteById(relId) > 0;
-        return deleted ? AjaxResult.success("删除成功") : AjaxResult.error("删除失败");
-    }
-
-    @GetMapping("/point-rel/by-point/{pointId}")
-    @ApiOperation("获取管点关联的设备")
-    @Anonymous
-    public AjaxResult getPointRelByPoint(@PathVariable String pointId) {
-        LambdaQueryWrapper<EquipmentPointRel> wrapper = new LambdaQueryWrapper<>();
-        wrapper.eq(EquipmentPointRel::getPointId, pointId);
-        List<EquipmentPointRel> list = equipmentPointRelMapper.selectList(wrapper);
-        return AjaxResult.success(list);
-    }
-
-    @PostMapping("/point-rel/save-batch")
-    @ApiOperation("批量关联设备到管点")
-    @Anonymous
-    public AjaxResult savePointRelBatch(@RequestBody List<EquipmentPointRel> relList) {
-        if (relList == null || relList.isEmpty()) {
-            return AjaxResult.error("关联列表不能为空");
-        }
-        LocalDateTime now = LocalDateTime.now();
-        for (EquipmentPointRel rel : relList) {
-            rel.setCreateTime(now);
-            equipmentPointRelMapper.insert(rel);
-        }
-        return AjaxResult.success("批量关联成功");
-    }
-
     @PostMapping("/device-rel/save")
     @ApiOperation("更新设备管点关联(删旧插新)")
     @Anonymous

+ 5 - 0
pipe-network-service/zksy-system/src/main/java/com/zksy/base/domain/MonitorPoint.java

@@ -1,6 +1,7 @@
 package com.zksy.base.domain;
 
 import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.FieldStrategy;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableField;
 import com.baomidou.mybatisplus.annotation.TableLogic;
@@ -34,6 +35,10 @@ public class MonitorPoint {
     @ApiModelProperty("监测点名称")
     private String pointName;
 
+    @ApiModelProperty("监测点类型(1-供水监测点 2-排水监测点 3-燃气监测点)")
+    @TableField(updateStrategy = FieldStrategy.ALWAYS)
+    private Integer pointType;
+
     @ApiModelProperty("位置描述")
     private String location;
 

+ 30 - 0
pipe-network-service/zksy-system/src/main/java/com/zksy/base/enums/MonitorPointTypeEnum.java

@@ -0,0 +1,30 @@
+package com.zksy.base.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Objects;
+
+/** 监测点所属子系统类型。 */
+@Getter
+@AllArgsConstructor
+public enum MonitorPointTypeEnum {
+    WATER_SUPPLY(1, "供水监测点"),
+    DRAINAGE(2, "排水监测点"),
+    GAS(3, "燃气监测点");
+
+    private final Integer code;
+    private final String description;
+
+    public static MonitorPointTypeEnum getByCode(Integer code) {
+        if (code == null) return null;
+        for (MonitorPointTypeEnum type : values()) {
+            if (Objects.equals(type.code, code)) return type;
+        }
+        return null;
+    }
+
+    public static boolean isValid(Integer code) {
+        return code == null || getByCode(code) != null;
+    }
+}

+ 3 - 2
pipe-network-service/zksy-system/src/main/java/com/zksy/base/service/MonitorPointService.java

@@ -9,7 +9,8 @@ import java.util.List;
 
 public interface MonitorPointService extends IService<MonitorPoint> {
 
-    Page<MonitorPoint> findByPage(long pageNum, long pageSize, String pointCode, String pointName, String associationStatus);
+    Page<MonitorPoint> findByPage(long pageNum, long pageSize, String pointCode, String pointName,
+                                  String associationStatus, Integer pointType);
 
     boolean saveWithCheck(MonitorPoint entity);
 
@@ -27,7 +28,7 @@ public interface MonitorPointService extends IService<MonitorPoint> {
 
     Map<String, Object> getPointDetail(String pointId);
 
-    Map<String, Object> statistics();
+    Map<String, Object> statistics(Integer pointType);
 
     List<Map<String, Object>> listEquipmentForAssociation(String pointId);
 }

+ 75 - 16
pipe-network-service/zksy-system/src/main/java/com/zksy/base/service/impl/MonitorPointServiceImpl.java

@@ -6,7 +6,9 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.zksy.base.domain.EquipmentBase;
 import com.zksy.base.domain.EquipmentPointRel;
+import com.zksy.base.domain.EquipmentType;
 import com.zksy.base.domain.MonitorPoint;
+import com.zksy.base.enums.MonitorPointTypeEnum;
 import com.zksy.base.mapper.EquipmentBaseMapper;
 import com.zksy.base.mapper.EquipmentPointRelMapper;
 import com.zksy.base.mapper.EquipmentTypeMapper;
@@ -37,7 +39,8 @@ public class MonitorPointServiceImpl extends ServiceImpl<MonitorPointMapper, Mon
     private EquipmentTypeMapper equipmentTypeMapper;
 
     @Override
-    public Page<MonitorPoint> findByPage(long pageNum, long pageSize, String pointCode, String pointName, String associationStatus) {
+    public Page<MonitorPoint> findByPage(long pageNum, long pageSize, String pointCode, String pointName,
+                                         String associationStatus, Integer pointType) {
         Page<MonitorPoint> page = new Page<>(pageNum, pageSize);
         LambdaQueryWrapper<MonitorPoint> queryWrapper = new LambdaQueryWrapper<>();
         if (pointCode != null && !pointCode.isEmpty()) {
@@ -46,6 +49,9 @@ public class MonitorPointServiceImpl extends ServiceImpl<MonitorPointMapper, Mon
         if (pointName != null && !pointName.isEmpty()) {
             queryWrapper.like(MonitorPoint::getPointName, pointName);
         }
+        if (pointType != null) {
+            queryWrapper.eq(MonitorPoint::getPointType, pointType);
+        }
         if ("linked".equalsIgnoreCase(associationStatus) || "pending".equalsIgnoreCase(associationStatus)) {
             List<String> linkedPointIds = equipmentPointRelMapper.selectList(new LambdaQueryWrapper<>()).stream()
                     .map(EquipmentPointRel::getPointId).filter(Objects::nonNull).distinct().collect(Collectors.toList());
@@ -64,6 +70,7 @@ public class MonitorPointServiceImpl extends ServiceImpl<MonitorPointMapper, Mon
 
     @Override
     public boolean saveWithCheck(MonitorPoint entity) {
+        validatePointType(entity.getPointType());
         LambdaQueryWrapper<MonitorPoint> queryWrapper = new LambdaQueryWrapper<>();
         queryWrapper.eq(MonitorPoint::getPointCode, entity.getPointCode());
         long count = this.count(queryWrapper);
@@ -77,6 +84,7 @@ public class MonitorPointServiceImpl extends ServiceImpl<MonitorPointMapper, Mon
     public boolean saveBatchWithCheck(List<MonitorPoint> entityList) {
         Set<String> codeSet = new HashSet<>();
         for (MonitorPoint entity : entityList) {
+            validatePointType(entity.getPointType());
             String code = entity.getPointCode();
             if (codeSet.contains(code)) {
                 throw new ServiceException("列表中存在重复编码:" + code);
@@ -98,9 +106,15 @@ public class MonitorPointServiceImpl extends ServiceImpl<MonitorPointMapper, Mon
     @Transactional
     public boolean updateWithCheck(MonitorPoint entity) {
         String pointId = entity.getPointId();
-        if (this.getById(pointId) == null) {
+        MonitorPoint existingPoint = this.getById(pointId);
+        if (existingPoint == null) {
             throw new ServiceException("监测点不存在,无法修改:" + pointId);
         }
+        validatePointType(entity.getPointType());
+        if (!Objects.equals(existingPoint.getPointType(), entity.getPointType())
+                && equipmentCount(pointId) > 0) {
+            throw new ServiceException("监测点已关联设备,不能修改监测点类型,请先解除全部设备关联");
+        }
 
         String newCode = entity.getPointCode();
         if (newCode != null) {
@@ -174,12 +188,16 @@ public class MonitorPointServiceImpl extends ServiceImpl<MonitorPointMapper, Mon
             throw new ServiceException("设备ID列表不能为空");
         }
         equipmentIds = new ArrayList<>(new LinkedHashSet<>(equipmentIds));
+        Map<Integer, EquipmentType> equipmentTypeMap = loadEquipmentTypeMap();
 
         for (String equipmentId : equipmentIds) {
             EquipmentBase equipment = equipmentBaseMapper.selectById(equipmentId);
             if (equipment == null) {
                 throw new ServiceException("设备不存在:" + equipmentId);
             }
+            if (!belongsToPointType(equipment.getEquipmentTypeId(), point.getPointType(), equipmentTypeMap)) {
+                throw new ServiceException("设备[" + equipment.getEquipmentCode() + "]类型与监测点类型不匹配");
+            }
             LambdaQueryWrapper<EquipmentPointRel> checkWrapper = new LambdaQueryWrapper<>();
             checkWrapper.eq(EquipmentPointRel::getEquipmentId, equipmentId);
             List<EquipmentPointRel> existing = equipmentPointRelMapper.selectList(checkWrapper);
@@ -243,24 +261,38 @@ public class MonitorPointServiceImpl extends ServiceImpl<MonitorPointMapper, Mon
     }
 
     @Override
-    public Map<String, Object> statistics() {
-        List<EquipmentPointRel> relations = equipmentPointRelMapper.selectList(new LambdaQueryWrapper<>());
+    public Map<String, Object> statistics(Integer pointType) {
+        LambdaQueryWrapper<MonitorPoint> pointWrapper = new LambdaQueryWrapper<>();
+        pointWrapper.eq(pointType != null, MonitorPoint::getPointType, pointType);
+        List<MonitorPoint> points = this.list(pointWrapper);
+        Set<String> pointIds = points.stream().map(MonitorPoint::getPointId).collect(Collectors.toSet());
+        List<EquipmentPointRel> relations = pointIds.isEmpty()
+                ? Collections.emptyList()
+                : equipmentPointRelMapper.selectList(new LambdaQueryWrapper<EquipmentPointRel>()
+                        .in(EquipmentPointRel::getPointId, pointIds));
         Set<String> linkedPointIds = relations.stream().map(EquipmentPointRel::getPointId).filter(Objects::nonNull).collect(Collectors.toSet());
         Map<String, Object> result = new LinkedHashMap<>();
-        result.put("totalPoints", this.count());
+        result.put("totalPoints", points.size());
         result.put("linkedPoints", linkedPointIds.size());
-        result.put("pendingPoints", Math.max(0, this.count() - linkedPointIds.size()));
+        result.put("pendingPoints", Math.max(0, points.size() - linkedPointIds.size()));
         result.put("totalDevices", relations.stream().map(EquipmentPointRel::getEquipmentId).filter(Objects::nonNull).distinct().count());
         return result;
     }
 
     @Override
     public List<Map<String, Object>> listEquipmentForAssociation(String pointId) {
+        MonitorPoint point = this.getById(pointId);
+        if (point == null) {
+            throw new ServiceException("监测点不存在:" + pointId);
+        }
         Map<String, String> ownerByEquipment = equipmentPointRelMapper.selectList(new LambdaQueryWrapper<>()).stream()
                 .filter(rel -> rel.getEquipmentId() != null)
                 .collect(Collectors.toMap(EquipmentPointRel::getEquipmentId, EquipmentPointRel::getPointId, (left, right) -> left));
-        List<EquipmentBase> devices = equipmentBaseMapper.selectList(null);
-        fillEquipmentTypeNames(devices);
+        Map<Integer, EquipmentType> equipmentTypeMap = loadEquipmentTypeMap();
+        List<EquipmentBase> devices = equipmentBaseMapper.selectList(null).stream()
+                .filter(device -> belongsToPointType(device.getEquipmentTypeId(), point.getPointType(), equipmentTypeMap))
+                .collect(Collectors.toList());
+        fillEquipmentTypeNames(devices, equipmentTypeMap);
         return devices.stream().map(device -> {
             Map<String, Object> row = new LinkedHashMap<>();
             row.put("equipmentId", device.getEquipmentId());
@@ -276,17 +308,44 @@ public class MonitorPointServiceImpl extends ServiceImpl<MonitorPointMapper, Mon
 
     private void fillEquipmentTypeNames(List<EquipmentBase> devices) {
         if (devices == null || devices.isEmpty()) return;
-        Set<Integer> typeIds = devices.stream()
-                .map(EquipmentBase::getEquipmentTypeId)
-                .filter(Objects::nonNull)
-                .collect(Collectors.toSet());
-        if (typeIds.isEmpty()) return;
-        equipmentTypeMapper.selectBatchIds(typeIds).forEach(type -> devices.stream()
-                .filter(device -> Objects.equals(device.getEquipmentTypeId(), type.getId()))
-                .forEach(device -> device.setEquipmentTypeName(type.getTypeName())));
+        fillEquipmentTypeNames(devices, loadEquipmentTypeMap());
+    }
+
+    private void fillEquipmentTypeNames(List<EquipmentBase> devices, Map<Integer, EquipmentType> equipmentTypeMap) {
+        if (devices == null || devices.isEmpty()) return;
+        devices.forEach(device -> {
+            EquipmentType type = equipmentTypeMap.get(device.getEquipmentTypeId());
+            device.setEquipmentTypeName(type == null ? null : type.getTypeName());
+        });
+    }
+
+    private Map<Integer, EquipmentType> loadEquipmentTypeMap() {
+        return equipmentTypeMapper.selectList(null).stream()
+                .collect(Collectors.toMap(EquipmentType::getId, type -> type, (left, right) -> left));
+    }
+
+    private boolean belongsToPointType(Integer equipmentTypeId, Integer pointType,
+                                       Map<Integer, EquipmentType> equipmentTypeMap) {
+        if (pointType == null) return true;
+        if (equipmentTypeId == null) return false;
+        Integer currentTypeId = equipmentTypeId;
+        Set<Integer> visited = new HashSet<>();
+        while (currentTypeId != null && currentTypeId != 0 && visited.add(currentTypeId)) {
+            if (Objects.equals(currentTypeId, pointType)) return true;
+            EquipmentType currentType = equipmentTypeMap.get(currentTypeId);
+            if (currentType == null) return false;
+            currentTypeId = currentType.getParentId();
+        }
+        return false;
     }
 
     private long equipmentCount(String pointId) {
         return equipmentPointRelMapper.selectCount(new LambdaQueryWrapper<EquipmentPointRel>().eq(EquipmentPointRel::getPointId, pointId));
     }
+
+    private void validatePointType(Integer pointType) {
+        if (!MonitorPointTypeEnum.isValid(pointType)) {
+            throw new ServiceException("监测点类型无效:" + pointType);
+        }
+    }
 }

+ 44 - 0
pipe-network-service/zksy-system/src/main/java/com/zksy/drainage/service/impl/DrainageEquipmentPointRelServiceImpl.java

@@ -5,9 +5,11 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.zksy.base.domain.EquipmentBase;
 import com.zksy.base.domain.EquipmentPointRel;
+import com.zksy.base.domain.EquipmentType;
 import com.zksy.base.domain.MonitorPoint;
 import com.zksy.base.mapper.EquipmentBaseMapper;
 import com.zksy.base.mapper.EquipmentPointRelMapper;
+import com.zksy.base.mapper.EquipmentTypeMapper;
 import com.zksy.base.mapper.MonitorPointMapper;
 import com.zksy.common.exception.ServiceException;
 import com.zksy.drainage.service.DrainageEquipmentPointRelService;
@@ -19,6 +21,7 @@ import org.springframework.transaction.annotation.Transactional;
 
 import java.time.LocalDateTime;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 import java.util.stream.Collectors;
 
@@ -33,6 +36,9 @@ public class DrainageEquipmentPointRelServiceImpl extends ServiceImpl<EquipmentP
     @Autowired
     private MonitorPointMapper monitorPointMapper;
 
+    @Autowired
+    private EquipmentTypeMapper equipmentTypeMapper;
+
     @Override
     public Page<EquipmentPointRel> findByPage(long pageNum, long pageSize,
                                               String equipmentId, String pointId) {
@@ -65,6 +71,7 @@ public class DrainageEquipmentPointRelServiceImpl extends ServiceImpl<EquipmentP
         if (monitorPointMapper.selectById(pointId) == null) {
             throw new ServiceException("关联的监测点不存在:" + pointId);
         }
+        validateEquipmentType(entity);
         // 校验是否已存在相同关联
         LambdaQueryWrapper<EquipmentPointRel> wrapper = new LambdaQueryWrapper<>();
         wrapper.eq(EquipmentPointRel::getEquipmentId, equipmentId)
@@ -112,6 +119,17 @@ public class DrainageEquipmentPointRelServiceImpl extends ServiceImpl<EquipmentP
         if (!invalidPointIds.isEmpty()) {
             throw new ServiceException("存在无效的监测点ID:" + invalidPointIds);
         }
+        Map<Integer, EquipmentType> typeMap = equipmentTypeMapper.selectList(null).stream()
+                .collect(Collectors.toMap(EquipmentType::getId, type -> type, (left, right) -> left));
+        Map<String, MonitorPoint> pointMap = monitorPointMapper.selectBatchIds(pointIds).stream()
+                .collect(Collectors.toMap(MonitorPoint::getPointId, point -> point));
+        Map<String, EquipmentBase> equipmentMap = equipmentBaseMapper.selectBatchIds(equipmentIds).stream()
+                .collect(Collectors.toMap(EquipmentBase::getEquipmentId, equipment -> equipment));
+        for (EquipmentPointRel relation : entityList) {
+            if (!belongsToPointType(equipmentMap.get(relation.getEquipmentId()), pointMap.get(relation.getPointId()), typeMap)) {
+                throw new ServiceException("设备与监测点类型不匹配:" + relation.getEquipmentId());
+            }
+        }
         Set<String> duplicateEquipmentIds = equipmentIds.stream()
                 .filter(equipmentId -> this.count(new LambdaQueryWrapper<EquipmentPointRel>()
                         .eq(EquipmentPointRel::getEquipmentId, equipmentId)) > 0)
@@ -152,4 +170,30 @@ public class DrainageEquipmentPointRelServiceImpl extends ServiceImpl<EquipmentP
         wrapper.eq(EquipmentPointRel::getPointId, pointId);
         return this.list(wrapper);
     }
+
+    private void validateEquipmentType(EquipmentPointRel relation) {
+        MonitorPoint point = monitorPointMapper.selectById(relation.getPointId());
+        EquipmentBase equipment = equipmentBaseMapper.selectById(relation.getEquipmentId());
+        Map<Integer, EquipmentType> typeMap = equipmentTypeMapper.selectList(null).stream()
+                .collect(Collectors.toMap(EquipmentType::getId, type -> type, (left, right) -> left));
+        if (!belongsToPointType(equipment, point, typeMap)) {
+            throw new ServiceException("设备与监测点类型不匹配:" + relation.getEquipmentId());
+        }
+    }
+
+    private boolean belongsToPointType(EquipmentBase equipment, MonitorPoint point,
+                                       Map<Integer, EquipmentType> typeMap) {
+        if (point == null) return false;
+        if (point.getPointType() == null) return true;
+        if (equipment == null || equipment.getEquipmentTypeId() == null) return false;
+        Integer currentId = equipment.getEquipmentTypeId();
+        Set<Integer> visited = new java.util.HashSet<>();
+        while (currentId != null && currentId != 0 && visited.add(currentId)) {
+            if (point.getPointType().equals(currentId)) return true;
+            EquipmentType type = typeMap.get(currentId);
+            if (type == null) return false;
+            currentId = type.getParentId();
+        }
+        return false;
+    }
 }