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

feature: 定时任务向海康设备下发删除过期账户的数据

xiari 8 hónapja
szülő
commit
8724b6de3f

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

@@ -98,4 +98,12 @@ public interface RemoteUserAccountService {
     List<RemoteUserAccountVo> getUpdateUserAccountVo(Date startDate);
 
     RemoteUserAccountVo getById(Long userId);
+
+    /**
+     * 获取指定时间段内到期的账户信息
+     * @param startDate 开始时间
+     * @param endDate 结束时间
+     * @return 账户信息
+     */
+    List<RemoteUserAccountVo> getExpireAccount(Date startDate, Date endDate);
 }

+ 4 - 1
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/CacheNames.java

@@ -203,9 +203,12 @@ public interface CacheNames {
     String XF_ORIGINAL_ID = "xf_original_id";
     String XF_DETAIL_ID = "xf_detail_id";
 
-    // 记录最后一次下发给设备的时间
+    // 记录最后一次下发给设备的时间 hik
     String XF_MAC_DOWN_SEND_TIME = "mac_down_send_time:";
 
+    // 最后一次的删除过期账户的时间 hik
+    String LAST_TIME_DELETE_LIST = "last_time_delete_list";
+
     String USER_HAS_CARD = "user_has_card";
 
     // 最后心跳时间 to hik

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

@@ -217,5 +217,18 @@ public class RemoteUserAccountServiceImpl implements RemoteUserAccountService {
         return MapstructUtils.convert(byId, RemoteUserAccountVo.class);
     }
 
+    /**
+     * 获取指定时间段内到期的账户信息
+     *
+     * @param startDate 开始时间
+     * @param endDate   结束时间
+     * @return 账户信息
+     */
+    @Override
+    public List<RemoteUserAccountVo> getExpireAccount(Date startDate, Date endDate) {
+        List<PtUserAccountVo> list = userAccountService.obtainExpireUserAccountVo(startDate, endDate);
+        return MapstructUtils.convert(list, RemoteUserAccountVo.class);
+    }
+
 
 }

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

@@ -214,4 +214,12 @@ public interface IPtUserAccountService {
      * 获取指定天数内有更新账户信息的和有卡片更新的账户信息
      */
     List<PtUserAccountVo> getUserAccountVoByDay(Date startDate);
+
+    /**
+     * 获取指定时间段内到期的账户信息
+     * @param startDate 开始时间
+     * @param endDate 结束时间
+     * @return 账户信息
+     */
+    List<PtUserAccountVo> obtainExpireUserAccountVo(Date startDate, Date endDate);
 }

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

@@ -739,6 +739,20 @@ public class PtUserAccountServiceImpl implements IPtUserAccountService {
         return rs;
     }
 
+    /**
+     * 获取指定时间段内到期的账户信息
+     *
+     * @param startDate 开始时间
+     * @param endDate   结束时间
+     * @return 账户信息
+     */
+    @Override
+    public List<PtUserAccountVo> obtainExpireUserAccountVo(Date startDate, Date endDate) {
+        LambdaQueryWrapper<PtUserAccount> between = Wrappers.lambdaQuery(PtUserAccount.class);
+        between.between(PtUserAccount::getLifespan, startDate, endDate);
+        return baseMapper.selectVoList(between);
+    }
+
     private void addOtherInfoForList(List<PtUserAccountVo> list){
         // 附加部门信息
         List<RemoteDeptVo> deptVoList = remoteDeptService.selectDeptList();

+ 26 - 0
ruoyi-server/ruoyi-server-hik/src/main/java/org/dromara/server/hik/controller/TestController.java

@@ -280,6 +280,14 @@ public class TestController {
         return R.ok();
     }
 
+    //设置设备下发数据的时间
+    @PostMapping(("/increase/setTime/{termNo}/{time}"))
+    public R<Void> setSendTime(@PathVariable("termNo") Long termNo,@PathVariable("time") String time) {
+        sendDeviceService.setSendTime(termNo, time);
+        return R.ok();
+    }
+
+
     /**
      * 接收并处理发送到服务器的设备推送数据。
      * 该方法处理不同类型的 content,包括 multipart/form-data 和 application/json。
@@ -532,4 +540,22 @@ public class TestController {
     public record SetSendTimeCache(Date date, String macAddress) {
     }
 
+    //  初始化:设置最近一次 下发删除账户的时间 LAST_TIME_DELETE_LIST
+    @PostMapping("/init/setDeleteTimeCache")
+    public R<Void> setDeleteTimeCache(@RequestBody SetDeleteTimeCache setDeleteTimeCache) {
+        sendDeviceService.setDeleteTimeCache(setDeleteTimeCache.date, setDeleteTimeCache.termNo);
+        return R.ok("缓存设置成功");
+    }
+
+    public record SetDeleteTimeCache(Date date, Long termNo) {
+    }
+
+    // 向设备编号为termNo的设备下发删除账户
+    @PostMapping("/deleteAccount/send/{termNo}")
+    public R<Void> deleteExpireAccountSend(@PathVariable("termNo") Long termNo) {
+        sendDeviceService.deleteExpireAccountSend(termNo);
+        return R.ok();
+    }
+
+
 }

+ 26 - 0
ruoyi-server/ruoyi-server-hik/src/main/java/org/dromara/server/hik/service/ISendDeviceService.java

@@ -48,6 +48,16 @@ public interface ISendDeviceService {
      */
     R<Void> setHttpHostByTermNo(Long termNo);
 
+    /**
+     * 设置数据下发数据的时间
+     * <p>
+     * 该方法用于将员工信息传输到所有设备,基于提供的员工信息数据传输对象。
+     *
+     * @param uploadEmpDto 员工信息数据传输对象,包含员工信息数据
+     * @return 响应信息主体,表示上传操作的结果状态及可能的附加信息
+     */
+    void setSendTime(Long termNo, String time);
+
     /**
      * 批量查询设备上人员信息。
      * <p>
@@ -247,4 +257,20 @@ public interface ISendDeviceService {
      * @param termNo 指定设备编号
      */
     void increaseSend(Long termNo);
+
+    /**
+     * 设置设备删除过期账户时间缓存
+     */
+    void setDeleteTimeCache(Date date, Long termNo);
+
+    /**
+     * 删除设备过期账户
+     * @param termNo 设备编号
+     */
+    void deleteExpireAccountSend(Long termNo);
+
+    /**
+     * 删除所有设备过期账户
+     */
+    void deleteExpireAccountSendAllDevice();
 }

+ 178 - 0
ruoyi-server/ruoyi-server-hik/src/main/java/org/dromara/server/hik/service/impl/SendDeviceServiceImpl.java

@@ -1,5 +1,6 @@
 package org.dromara.server.hik.service.impl;
 
+import cn.hutool.core.bean.BeanUtil;
 import cn.hutool.core.collection.CollectionUtil;
 import cn.hutool.core.date.DateUtil;
 import cn.hutool.core.util.ObjectUtil;
@@ -23,6 +24,7 @@ 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.MapstructUtils;
 import org.dromara.common.core.utils.StringUtils;
 import org.dromara.common.core.utils.ValidatorUtils;
 import org.dromara.common.redis.utils.RedisUtils;
@@ -48,7 +50,13 @@ import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
 import org.springframework.stereotype.Service;
 import org.springframework.validation.annotation.Validated;
 
+import java.text.DateFormat;
 import java.text.MessageFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.time.LocalDate;
+import java.time.ZoneId;
+import java.time.ZoneOffset;
 import java.time.ZonedDateTime;
 import java.time.format.DateTimeFormatter;
 import java.util.*;
@@ -563,6 +571,37 @@ public class SendDeviceServiceImpl implements ISendDeviceService {
         return this.setHttpHostByDto(dto);
     }
 
+    /**
+     * 设置数据下发数据的时间
+     * <p>
+     * 该方法用于将员工信息传输到所有设备,基于提供的员工信息数据传输对象。
+     *
+     * @param termNo
+     * @param time
+     * @return 响应信息主体,表示上传操作的结果状态及可能的附加信息
+     */
+    @Override
+    public void setSendTime(Long termNo, String time) {
+        // time的日期格式必须是yyyy-MM-dd,否则抛出异常
+        DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
+        Date date;
+        try {
+            date = dateFormat.parse(time);
+        } catch (ParseException e) {
+            throw new ServiceException("时间格式错误,请输入yyyy-MM-dd格式的时间");
+        }
+        XfTermVo byTermNo = xfTermService.getByTermNo(termNo);
+        if (byTermNo == null) {
+            throw new ServiceException("设备不存在" + termNo);
+        }
+        String termMac = byTermNo.getTermMac();
+        if (StringUtils.isBlank(termMac)) {
+            throw new ServiceException("设备的mac为空," + termNo);
+        }
+        String newTermMac = termMac.replaceAll(":", "-");
+        RedisUtils.setCacheObject(CacheNames.XF_MAC_DOWN_SEND_TIME + newTermMac, date.getTime());
+    }
+
     @Override
     public R<Void> setHttpHostAll() {
         List<RemoteXfTermVo> termList = remotePtXfTermService.queryListByBrand("hk");
@@ -1200,6 +1239,145 @@ public class SendDeviceServiceImpl implements ISendDeviceService {
         RedisUtils.setCacheObject(CacheNames.XF_MAC_DOWN_SEND_TIME + newTermMac, System.currentTimeMillis());
     }
 
+    /**
+     * 设置设备删除过期账户时间缓存
+     *
+     * @param date 缓存值
+     * @param termNo 机号
+     */
+    @Override
+    public void setDeleteTimeCache(Date date, Long termNo) {
+        XfTermVo byTermNo = xfTermService.getByTermNo(termNo);
+        if (byTermNo == null) {
+            throw new ServiceException("设备不存在" + termNo);
+        }
+        if(!StringUtils.equals(byTermNo.getBrand(), DeviceBrandEnum.HK.getCode())){
+            throw new ServiceException("设备不是海康设备: " + termNo);
+        }
+        String termMac = byTermNo.getTermMac();
+        if (StringUtils.isBlank(termMac)) {
+            throw new ServiceException("设备的mac为空," + termNo);
+        }
+        if(date != null){
+            RedisUtils.setCacheMapValue(CacheNames.LAST_TIME_DELETE_LIST, termMac, date.getTime());
+        }else{
+            LocalDate localDate = LocalDate.now();
+            // localDate 转Date
+            Date now = Date.from(localDate.atStartOfDay(ZoneId.systemDefault()).toInstant());
+//            Date now1 = Date.from(localDate.atStartOfDay(ZoneOffset.ofHours(8)).toInstant());
+            RedisUtils.setCacheMapValue(CacheNames.LAST_TIME_DELETE_LIST, termMac, now.getTime());
+        }
+    }
+
+    /**
+     * 删除设备过期账户
+     *
+     * @param termNo 设备编号
+     */
+    @Override
+    public void deleteExpireAccountSend(Long termNo) {
+        XfTermVo byTermNo = xfTermService.getByTermNo(termNo);
+        if (byTermNo == null) {
+            throw new ServiceException("设备不存在" + termNo);
+        }
+        if(!StringUtils.equals(byTermNo.getBrand(), DeviceBrandEnum.HK.getCode())){
+            throw new ServiceException("设备不是海康设备: " + termNo);
+        }
+        RemoteXfTermVo remoteXfTermVo = BeanUtil.copyProperties(byTermNo, RemoteXfTermVo.class);
+        removeExpireAccountForTerm(remoteXfTermVo);
+    }
+
+    /**
+     * 删除所有设备过期账户
+     */
+    @Override
+    public void deleteExpireAccountSendAllDevice() {
+        List<RemoteXfTermVo> termList = remotePtXfTermService.queryListByBrand(DeviceBrandEnum.HK.getCode());
+        if (CollectionUtil.isEmpty(termList)) {
+            throw new ServiceException("未查询到海康的设备");
+        }
+        List<Long> onlineDeviceList = getOnlineDeviceList();
+        //只给在线设备下发数据
+        List<RemoteXfTermVo> list = termList.stream().filter(p -> onlineDeviceList.contains(p.getTermNo())).toList();
+        list.forEach(p -> {
+            try {
+                removeExpireAccountForTerm(p);
+            } catch (Exception e) {
+                log.error("删除设备过期账户异常:{}, 错误信息:{}", p.getTermNo(),e.getMessage(), e);
+            }
+        });
+
+    }
+
+    private void removeExpireAccountForTerm(RemoteXfTermVo p) {
+        String termMac = p.getTermMac();
+        Long termNo = p.getTermNo();
+        if (StringUtils.isBlank(termMac)) {
+            throw new ServiceException("设备的mac为空," + termNo);
+        }
+        Long lastTime = RedisUtils.getCacheMapValue(CacheNames.LAST_TIME_DELETE_LIST, termMac);
+        if (lastTime == null) {
+            log.warn("设备未设置删除时间缓存,设置为当天0点:{}", termNo);
+            LocalDate localDate = LocalDate.now();
+            // localDate 转Date
+            Date now = Date.from(localDate.atStartOfDay(ZoneId.systemDefault()).toInstant());
+            RedisUtils.setCacheMapValue(CacheNames.LAST_TIME_DELETE_LIST, termMac, now.getTime());
+            return;
+        }
+        Date last = new Date(lastTime);
+        Date now = new Date();
+        List<RemoteUserAccountVo> expireAccount = remoteUserAccountService.getExpireAccount(last, now);
+        if (expireAccount.isEmpty()) {
+            log.info("未查询到过期账户,设备:{}", termNo);
+            return;
+        }
+
+        DeviceDto device = getDeviceDto(p);
+
+        // 下发数据
+        int consecutiveTimeouts = 0; // 连续超时计数器
+
+        for (RemoteUserAccountVo account : expireAccount) {
+            // 调用第三方接口前重置超时标志
+            boolean isTimeout = false;
+            try {
+                // 删除账户
+                EmpInfoDto empInfo = getEmpInfoDto(account, Boolean.TRUE, Boolean.TRUE,
+                    Boolean.TRUE, Boolean.TRUE, Boolean.TRUE, Boolean.FALSE);
+                R<Void> result = createOperatorEmpInfo(device, empInfo);
+                log.info("删除账户,rs: {}",result.getMsg());
+            } catch (Exception e) {
+                String message = e.getMessage();
+                log.error("删除账户异常:{},异常信息:{}", account.getRealName(), message, e);
+                // 判断是否为超时异常
+                String lowerCase = message.toLowerCase().replaceAll(" ", "");
+                // Connection timed out: connect
+                if (lowerCase.contains("timeout") || lowerCase.contains("timedout") || lowerCase.contains("connecttimedout")) {
+                    isTimeout = true;
+                    consecutiveTimeouts++;
+                } else {
+                    // 其他异常重置计数器
+                    consecutiveTimeouts = 0;
+                }
+            }
+
+            // 如果不是超时异常,重置计数器
+            if (!isTimeout) {
+                consecutiveTimeouts = 0;
+            }
+
+            // 如果连续三次超时,抛出异常或处理
+            if (consecutiveTimeouts >= 3) {
+                log.warn("连续3次调用超时,停止处理,设备网络断开,避免一直超时等待而暂用系统资源,设备:{}", termNo);
+                break;
+            }
+        }
+
+        if(consecutiveTimeouts < 3){
+            RedisUtils.setCacheMapValue(CacheNames.LAST_TIME_DELETE_LIST, termMac, now.getTime());
+        }
+    }
+
     /**
      * 获取在线设备列表
      *

+ 23 - 1
ruoyi-server/ruoyi-server-hik/src/main/java/org/dromara/server/hik/task/ScheduledTasks.java

@@ -4,6 +4,7 @@ import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.dromara.server.hik.service.ISendDeviceService;
 import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
 import org.springframework.stereotype.Component;
 
 import java.util.concurrent.ScheduledExecutorService;
@@ -24,7 +25,7 @@ import java.util.concurrent.ScheduledExecutorService;
 @RequiredArgsConstructor
 public class ScheduledTasks {
     private final ISendDeviceService sendDeviceService;
-    private final ScheduledExecutorService scheduledExecutorService;
+//    private final ScheduledExecutorService scheduledExecutorService;
 
     /**
      * 定时任务方法,用于每天凌晨1点30分将员工信息上传至设备。
@@ -63,4 +64,25 @@ public class ScheduledTasks {
         log.info("向所有海康消费机同步时间定时任务开始执行");
         sendDeviceService.syncTimeToAll();
     }
+
+    // 初始化缓存:手动初始化目前所有已经上线的海康设备的 上次删除时间 为 2025年8月25日
+
+    // 定时任务清理 下发时间 <= lifespan 的账户 (2025年8月25日 海康设备 上线)
+    // 定时任务在海康设备清理已过期的账户
+    // 过期账户:上一次的删除时间 <= lifespan <= now
+    // 上一次的删除时间 存到缓存中,key为设备的mac地址,value为上一次删除的时间(这次处理的当前时间)
+    // 上一次的删除时间 为 空时(第一次肯定是为空)不做任何处理,直接设置缓存,上一次的删除时间为当天的00:00:00
+    // 上一次的删除时间 不为 空时 正常执行逻辑(只处理在线的设备)
+    // 7点、8点,11点、12点,17点、18点,23点执行 (这个时间段机器都在线)
+    @Scheduled(cron = "0 0 7,8,11,12,17,18,23 * * *")
+    public void clearExpiredAccount() {
+        log.info("向所有海康消费机清理过期账户定时任务开始执行");
+        sendDeviceService.deleteExpireAccountSendAllDevice();
+    }
+
+
+    /*public static void main(String[] args) {
+        System.out.println(new BCryptPasswordEncoder().encode("datu@XR#2025!!"));
+    }*/
+
 }