Parcourir la source

海康需求,实时场景新增账户、取卡、补卡、回收卡片、删除账户、解挂、挂失实时下发数据到海康设备

xiari il y a 9 mois
Parent
commit
cdfc8f59a1
32 fichiers modifiés avec 664 ajouts et 96 suppressions
  1. 1 0
      ruoyi-api/pom.xml
  2. 2 0
      ruoyi-api/ruoyi-api-backstage/src/main/java/org/dromara/backstage/api/RemoteUserAccountService.java
  3. 5 0
      ruoyi-api/ruoyi-api-backstage/src/main/java/org/dromara/backstage/api/domain/vo/RemoteUserAccountVo.java
  4. 27 0
      ruoyi-api/ruoyi-api-hik/pom.xml
  5. 20 0
      ruoyi-api/ruoyi-api-hik/src/main/java/org/dromara/hik/api/service/RemoteSyncDownSendToHikService.java
  6. 1 1
      ruoyi-auth/src/main/java/org/dromara/auth/controller/TokenController.java
  7. 19 0
      ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/BusinessOperationConstants.java
  8. 3 0
      ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/CacheNames.java
  9. 6 0
      ruoyi-modules/ruoyi-backstage/pom.xml
  10. 7 0
      ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/business/card/CardBusiness.java
  11. 10 0
      ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/cardCenter/service/RemoteHikWrapperService.java
  12. 15 1
      ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/cardCenter/service/impl/PtCardServiceImpl.java
  13. 32 0
      ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/cardCenter/service/impl/RemoteHikWrapperServiceImpl.java
  14. 7 0
      ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/payment/dubbo/RemoteUserAccountServiceImpl.java
  15. 9 0
      ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/payment/mapper/PtUserAccountMapper.java
  16. 1 0
      ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/payment/service/IPtUserAccountService.java
  17. 56 17
      ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/payment/service/impl/PtUserAccountServiceImpl.java
  18. 24 8
      ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/wx/service/impl/WxServiceImpl.java
  19. 11 0
      ruoyi-modules/ruoyi-backstage/src/test/java/org/dromara/backstage/mq/KafkaProducerTest.java
  20. 6 0
      ruoyi-server/ruoyi-server-hik/pom.xml
  21. 11 4
      ruoyi-server/ruoyi-server-hik/src/main/java/org/dromara/server/hik/controller/TestController.java
  22. 27 0
      ruoyi-server/ruoyi-server-hik/src/main/java/org/dromara/server/hik/dubbo/RemoteSyncDownSendToHikServiceImpl.java
  23. 14 13
      ruoyi-server/ruoyi-server-hik/src/main/java/org/dromara/server/hik/event/handler/HeatBeatHandler.java
  24. 2 2
      ruoyi-server/ruoyi-server-hik/src/main/java/org/dromara/server/hik/mq/event/impl/HikBackStageEventImpl.java
  25. 18 1
      ruoyi-server/ruoyi-server-hik/src/main/java/org/dromara/server/hik/service/ISendDeviceService.java
  26. 256 39
      ruoyi-server/ruoyi-server-hik/src/main/java/org/dromara/server/hik/service/impl/SendDeviceServiceImpl.java
  27. 6 0
      ruoyi-server/ruoyi-server-sync/pom.xml
  28. 6 1
      ruoyi-server/ruoyi-server-sync/src/main/java/org/dromara/server/sync/business/user/SyncUserBusiness.java
  29. 10 0
      ruoyi-server/ruoyi-server-sync/src/main/java/org/dromara/server/sync/service/RemoteHikWrapperService.java
  30. 32 0
      ruoyi-server/ruoyi-server-sync/src/main/java/org/dromara/server/sync/service/impl/RemoteHikWrapperServiceImpl.java
  31. 9 1
      ruoyi-server/ruoyi-server-sync/src/main/java/org/dromara/server/sync/service/impl/UserAccountServiceImpl.java
  32. 11 8
      ruoyi-server/ruoyi-server-sync/src/main/java/org/dromara/server/sync/service/impl/UserServiceImpl.java

+ 1 - 0
ruoyi-api/pom.xml

@@ -17,6 +17,7 @@
         <module>ruoyi-api-consume</module>
         <module>ruoyi-api-hotel</module>
         <module>ruoyi-api-sync</module>
+        <module>ruoyi-api-hik</module>
     </modules>
 
     <artifactId>ruoyi-api</artifactId>

+ 2 - 0
ruoyi-api/ruoyi-api-backstage/src/main/java/org/dromara/backstage/api/RemoteUserAccountService.java

@@ -96,4 +96,6 @@ public interface RemoteUserAccountService {
      * @return
      */
     List<RemoteUserAccountVo> getUpdateUserAccountVo(Date startDate);
+
+    RemoteUserAccountVo getById(Long userId);
 }

+ 5 - 0
ruoyi-api/ruoyi-api-backstage/src/main/java/org/dromara/backstage/api/domain/vo/RemoteUserAccountVo.java

@@ -179,6 +179,11 @@ public class RemoteUserAccountVo implements Serializable {
      */
     private Long factoryId;
 
+    /**
+     * 物理卡号对应的卡片状态
+     */
+    private String cardStatus;
+
     /**
      * 完整的人脸照片地址
      */

+ 27 - 0
ruoyi-api/ruoyi-api-hik/pom.xml

@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xmlns="http://maven.apache.org/POM/4.0.0"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>org.dromara</groupId>
+        <artifactId>ruoyi-api</artifactId>
+        <version>${revision}</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>ruoyi-api-hik</artifactId>
+
+    <description>
+        ruoyi-api-hik 海康设备系统接口服务模块
+    </description>
+
+    <dependencies>
+
+        <!-- RuoYi Common Core-->
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-core</artifactId>
+        </dependency>
+    </dependencies>
+
+</project>

+ 20 - 0
ruoyi-api/ruoyi-api-hik/src/main/java/org/dromara/hik/api/service/RemoteSyncDownSendToHikService.java

@@ -0,0 +1,20 @@
+package org.dromara.hik.api.service;
+
+
+/**
+ * @ClassName RemoteSyncDownSendToHikService
+ * @Description 海康设备系统同步下发数据到海康设备远程服务接口
+ * @Author xiari
+ * @Date 2025-8-13
+ * @Version 1.0
+ * @since jdk17
+ */
+public interface RemoteSyncDownSendToHikService {
+
+
+    /**
+     * 同步下发单个账号数据到所有海康设备
+     * @param userId 账号id
+     */
+   void send(Long userId, String operationType, Long factoryId);
+}

+ 1 - 1
ruoyi-auth/src/main/java/org/dromara/auth/controller/TokenController.java

@@ -128,7 +128,7 @@ public class TokenController {
             throw new ServiceException("参数缺失或有误!");
         }
         LoginVo loginVo = sysLoginService.codeLogin(code, otherInfo);
-        Long userId = LoginHelper.getUserId();
+//        Long userId = LoginHelper.getUserId();
 
         return R.ok(loginVo);
     }

+ 19 - 0
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/BusinessOperationConstants.java

@@ -0,0 +1,19 @@
+package org.dromara.common.core.constant;
+
+public interface BusinessOperationConstants {
+    // 业务操作
+    // 新增账户
+    String ADD_ACCOUNT = "add_user";
+    // 删除账户
+    String DELETE_ACCOUNT = "delete_user";
+    // 回收卡片
+    String RECYCLE_CARD = "recycle_card";
+    // 取卡
+    String GET_CARD = "get_card";
+    // 补卡
+    String AGAIN_GET_CARD = "again_get_card";
+    // 挂失
+    String LOCK_CARD = "lock_card";
+    // 解挂
+    String UNLOCK_CARD = "unlock_card";
+}

+ 3 - 0
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/CacheNames.java

@@ -206,4 +206,7 @@ public interface CacheNames {
     String XF_TERM_IP = "term_ip:";
 
     String USER_HAS_CARD = "user_has_card";
+
+    // 最后心跳时间 to hik
+    String LAST_TIME_HEARTBEAT_LIST = "last_time_heartbeat_list";
 }

+ 6 - 0
ruoyi-modules/ruoyi-backstage/pom.xml

@@ -179,6 +179,12 @@
             <version>2.2.0</version>
             <scope>compile</scope>
         </dependency>
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-api-hik</artifactId>
+            <version>2.2.0</version>
+            <scope>compile</scope>
+        </dependency>
 
 
     </dependencies>

+ 7 - 0
ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/business/card/CardBusiness.java

@@ -12,6 +12,7 @@ import org.dromara.backstage.basics.service.IPtParameterService;
 import org.dromara.backstage.basics.service.IPtWorkstationService;
 import org.dromara.backstage.cardCenter.domain.bo.PtCardBo;
 import org.dromara.backstage.cardCenter.service.IPtCardService;
+import org.dromara.backstage.cardCenter.service.RemoteHikWrapperService;
 import org.dromara.backstage.domain.vo.card.InitCardVo;
 import org.dromara.backstage.domain.vo.card.PtCardVo;
 import org.dromara.backstage.payment.domain.bo.PtBagBo;
@@ -20,6 +21,7 @@ import org.dromara.backstage.payment.domain.vo.PtUserAccountVo;
 import org.dromara.backstage.payment.service.IPtBagService;
 import org.dromara.backstage.payment.service.IPtUserAccountService;
 import org.dromara.common.core.config.DefaultConfig;
+import org.dromara.common.core.constant.BusinessOperationConstants;
 import org.dromara.common.core.domain.R;
 import org.dromara.common.core.domain.model.ResultInfo;
 import org.dromara.common.core.enums.*;
@@ -50,6 +52,7 @@ public class CardBusiness {
     private final IPtParameterService ptParameterService;
     private final DefaultConfig defaultConfig;
     //private final PushKafkaData kafkaNormalProducer;
+    private final RemoteHikWrapperService remoteHikWrapperService;
 
     public R<String> openVirtualCard(PtCardBo cardBo) {
         String resultMsg;
@@ -205,6 +208,10 @@ public class CardBusiness {
             //     //kafkaNormalProducer.sendKafkaMessage(KafkaTopicConstants.SYNC_DATA_TOPIC, EventTypeConstants.CARD, EventSenderEnum.BACKSTAGE.code(),
             //     //                                     recycleVo);
             // }
+            if (result){
+                //回收卡片,将海康设备上的这个用户vo.getUserId()的,这张卡片vo.getFactoryId()信息删除,下发时如果账号已过期,则删除账号信息,没有就删除账户的卡信息
+                remoteHikWrapperService.send(vo.getUserId(), BusinessOperationConstants.RECYCLE_CARD, vo.getFactoryId());
+            }
             return result ? R.ok() : R.fail();
         } else {
             return R.fail(MessageFormat.format("[卡片回收]-[没有对应的卡片]-[{0}]", JSONUtil.toJsonStr(cardBo)));

+ 10 - 0
ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/cardCenter/service/RemoteHikWrapperService.java

@@ -0,0 +1,10 @@
+package org.dromara.backstage.cardCenter.service;
+
+/**
+ * hik远程接口的包装类
+ *
+ */
+public interface RemoteHikWrapperService {
+
+    void send(Long userId, String operationType, Long factoryId);
+}

+ 15 - 1
ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/cardCenter/service/impl/PtCardServiceImpl.java

@@ -20,8 +20,10 @@ import org.dromara.backstage.cardCenter.domain.PtCard;
 import org.dromara.backstage.cardCenter.domain.bo.PtCardBo;
 import org.dromara.backstage.cardCenter.mapper.PtCardMapper;
 import org.dromara.backstage.cardCenter.service.IPtCardService;
+import org.dromara.backstage.cardCenter.service.RemoteHikWrapperService;
 import org.dromara.backstage.domain.vo.card.PtCardVo;
 import org.dromara.backstage.payment.domain.bo.PtBagBo;
+import org.dromara.common.core.constant.BusinessOperationConstants;
 import org.dromara.common.core.constant.CacheNames;
 import org.dromara.common.core.constant.Constants;
 import org.dromara.common.core.constant.DefaultConstants;
@@ -59,6 +61,7 @@ public class PtCardServiceImpl implements IPtCardService {
     private final IPtParameterService parameterService;
     private final IPtCardtypeService cardTypeService;
     //private final PushKafkaData kafkaNormalProducer;
+    private final RemoteHikWrapperService remoteHikWrapperService;
 
     @DubboReference
     private final RemoteCardDataService remoteCardDataService;
@@ -132,7 +135,8 @@ public class PtCardServiceImpl implements IPtCardService {
      */
     @Override
     public List<PtCardVo> getList(Date startDate, Date endDate) {
-        LambdaQueryWrapper<PtCard> between = Wrappers.lambdaQuery(PtCard.class).eq(PtCard::getStatus, CardStatusEnum.NORMAL.code().toString())
+        //.eq(PtCard::getStatus, CardStatusEnum.NORMAL.code().toString())
+        LambdaQueryWrapper<PtCard> between = Wrappers.lambdaQuery(PtCard.class)
             .and(wrapper -> wrapper.between(PtCard::getChangeTime, startDate, endDate).or().between(PtCard::getCreateTime, startDate, endDate));
         return baseMapper.selectVoList(between);
     }
@@ -303,6 +307,7 @@ public class PtCardServiceImpl implements IPtCardService {
      */
     @Override
     public boolean lockCard(Long cardId) {
+        PtCard ptCard = baseMapper.selectById(cardId);
         int iCount = baseMapper.update(null, new LambdaUpdateWrapper<PtCard>()
             .set(PtCard::getStatus, '2')
             .set(PtCard::getChangeTime, DateUtil.date())
@@ -310,6 +315,8 @@ public class PtCardServiceImpl implements IPtCardService {
             .eq(PtCard::getCardId, cardId));
         if (iCount > 0) {
             resetCardCache(cardId);
+            // 挂失下发清除卡片信息 海康
+            remoteHikWrapperService.send(ptCard.getUserId(), BusinessOperationConstants.LOCK_CARD, null);
             return true;
         }
         return false;
@@ -323,6 +330,7 @@ public class PtCardServiceImpl implements IPtCardService {
      */
     @Override
     public boolean unlockCard(Long cardId) {
+        PtCard ptCard = baseMapper.selectById(cardId);
         int iCount = baseMapper.update(null, new LambdaUpdateWrapper<PtCard>()
             .set(PtCard::getStatus, '1')
             .set(PtCard::getChangeTime, DateUtil.date())
@@ -330,6 +338,8 @@ public class PtCardServiceImpl implements IPtCardService {
             .eq(PtCard::getCardId, cardId));
         if (iCount > 0) {
             resetCardCache(cardId);
+            //重新下发卡片信息到海康设备   调用下发个人数据到所有设备
+            remoteHikWrapperService.send(ptCard.getUserId(), BusinessOperationConstants.UNLOCK_CARD, null);
             return true;
         }
         return false;
@@ -460,6 +470,8 @@ public class PtCardServiceImpl implements IPtCardService {
 
                 //更新缓存
                 resetCardCache(entity.getCardId());
+                //下发这个用户的卡片信息到海康设备 vo.getUserId()
+                remoteHikWrapperService.send(vo.getUserId(),BusinessOperationConstants.GET_CARD,null);
                 return vo;
             }
         }
@@ -484,6 +496,8 @@ public class PtCardServiceImpl implements IPtCardService {
             if (CollectionUtil.isNotEmpty(list)) {
                 // 挂失后需要更新缓存
                 resetCardCache(list.get(0).getCardId());
+                //挂失后 下发卡片删除信息到海康设备 list.get(0).getUserId() 的所有卡片信息
+                remoteHikWrapperService.send(list.get(0).getUserId(), BusinessOperationConstants.LOCK_CARD, null);
                 return list.get(0);
             }
         }

+ 32 - 0
ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/cardCenter/service/impl/RemoteHikWrapperServiceImpl.java

@@ -0,0 +1,32 @@
+package org.dromara.backstage.cardCenter.service.impl;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.dubbo.config.annotation.DubboReference;
+import org.dromara.backstage.cardCenter.service.RemoteHikWrapperService;
+import org.dromara.hik.api.service.RemoteSyncDownSendToHikService;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
+import org.springframework.stereotype.Service;
+
+@RequiredArgsConstructor
+@Service
+@Slf4j
+public class RemoteHikWrapperServiceImpl implements RemoteHikWrapperService {
+
+    @DubboReference
+    private final RemoteSyncDownSendToHikService remoteSyncDownSendToHikService;
+
+    private final ThreadPoolTaskExecutor threadPoolTaskExecutor;
+
+    //是否开启海康设备
+    @Value("${hik.open:false}")
+    private Boolean hikOpen;
+
+
+    @Override
+    public void send(Long userId, String operationType, Long factoryId) {
+        if(!hikOpen) return;
+        threadPoolTaskExecutor.execute(() -> remoteSyncDownSendToHikService.send(userId, operationType, factoryId));
+    }
+}

+ 7 - 0
ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/payment/dubbo/RemoteUserAccountServiceImpl.java

@@ -203,6 +203,7 @@ public class RemoteUserAccountServiceImpl implements RemoteUserAccountService {
                 PtCardVo card = userAccountAndCard.getCard();
                 if(card != null){
                     vo.setFactoryId(card.getFactoryId());
+                    vo.setCardStatus(card.getStatus());
                 }
                 rs.add(vo);
             }
@@ -210,5 +211,11 @@ public class RemoteUserAccountServiceImpl implements RemoteUserAccountService {
         return rs;
     }
 
+    @Override
+    public RemoteUserAccountVo getById(Long userId) {
+        PtUserAccountVo byId = userAccountService.getById(userId);
+        return MapstructUtils.convert(byId, RemoteUserAccountVo.class);
+    }
+
 
 }

+ 9 - 0
ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/payment/mapper/PtUserAccountMapper.java

@@ -4,6 +4,7 @@ import cn.hutool.core.collection.CollUtil;
 import com.baomidou.mybatisplus.core.conditions.Wrapper;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
 import org.dromara.backstage.domain.vo.yc.YcTraineeVo;
 import org.dromara.backstage.payment.domain.PtUserAccount;
 import org.dromara.backstage.payment.domain.PtUserAccount4SelectVo;
@@ -46,4 +47,12 @@ public interface PtUserAccountMapper extends BaseMapperPlus<PtUserAccount, PtUse
     YcTraineeVo selectTraineeByBo(@Param("bo") PtUserAccountBo bo, @Param("doingDate") Date doingDate);
 
     List<PtUserAccount4SelectVo> getCardInfoByFactoryId (@Param("factoryId") String factoryId);
+
+    @Select("select * from t_pt_userAccount where del_flag = #{delFlag} and update_time between #{startDate} and #{endDate}")
+    List<PtUserAccountVo> getUserAccountByDateScope(@Param("startDate") Date startDate, @Param("endDate") Date endDate, @Param("delFlag")String delFlag);
+
+    @Select("select * from t_pt_userAccount where user_id = #{userId}")
+    PtUserAccountVo getById(@Param("userId")Long userId);
+
+
 }

+ 1 - 0
ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/payment/service/IPtUserAccountService.java

@@ -29,6 +29,7 @@ public interface IPtUserAccountService {
      * @return 一卡通账户
      */
     PtUserAccountVo queryById(Long userId);
+    PtUserAccountVo getById(Long userId);
 
     /**
      * 分页查询一卡通账户列表

+ 56 - 17
ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/payment/service/impl/PtUserAccountServiceImpl.java

@@ -32,6 +32,7 @@ import org.dromara.common.core.constant.CacheNames;
 import org.dromara.common.core.constant.DefaultConstants;
 import org.dromara.common.core.domain.R;
 import org.dromara.common.core.enums.CardStatusEnum;
+import org.dromara.common.core.enums.UserAccountStatusEnum;
 import org.dromara.common.core.service.DictService;
 import org.dromara.common.core.utils.MapstructUtils;
 import org.dromara.common.core.utils.SpringUtils;
@@ -55,8 +56,7 @@ import java.text.MessageFormat;
 import java.util.*;
 import java.util.stream.Collectors;
 
-import static org.dromara.common.core.constant.DefaultConstants.FULL_SYNC_ADMIN;
-import static org.dromara.common.core.constant.DefaultConstants.KAFKA_SYNC_ADMIN;
+import static org.dromara.common.core.constant.DefaultConstants.*;
 
 /**
  * 一卡通账户Service业务层处理
@@ -99,6 +99,11 @@ public class PtUserAccountServiceImpl implements IPtUserAccountService {
         return vo;
     }
 
+    @Override
+    public PtUserAccountVo getById(Long userId) {
+        return baseMapper.getById(userId);
+    }
+
     /**
      * 分页查询一卡通账户列表
      *
@@ -633,6 +638,7 @@ public class PtUserAccountServiceImpl implements IPtUserAccountService {
         // 正常卡信息
         PtCardBo ptCardBo = new PtCardBo();
         ptCardBo.setStatus(CardStatusEnum.NORMAL.code().toString());
+        ptCardBo.setMainCard("Y");
         if (userId != null) {
             ptCardBo.setUserId(userId);
         }
@@ -647,14 +653,15 @@ public class PtUserAccountServiceImpl implements IPtUserAccountService {
             // 按时间排序
             PtCardVo ptCardVo = new PtCardVo();
             Optional<PtCardVo> first = cards.stream()
-                .filter(p -> Objects.equals(p.getUserId(), id))
-                .sorted(Comparator.comparing(PtCardVo::getChangeTime).reversed()).findFirst();
+                .filter(p -> Objects.equals(p.getUserId(), id) && p.getFactoryId() != 0L).max(Comparator.comparing(PtCardVo::getChangeTime));
             if (first.isPresent()) {
                 ptCardVo = first.get();
             }
             userAccount.setCard(ptCardVo);
-            // 加个时间戳,下载最新文件
-            userAccount.setFacePicUrl(photoPrefix + userAccount.getPhoto()+"?t="+System.currentTimeMillis());
+            if(StringUtils.isNotBlank(userAccount.getPhoto())){
+                // 加个时间戳,下载最新文件
+                userAccount.setFacePicUrl(photoPrefix + userAccount.getPhoto()+"?t="+System.currentTimeMillis());
+            }
 
             res.add(userAccount);
         }
@@ -669,30 +676,62 @@ public class PtUserAccountServiceImpl implements IPtUserAccountService {
     @Override
     public List<PtUserAccountVo> getUserAccountVoByDay(Date startDate) {
         // 获取指定天数内有更新账户信息的
-        // 人脸的
         Date date = new Date();
+        // 新增的账户和修改的账户
+        LambdaQueryWrapper<PtUserAccount> between1 = Wrappers.lambdaQuery(PtUserAccount.class)
+            .between(PtUserAccount::getCreateTime, startDate, date).or().between(PtUserAccount::getUpdateTime, startDate, date);
+//            .eq(PtUserAccount::getAccountStatus, UserAccountStatusEnum.IS_OPEN.code());
+        List<PtUserAccountVo> increaseList = baseMapper.selectVoList(between1);
+
+        List<PtUserAccountVo> rs = new ArrayList<>();
+        if(CollectionUtil.isNotEmpty(increaseList)){
+            rs.addAll(increaseList);
+        }
+
+        // 查询账户删除的
+        List<PtUserAccountVo> deleteAccounts = baseMapper.getUserAccountByDateScope(startDate, date, DELETED);
+        if(CollectionUtil.isNotEmpty(deleteAccounts)){
+            rs.addAll(deleteAccounts);
+        }
+
+
+        // 更新人脸的
         //排除 全量同步的数据
         LambdaQueryWrapper<PtUserAccount> between = Wrappers.lambdaQuery(PtUserAccount.class)
-            .ne(PtUserAccount::getUpdateBy, FULL_SYNC_ADMIN)
-            .ne(PtUserAccount::getUpdateBy, KAFKA_SYNC_ADMIN)
+//            .ne(PtUserAccount::getUpdateBy, FULL_SYNC_ADMIN)
+//            .ne(PtUserAccount::getUpdateBy, KAFKA_SYNC_ADMIN)
+//            .eq(PtUserAccount::getAccountStatus, UserAccountStatusEnum.IS_OPEN.code())
             .between(PtUserAccount::getUpdateTime, startDate, date);
         List<PtUserAccountVo> userAccountVos = baseMapper.selectVoList(between);
-        List<PtUserAccountVo> updatePhotos = userAccountVos.stream().filter(e -> StringUtils.isNotBlank(e.getPhoto())).toList();
+        List<PtUserAccountVo> updatePhotos = userAccountVos.stream()
+            .filter(e -> StringUtils.isNotBlank(e.getPhoto())
+                        && !Objects.equals(e.getUpdateBy(), FULL_SYNC_ADMIN)
+                        && !Objects.equals(e.getUpdateBy(), KAFKA_SYNC_ADMIN)).toList();
         updatePhotos.forEach(e -> e.setFacePicUrl(photoPrefix + e.getPhoto() +"?t="+System.currentTimeMillis()));
 
-        List<PtUserAccountVo> rs = new ArrayList<>(updatePhotos);
+        if(CollectionUtil.isNotEmpty(updatePhotos)){
+            rs.addAll(updatePhotos);
+        }
 
-        // 卡片的
+        // 更新卡片的 卡片有可能是注销的
         List<PtCardVo> cardVos = ptCardService.getList(startDate, date);
+        cardVos = cardVos.stream().filter(e -> e.getFactoryId() != 0L).toList();
         List<Long> userIds = cardVos.stream().map(PtCardVo::getUserId).toList();
-        Map<Long, PtCardVo> collect = cardVos.stream().collect(Collectors.toMap(PtCardVo::getUserId, e -> e));
         if (CollectionUtil.isNotEmpty(userIds)) {
             LambdaQueryWrapper<PtUserAccount> in = Wrappers.lambdaQuery(PtUserAccount.class).in(PtUserAccount::getUserId, userIds);
             List<PtUserAccountVo> userAccountVos1 = baseMapper.selectVoList(in);
-            userAccountVos1.forEach(e -> e.setCard(collect.get(e.getUserId())));
-            //不更新人脸
-            //userAccount.setFacePicUrl(photoPrefix+userAccount.getPhoto());
-            rs.addAll(userAccountVos1);
+            Map<Long, PtUserAccountVo> collect = userAccountVos1.stream().collect(Collectors.toMap(PtUserAccountVo::getUserId, e -> e));
+            List<PtUserAccountVo> userAccountVoList = new ArrayList<>();
+            cardVos.forEach(x -> {
+                Long userId = x.getUserId();
+                PtUserAccountVo userAccountVo = collect.get(userId);
+                // 没有对应的用户账户userAccountVo==null 说明 这个账号被删除了
+                if (userAccountVo != null) {
+                    userAccountVo.setCard(x);
+                    userAccountVoList.add(userAccountVo);
+                }
+            });
+            rs.addAll(userAccountVoList);
         }
 
         return rs;

+ 24 - 8
ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/wx/service/impl/WxServiceImpl.java

@@ -16,6 +16,7 @@ import org.apache.commons.lang3.time.DateFormatUtils;
 import org.dromara.backstage.business.accouunt.UserFaceBusiness;
 import org.dromara.backstage.cardCenter.domain.PtCard;
 import org.dromara.backstage.cardCenter.mapper.PtCardMapper;
+import org.dromara.backstage.cardCenter.service.IPtCardService;
 import org.dromara.backstage.consumption.mapper.XfCreditAccountMapper;
 import org.dromara.backstage.payment.domain.vo.PtUserAccountVo;
 import org.dromara.backstage.payment.mapper.PtUserAccountMapper;
@@ -25,6 +26,7 @@ import org.dromara.backstage.wx.service.IWxService;
 import org.dromara.common.core.config.DefaultConfig;
 import org.dromara.common.core.constant.DefaultConstants;
 import org.dromara.common.core.domain.R;
+import org.dromara.common.core.exception.ServiceException;
 import org.dromara.common.core.utils.ByteArrayUtilByYC;
 import org.dromara.common.core.utils.StringUtilsByYC;
 import org.dromara.common.core.utils.file.FileUtils;
@@ -52,9 +54,9 @@ public class WxServiceImpl implements IWxService {
     private final XfCreditAccountMapper creditAccountMapper;
     private final PtCardMapper cardMapper;
     private final RemoteDictService dictService;
-    private final DefaultConfig defaultConfig;
     private final UserFaceBusiness userFaceBusiness;
     private final IPtBagService bagService;
+    private final IPtCardService cardService;
 
     @Value("${dzbp.sync-img.url}/")     // 电子班牌照片推送接口
     private String syncImgToDzbpUrl;
@@ -149,11 +151,24 @@ public class WxServiceImpl implements IWxService {
 
     @Override
     public boolean updateCardStatus(Long userId, String cardStatus) {
-        int count = cardMapper.update(new LambdaUpdateWrapper<PtCard>()
-                                          .set(PtCard::getStatus, cardStatus)
-                                          .set(PtCard::getChangeTime, DateUtil.date())
-                                          .eq(PtCard::getUserId, userId));
-        return count > 0;
+        LambdaUpdateWrapper<PtCard> eq = new LambdaUpdateWrapper<PtCard>().eq(PtCard::getUserId, userId);
+        // 1是解挂2是挂失
+        if(("1").equals(cardStatus)){
+            //解挂
+            eq.eq(PtCard::getStatus, "2").orderBy(true, false,PtCard::getChangeTime).last("limit 1");
+            PtCard ptCard = cardMapper.selectOne(eq);
+            if(ptCard == null){
+                throw new ServiceException("您没有可以解挂的卡片");
+            }
+            return cardService.unlockCard(ptCard.getCardId());
+        }else{
+            eq.eq(PtCard::getStatus, "1").orderBy(true, false,PtCard::getChangeTime).last("limit 1");
+            PtCard ptCard = cardMapper.selectOne(eq);
+            if(ptCard == null){
+                throw new ServiceException("您没有可以挂失的卡片");
+            }
+            return cardService.lockCard(ptCard.getCardId());
+        }
     }
 
     @Override
@@ -169,10 +184,11 @@ public class WxServiceImpl implements IWxService {
         if (R.isError(result)) {
             return R.fail("图片上传异常", result.getMsg());
         } else {
-            PtUserAccountVo vo = accountMapper.selectVoById(userId);
+            // 不用再同步图片到电子班牌
+            /*PtUserAccountVo vo = accountMapper.selectVoById(userId);
             if ("2".equals(vo.getCategory())) {
                 syncImgToDZBP(vo, imgData);
-            }
+            }*/
             return R.ok("上传图片成功");
         }
     }

+ 11 - 0
ruoyi-modules/ruoyi-backstage/src/test/java/org/dromara/backstage/mq/KafkaProducerTest.java

@@ -9,6 +9,8 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import lombok.Data;
 import org.dromara.backstage.consumption.domain.bo.HandImportExcelConsumeBo;
 import org.dromara.backstage.payment.domain.PtUserAccount;
+import org.dromara.backstage.payment.domain.PtUserAccount4SelectVo;
+import org.dromara.backstage.payment.domain.vo.PtUserAccountVo;
 import org.dromara.backstage.payment.mapper.PtUserAccountMapper;
 import org.dromara.backstage.payment.service.IPtUserAccountService;
 import org.dromara.common.core.utils.StringUtils;
@@ -106,6 +108,14 @@ public class KafkaProducerTest {
         System.err.println(rs);
     }
 
+    @Test
+    public void test2() throws Exception {
+        Date startDate = DateUtil.parse("2025-03-01");
+//        List<PtUserAccountVo> cardInfoByFactoryId = userAccountMapper.getUserAccountByDateScope(startDate,new Date(), "2");
+        PtUserAccountVo byId = userAccountMapper.getById(1950749034597195777L);
+        System.err.println(byId);
+    }
+
 
     @Data
     public static class ParamBo implements Serializable {
@@ -138,4 +148,5 @@ public class KafkaProducerTest {
         @ExcelProperty(value = "备注")
         private String remark;
     }
+
 }

+ 6 - 0
ruoyi-server/ruoyi-server-hik/pom.xml

@@ -153,6 +153,12 @@
             <artifactId>commons-io</artifactId>
             <version>2.6</version>
         </dependency>
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-api-hik</artifactId>
+            <version>2.2.0</version>
+            <scope>compile</scope>
+        </dependency>
     </dependencies>
 
     <build>

+ 11 - 4
ruoyi-server/ruoyi-server-hik/src/main/java/org/dromara/server/hik/controller/TestController.java

@@ -157,11 +157,11 @@ public class TestController {
     }
 
     /**
-     * 删除所有设备上的所有员工
+     * 删除所有设备上的所有员工 不提供,只提供单个的设备操作
      *
      * @return 响应信息主体,包含操作结果的状态码、消息内容以及可能的附加数据
      */
-    @GetMapping("/emp/del/all")
+//    @GetMapping("/emp/del/all")
     public R<Void> deleteAllEmpByFromDevice() {
         return sendDeviceService.deleteEmpFromDevice();
     }
@@ -195,8 +195,9 @@ public class TestController {
         return sendDeviceService.upLoadEmpToDevice(termNo, userId);
     }
 
+    // 一般只在初始化设备时使用,这个调用耗时比较长,请勿频繁调用
     /**
-     * 上传所有员工信息到指定设备。
+     * 上传所有员工信息到指定设备。    一般只在初始化设备时使用,这个调用耗时比较长,请勿频繁调用。
      * <p>
      * 根据终端编号,将员工信息上传至指定设备。该方法通过HTTP GET请求触发,
      * 并将终端编号作为路径变量传递。
@@ -232,7 +233,7 @@ public class TestController {
      *
      * @return 响应信息主体,包含操作结果的状态码、消息内容以及可能的附加数据
      */
-    @GetMapping("/emp/upload/all")
+//    @GetMapping("/emp/upload/all")
     public R<Void> upLoadAllEmpToAllDevice(){
         return sendDeviceService.upLoadEmpToDevice(true);
     }
@@ -252,6 +253,12 @@ public class TestController {
         return sendDeviceService.deleteAllCardByUserNo(device,userNo);
     }
 
+    //删除所有设备上指定用户userId的所有卡片信息
+    @PostMapping("/card/deleteAllCard/{userId}")
+    public R<Void> deleteAllCardByUserNo(@PathVariable("userId") Long userId) {
+        return sendDeviceService.deleteAllCardByUserId(userId);
+    }
+
     /**
      * 接收并处理发送到服务器的设备推送数据。
      * 该方法处理不同类型的 content,包括 multipart/form-data 和 application/json。

+ 27 - 0
ruoyi-server/ruoyi-server-hik/src/main/java/org/dromara/server/hik/dubbo/RemoteSyncDownSendToHikServiceImpl.java

@@ -0,0 +1,27 @@
+package org.dromara.server.hik.dubbo;
+
+import lombok.RequiredArgsConstructor;
+import org.apache.dubbo.config.annotation.DubboService;
+import org.dromara.hik.api.service.RemoteSyncDownSendToHikService;
+import org.dromara.server.hik.service.ISendDeviceService;
+import org.springframework.stereotype.Service;
+
+@RequiredArgsConstructor
+@Service
+@DubboService
+public class RemoteSyncDownSendToHikServiceImpl implements RemoteSyncDownSendToHikService {
+
+    private final ISendDeviceService sendDeviceService;
+
+    /**
+     * 同步下发单个账号数据到所有海康设备
+     *
+     * @param userId        账号id
+     * @param operationType 操作类型
+     * @param factoryId 物理卡号,回收卡时必传
+     */
+    @Override
+    public void send(Long userId, String operationType, Long factoryId) {
+        sendDeviceService.currentDownSendUserCardInfo(userId, operationType, factoryId);
+    }
+}

+ 14 - 13
ruoyi-server/ruoyi-server-hik/src/main/java/org/dromara/server/hik/event/handler/HeatBeatHandler.java

@@ -8,6 +8,7 @@ import org.dromara.backstage.api.RemoteUserAccountService;
 import org.dromara.backstage.api.domain.vo.RemoteUserAccountVo;
 import org.dromara.common.core.constant.CacheConstants;
 import org.dromara.common.core.constant.CacheNames;
+import org.dromara.common.core.domain.R;
 import org.dromara.common.redis.utils.RedisUtils;
 import org.dromara.server.hik.domain.vo.XfTermVo;
 import org.dromara.server.hik.event.HikEventHandler;
@@ -38,22 +39,26 @@ public class HeatBeatHandler implements HikEventHandler {
         HeatBeatData heatBeatData = jsonObject.toJavaObject(HeatBeatData.class);
         log.info("接收到心跳数据:{}", heatBeatData);
 
-        //更新设备的IP地址
-        // mac -> ip 和 心跳的IP 是否相等,不相等则更新
-        String ipAddress = heatBeatData.getIpAddress();
+
+        // mac -> ip
         String macAddress = heatBeatData.getMacAddress();
         XfTermVo termVo = xfTermService.getByMac(macAddress);
-        if(termVo == null) return null;
-        if(!StringUtils.equals(ipAddress, termVo.getTermIp())){
-            log.info("设备IP地址更新:{} -> {}", ipAddress, termVo.getTermIp());
-            xfTermService.updateByMac(macAddress, ipAddress);
+        if(termVo == null) {
+            log.info("设备不存在:{}", macAddress);
+            return null;
         }
 
-        //增量下发 人脸数据和卡数据
+        //不更新设备的IP地址 固定,不能变更
+        String ipAddress = termVo.getTermIp();
+
+        // 维护一个心跳列表的缓存
+        RedisUtils.setCacheMapValue(CacheNames.LAST_TIME_HEARTBEAT_LIST ,termVo.getTermNo().toString(), System.currentTimeMillis());
+
+        //增量下发 用户数据、人脸数据和卡数据
         Long cacheObject = RedisUtils.getCacheObject(CacheNames.XF_TERM_IP + ipAddress);
         if(cacheObject == null){
             // 第一次上线 默认只下发最近1天的 新增或修改的数据
-            //记录每天第一次上来的心跳数据时 下发最近3天的 新增或修改的数据
+            //记录每天第一次上来的心跳数据时 新增或修改的数据
             //        t_pt_useraccount表 update_time  更新人脸
             //        t_pt_card表   change_time、create_time  更新卡片
             //下发数据
@@ -64,8 +69,6 @@ public class HeatBeatHandler implements HikEventHandler {
             minus = minus.with(LocalTime.of(0, 0, 0));
             // LocalDateTime 转 date
             Date date = Date.from(minus.atZone(ZoneId.systemDefault()).toInstant());
-//            System.err.println("minus: " + minus);
-//            System.err.println("date: " + date);
             sendDeviceService.upLoadEmpToDevice(macAddress,date,true);
 
             RedisUtils.setCacheObject(CacheNames.XF_TERM_IP + ipAddress, date.getTime());
@@ -76,8 +79,6 @@ public class HeatBeatHandler implements HikEventHandler {
             // 舍弃时分秒,只保留日期部分
             LocalDate nowDate = now.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
             LocalDate lastDate = last.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
-//            System.err.println("nowDate: " + nowDate);
-//            System.err.println("lastDate: " + lastDate);
             // 计算两个日期之间的天数差
             long daysBetween = ChronoUnit.DAYS.between(lastDate, nowDate);
 

+ 2 - 2
ruoyi-server/ruoyi-server-hik/src/main/java/org/dromara/server/hik/mq/event/impl/HikBackStageEventImpl.java

@@ -42,8 +42,8 @@ public class HikBackStageEventImpl implements IHIKEventStrategy {
                 Long userNo = remoteVo.getUserNo();
                 Boolean deleteUser = !"0".equals(remoteVo.getDelFlag());
                 Date lifespan = remoteVo.getLifespan();
-                R<Void> result = sendDeviceService.upLoadEmpToAllDeviceByUserNo(userNo, lifespan, deleteUser);
-                log.info(result.getMsg());
+//                R<Void> result = sendDeviceService.upLoadEmpToAllDeviceByUserNo(userNo, lifespan, deleteUser);
+//                log.info(result.getMsg());
             }
             case EventTypeConstants.CARD -> {
                 RemoteCardDto remoteCard = JSONUtil.toBean(JSONUtil.parseObj(msg), RemoteCardDto.class);

+ 18 - 1
ruoyi-server/ruoyi-server-hik/src/main/java/org/dromara/server/hik/service/ISendDeviceService.java

@@ -114,6 +114,13 @@ public interface ISendDeviceService {
      */
     R<Void> deleteAllCardByUserNo(DeviceDto device, String userNo);
 
+    /**
+     * 删除所有设备上指定用户编号的卡片信息
+     * @param userId
+     * @return
+     */
+    R<Void> deleteAllCardByUserId(Long userId);
+
     /**
      * 删除指定用户在设备上的指定卡片信息。
      * <p>
@@ -210,7 +217,7 @@ public interface ISendDeviceService {
      * @param deleteUser 是否删除用户标志,用于指示是否在设备上删除该用户的相关信息
      * @return 响应信息主体,表示上传操作的结果状态及可能的附加信息
      */
-    R<Void> upLoadEmpToAllDeviceByUserNo(Long userNo, Date lifeSpan, Boolean deleteUser);
+    R<Void> upLoadEmpToAllDeviceByUserNo(Long userNo,String name, Date lifeSpan, Boolean deleteUser);
 
     /**
      * 上传指定员工的卡片到所有设备,并设置相关信息。
@@ -222,4 +229,14 @@ public interface ISendDeviceService {
      * @return n 响应信息主体,表示上传操作的结果状态及可能的附加信息
      */
     R<Void> upLoadEmpCardToAllDevice(Long userId, Long factorId,Boolean deleteAllCard,Boolean deleteCard);
+
+    /**
+     * 实时根据操作下发数据到所有海康设备
+     * @param userId 用户ID
+     * @param factoryId 卡片ID
+     * @param deleteAllCard 是否删除所有卡片 false-不删除
+     * @param deleteCard 是否删除物理卡号对应的卡片, false-不删除
+     * @return n 响应信息主体,表示上传操作的结果状态及可能的附加信息
+     */
+    void currentDownSendUserCardInfo(Long userId,String operationType,Long factoryId);
 }

+ 256 - 39
ruoyi-server/ruoyi-server-hik/src/main/java/org/dromara/server/hik/service/impl/SendDeviceServiceImpl.java

@@ -13,12 +13,18 @@ import org.dromara.backstage.api.RemoteUserAccountService;
 import org.dromara.backstage.api.domain.vo.RemoteUserAccountVo;
 import org.dromara.backstage.api.domain.vo.RemoteXfTermVo;
 import org.dromara.common.core.config.DefaultConfig;
+import org.dromara.common.core.constant.BusinessOperationConstants;
+import org.dromara.common.core.constant.CacheNames;
 import org.dromara.common.core.constant.DefaultConstants;
+import org.dromara.common.core.constant.UserConstants;
 import org.dromara.common.core.domain.R;
+import org.dromara.common.core.enums.CardStatusEnum;
 import org.dromara.common.core.enums.DeviceBrandEnum;
+import org.dromara.common.core.enums.UserAccountStatusEnum;
 import org.dromara.common.core.exception.ServiceException;
 import org.dromara.common.core.utils.StringUtils;
 import org.dromara.common.core.utils.ValidatorUtils;
+import org.dromara.common.redis.utils.RedisUtils;
 import org.dromara.server.hik.constant.ErrCodeConstants;
 import org.dromara.server.hik.constant.HikApiConstants;
 import org.dromara.server.hik.constant.HikDefaultConstants;
@@ -104,7 +110,6 @@ public class SendDeviceServiceImpl implements ISendDeviceService {
         setCardToEmpInfoDto(empDto, factoryId, deleteAllCard, deleteCard);
 
         if(uploadPhoto){
-            // TODO 2025-05-24 因为人员照片原因,暂时不将人脸照片上传到消费机
             // 设置用户人脸图片信息
             String photo = accountVo.getFacePicUrl();
             setPhotoToEmpInfoDto(empDto, photo, deleteAllFace, deleteFace);
@@ -149,6 +154,9 @@ public class SendDeviceServiceImpl implements ISendDeviceService {
 
     // 更新卡片
     private static void setCardToEmpInfoDto(EmpInfoDto empDto,Long factoryId,Boolean deleteAllCard, Boolean deleteCard) {
+        CardListDto cardListDto = new CardListDto();
+        // 更新卡片时deleteAllCard为true,保证最多只有一张卡,其实固定为true就好
+        cardListDto.setDeleteAllCard(deleteAllCard);
         if (ObjectUtil.isNotEmpty(factoryId) && factoryId > 0L) {
             CardDto cardDto = new CardDto().setCardNo(factoryId.toString());
             cardDto.setDeleteCard(deleteCard);
@@ -156,12 +164,9 @@ public class SendDeviceServiceImpl implements ISendDeviceService {
             List<CardDto> cardList = new ArrayList<>();
             cardList.add(cardDto);
 
-            CardListDto cardListDto = new CardListDto();
             cardListDto.setList(cardList);
-            cardListDto.setDeleteAllCard(deleteAllCard);
-
-            empDto.setCardInfo(cardListDto);
         }
+        empDto.setCardInfo(cardListDto);
     }
 
     /**
@@ -491,13 +496,23 @@ public class SendDeviceServiceImpl implements ISendDeviceService {
         if (CollectionUtil.isEmpty(termList)) {
             throw new ServiceException("没有要处理的设备");
         }
-        termList.forEach(p -> {
-            threadPoolTaskExecutor.execute(() -> {
+        List<Long> onlineDeviceList = getOnlineDeviceList();
+        //只给在线设备下发数据
+        List<RemoteXfTermVo> list = termList.stream().filter(p -> onlineDeviceList.contains(p.getTermNo())).toList();
+        if(CollectionUtil.isEmpty(list)){
+            log.info("没有在线设备");
+            return;
+        }
+        threadPoolTaskExecutor.execute(() -> list.forEach(p -> {
+            try{
                 DeviceDto deviceDto = getDeviceDto(p);
-                R<Void> result = this.createOperatorEmpInfo(deviceDto, empDto);
-                log.info(result.getMsg());
-            });
-        });
+                this.createOperatorEmpInfo(deviceDto, empDto);
+//                log.info(result.getMsg());
+            }catch (Exception e){
+                log.error("下发数据失败:{}, 机号:{}",e.getMessage(), p.getTermNo());
+            }
+        }));
+
     }
 
     // endregion
@@ -565,13 +580,21 @@ public class SendDeviceServiceImpl implements ISendDeviceService {
         if (CollectionUtil.isEmpty(termList)) {
             throw new ServiceException("没有要处理的设备");
         }
-        termList.forEach(p -> {
-            threadPoolTaskExecutor.execute(() -> {
+        List<Long> onlineDeviceList = getOnlineDeviceList();
+        if (CollectionUtil.isEmpty(onlineDeviceList)) {
+            log.info("没有在线设备.");
+            return R.ok();
+        }
+        List<RemoteXfTermVo> list = termList.stream().filter(p -> onlineDeviceList.contains(p.getTermNo())).toList();
+        list.forEach(p -> threadPoolTaskExecutor.execute(() -> {
+            try{
                 DeviceDto dto = getDeviceDto(p);
                 R<Void> result = syncTimeToDevice(dto);
                 log.info(result.getMsg());
-            });
-        });
+            } catch (Exception e) {
+                log.error("同步时间失败:{},机号 {}", e.getMessage(), p.getTermNo());
+            }
+        }));
         return R.ok("处理完成");
     }
 
@@ -629,7 +652,7 @@ public class SendDeviceServiceImpl implements ISendDeviceService {
             return R.fail(
                 MessageFormat.format("[处理人员失败]-[设备IP:{0}, 人员Id:{1}, 错误信息:无此Id对应的人员信息", device.getDeviceIp(), userId));
         }
-        EmpInfoDto empInfo = getEmpInfoDto(accountVo, true, false, false, false, false, true);
+        EmpInfoDto empInfo = getEmpInfoDto(accountVo, true, false, false, false, false, false);
         R<Void> result = createOperatorEmpInfo(device, empInfo);
 
         log.info(result.getMsg());
@@ -643,7 +666,7 @@ public class SendDeviceServiceImpl implements ISendDeviceService {
         List<RemoteUserAccountVo> accountVoList = remoteUserAccountService.getUserAccountVoList();
         accountVoList.forEach(p -> {
             threadPoolTaskExecutor.execute(() -> {
-                EmpInfoDto empDto = getEmpInfoDto(p, true, false, false, false, false, true);
+                EmpInfoDto empDto = getEmpInfoDto(p, true, false, false, false, false, false);
                 R<Void> result = createOperatorEmpInfo(deviceDto, empDto);
                 log.info(result.getMsg());
             });
@@ -658,12 +681,12 @@ public class SendDeviceServiceImpl implements ISendDeviceService {
             return R.fail(
                 MessageFormat.format("[处理人员失败]-[人员Id:{0}, 错误信息:无此Id对应的人员信息", userId));
         }
-        EmpInfoDto empInfo = getEmpInfoDto(accountVo, true, false, false, false, false, true);
 
         List<RemoteXfTermVo> termList = remotePtXfTermService.queryListByBrand("hk");
         if (CollectionUtil.isEmpty(termList)) {
             return R.warn("没有要处理人员的设备");
         }
+        EmpInfoDto empInfo = getEmpInfoDto(accountVo, true, false, false, false, false, false);
 
         termList.forEach(p -> {
             threadPoolTaskExecutor.execute(() -> {
@@ -693,7 +716,7 @@ public class SendDeviceServiceImpl implements ISendDeviceService {
             threadPoolTaskExecutor.execute(() -> {
                 DeviceDto device = getDeviceDto(p);
                 accountVoList.parallelStream().forEach(t -> {
-                    EmpInfoDto empInfo = getEmpInfoDto(t, true, false, false, false, false, true);
+                    EmpInfoDto empInfo = getEmpInfoDto(t, true, false, false, false, false, false);
                     R<Void> result = createOperatorEmpInfo(device, empInfo);
                     log.info(result.getMsg());
                 });
@@ -710,7 +733,6 @@ public class SendDeviceServiceImpl implements ISendDeviceService {
 
         EmpInfoDto delEmpDto = new EmpInfoDto();
         delEmpDto.setEmployeeNo(userNo);
-        delEmpDto.setName("胡哲");
         delEmpDto.setCardInfo(cardList);
 
         R<Void> check = this.createOperatorEmpInfo(device, delEmpDto);
@@ -721,6 +743,30 @@ public class SendDeviceServiceImpl implements ISendDeviceService {
         return R.ok(MessageFormat.format("[处理人员所有卡片成功]-[设备IP:{0}, 人员编号:{1}]", device.getDeviceIp(), userNo));
     }
 
+    /**
+     * 删除所有设备上指定用户编号的所有卡片信息
+     *
+     * @param userId
+     * @return
+     */
+    @Override
+    public R<Void> deleteAllCardByUserId(Long userId) {
+        RemoteUserAccountVo byId = remoteUserAccountService.getById(userId);
+        if(byId == null){
+            return R.fail("未找到该用户");
+        }
+        // 获取所有设备
+        List<RemoteXfTermVo> termList = remotePtXfTermService.queryListByBrand("hk");
+        if (CollectionUtil.isEmpty(termList)) {
+            return R.warn("没有要处理人员的设备");
+        }
+        EmpInfoDto empDto = getEmpInfoDto(byId, false, false, false, true, false,false);
+
+        sendEmpToAllDevice(empDto);
+        return R.ok();
+    }
+
+
     @Override
     public R<Void> deleteCardByUserNo(DeviceDto device, String userNo, String factoryId) {
         return null;
@@ -784,8 +830,10 @@ public class SendDeviceServiceImpl implements ISendDeviceService {
         // 处理人员列表
         accountVoList.forEach(p -> {
             threadPoolTaskExecutor.execute(() -> {
-                try {
-                    EmpInfoDto empDto = getEmpInfoDto(p, false, false, false, false, false,true);
+                try {//如果过了有效期,则直接删除 deleteUser为true
+                    ResultBoolean obtainBoolean = getResult(p);
+                    // deleteAllCard为true 保证只有一张卡片
+                    EmpInfoDto empDto = getEmpInfoDto(p, obtainBoolean.deleteUser(), obtainBoolean.deleteAllFace(), obtainBoolean.deleteFace(), obtainBoolean.deleteAllCard(), obtainBoolean.deleteCard(),true);
                     R<Void> result = createOperatorEmpInfo(deviceDto, empDto);
                     log.info(result.getMsg());
                 } catch (Exception e) {
@@ -796,17 +844,63 @@ public class SendDeviceServiceImpl implements ISendDeviceService {
         return R.ok();
     }
 
+    @NotNull
+    private static ResultBoolean getResult(RemoteUserAccountVo p) {
+        boolean deleteUser = false;
+        if(p.getLifespan()!=null){
+            //如果lifespan比当前时间小或者非开户的,则设置deleteUser为true
+            if(p.getLifespan().getTime()<System.currentTimeMillis()){
+                deleteUser = true;
+            }
+        }
+        if (!UserAccountStatusEnum.IS_OPEN.code().toString().equals(p.getAccountStatus())) {
+            deleteUser = true;
+        }
+        // 如果 照片不为空,deleteAllFace为true,deleteFace为false
+        boolean deleteAllFace = false;
+        boolean deleteFace = false;
+        if(StringUtils.isNotBlank(p.getPhoto())){
+            deleteAllFace = true;
+        }
+        // 如果 factoryId 不为空且cardStatus 为 1,deleteAllCard为true,deleteCard为false,否则 deleteAllCard为false,deleteCard为true
+        boolean deleteAllCard = false;
+        boolean deleteCard = false;
+        Long factoryId = p.getFactoryId();
+        String cardStatus = p.getCardStatus();
+        if(Objects.nonNull(factoryId)&& factoryId!=0){
+            deleteAllCard = true;
+            if(StringUtils.isNotBlank(cardStatus) && !CardStatusEnum.NORMAL.code().toString().equals(cardStatus)){
+                deleteAllCard = false;
+                //其他状态的卡直接删除
+                deleteCard = true;
+            }
+        }
+        if (StringUtils.isNotBlank(p.getDelFlag()) && !UserConstants.USER_NORMAL.equals(p.getDelFlag())) {
+            //已删除用户 进行删除
+            deleteUser = true;
+        }
+        return new ResultBoolean(deleteUser, deleteAllFace, deleteFace, deleteAllCard, deleteCard);
+    }
+
+    private record ResultBoolean(boolean deleteUser, boolean deleteAllFace, boolean deleteFace, boolean deleteAllCard, boolean deleteCard) {
+    }
+
     @Override
     public R<Void> upLoadEmpToDevice(Long termNo, Long userId) {
         DeviceDto deviceDto = getDeviceDto(termNo);
 
         RemoteUserAccountVo accountVo = remoteUserAccountService.getUserAccountVoBy(userId);
 
-        EmpInfoDto empDto = getEmpInfoDto(accountVo, false, false, false, false, false,true);
+        EmpInfoDto empDto = getEmpInfoDto(accountVo, false, false, false, true, false,true);
 
         return this.createOperatorEmpInfo(deviceDto, empDto);
     }
 
+    /**
+     *  暂不使用,不能一次上传所有用户到所有设备上
+     * @param uploadPhoto
+     * @return
+     */
     @Override
     public R<Void> upLoadEmpToDevice(Boolean uploadPhoto) {
         // 获取所有设备
@@ -825,7 +919,7 @@ public class SendDeviceServiceImpl implements ISendDeviceService {
             DeviceDto device = getDeviceDto(p);
             accountVoList.forEach(t -> {
                 threadPoolTaskExecutor.execute(()->{
-                    EmpInfoDto empInfo = getEmpInfoDto(t, false, false, false, false, false,uploadPhoto);
+                    EmpInfoDto empInfo = getEmpInfoDto(t, false, false, false, true, false,uploadPhoto);
                     R<Void> result = createOperatorEmpInfo(device, empInfo);
                     log.info(result.getMsg());
                 });
@@ -845,6 +939,7 @@ public class SendDeviceServiceImpl implements ISendDeviceService {
     public R<Void> upLoadEmpToDevice(String macAddress,Date startDate, Boolean uploadPhoto) {
         List<RemoteUserAccountVo> vos = remoteUserAccountService.getUpdateUserAccountVo(startDate);
         if (CollectionUtil.isEmpty(vos)) {
+            log.info("heartBeat没有要处理人员数据");
             return R.warn("没有要处理人员数据");
         }
         log.info("day处理人员数据条数{}", vos.size());
@@ -853,18 +948,18 @@ public class SendDeviceServiceImpl implements ISendDeviceService {
             return R.warn(MessageFormat.format("设备未找到,mac:{0}", macAddress));
         }
         DeviceDto device = getDeviceDto(termVo);
-        vos.forEach(t -> {
-            threadPoolTaskExecutor.execute(()->{
-                try{
-                    EmpInfoDto empInfo = getEmpInfoDto(t, false, false, false, false, false,uploadPhoto);
-                    R<Void> result = createOperatorEmpInfo(device, empInfo);
-                    log.info(result.getMsg());
-                }catch (Exception e){
-                    e.printStackTrace();
-                    log.error("heartBeat处理人员{}异常: {}", t.getUserId(), e.getMessage(), e);
-                }
-            });
-        });
+        threadPoolTaskExecutor.execute(()-> vos.forEach(t -> {
+            try{
+                ResultBoolean obtainBoolean = getResult(t);
+                EmpInfoDto empInfo = getEmpInfoDto(t, obtainBoolean.deleteUser(), obtainBoolean.deleteAllFace(),
+                    obtainBoolean.deleteFace(), obtainBoolean.deleteAllCard(), obtainBoolean.deleteCard(),uploadPhoto);
+                R<Void> result = createOperatorEmpInfo(device, empInfo);
+                log.info(result.getMsg());
+            }catch (Exception e){
+                e.printStackTrace();
+                log.error("heartBeat下发人员{}异常: {}", t.getUserId(), e.getMessage(), e);
+            }
+        }));
         return R.ok();
     }
 
@@ -875,7 +970,8 @@ public class SendDeviceServiceImpl implements ISendDeviceService {
         if (ObjectUtil.isEmpty(accountVo)) {
             return R.warn(MessageFormat.format("没有要处理的人员信息,userId:{0}", userId));
         }
-        EmpInfoDto empDto = getEmpInfoDto(accountVo, false, false, false, false, false,true);
+        //保持只有一张卡 deleteAllCard为true,deleteCard为false
+        EmpInfoDto empDto = getEmpInfoDto(accountVo, false, false, false, true, false,true);
 
         sendEmpToAllDevice(empDto);
 
@@ -883,13 +979,14 @@ public class SendDeviceServiceImpl implements ISendDeviceService {
     }
 
     @Override
-    public R<Void> upLoadEmpToAllDeviceByUserNo(Long userNo, Date lifeSpan, Boolean deleteUser) {
+    public R<Void> upLoadEmpToAllDeviceByUserNo(Long userNo,String name, Date lifeSpan, Boolean deleteUser) {
         RemoteUserAccountVo accountVo = new RemoteUserAccountVo();
         accountVo.setUserNo(userNo);
         accountVo.setLifespan(lifeSpan);
+        accountVo.setRealName(name);
 
         // 实时下发数据不下发人脸,避免引起卡机
-        EmpInfoDto empDto = getEmpInfoDto(accountVo, deleteUser, false, false, false, false, false);
+        EmpInfoDto empDto = getEmpInfoDto(accountVo, deleteUser, false, false, true, false, false);
 
         sendEmpToAllDevice(empDto);
 
@@ -912,6 +1009,87 @@ public class SendDeviceServiceImpl implements ISendDeviceService {
         return R.ok();
     }
 
+    /**
+     * 挂失卡片时下发数据给所有消费机
+     *
+     * @param userId        用户Id,用于标识需要上传的员工信息
+     * @param factorId      物理卡号
+     * @param deleteAllCard 是否删除所有卡片 false-不删除
+     * @param deleteCard    是否删除物理卡号对应的卡片, false-不删除
+     */
+    public void whenLockCardSend(Long userId) {
+        RemoteUserAccountVo accountVo = remoteUserAccountService.getById(userId);
+        if (ObjectUtil.isEmpty(accountVo)) {
+            R.warn(MessageFormat.format("没有要处理的人员信息,userId:{0}", userId));
+            return;
+        }
+        // 实时下发数据不下发人脸,避免引起卡机
+        EmpInfoDto empDto = getEmpInfoDto(accountVo, false, false, false, true, true, false);
+
+        sendEmpToAllDevice(empDto);
+    }
+
+    /**
+     * 删除账户时下发数据
+     * @param userId
+     */
+    public void whenDeleteSend(Long userId){
+        RemoteUserAccountVo accountVo = remoteUserAccountService.getById(userId);
+        if (ObjectUtil.isEmpty(accountVo)) {
+            R.warn(MessageFormat.format("没有要处理的人员信息,userId:{0}", userId));
+            return;
+        }
+        // 实时下发数据不下发人脸,避免引起卡机
+        EmpInfoDto empDto = getEmpInfoDto(accountVo, true, false, false, true, true, false);
+
+        sendEmpToAllDevice(empDto);
+    }
+
+    /**
+     * 回收卡片时下发数据
+     *
+     * @param userId
+     * @param factoryId
+     */
+    public void whenRecycleSend(Long userId,Long factoryId){
+        //回收卡片,将海康设备上的这个用户vo.getUserId()的,这张卡片vo.getFactoryId()信息删除,下发时如果账号已过期,则删除账号信息,没有就删除账户的卡信息
+        RemoteUserAccountVo accountVo = remoteUserAccountService.getById(userId);
+        if (ObjectUtil.isEmpty(accountVo)) {
+            R.warn(MessageFormat.format("没有要处理的人员信息,userId:{0}", userId));
+            return;
+        }
+        accountVo.setFactoryId(factoryId);
+        Date lifespan = accountVo.getLifespan();
+        boolean deleteUser = false;
+        if(lifespan!=null){
+            if(lifespan.getTime()<System.currentTimeMillis()){
+                deleteUser = true;
+            }
+        }
+        if(!deleteUser && factoryId == null){
+            // 没有卡号,不用在下发了
+            log.warn("消费机交易记录事件,无卡号,不进行下发,userId {}",userId);
+            return;
+        }
+        // 实时下发数据不下发人脸,避免引起卡机
+        EmpInfoDto empDto = getEmpInfoDto(accountVo, deleteUser, false, false, false, true, false);
+
+        sendEmpToAllDevice(empDto);
+    }
+
+    //当新增账户、取卡、补卡、解挂时下发账户信息到所有设备上
+    public void whenOtherOperationSend(Long userId){
+        RemoteUserAccountVo accountVo = remoteUserAccountService.getUserAccountVoBy(userId);
+        if (ObjectUtil.isEmpty(accountVo)) {
+            R.warn(MessageFormat.format("没有要处理的人员信息,userId:{0}", userId));
+            return;
+        }
+        //保持只有一张卡 deleteAllCard为true,deleteCard为false
+        EmpInfoDto empDto = getEmpInfoDto(accountVo, false, false, false, true, false,false);
+
+        sendEmpToAllDevice(empDto);
+    }
+
     @Override
     public R<Void> queryBatchEmpFormDevice(@NotNull QueryDto dto) {
         DeviceDto device = dto.getDevice();
@@ -929,4 +1107,43 @@ public class SendDeviceServiceImpl implements ISendDeviceService {
         }
         return R.ok(StringUtils.format("[IP:{}的设备查询成功],结果:{}", device.getDeviceIp(), doResult.getMsg()));
     }
+
+    public void currentDownSendUserCardInfo(Long userId,String operationType,Long factoryId){
+        if (userId == null) {
+            log.error("userId不能为null");
+            return;
+        }
+        switch (operationType){
+            case BusinessOperationConstants.ADD_ACCOUNT,BusinessOperationConstants.GET_CARD,
+                 BusinessOperationConstants.AGAIN_GET_CARD,BusinessOperationConstants.UNLOCK_CARD -> // 查询用户然后下发即可
+                whenOtherOperationSend(userId);
+            case BusinessOperationConstants.DELETE_ACCOUNT -> // 删除账户
+                whenDeleteSend(userId);
+            case BusinessOperationConstants.RECYCLE_CARD -> // 回收卡片
+                whenRecycleSend(userId,factoryId);
+            case BusinessOperationConstants.LOCK_CARD -> // 挂失
+                whenLockCardSend(userId);
+            default -> log.info("未知操作");
+        }
+    }
+
+    /**
+     * 获取在线设备列表
+     * @return 列表
+     */
+    public List<Long> getOnlineDeviceList(){
+        List<Long> rs = new ArrayList<>();
+        Map<String, Object> cacheMap = RedisUtils.getCacheMap(CacheNames.LAST_TIME_HEARTBEAT_LIST);
+
+        cacheMap.forEach((k,v)->{
+            if (v instanceof Long t){
+                long l = System.currentTimeMillis() - t;
+                // 正常在线的心跳时间间隔是30秒,但怕有网络延迟,这里设置60秒内都算在线
+                if ( l <= 60 * 1000 ){
+                    rs.add(Long.valueOf(k));
+                }
+            }
+        });
+        return rs;
+    }
 }

+ 6 - 0
ruoyi-server/ruoyi-server-sync/pom.xml

@@ -119,6 +119,12 @@
             <version>2.2.0</version>
             <scope>compile</scope>
         </dependency>
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-api-hik</artifactId>
+            <version>2.2.0</version>
+            <scope>compile</scope>
+        </dependency>
     </dependencies>
 
     <build>

+ 6 - 1
ruoyi-server/ruoyi-server-sync/src/main/java/org/dromara/server/sync/business/user/SyncUserBusiness.java

@@ -9,6 +9,7 @@ import cn.hutool.core.util.StrUtil;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.dubbo.config.annotation.DubboReference;
+import org.dromara.common.core.constant.BusinessOperationConstants;
 import org.dromara.common.core.constant.DefaultConstants;
 import org.dromara.common.core.domain.R;
 import org.dromara.common.core.domain.model.ErrorInfo;
@@ -31,6 +32,7 @@ import org.dromara.server.sync.domain.vo.SysUserVo;
 import org.dromara.server.sync.service.IDeptService;
 import org.dromara.server.sync.service.IPostService;
 import org.dromara.server.sync.service.IUserService;
+import org.dromara.server.sync.service.RemoteHikWrapperService;
 import org.dromara.system.api.RemoteDeptService;
 import org.dromara.system.api.RemotePostService;
 import org.dromara.system.api.RemoteUserService;
@@ -67,6 +69,8 @@ public class SyncUserBusiness {
     @DubboReference
     private final RemoteTeamService remoteTeamService;
 
+    private final RemoteHikWrapperService remoteHikWrapperService;
+
     private final IDeptService deptService;
     private final IUserService userService;
     private final IPostService postService;
@@ -96,7 +100,8 @@ public class SyncUserBusiness {
                     // 存在此人员,修改
                     syncResult = userService.updateUser(result.getData());
                 }
-
+                //下发账号信息到海康设备
+                remoteHikWrapperService.send(result.getData().getUserId(), BusinessOperationConstants.ADD_ACCOUNT, null);
                 return syncResult ? R.ok() : R.fail();
             }
             return R.fail(result.getMsg());

+ 10 - 0
ruoyi-server/ruoyi-server-sync/src/main/java/org/dromara/server/sync/service/RemoteHikWrapperService.java

@@ -0,0 +1,10 @@
+package org.dromara.server.sync.service;
+
+/**
+ * hik远程接口的包装类
+ *
+ */
+public interface RemoteHikWrapperService {
+
+    void send(Long userId, String operationType, Long factoryId);
+}

+ 32 - 0
ruoyi-server/ruoyi-server-sync/src/main/java/org/dromara/server/sync/service/impl/RemoteHikWrapperServiceImpl.java

@@ -0,0 +1,32 @@
+package org.dromara.server.sync.service.impl;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.dubbo.config.annotation.DubboReference;
+import org.dromara.hik.api.service.RemoteSyncDownSendToHikService;
+import org.dromara.server.sync.service.RemoteHikWrapperService;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
+import org.springframework.stereotype.Service;
+
+@RequiredArgsConstructor
+@Service
+@Slf4j
+public class RemoteHikWrapperServiceImpl implements RemoteHikWrapperService {
+
+    @DubboReference
+    private final RemoteSyncDownSendToHikService remoteSyncDownSendToHikService;
+
+    private final ThreadPoolTaskExecutor threadPoolTaskExecutor;
+
+    //是否开启海康设备
+    @Value("${hik.open:false}")
+    private Boolean hikOpen;
+
+
+    @Override
+    public void send(Long userId, String operationType, Long factoryId) {
+        if(!hikOpen) return;
+        threadPoolTaskExecutor.execute(() -> remoteSyncDownSendToHikService.send(userId, operationType, factoryId));
+    }
+}

+ 9 - 1
ruoyi-server/ruoyi-server-sync/src/main/java/org/dromara/server/sync/service/impl/UserAccountServiceImpl.java

@@ -6,6 +6,7 @@ import cn.hutool.core.util.ObjectUtil;
 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.core.constant.BusinessOperationConstants;
 import org.dromara.common.core.constant.DefaultConstants;
 import org.dromara.common.core.domain.R;
 import org.dromara.common.core.utils.MapstructUtils;
@@ -18,6 +19,7 @@ import org.dromara.server.sync.domain.vo.UserAccountVo;
 import org.dromara.server.sync.mapper.UserAccountMapper;
 import org.dromara.server.sync.service.IUserAccountService;
 import org.dromara.server.sync.service.IUserBagService;
+import org.dromara.server.sync.service.RemoteHikWrapperService;
 import org.springframework.stereotype.Service;
 
 /**
@@ -33,6 +35,7 @@ public class UserAccountServiceImpl implements IUserAccountService {
     private final UserAccountMapper baseMapper;
     private final IUserBagService bagService;
     private final CardBusiness cardBusiness;
+    private final RemoteHikWrapperService remoteHikWrapperService;
 
     /**
      * 查询一卡通账户
@@ -44,6 +47,8 @@ public class UserAccountServiceImpl implements IUserAccountService {
     public UserAccountVo queryById(Long userId) {
         return baseMapper.selectVoById(userId);
     }
+
+
     /**
      * 用户账户处理
      * @param bo 账户业务对象
@@ -107,10 +112,13 @@ public class UserAccountServiceImpl implements IUserAccountService {
         vo.setDelFlag(DefaultConstants.DELETED);
         vo.setUpdateBy(operationId);
         vo.setUpdateTime(DateUtil.date());
-        return TenantHelper.ignore(() -> baseMapper.update(null, new LambdaUpdateWrapper<UserAccount>()
+        Boolean ignore = TenantHelper.ignore(() -> baseMapper.update(null, new LambdaUpdateWrapper<UserAccount>()
             .set(UserAccount::getDelFlag, DefaultConstants.DELETED)
             .set(UserAccount::getUpdateBy, operationId)
             .set(UserAccount::getUpdateTime, DateUtil.date())
             .eq(UserAccount::getUserId, userId)) > 0);
+        //下发账号信息到海康设备
+        remoteHikWrapperService.send(userId, BusinessOperationConstants.DELETE_ACCOUNT, null);
+        return ignore;
     }
 }

+ 11 - 8
ruoyi-server/ruoyi-server-sync/src/main/java/org/dromara/server/sync/service/impl/UserServiceImpl.java

@@ -22,6 +22,7 @@ import org.dromara.server.sync.service.IUserAccountService;
 import org.dromara.server.sync.service.IUserDeptService;
 import org.dromara.server.sync.service.IUserService;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
 
 import java.util.Date;
 import java.util.List;
@@ -88,17 +89,19 @@ public class UserServiceImpl implements IUserService {
         // 删除人员部门对应关系
         baseMapper.delUserDeptByUserId(DefaultConstants.DELETED, operatorId.toString(), DateUtil.formatDate(new Date()), userId);
 
-        SysUserVo vo = TenantHelper.ignore(()->baseMapper.selectVoById(userId));
+       /* SysUserVo vo = TenantHelper.ignore(()->baseMapper.selectVoById(userId));
         vo.setDelFlag(DefaultConstants.DELETED);
-        vo.setUpdateBy(operatorId);
+        vo.setUpdateBy(operatorId);*/
 
-        userAccountService.deleteById(Long.valueOf(userId), operatorId);
         // 删除人员
-        return TenantHelper.ignore(() -> baseMapper.update(null, new LambdaUpdateWrapper<SysUser>()
-                                                                     .set(SysUser::getDelFlag, DefaultConstants.DELETED)
-                                                                     .set(SysUser::getUpdateBy, operatorId)
-                                                                     .set(SysUser::getUpdateTime, DateUtil.date())
-                                                                     .eq(SysUser::getUserId, userId)) > 0);
+        Boolean ignore = TenantHelper.ignore(() -> baseMapper.update(null, new LambdaUpdateWrapper<SysUser>()
+            .set(SysUser::getDelFlag, DefaultConstants.DELETED)
+            .set(SysUser::getUpdateBy, operatorId)
+            .set(SysUser::getUpdateTime, DateUtil.date())
+            .eq(SysUser::getUserId, userId)) > 0);
+
+        userAccountService.deleteById(Long.valueOf(userId), operatorId);
+        return ignore;
 
     }