Sfoglia il codice sorgente

1.新增下发 域名URL的http监听设置
2.定时任务 同步服务器时间到消费机上,每隔一分钟同步一次时间

xiari 10 mesi fa
parent
commit
648fbd8628

+ 2 - 7
ruoyi-server/ruoyi-server-hik/src/main/java/org/dromara/server/hik/constant/HikApiConstants.java

@@ -29,12 +29,7 @@ public interface HikApiConstants {
 
 
     /**
-     * PUT 确认交易记录; 应答 ConsumptionEvent
+     * PUT 同步设备时间
      */
-   String Consumption_Event_confirm = "/ISAPI/Consume/consumptionEventConfirm?format=json";
-
-    /**
-     * PUT 确认交易记录; 应答TransactionRecordEvent
-     */
-    String TRANSACTION_RECORD_Event_confirm = "/ISAPI/Consume/transactionRecordEventConfirm?format=json";
+   String SYNC_TIME_URL = "/ISAPI/System/time";
 }

+ 3 - 5
ruoyi-server/ruoyi-server-hik/src/main/java/org/dromara/server/hik/controller/TestController.java

@@ -52,7 +52,7 @@ import java.util.Map;
 @Validated
 @RequiredArgsConstructor
 @RestController
-@RequestMapping("/hik/test")
+@RequestMapping("/hik/api")
 @SaIgnore
 @Slf4j
 public class TestController {
@@ -296,9 +296,7 @@ public class TestController {
 
                             FileUtils.copyInputStreamToFile(inputStream, image_file);*/
                         }
-                        default -> {
-                            log.warn("out contentType : multipart/form-data; unknown contentType " + contentType + " 跳过");
-                        }
+                        default -> log.warn("out contentType : multipart/form-data; unknown contentType {} 跳过", contentType);
                     }
 
                     /*MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
@@ -324,7 +322,7 @@ public class TestController {
                     String jsonData = handleJsonRequest(request);
                     jsonListString.add(jsonData);
             }else{
-                log.warn("unknown out contentType " + s);
+                log.warn("unknown out contentType {}", s);
                 return null;
             }
             return eventHandleRouter.route(jsonListString,fileByteList);

+ 2 - 2
ruoyi-server/ruoyi-server-hik/src/main/java/org/dromara/server/hik/controller/XfFailedRecordController.java

@@ -31,8 +31,8 @@ import org.dromara.common.mybatis.core.page.TableDataInfo;
  */
 @Validated
 @RequiredArgsConstructor
-@RestController
-@RequestMapping("/FailedRecord/xfFailedRecord")
+//@RestController
+//@RequestMapping("/FailedRecord/xfFailedRecord")
 public class XfFailedRecordController extends BaseController {
 
     private final IXfFailedRecordService xfFailedRecordService;

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

@@ -35,6 +35,11 @@ public interface ISendDeviceService {
      */
     R<Void> setHttpHostAll();
 
+    /*
+    * 同步时间到所有 设备
+     */
+    R<Void> syncTimeToAll();
+
     /**
      * 根据设备编号设置监听服务地址。
      *

+ 141 - 8
ruoyi-server/ruoyi-server-hik/src/main/java/org/dromara/server/hik/service/impl/SendDeviceServiceImpl.java

@@ -36,11 +36,14 @@ import org.dromara.server.hik.service.IXfTermService;
 import org.dromara.server.hik.utils.DigestHttpUtil;
 import org.dromara.server.hik.utils.JsonConfig;
 import org.jetbrains.annotations.NotNull;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
 import org.springframework.stereotype.Service;
 import org.springframework.validation.annotation.Validated;
 
 import java.text.MessageFormat;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
 import java.util.*;
 
 /**
@@ -68,6 +71,15 @@ public class SendDeviceServiceImpl implements ISendDeviceService {
     @DubboReference
     private final RemoteUserAccountService remoteUserAccountService;
 
+    /**
+     * 映射的出口IP
+     */
+    @Value("${common.ip:}")
+    private String commonIP;
+
+    @Value("${device.listen.url}")
+    private String listenUrl;
+
     // region 私有方法
 
     /**
@@ -160,27 +172,32 @@ public class SendDeviceServiceImpl implements ISendDeviceService {
      * @return 转换后的 DeviceDto 对象
      */
     @NotNull
-    private static DeviceDto getDeviceDto(@NotNull RemoteXfTermVo termVo) {
+    private DeviceDto getDeviceDto(@NotNull RemoteXfTermVo termVo) {
         return getDeviceDto(termVo.getTermNo(), termVo.getAdminName(), termVo.getAdminPwd(), termVo.getTermIp(),
             termVo.getCommPort(), termVo.getServerIp(), termVo.getServerPort());
     }
 
     @NotNull
-    private static DeviceDto getDeviceDto(@NotNull XfTermVo termVo) {
+    private  DeviceDto getDeviceDto(@NotNull XfTermVo termVo) {
         return getDeviceDto(termVo.getTermNo(), termVo.getAdminName(), termVo.getAdminPwd(), termVo.getTermIp(),
             termVo.getCommPort(), termVo.getServerIp(), termVo.getServerPort());
     }
 
     @NotNull
-    private static DeviceDto getDeviceDto(Long termNo, String adminName, String adminPwd, String termIp, Long commPort, String serverIp, Long serverPort) {
+    private DeviceDto getDeviceDto(Long termNo, String adminName, String adminPwd, String termIp, Long commPort, String serverIp, Long serverPort) {
         DeviceDto dto = new DeviceDto();
         dto.setTermNo(termNo.intValue());
         dto.setAdminName(adminName);
         dto.setAdminPwd(adminPwd);
-        dto.setDeviceIp(termIp);
-        dto.setDevicePort(commPort.intValue());
+        // 如果配置了映射IP,则使用映射IP
+        boolean hasCommonIp = StringUtils.isNotBlank(commonIP);
+        dto.setDeviceIp(hasCommonIp ? commonIP :termIp);
+        // 海康设备的端口 都是80
+        dto.setDevicePort(hasCommonIp ? commPort.intValue() : 80);
         dto.setServerIp(serverIp);
         dto.setServerPort(serverPort.intValue());
+        // 监听接口地址
+        dto.setServerUrl(listenUrl);
 
         return dto;
     }
@@ -228,6 +245,65 @@ public class SendDeviceServiceImpl implements ISendDeviceService {
         return StringUtils.format(xmlTemplate,dto.getServerUrl(), dto.getServerIp(), dto.getServerPort());
     }
 
+    /**
+     * 动态构建下发监听服务地址的请求报文  监听地址为域名
+     *
+     * @param dto 设备信息
+     * @return 下发的请求报文
+     */
+    private String createDomainXml(@NotNull DeviceDto dto) {
+        String xmlTemplate = """
+            <?xml version="1.0" encoding="UTF-8"?>
+            <HttpHostNotification version="2.0" xmlns="http://www.isapi.org/ver20/XMLSchema">
+                <id>1</id>
+                <url>{}</url>
+                <protocolType>HTTPS</protocolType>
+                <parameterFormatType>XML</parameterFormatType>
+                <addressingFormatType>hostname</addressingFormatType>
+                <hostName>{}</hostName>
+                <portNo>443</portNo>
+                <httpAuthenticationMethod>none</httpAuthenticationMethod>
+                <SubscribeEvent>
+                    <heartbeat>30</heartbeat>
+                    <eventMode>list</eventMode>
+                        <EventList>
+                            <Event>
+                                <type>ConsumptionAndTransactionRecordEvent</type>
+                            </Event>
+                        </EventList>
+                </SubscribeEvent>
+            </HttpHostNotification>
+            """;
+
+        return StringUtils.format(xmlTemplate,dto.getServerUrl(), dto.getServerIp());
+    }
+
+    /**
+     * 动态构建下发校时时间
+     *
+     * @return 下发的请求报文
+     */
+    public static String createTimeXml() {
+        String xmlTemplate = """
+            <?xml version="1.0" encoding="UTF-8" ?>
+            <Time xmlns="http://www.isapi.org/ver20/XMLSchema" version="2.0">
+            	<timeMode>manual</timeMode>
+            	<localTime>{}</localTime>
+            	<timeZone>CST-8:00:00</timeZone>
+            </Time>
+            """;
+        // 获取当前时间(带时区)
+        ZonedDateTime now = ZonedDateTime.now();
+
+        // 定义格式
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss");
+
+        // 格式化时间
+        String nowTime = now.format(formatter);
+        return StringUtils.format(xmlTemplate,nowTime);
+    }
+
+
     /**
      * 处理设置监听服务地址返回数据
      * <p>
@@ -437,7 +513,19 @@ public class SendDeviceServiceImpl implements ISendDeviceService {
         if (ObjectUtil.isEmpty(dto.getServerPort())) {
             return R.fail("监听端口不能为空");
         }
+
+        if(ObjectUtil.isEmpty(dto.getServerUrl())){
+            dto.setServerUrl(listenUrl);
+        }
         String setData = this.createHostXml(dto);
+        // 判断地址是域名还是IP
+        List<String> commonSuffix = Arrays.asList("com", "cn", "org", "net", "edu", "gov", "io", "biz", "info", "xyz", "top");
+        for (String item : commonSuffix) {
+            if (dto.getServerIp().contains(item)) {
+                setData = this.createDomainXml(dto);
+                break;
+            }
+        }
 
         JSONObject sendResult = digestHttpUtil.sendPost(dto, setData, HikApiConstants.SET_HTTP_HOSTS, ContentTypeEnum.XML.getCode());
         R<Void> doResult = this.doSetHostReturnData(sendResult);
@@ -470,7 +558,53 @@ public class SendDeviceServiceImpl implements ISendDeviceService {
         });
         return R.ok("处理完成,详情见处理日志");
     }
-    // endregion
+
+    @Override
+    public R<Void> syncTimeToAll() {
+        List<RemoteXfTermVo> termList = remotePtXfTermService.queryListByBrand("hk");
+        if (CollectionUtil.isEmpty(termList)) {
+            throw new ServiceException("没有要处理的设备");
+        }
+        termList.forEach(p -> {
+            threadPoolTaskExecutor.execute(() -> {
+                DeviceDto dto = getDeviceDto(p);
+                R<Void> result = syncTimeToDevice(dto);
+                log.info(result.getMsg());
+            });
+        });
+        return R.ok("处理完成");
+    }
+
+    /**
+     * 同步时间到设备
+     * @param device 设备信息
+     * @return  R
+     */
+    private R<Void> syncTimeToDevice(DeviceDto device) {
+        String setData = createTimeXml();
+        if (ObjectUtil.isEmpty(device.getDeviceIp())) {
+            return R.fail("设备IP不能为空");
+        }
+        Integer termNo = device.getTermNo();
+        String requestUrl = StringUtils.format("http://{}:{}{}", device.getDeviceIp(), device.getDevicePort(), HikApiConstants.SYNC_TIME_URL);
+        JSONObject sendResult = digestHttpUtil.sendPut(setData, requestUrl, ContentTypeEnum.XML.getCode(), device.getAdminName(), device.getAdminPwd());
+        if(sendResult == null){
+//            log.error("同步时间失败,设备无返回,设备号:{}", termNo);
+            return R.fail("同步时间失败,设备无返回,设备号:"+ termNo);
+        }
+        if (ObjectUtil.isEmpty(sendResult.getObj("ResponseStatus"))) {
+//            log.error("同步时间失败,设备返回格式异常,设备号:{}", termNo);
+            return R.fail("同步时间失败,设备返回格式异常,设备号:"+ termNo);
+        }
+        JSONObject responseStatus = sendResult.getJSONObject("ResponseStatus");
+        Integer statusCode = responseStatus.getInt("statusCode");
+        if (ObjectUtil.notEqual(statusCode, ErrCodeConstants.OK) && ObjectUtil.notEqual(statusCode, ErrCodeConstants.YES)) {
+            String message = StatusCodeEnum.getMessage(statusCode.toString());
+//            log.error("同步时间失败,设备返回错误码:{}{},设备号:{}", statusCode,message, termNo);
+            return R.fail(message);
+        }
+        return R.ok("同步时间成功,设备号:"+ termNo);
+    }
 
     // region 从消费机删除人员相关
     @Override
@@ -713,7 +847,7 @@ public class SendDeviceServiceImpl implements ISendDeviceService {
         if (CollectionUtil.isEmpty(vos)) {
             return R.warn("没有要处理人员数据");
         }
-        log.info("day处理人员数据条数" + vos.size());
+        log.info("day处理人员数据条数{}", vos.size());
         XfTermVo termVo = xfTermService.getByMac(macAddress);
         if (ObjectUtil.isEmpty(termVo)) {
             return R.warn(MessageFormat.format("设备未找到,mac:{0}", macAddress));
@@ -775,7 +909,6 @@ public class SendDeviceServiceImpl implements ISendDeviceService {
         EmpInfoDto empDto = getEmpInfoDto(accountVo, false, false, false, deleteAllCard, deleteCard, false);
 
         sendEmpToAllDevice(empDto);
-
         return R.ok();
     }
 

+ 15 - 10
ruoyi-server/ruoyi-server-hik/src/main/java/org/dromara/server/hik/task/ScheduledTasks.java

@@ -36,11 +36,9 @@ public class ScheduledTasks {
     // 每天11:10和17:10、7:00执行
 //    @Scheduled(cron = "0 20 11,17 * * *")
     public void upLoadEmpToDevice() {
-        scheduledExecutorService.execute(() -> {
-            log.info("向所有海康消费机下发人员定时任务开始执行");
-            // 下发人脸会是设备卡机,所以这里设置为false
-            sendDeviceService.upLoadEmpToDevice(false);
-        });
+        log.info("向所有海康消费机下发人员定时任务开始执行");
+        // 下发人脸会是设备卡机,所以这里设置为false
+        sendDeviceService.upLoadEmpToDevice(false);
     }
 
     /**
@@ -52,10 +50,17 @@ public class ScheduledTasks {
      */
 //    @Scheduled(cron = "0 0 7 * * *")
     public void upLoadEmpToDevice2() {
-        scheduledExecutorService.execute(() -> {
-            log.info("向所有海康消费机下发人员定时任务开始执行");
-            // 下发人脸会是设备卡机,所以这里设置为false
-            sendDeviceService.upLoadEmpToDevice(false);
-        });
+        log.info("向所有海康消费机下发人员定时任务开始执行");
+        // 下发人脸会是设备卡机,所以这里设置为false
+        sendDeviceService.upLoadEmpToDevice(false);
+    }
+
+    /*
+    每隔一分钟执行一次
+     */
+    @Scheduled(cron = "0 0/1 * * * ?")
+    public void syncTimeToAllHKDevice(){
+        log.info("向所有海康消费机同步时间定时任务开始执行");
+        sendDeviceService.syncTimeToAll();
     }
 }

+ 16 - 8
ruoyi-server/ruoyi-server-hik/src/main/java/org/dromara/server/hik/utils/DigestHttpUtil.java

@@ -45,6 +45,14 @@ public class DigestHttpUtil {
         return HttpClients.custom().setDefaultCredentialsProvider(provider).build();
     }
 
+    private static CloseableHttpClient createDigestHttpClient(String adminName, String adminPwd) {
+        CredentialsProvider provider = new BasicCredentialsProvider();
+        provider.setCredentials(new AuthScope(AuthScope.ANY_HOST, AuthScope.ANY_PORT),
+            new UsernamePasswordCredentials(adminName, adminPwd));
+
+        return HttpClients.custom().setDefaultCredentialsProvider(provider).build();
+    }
+
     /**
      * 以POST方式 发送请求
      *
@@ -88,24 +96,24 @@ public class DigestHttpUtil {
     }
 
     /**
-     * 以POST方式 发送请求
+     * 以PuT方式 发送请求
      *
-     * @param device      设备信息
      * @param data        发送的数据
-     * @param apiUrl      接口地址
+     * @param requestUrl      接口地址
      * @param contentType 请求内容格式
+     * @param adminName 设备管理员名称
+     * @param adminPwd 设备管理员密码
      * @return 请求结果
      * @see org.dromara.server.hik.enums.ContentTypeEnum
      */
-    public JSONObject sendPut(DeviceDto device, String data, String apiUrl, String contentType) {
-        String requestUrl = StringUtils.format("http://{}:{}{}", device.getDeviceIp(), device.getDevicePort(), apiUrl);
+    public JSONObject sendPut(String data, String requestUrl, String contentType,String adminName, String adminPwd) {
         HttpPut httpPut = new HttpPut(requestUrl);
         httpPut.setHeader("Content-Type", contentType);
         httpPut.setHeader("Accept", "*/*");
         StringEntity entity = new StringEntity(data, "UTF-8");
         httpPut.setEntity(entity);
 
-        try (CloseableHttpClient httpClient = createDigestHttpClient(device)) {
+        try (CloseableHttpClient httpClient = createDigestHttpClient(adminName,adminPwd)) {
             JSONObject jsonObject;
             String returnContentType = contentType;
             HttpResponse response = httpClient.execute(httpPut);
@@ -119,8 +127,8 @@ public class DigestHttpUtil {
             } else {
                 jsonObject = JSONUtil.parseObj(str);
             }
-            // TODO 2025-05-21 luoyibo 开发过程中打印,正式发布时不再打印
-             log.info("str={}", jsonObject);
+            //  开发过程中打印,正式发布时不再打印
+//             log.info("sendPudt={}", jsonObject);
 
             return jsonObject;
         } catch (Exception e) {