Forráskód Böngészése

调整门禁设备domain相关逻辑、设备绑定相关方法、新增门禁设备心跳接口

fanghuisheng 1 hete
szülő
commit
4071ea89fd
16 módosított fájl, 587 hozzáadás és 73 törlés
  1. 2 0
      service-eg/service-eg-api/src/main/java/com/usky/eg/RemoteEgService.java
  2. 5 1
      service-eg/service-eg-api/src/main/java/com/usky/eg/factory/RemoteEgFallbackFactory.java
  3. 17 0
      service-eg/service-eg-biz/src/main/java/com/usky/eg/controller/api/ServiceEgTaskApi.java
  4. 41 0
      service-eg/service-eg-biz/src/main/java/com/usky/eg/controller/web/EgDeviceHeartbeatController.java
  5. 9 10
      service-eg/service-eg-biz/src/main/java/com/usky/eg/domain/EgDevice.java
  6. 84 0
      service-eg/service-eg-biz/src/main/java/com/usky/eg/domain/EgDeviceHeartbeat.java
  7. 18 0
      service-eg/service-eg-biz/src/main/java/com/usky/eg/mapper/EgDeviceHeartbeatMapper.java
  8. 19 0
      service-eg/service-eg-biz/src/main/java/com/usky/eg/service/EgDeviceHeartbeatService.java
  9. 120 0
      service-eg/service-eg-biz/src/main/java/com/usky/eg/service/impl/EgDeviceHeartbeatServiceImpl.java
  10. 210 50
      service-eg/service-eg-biz/src/main/java/com/usky/eg/service/impl/EgDeviceServiceImpl.java
  11. 17 10
      service-eg/service-eg-biz/src/main/java/com/usky/eg/service/impl/EgRecordServiceImpl.java
  12. 9 0
      service-eg/service-eg-biz/src/main/java/com/usky/eg/service/vo/EgDeviceRequestVO.java
  13. 20 0
      service-eg/service-eg-biz/src/main/resources/mapper/eg/EgDeviceHeartbeatMapper.xml
  14. 0 2
      service-eg/service-eg-biz/src/main/resources/mapper/eg/EgDeviceMapper.xml
  15. 6 0
      service-job/pom.xml
  16. 10 0
      service-job/src/main/java/com/ruoyi/job/task/RyTask.java

+ 2 - 0
service-eg/service-eg-api/src/main/java/com/usky/eg/RemoteEgService.java

@@ -13,4 +13,6 @@ import java.util.Map;
 @FeignClient(contextId = "remoteEgService", value = "service-eg", fallbackFactory = RemoteEgFallbackFactory.class)
 public interface RemoteEgService {
 
+    @GetMapping("/egDeviceStatus")
+    void egDeviceStatus();
 }

+ 5 - 1
service-eg/service-eg-api/src/main/java/com/usky/eg/factory/RemoteEgFallbackFactory.java

@@ -1,5 +1,6 @@
 package com.usky.eg.factory;
 
+import com.usky.common.core.exception.FeignBadRequestException;
 import com.usky.eg.RemoteEgService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -23,7 +24,10 @@ public class RemoteEgFallbackFactory implements FallbackFactory<RemoteEgService>
     {
         log.error("用户服务调用失败:{}", throwable.getMessage());
         return new RemoteEgService() {
-
+            @Override
+            public void egDeviceStatus() {
+                throw new FeignBadRequestException(500,"同步门禁设备状态异常"+throwable.getMessage());
+            }
         };
     }
 }

+ 17 - 0
service-eg/service-eg-biz/src/main/java/com/usky/eg/controller/api/ServiceEgTaskApi.java

@@ -0,0 +1,17 @@
+package com.usky.eg.controller.api;
+
+import com.usky.eg.RemoteEgService;
+import com.usky.eg.service.EgDeviceHeartbeatService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+public class ServiceEgTaskApi implements RemoteEgService {
+    @Autowired
+    private EgDeviceHeartbeatService egDeviceHeartbeatService;
+
+    @Override
+    public void egDeviceStatus(){
+        egDeviceHeartbeatService.updateEgDeviceStatus();
+    }
+}

+ 41 - 0
service-eg/service-eg-biz/src/main/java/com/usky/eg/controller/web/EgDeviceHeartbeatController.java

@@ -0,0 +1,41 @@
+package com.usky.eg.controller.web;
+
+
+import com.usky.common.core.bean.ApiResult;
+import com.usky.eg.domain.EgDeviceHeartbeat;
+import com.usky.eg.service.EgDeviceHeartbeatService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * <p>
+ * 门禁设备心跳表 前端控制器
+ * </p>
+ *
+ * @author fhs
+ * @since 2026-02-03
+ */
+@RestController
+@RequestMapping("/egDeviceHeartbeat")
+public class EgDeviceHeartbeatController {
+    private static final Logger log = LoggerFactory.getLogger(EgDeviceHeartbeatController.class);
+    @Autowired
+    private EgDeviceHeartbeatService heartbeatService;
+
+    @PostMapping("/escalation")
+    public ApiResult<Void> heartbeatEscalation(@RequestBody EgDeviceHeartbeat heartbeat) {
+        try {
+            heartbeatService.heartbeatEscalation(heartbeat);
+        } catch (Exception e) {
+            log.error("设备心跳异常", e);
+            return ApiResult.error("设备心跳入库异常!");
+        }
+        return ApiResult.success();
+    }
+}

+ 9 - 10
service-eg/service-eg-biz/src/main/java/com/usky/eg/domain/EgDevice.java

@@ -62,16 +62,6 @@ public class EgDevice implements Serializable {
      */
     private String deviceIp;
 
-    /**
-     * 端口
-     */
-    private Integer devicePort;
-
-    /**
-     * 门禁号
-     */
-    private String egNumber;
-
     /**
      * 绑定人脸信息
      */
@@ -134,6 +124,15 @@ public class EgDevice implements Serializable {
     private String workStatus;
 
     /**
+     * 设备code
+     */
+    private String deviceCode;
+
+    /**
+     * 设备状态;1:在线,0:离线(从心跳表获取,不映射到数据库)
+     */
+    @TableField(exist = false)
+    private Integer deviceStatus;
 
     /**
      * 用户人脸信息记录

+ 84 - 0
service-eg/service-eg-biz/src/main/java/com/usky/eg/domain/EgDeviceHeartbeat.java

@@ -0,0 +1,84 @@
+package com.usky.eg.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+
+import java.time.LocalDateTime;
+import java.io.Serializable;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * <p>
+ * 门禁设备心跳表
+ * </p>
+ *
+ * @author fhs
+ * @since 2026-02-03
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+public class EgDeviceHeartbeat implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 门禁设备心跳表主键ID
+     */
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    /**
+     * 设备code
+     */
+    private String deviceCode;
+
+    /**
+     * 设备ip地址
+     */
+    private String ipAddr;
+
+    /**
+     * 设备mac地址
+     */
+    private String macAddr;
+
+    /**
+     * 设备类型(1.会议屏 2.信息发布屏 3.综合屏)
+     */
+    private Boolean deviceType;
+
+    /**
+     * 创建日期
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime createTime;
+
+    /**
+     * 设备型号
+     */
+    private String model;
+
+    /**
+     *  设备厂商
+     */
+    private String manuFacturer;
+
+    /**
+     * 设备版本号
+     */
+    private String version;
+
+    /**
+     * 设备sdk
+     */
+    private String sdk;
+
+    /**
+     * 设备状态;1:在线,0:离线
+     */
+    private Integer deviceStatus;
+
+}

+ 18 - 0
service-eg/service-eg-biz/src/main/java/com/usky/eg/mapper/EgDeviceHeartbeatMapper.java

@@ -0,0 +1,18 @@
+package com.usky.eg.mapper;
+
+import com.usky.eg.domain.EgDeviceHeartbeat;
+import com.usky.common.mybatis.core.CrudMapper;
+import org.springframework.stereotype.Repository;
+
+/**
+ * <p>
+ * 门禁设备心跳表 Mapper 接口
+ * </p>
+ *
+ * @author fhs
+ * @since 2026-02-03
+ */
+@Repository
+public interface EgDeviceHeartbeatMapper extends CrudMapper<EgDeviceHeartbeat> {
+
+}

+ 19 - 0
service-eg/service-eg-biz/src/main/java/com/usky/eg/service/EgDeviceHeartbeatService.java

@@ -0,0 +1,19 @@
+package com.usky.eg.service;
+
+import com.usky.eg.domain.EgDeviceHeartbeat;
+import com.usky.common.mybatis.core.CrudService;
+
+/**
+ * <p>
+ * 门禁设备心跳表 服务类
+ * </p>
+ *
+ * @author fhs
+ * @since 2026-02-03
+ */
+public interface EgDeviceHeartbeatService extends CrudService<EgDeviceHeartbeat> {
+
+    void heartbeatEscalation(EgDeviceHeartbeat heartbeat);
+
+    void updateEgDeviceStatus();
+}

+ 120 - 0
service-eg/service-eg-biz/src/main/java/com/usky/eg/service/impl/EgDeviceHeartbeatServiceImpl.java

@@ -0,0 +1,120 @@
+package com.usky.eg.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.usky.common.core.exception.BusinessException;
+import com.usky.eg.domain.EgDevice;
+import com.usky.eg.domain.EgDeviceHeartbeat;
+import com.usky.eg.mapper.EgDeviceHeartbeatMapper;
+import com.usky.eg.mapper.EgDeviceMapper;
+import com.usky.eg.service.EgDeviceHeartbeatService;
+import com.usky.common.mybatis.core.AbstractCrudService;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.time.LocalDateTime;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * <p>
+ * 门禁设备心跳表 服务实现类
+ * </p>
+ *
+ * @author fhs
+ * @since 2026-02-03
+ */
+@Service
+public class EgDeviceHeartbeatServiceImpl extends AbstractCrudService<EgDeviceHeartbeatMapper, EgDeviceHeartbeat> implements EgDeviceHeartbeatService {
+
+    @Autowired
+    private EgDeviceMapper egDeviceMapper;
+
+    @Transactional(rollbackFor = Exception.class)
+    @Override
+    public void heartbeatEscalation(EgDeviceHeartbeat heartbeat) {
+        if (StringUtils.isBlank(heartbeat.getDeviceCode())) {
+            throw new BusinessException("设备编码不能为空!");
+        }
+        if (StringUtils.isBlank(heartbeat.getIpAddr())) {
+            throw new BusinessException("设备IP不能为空!");
+        }
+        if (heartbeat.getCreateTime() == null) {
+            heartbeat.setCreateTime(LocalDateTime.now());
+        }
+        if (heartbeat.getDeviceType() == null) {
+            throw new BusinessException("设备类型不能为空!");
+        }
+
+        // 删除旧数据
+        LambdaQueryWrapper<EgDeviceHeartbeat> deleteWrapper = Wrappers.lambdaQuery();
+        deleteWrapper.eq(EgDeviceHeartbeat::getDeviceCode, heartbeat.getDeviceCode());
+        baseMapper.delete(deleteWrapper);
+
+        baseMapper.insert(heartbeat);
+    }
+
+    /**
+     * 更新设备状态定时任务服务接口
+     * 从心跳表中获取设备状态并同步更新
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void updateEgDeviceStatus() {
+        try {
+            LocalDateTime now = LocalDateTime.now();
+            
+            // 1. 查询所有心跳记录,按deviceCode分组,获取最新的心跳记录
+            LambdaQueryWrapper<EgDeviceHeartbeat> heartbeatQueryWrapper = Wrappers.lambdaQuery();
+            heartbeatQueryWrapper.orderByDesc(EgDeviceHeartbeat::getCreateTime);
+            List<EgDeviceHeartbeat> heartbeatList = baseMapper.selectList(heartbeatQueryWrapper);
+            
+            // 2. 构建deviceCode到最新心跳记录的映射
+            Map<String, EgDeviceHeartbeat> deviceHeartbeatMap = new HashMap<>();
+            for (EgDeviceHeartbeat heartbeat : heartbeatList) {
+                String deviceCode = heartbeat.getDeviceCode();
+                if (StringUtils.isNotBlank(deviceCode)) {
+                    // 如果已存在,保留最新的(因为已按createTime降序排列)
+                    deviceHeartbeatMap.putIfAbsent(deviceCode, heartbeat);
+                }
+            }
+            
+            // 3. 查询所有有deviceCode的设备
+            LambdaQueryWrapper<EgDevice> deviceQueryWrapper = Wrappers.lambdaQuery();
+            deviceQueryWrapper.isNotNull(EgDevice::getDeviceCode);
+            List<EgDevice> deviceList = egDeviceMapper.selectList(deviceQueryWrapper);
+            
+            // 4. 根据心跳表中的deviceStatus更新心跳记录的状态(如果需要根据时间判断)
+            // 或者同步设备状态到其他服务
+            for (EgDevice device : deviceList) {
+                String deviceCode = device.getDeviceCode();
+                if (StringUtils.isNotBlank(deviceCode) && deviceHeartbeatMap.containsKey(deviceCode)) {
+                    EgDeviceHeartbeat heartbeat = deviceHeartbeatMap.get(deviceCode);
+                    LocalDateTime heartbeatTime = heartbeat.getCreateTime();
+                    Integer deviceStatus = heartbeat.getDeviceStatus();
+                    
+                    // 如果心跳时间超过5分钟,且当前状态为在线,则更新为离线
+                    if (heartbeatTime != null && deviceStatus != null && deviceStatus == 1) {
+                        if (!heartbeatTime.isAfter(now.minusMinutes(5))) {
+                            // 心跳超时,更新心跳表中的状态为离线
+                            LambdaUpdateWrapper<EgDeviceHeartbeat> updateWrapper = Wrappers.lambdaUpdate();
+                            updateWrapper.set(EgDeviceHeartbeat::getDeviceStatus, 0)
+                                    .eq(EgDeviceHeartbeat::getId, heartbeat.getId());
+                            baseMapper.update(null, updateWrapper);
+                        }
+                    }
+                    
+                    // 这里可以根据实际需求添加其他业务逻辑
+                    // 例如:同步到IoT服务、发送告警等
+                }
+            }
+        } catch (Exception e) {
+            // 定时任务失败不影响主流程,记录日志即可
+            throw new BusinessException("更新设备状态失败:" + (e.getMessage() != null ? e.getMessage() : "未知错误"));
+        }
+    }
+}

+ 210 - 50
service-eg/service-eg-biz/src/main/java/com/usky/eg/service/impl/EgDeviceServiceImpl.java

@@ -13,9 +13,11 @@ import com.usky.common.core.exception.BusinessException;
 import com.usky.common.core.util.UUIDUtils;
 import com.usky.common.security.utils.SecurityUtils;
 import com.usky.eg.domain.EgDevice;
+import com.usky.eg.domain.EgDeviceHeartbeat;
 import com.usky.eg.domain.MeetingFace;
 import com.usky.eg.domain.MeetingFaceDevice;
 import com.usky.eg.mapper.EgDeviceMapper;
+import com.usky.eg.mapper.EgDeviceHeartbeatMapper;
 import com.usky.eg.mapper.MeetingFaceDeviceMapper;
 import com.usky.eg.mapper.MeetingFaceMapper;
 import com.usky.eg.service.EgDeviceService;
@@ -29,6 +31,7 @@ import org.springframework.stereotype.Service;
 
 import java.time.LocalDateTime;
 import java.util.*;
+import java.util.concurrent.CompletableFuture;
 
 /**
  * <p>
@@ -47,6 +50,8 @@ public class EgDeviceServiceImpl extends AbstractCrudService<EgDeviceMapper, EgD
     @Autowired
     private EgDeviceMapper egDeviceMapper;
     @Autowired
+    private EgDeviceHeartbeatMapper egDeviceHeartbeatMapper;
+    @Autowired
     private RemoteIotTaskService remoteIotTaskService;
 
     @Autowired
@@ -55,10 +60,17 @@ public class EgDeviceServiceImpl extends AbstractCrudService<EgDeviceMapper, EgD
     @Override
     public CommonPage<EgDevice> page(EgDeviceRequestVO requestVO){
         IPage<EgDevice> page = new Page<>(requestVO.getCurrent(),requestVO.getSize());
-        Integer tenantId ;
+        Integer tenantId;
 
-        if(StringUtils.isNotBlank(requestVO.getDomain())){
-            tenantId = egDeviceMapper.sysTenantId(requestVO.getDomain());
+        if(StringUtils.isNotBlank(requestVO.getDeviceCode())){
+            LambdaQueryWrapper<EgDevice> tempQueryWrapper = new LambdaQueryWrapper<>();
+            tempQueryWrapper.eq(EgDevice::getDeviceCode, requestVO.getDeviceCode());
+            EgDevice tempDevice = egDeviceMapper.selectOne(tempQueryWrapper);
+            if(tempDevice == null){
+                // 未查询到数据,返回空数组
+                return new CommonPage<>(new ArrayList<>(), 0L, requestVO.getSize(), requestVO.getCurrent());
+            }
+            tenantId = tempDevice.getTenantId();
         }else{
             tenantId = SecurityUtils.getTenantId();
         }
@@ -69,10 +81,61 @@ public class EgDeviceServiceImpl extends AbstractCrudService<EgDeviceMapper, EgD
                 .eq(null != requestVO.getServiceStatus(),EgDevice::getServiceStatus,requestVO.getServiceStatus())
                 .eq(null != requestVO.getId(),EgDevice::getId,requestVO.getId())
                 .eq(null != requestVO.getDeviceUuid(),EgDevice::getDeviceUuid, requestVO.getDeviceUuid())
+                .eq(StringUtils.isNotBlank(requestVO.getDeviceCode()),EgDevice::getDeviceCode, requestVO.getDeviceCode())
                 .eq(EgDevice::getTenantId,tenantId)
                 .orderByDesc(EgDevice::getId);
         page = this.page(page,queryWrapper);
+
         if(!page.getRecords().isEmpty()){
+            // 检查设备心跳
+            List<String> validDeviceCodes = new ArrayList<>();
+            Map<String, EgDevice> deviceCodeMap = new HashMap<>();
+            for (EgDevice device : page.getRecords()) {
+                String deviceCode = device.getDeviceCode();
+                if (StringUtils.isNotBlank(deviceCode)) {
+                    validDeviceCodes.add(deviceCode);
+                    deviceCodeMap.put(deviceCode, device);
+                }
+            }
+
+            // 批量查询心跳数据
+            if (!validDeviceCodes.isEmpty()) {
+                LambdaQueryWrapper<EgDeviceHeartbeat> heartbeatQueryWrapper = Wrappers.lambdaQuery();
+                heartbeatQueryWrapper.in(EgDeviceHeartbeat::getDeviceCode, validDeviceCodes)
+                        .orderByDesc(EgDeviceHeartbeat::getCreateTime);
+                List<EgDeviceHeartbeat> heartbeatList = egDeviceHeartbeatMapper.selectList(heartbeatQueryWrapper);
+                
+                // 构建deviceCode到最新心跳记录的映射,并同时设置deviceStatus
+                Map<String, EgDeviceHeartbeat> deviceHeartbeatMap = new HashMap<>();
+                for (EgDeviceHeartbeat heartbeat : heartbeatList) {
+                    String deviceCode = heartbeat.getDeviceCode();
+                    if (StringUtils.isNotBlank(deviceCode)) {
+                        // 如果已存在,保留最新的(因为已按createTime降序排列)
+                        if (deviceHeartbeatMap.putIfAbsent(deviceCode, heartbeat) == null) {
+                            // 首次遇到该deviceCode,从心跳表直接获取deviceStatus并设置到EgDevice
+                            EgDevice device = deviceCodeMap.get(deviceCode);
+                            if (device != null && heartbeat.getDeviceStatus() != null) {
+                                device.setDeviceStatus(heartbeat.getDeviceStatus());
+                            }
+                        }
+                    }
+                }
+                
+                // 如果未找到对应的设备心跳记录,则设置DeviceStatus为0(离线)
+                for (String deviceCode : validDeviceCodes) {
+                    if (!deviceHeartbeatMap.containsKey(deviceCode)) {
+                        EgDevice device = deviceCodeMap.get(deviceCode);
+                        if (device != null) {
+                            device.setDeviceStatus(0);
+                        }
+                    }
+                }
+            } else {
+                // 如果没有有效的deviceCode,将所有设备的deviceStatus设置为0
+                for (EgDevice device : page.getRecords()) {
+                    device.setDeviceStatus(0);
+                }
+            }
 
             LambdaQueryWrapper<MeetingFace> meetingFaceQuery = Wrappers.lambdaQuery();
             meetingFaceQuery.select(MeetingFace::getFid,MeetingFace::getCreateTime,MeetingFace::getVefNum,MeetingFace::getFaceName,MeetingFace::getRemark,MeetingFace::getFaceStatus,MeetingFace::getCardNum,MeetingFace::getBindDevice,MeetingFace::getDeptId,MeetingFace::getTenantId,MeetingFace::getUserId)
@@ -95,7 +158,6 @@ public class EgDeviceServiceImpl extends AbstractCrudService<EgDeviceMapper, EgD
                     }
                     page.getRecords().get(i).setMeetingFaceList(meetingFaceList);
                 }
-
             }
         }
 
@@ -122,7 +184,7 @@ public class EgDeviceServiceImpl extends AbstractCrudService<EgDeviceMapper, EgD
                     .like(StringUtils.isNotBlank(requestVO.getInstallAddress()),EgDevice::getInstallAddress,requestVO.getInstallAddress())
                     .eq(null != requestVO.getServiceStatus(),EgDevice::getServiceStatus,requestVO.getServiceStatus())
                     .eq(null != requestVO.getId(),EgDevice::getId,requestVO.getId())
-                    .in(EgDevice::getId,deviceFid)
+                    .in(EgDevice::getId, (Object[]) deviceFid)
                     .eq(EgDevice::getTenantId,SecurityUtils.getTenantId())
                     .orderByDesc(EgDevice::getId);
             page = this.page(page,queryWrapper);
@@ -133,32 +195,87 @@ public class EgDeviceServiceImpl extends AbstractCrudService<EgDeviceMapper, EgD
     }
 
     @Override
-    public void add(EgDevice egDevice){
-        if(checkNameUnique(egDevice)){
-            throw new BusinessException("新增门禁门号设备'"+egDevice.getDeviceId()+","+egDevice.getEgNumber()+"'失败,设备已存在");
+    public void add(EgDevice egDevice) {
+        LocalDateTime now = LocalDateTime.now();
+        
+        // 1. 校验设备名称唯一性
+        if (checkDeviceNameUnique(egDevice)) {
+            throw new BusinessException("新增门禁设备'" + egDevice.getDeviceName() + "'失败,设备已存在");
         }
-        if(checkDeviceNameUnique(egDevice)){
-            throw new BusinessException("新增门禁设备'"+egDevice.getDeviceName()+"'失败,设备已存在");
+
+        // 2. 通过IP校验心跳表,确保设备在线且心跳时间在5分钟内
+        String deviceIp = egDevice.getDeviceIp();
+        if (StringUtils.isNotBlank(deviceIp)) {
+            LambdaQueryWrapper<EgDeviceHeartbeat> heartbeatWrapper = Wrappers.lambdaQuery();
+            heartbeatWrapper.eq(EgDeviceHeartbeat::getIpAddr, deviceIp)
+                    .orderByDesc(EgDeviceHeartbeat::getCreateTime)
+                    .last("LIMIT 1");
+            EgDeviceHeartbeat heartbeat = egDeviceHeartbeatMapper.selectOne(heartbeatWrapper);
+            
+            // 校验心跳记录存在且时间在5分钟内
+            if (heartbeat == null || heartbeat.getCreateTime() == null 
+                    || !heartbeat.getCreateTime().isAfter(now.minusMinutes(5))) {
+                throw new BusinessException("设备不在线,请检查设备网络是否正常!");
+            }
+            
+            // 设置设备编码并校验唯一性
+            String deviceCode = heartbeat.getDeviceCode();
+            egDevice.setDeviceCode(deviceCode);
+            
+            if (StringUtils.isNotBlank(deviceCode)) {
+                LambdaQueryWrapper<EgDevice> codeCheckWrapper = Wrappers.lambdaQuery();
+                codeCheckWrapper.eq(EgDevice::getDeviceCode, deviceCode)
+                        .eq(EgDevice::getTenantId, SecurityUtils.getTenantId());
+                EgDevice existingDevice = this.getOne(codeCheckWrapper);
+                if (existingDevice != null) {
+                    throw new BusinessException("设备已被设备'" + existingDevice.getDeviceName() 
+                            + "'注册使用,请检查设备配置或更换设备IP!");
+                }
+            }
         }
 
+        // 3. 设置设备基本信息
         egDevice.setDeviceUuid(UUIDUtils.uuid());
         egDevice.setCreateBy(SecurityUtils.getUsername());
-        egDevice.setCreateTime(LocalDateTime.now());
+        egDevice.setCreateTime(now);
         egDevice.setTenantId(SecurityUtils.getTenantId());
+        egDevice.setOpenMode("人脸");
+        egDevice.setWorkStatus("4");
 
+        // 4. 保存设备
         this.save(egDevice);
 
-        String[] fids = new String[0];
-        if(Objects.nonNull(egDevice.getBindFace()) || StringUtils.isNotBlank(egDevice.getBindFace())){
-            fids = egDevice.getBindFace().split(",");
-        }
-        if(fids.length > 0){
-            for (int i = 0; i < fids.length; i++) {
-                egDeviceMapper.insertMeetingFaceDevice(Integer.parseInt(fids[i]),egDevice.getId());
+        // 5. 处理绑定人脸信息
+        String bindFace = egDevice.getBindFace();
+        if (StringUtils.isNotBlank(bindFace)) {
+            String[] fids = bindFace.split(",");
+            for (String fid : fids) {
+                if (StringUtils.isNotBlank(fid)) {
+                    egDeviceMapper.insertMeetingFaceDevice(Integer.parseInt(fid.trim()), egDevice.getId());
+                }
             }
         }
 
-        remoteIotTaskService.addDeviceInfo("502_USKY", egDevice.getDeviceUuid(),egDevice.getDeviceId()+egDevice.getEgNumber(),egDevice.getDeviceName(),egDevice.getInstallAddress(),egDevice.getServiceStatus());
+        // 6. 异步调用远程服务添加设备信息,静默处理异常,不影响设备保存成功
+        String deviceUuid = egDevice.getDeviceUuid();
+        String deviceName = egDevice.getDeviceName();
+        if (StringUtils.isNotBlank(deviceUuid) && StringUtils.isNotBlank(deviceName)) {
+            String installAddress = StringUtils.isNotBlank(egDevice.getInstallAddress()) 
+                    ? egDevice.getInstallAddress() 
+                    : "";
+            Integer serviceStatus = egDevice.getServiceStatus() != null 
+                    ? egDevice.getServiceStatus() 
+                    : 1;
+            
+            CompletableFuture.runAsync(() -> {
+                try {
+                    remoteIotTaskService.addDeviceInfo("502_USKY", deviceUuid, deviceUuid, 
+                            deviceName, installAddress, serviceStatus);
+                } catch (Exception e) {
+                    // 远程服务调用失败,静默处理,不影响设备保存成功
+                }
+            });
+        }
     }
 
     @Override
@@ -179,14 +296,46 @@ public class EgDeviceServiceImpl extends AbstractCrudService<EgDeviceMapper, EgD
             }
         }
 
-
-        if(checkNameUnique(egDevice)){
-            throw new BusinessException("修改门禁门号设备'"+egDevice.getDeviceId()+","+egDevice.getEgNumber()+"'失败,设备已存在");
-        }
         if(checkDeviceNameUnique(egDevice)){
             throw new BusinessException("新增门禁设备'"+egDevice.getDeviceName()+"'失败,设备已存在");
         }
 
+        // 通过IP校验心跳表中的IP是否一致并且心跳时间必须在五分钟之内
+        if(StringUtils.isNotBlank(egDevice.getDeviceIp())){
+            LambdaQueryWrapper<EgDeviceHeartbeat> heartbeatQueryWrapper = Wrappers.lambdaQuery();
+            heartbeatQueryWrapper.eq(EgDeviceHeartbeat::getIpAddr, egDevice.getDeviceIp())
+                    .orderByDesc(EgDeviceHeartbeat::getCreateTime)
+                    .last("LIMIT 1");
+            EgDeviceHeartbeat heartbeat = egDeviceHeartbeatMapper.selectOne(heartbeatQueryWrapper);
+            
+            // 如果找不到心跳记录,抛出异常
+            if(heartbeat == null){
+                throw new BusinessException("设备不在线,请检查设备网络是否正常!");
+            }
+            
+            // 检查心跳时间是否在5分钟内
+            LocalDateTime now = LocalDateTime.now();
+            LocalDateTime heartbeatTime = heartbeat.getCreateTime();
+            if(heartbeatTime == null || !heartbeatTime.isAfter(now.minusMinutes(5))){
+                throw new BusinessException("设备不在线,请检查设备网络是否正常!");
+            }
+            
+            // 校验通过
+            egDevice.setDeviceCode(heartbeat.getDeviceCode());
+            
+            // 校验DeviceCode是否已经被其他设备使用(排除当前设备自己)
+            if(StringUtils.isNotBlank(egDevice.getDeviceCode())){
+                LambdaQueryWrapper<EgDevice> deviceCodeCheckQueryWrapper = Wrappers.lambdaQuery();
+                deviceCodeCheckQueryWrapper.eq(EgDevice::getDeviceCode, egDevice.getDeviceCode())
+                        .eq(EgDevice::getTenantId, SecurityUtils.getTenantId())
+                        .ne(EgDevice::getId, egDevice.getId());
+                EgDevice existingDevice = this.getOne(deviceCodeCheckQueryWrapper);
+                if(existingDevice != null){
+                    throw new BusinessException("设备编码'" + egDevice.getDeviceCode() + "'已被设备'" + existingDevice.getDeviceName() + "'使用,请检查设备配置");
+                }
+            }
+        }
+
         egDevice.setUpdateBy(SecurityUtils.getUsername());
         egDevice.setUpdateTime(LocalDateTime.now());
 
@@ -199,9 +348,7 @@ public class EgDeviceServiceImpl extends AbstractCrudService<EgDeviceMapper, EgD
         EgDevice one = this.getById(egDevice.getId());
         egDevice.setBindFace(one.getBindFace());
 
-        if(checkNameUnique(egDevice)){
-            throw new BusinessException("更新门禁设备附加功能'"+egDevice.getDeviceId()+"'失败,设备已存在");
-        }
+
         if(checkDeviceNameUnique(egDevice)){
             throw new BusinessException("新增门禁设备'"+egDevice.getDeviceName()+"'失败,设备已存在");
         }
@@ -226,7 +373,17 @@ public class EgDeviceServiceImpl extends AbstractCrudService<EgDeviceMapper, EgD
 
         this.removeById(id);
 
-        remoteIotTaskService.deleteDeviceInfo(egDevice.getDeviceUuid());
+        // 异步调用远程服务删除设备信息,静默处理异常,不影响设备删除
+        String deviceUuid = egDevice.getDeviceUuid();
+        if (StringUtils.isNotBlank(deviceUuid)) {
+            CompletableFuture.runAsync(() -> {
+                try {
+                    remoteIotTaskService.deleteDeviceInfo(deviceUuid);
+                } catch (Exception e) {
+                    // 远程服务调用失败,静默处理,不影响设备删除
+                }
+            });
+        }
     }
 
     @Override
@@ -234,7 +391,6 @@ public class EgDeviceServiceImpl extends AbstractCrudService<EgDeviceMapper, EgD
         Integer id = null == egDevice.getId() ? -1 : egDevice.getId();
         LambdaQueryWrapper<EgDevice> queryWrapper = Wrappers.lambdaQuery();
         queryWrapper.eq(EgDevice::getDeviceId,egDevice.getDeviceId())
-                .eq(EgDevice::getEgNumber,egDevice.getEgNumber())
                 .eq(EgDevice::getTenantId, SecurityUtils.getTenantId());
         EgDevice one = this.getOne(queryWrapper);
         return null != one && !Objects.equals(one.getId(),id);
@@ -255,10 +411,21 @@ public class EgDeviceServiceImpl extends AbstractCrudService<EgDeviceMapper, EgD
         Integer tenantId;
         long commandUserId;
         String commandUserName;
-        if(StringUtils.isNotBlank(domain)){
-            tenantId = baseMapper.sysTenantId(domain);
-            commandUserId = userId;
-            commandUserName = userName;
+        EgDevice device = null;
+        
+        // 通过deviceUuid查询设备获取tenantId
+        if(StringUtils.isNotBlank(deviceUuid)){
+            LambdaQueryWrapper<EgDevice> deviceQueryWrapper = Wrappers.lambdaQuery();
+            deviceQueryWrapper.select(EgDevice::getId, EgDevice::getTenantId)
+                    .eq(EgDevice::getDeviceUuid, deviceUuid);
+            device = this.getOne(deviceQueryWrapper);
+            if(device != null){
+                tenantId = device.getTenantId();
+                commandUserId = userId;
+                commandUserName = userName;
+            }else{
+                throw new BusinessException("设备未注册,请先注册");
+            }
         }else{
             tenantId = SecurityUtils.getTenantId();
             commandUserId = SecurityUtils.getUserId();
@@ -266,29 +433,22 @@ public class EgDeviceServiceImpl extends AbstractCrudService<EgDeviceMapper, EgD
         }
 
         //人员设备权限校验,校验通过,可以下发命令控制设备
-        Integer fid = baseMapper.getMeetingFaceData(commandUserId);
-        if(fid == null){
-            throw new BusinessException("人脸卡号信息未注册");
-        }
-        Integer[] deviceFid = baseMapper.getMeetingFaceDeviceList(fid);
-        if(deviceFid.length == 0){
-            throw new BusinessException("人员未绑定设备,请检查");
-        }
-
-        LambdaQueryWrapper<EgDevice> queryWrapper = Wrappers.lambdaQuery();
-        queryWrapper.select(EgDevice::getId)
-                .eq(EgDevice::getDeviceUuid,deviceUuid);
-        EgDevice one = this.getOne(queryWrapper);
-        if(one != null){
-            boolean exist = Arrays.asList(deviceFid).contains(one.getId());
+        if(device != null){
+            Integer fid = baseMapper.getMeetingFaceData(commandUserId);
+            if(fid == null){
+                throw new BusinessException("人脸卡号信息未注册");
+            }
+            Integer[] deviceFid = baseMapper.getMeetingFaceDeviceList(fid);
+            if(deviceFid.length == 0){
+                throw new BusinessException("人员未绑定设备,请检查");
+            }
+            
+            boolean exist = Arrays.asList(deviceFid).contains(device.getId());
             if(!exist){
                 throw new BusinessException("暂无权限");
             }
-        }else{
-            throw new BusinessException("设备未注册,请先注册");
         }
 
-
         Map<String,Object> map = new HashMap<>();
         map.put("method","control");
         map.put("deviceUuid", deviceUuid);

+ 17 - 10
service-eg/service-eg-biz/src/main/java/com/usky/eg/service/impl/EgRecordServiceImpl.java

@@ -47,23 +47,31 @@ public class EgRecordServiceImpl extends AbstractCrudService<EgRecordMapper, EgR
     @Override
     public void add(EgRecord egRecord){
         Integer tenantId;
-        if(StringUtils.isNotBlank(egRecord.getDomain())){
-            tenantId = egDeviceMapper.sysTenantId(egRecord.getDomain());
-        }else{
-            tenantId = SecurityUtils.getTenantId();
-        }
-
+        
+        // 通过deviceUuid查询设备获取tenantId
         LambdaQueryWrapper<EgDevice> queryWrapper = Wrappers.lambdaQuery();
         if(StringUtils.isBlank(egRecord.getDeviceUuid())){
             throw new BusinessException("设备Uuid不能为空");
         }
-        queryWrapper.eq(EgDevice::getDeviceUuid,egRecord.getDeviceUuid());
+        queryWrapper.select(EgDevice::getId, EgDevice::getTenantId)
+                .eq(EgDevice::getDeviceUuid,egRecord.getDeviceUuid());
         EgDevice one = egDeviceService.getOne(queryWrapper);
+        if(one == null){
+            throw new BusinessException("设备未注册,请先注册");
+        }
+        tenantId = one.getTenantId();
         egRecord.setEgDeviceId(one.getId());
-
         egRecord.setCreateTime(LocalDateTime.now());
         egRecord.setTenantId(tenantId);
-        egRecord.setDeptId(SecurityUtils.getLoginUser().getSysUser().getDeptId().intValue());
+        
+        // 安全获取部门ID
+        Integer deptId = null;
+        if(SecurityUtils.getLoginUser() != null 
+                && SecurityUtils.getLoginUser().getSysUser() != null 
+                && SecurityUtils.getLoginUser().getSysUser().getDeptId() != null){
+            deptId = SecurityUtils.getLoginUser().getSysUser().getDeptId().intValue();
+        }
+        egRecord.setDeptId(deptId);
 
         this.save(egRecord);
     }
@@ -114,7 +122,6 @@ public class EgRecordServiceImpl extends AbstractCrudService<EgRecordMapper, EgR
             }
         }
 
-
         return new CommonPage<>(page.getRecords(),page.getTotal(),requestVO.getSize(),requestVO.getCurrent());
     }
 }

+ 9 - 0
service-eg/service-eg-biz/src/main/java/com/usky/eg/service/vo/EgDeviceRequestVO.java

@@ -45,4 +45,13 @@ public class EgDeviceRequestVO implements Serializable {
      * 设备uuid
      */
     private String deviceUuid;
+
+    /**
+     * 设备编码
+     */
+    private String deviceCode;
+    /**
+     * 设备状态
+     */
+    private Integer deviceStatus;
 }

+ 20 - 0
service-eg/service-eg-biz/src/main/resources/mapper/eg/EgDeviceHeartbeatMapper.xml

@@ -0,0 +1,20 @@
+<?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.usky.eg.mapper.EgDeviceHeartbeatMapper">
+
+    <!-- 通用查询映射结果 -->
+    <resultMap id="BaseResultMap" type="com.usky.eg.domain.EgDeviceHeartbeat">
+        <id column="id" property="id"/>
+        <result column="device_code" property="deviceCode"/>
+        <result column="ip_addr" property="ipAddr"/>
+        <result column="mac_addr" property="macAddr"/>
+        <result column="device_type" property="deviceType"/>
+        <result column="create_time" property="createTime"/>
+        <result column="model" property="model"/>
+        <result column="manu_facturer"  property="manuFacturer"/>
+        <result column="version" property="version"/>
+        <result column="sdk" property="sdk"/>
+        <result column="device_status" property="deviceStatus"/>
+    </resultMap>
+
+</mapper>

+ 0 - 2
service-eg/service-eg-biz/src/main/resources/mapper/eg/EgDeviceMapper.xml

@@ -11,8 +11,6 @@
         <result column="install_address" property="installAddress" />
         <result column="service_status" property="serviceStatus" />
         <result column="device_ip" property="deviceIp" />
-        <result column="device_port" property="devicePort" />
-        <result column="eg_number" property="egNumber" />
         <result column="bind_face" property="bindFace" />
         <result column="create_by" property="createBy" />
         <result column="update_by" property="updateBy" />

+ 6 - 0
service-job/pom.xml

@@ -88,6 +88,12 @@
             <version>0.0.1</version>
             <scope>compile</scope>
         </dependency>
+        <dependency>
+            <groupId>com.usky</groupId>
+            <artifactId>service-eg-api</artifactId>
+            <version>0.0.1</version>
+            <scope>compile</scope>
+        </dependency>
     </dependencies>
 
     <build>

+ 10 - 0
service-job/src/main/java/com/ruoyi/job/task/RyTask.java

@@ -3,6 +3,7 @@ package com.ruoyi.job.task;
 import com.usky.cdi.AlarmDataSyncTaskService;
 import com.usky.cdi.RemotecdiTaskService;
 import com.usky.common.core.utils.StringUtils;
+import com.usky.eg.RemoteEgService;
 import com.usky.meeting.RemoteMeetingService;
 import com.usky.ems.RemoteEmsTaskService;
 import com.usky.fire.RemoteFireService;
@@ -39,6 +40,9 @@ public class RyTask {
     @Autowired
     private RemoteEmsTaskService remoteEmsTaskService;
 
+    @Autowired
+    private RemoteEgService remoteEgService;
+
     public void ryMultipleParams(String s, Boolean b, Long l, Double d, Integer i) {
         System.out.println(StringUtils.format("执行多参方法: 字符串类型{},布尔类型{},长整型{},浮点型{},整形{}", s, b, l, d, i));
     }
@@ -120,4 +124,10 @@ public class RyTask {
         remoteEmsTaskService.sendEnergyHeartbeat();
     }
 
+    // 门禁设备心跳状态
+    public void egDeviceStatus() {
+        System.out.println("egDeviceStatus start......");
+        remoteEgService.egDeviceStatus();
+    }
+
 }