浏览代码

feat(hik-server): 海康消费机向消费机请求接口

1.设备通过设备编号或全部用远程接口从现有的基础平台查询
2.人员(卡片、人脸)通过人员Id或全部用远程接口从现有的基础平台查询
3.完善了设置设备监听服务配置、上传人员信息到设备、从设备删除人员的相关接口
luo.yibo@datuai.com 1 年之前
父节点
当前提交
1836683419
共有 17 个文件被更改,包括 1022 次插入102 次删除
  1. 5 1
      ruoyi-api/ruoyi-api-bom/pom.xml
  2. 1 1
      ruoyi-auth/src/main/resources/application.yml
  3. 58 0
      ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/enums/DeviceBrandEnum.java
  4. 0 1
      ruoyi-common/ruoyi-common-json/src/main/java/org/dromara/common/json/utils/JsonUtils.java
  5. 4 7
      ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/payment/service/impl/PtUserAccountServiceImpl.java
  6. 4 0
      ruoyi-server/ruoyi-server-hik/pom.xml
  7. 16 0
      ruoyi-server/ruoyi-server-hik/src/main/java/org/dromara/server/hik/constant/HikDefaultConstants.java
  8. 160 4
      ruoyi-server/ruoyi-server-hik/src/main/java/org/dromara/server/hik/controller/TestController.java
  9. 8 2
      ruoyi-server/ruoyi-server-hik/src/main/java/org/dromara/server/hik/domain/dto/DeviceDto.java
  10. 54 0
      ruoyi-server/ruoyi-server-hik/src/main/java/org/dromara/server/hik/domain/dto/UploadEmpDto.java
  11. 2 0
      ruoyi-server/ruoyi-server-hik/src/main/java/org/dromara/server/hik/domain/dto/UserInfoDto.java
  12. 1 1
      ruoyi-server/ruoyi-server-hik/src/main/java/org/dromara/server/hik/domain/dto/base/CardDto.java
  13. 4 6
      ruoyi-server/ruoyi-server-hik/src/main/java/org/dromara/server/hik/domain/dto/base/EmpInfoDto.java
  14. 146 0
      ruoyi-server/ruoyi-server-hik/src/main/java/org/dromara/server/hik/service/ISendDeviceService.java
  15. 534 78
      ruoyi-server/ruoyi-server-hik/src/main/java/org/dromara/server/hik/service/impl/SendDeviceServiceImpl.java
  16. 1 1
      ruoyi-server/ruoyi-server-hik/src/main/java/org/dromara/server/hik/utils/DigestHttpUtil.java
  17. 24 0
      ruoyi-server/ruoyi-server-hik/src/main/java/org/dromara/server/hik/utils/JsonConfig.java

+ 5 - 1
ruoyi-api/ruoyi-api-bom/pom.xml

@@ -40,7 +40,11 @@
                 <artifactId>ruoyi-api-workflow</artifactId>
                 <version>${revision}</version>
             </dependency>
-
+            <dependency>
+                <groupId>org.dromara</groupId>
+                <artifactId>ruoyi-api-backstage</artifactId>
+                <version>${revision}</version>
+            </dependency>
         </dependencies>
     </dependencyManagement>
 </project>

+ 1 - 1
ruoyi-auth/src/main/resources/application.yml

@@ -1,6 +1,6 @@
 # Tomcat
 server:
-  port: 9210
+  port: 9212
 
 # Spring
 spring:

+ 58 - 0
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/enums/DeviceBrandEnum.java

@@ -0,0 +1,58 @@
+package org.dromara.common.core.enums;
+
+import cn.hutool.core.util.StrUtil;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.dromara.common.core.utils.StringUtils;
+
+import java.util.Arrays;
+
+/**
+ * 设备品牌枚举
+ * <p>
+ * 系统所接入设备品牌的类型枚举
+ *
+ * @author luoyibo
+ * @version 2.2.0
+ * @date 2025-05-21
+ * @since JDK17
+ */
+@Getter
+@AllArgsConstructor
+public enum DeviceBrandEnum {
+    /**
+     * 大图
+     */
+    DT("dt", "大图"),
+
+    /**
+     * 海康
+     */
+    HK("hk", "海康");
+
+    /**
+     * 编码
+     */
+    private final String code;
+
+    /**
+     * 描述
+     */
+    private final String message;
+
+    /**
+     * 获根据编码获取描述信息
+     *
+     * @param code 编码
+     */
+    public static String getMessage(String code) {
+        if (StringUtils.isBlank(code)) {
+            return StrUtil.EMPTY;
+        }
+        return Arrays.stream(DeviceBrandEnum.values())
+                   .filter(item -> item.getCode().equals(code))
+                   .findFirst()
+                   .map(DeviceBrandEnum::getMessage)
+                   .orElse(StrUtil.EMPTY);
+    }
+}

+ 0 - 1
ruoyi-common/ruoyi-common-json/src/main/java/org/dromara/common/json/utils/JsonUtils.java

@@ -166,5 +166,4 @@ public class JsonUtils {
             throw new RuntimeException(e);
         }
     }
-
 }

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

@@ -6,8 +6,6 @@ import cn.hutool.core.io.FileUtil;
 import cn.hutool.core.lang.UUID;
 import cn.hutool.core.util.ObjUtil;
 import cn.hutool.core.util.ObjectUtil;
-import cn.hutool.http.HttpRequest;
-import cn.hutool.http.HttpResponse;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
@@ -54,11 +52,8 @@ import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Service;
 import org.springframework.web.multipart.MultipartFile;
 
-import java.io.*;
+import java.io.IOException;
 import java.math.BigDecimal;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.nio.charset.Charset;
 import java.text.MessageFormat;
 import java.util.*;
 import java.util.stream.Collectors;
@@ -84,7 +79,7 @@ public class PtUserAccountServiceImpl implements IPtUserAccountService {
     @DubboReference
     private final RemoteDeptService remoteDeptService;
 
-    @Value("${photo-prefix:}")
+    @Value("${photo-prefix}")
     private String photoPrefix;
 
     /**
@@ -667,6 +662,8 @@ public class PtUserAccountServiceImpl implements IPtUserAccountService {
             }
             userAccount.setCard(ptCardVo);
             userAccount.setFacePicUrl(photoPrefix+userAccount.getPhoto());
+
+            res.add(userAccount);
         }
         return res;
     }

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

@@ -97,6 +97,10 @@
         <dependency>
             <groupId>org.dromara</groupId>
             <artifactId>ruoyi-api-system</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-api-backstage</artifactId>
         </dependency>
          <dependency>
             <groupId>cn.com.kingbase</groupId>

+ 16 - 0
ruoyi-server/ruoyi-server-hik/src/main/java/org/dromara/server/hik/constant/HikDefaultConstants.java

@@ -0,0 +1,16 @@
+package org.dromara.server.hik.constant;
+
+/**
+ * 海康默认配置
+ * <p>
+ * 海康默认配置
+ *
+ * @author luoyibo
+ * @version 2.2.0
+ * @date 2025-05-23
+ * @since JDK17
+ */
+public interface HikDefaultConstants {
+
+    String EMP_END_TIME = "2037-12-31 23:59:59";
+}

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

@@ -5,12 +5,10 @@ import lombok.RequiredArgsConstructor;
 import org.dromara.common.core.domain.R;
 import org.dromara.server.hik.domain.dto.DeviceDto;
 import org.dromara.server.hik.domain.dto.QueryDto;
+import org.dromara.server.hik.domain.dto.UploadEmpDto;
 import org.dromara.server.hik.service.ISendDeviceService;
 import org.springframework.validation.annotation.Validated;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.*;
 
 /**
  * 海康消费机
@@ -29,6 +27,8 @@ import org.springframework.web.bind.annotation.RestController;
 @SaIgnore
 public class TestController {
     private final ISendDeviceService sendDeviceService;
+
+    //region 设备监听相关
     /**
      * 设置指定设备监听服务地址
      * @param dto 设备信息
@@ -48,6 +48,18 @@ public class TestController {
         return sendDeviceService.setHttpHostAll();
     }
 
+    /**
+     * 设置指定终端设备的监听服务地址。
+     *
+     * @param termNo 终端编号,用于标识目标设备
+     * @return 响应信息主体,包含操作结果的状态码、消息内容以及可能的附加数据
+     */
+    @PostMapping("/set/hosts/{termNo}")
+    public R<Void> setHosts(@PathVariable("termNo") Long termNo) {
+        return sendDeviceService.setHttpHostByTermNo(termNo);
+    }
+    //endregion
+
     /**
      * 查询设备上所有人员信息
      * @param dto 设备信息
@@ -57,4 +69,148 @@ public class TestController {
     public R<Void> queryEmpAll(@RequestBody QueryDto dto) {
         return sendDeviceService.queryBatchEmpFormDevice(dto);
     }
+
+    //region 人员删除相关
+    /**
+     * 删除指定终端设备上的指定员工信息。
+     * <p>
+     * 根据终端编号和用户编号,从对应的设备中删除指定员工的信息。
+     *
+     * @param termNo 终端编号,用于标识目标设备
+     * @param userId 用户编号,用于标识需要删除的员工信息
+     * @return 响应信息主体,包含操作结果的状态码、消息内容以及可能的附加数据
+     */
+    @GetMapping("/emp/del/{termNo}/{userId}")
+    public R<Void> deleteEmpFromDevice(@PathVariable("termNo") Long termNo,@PathVariable("userId") Long userId) {
+        return sendDeviceService.deleteEmpFromDevice(termNo,userId);
+    }
+
+    /**
+     * 删除指定终端设备上的所有员工信息。
+     * <p>
+     * 根据终端编号,从对应的设备中删除
+        return sendDeviceService.deleteEmpFromDevice(termNo);
+    }
+
+    /**
+     * 删除指定终端设备上的员工信息。
+     * <p>
+     * 根据终端编号,从对应的设备中删除员工信息。该方法通过HTTP POST请求触发,
+     * 并将终端编号作为路径变量传递。
+     *
+     * @param termNo 终端编号,用于标识目标设备
+     * @return 响应信息主体,包含操作结果的状态码、消息内容以及可能的附加数据
+     */
+    @GetMapping("/emp/del/{termNo}")
+    public R<Void> deleteEmpByFromDevice(@PathVariable("termNo") Long termNo) {
+        return sendDeviceService.deleteEmpFromDevice(termNo);
+    }
+
+    /**
+     * 从所有终端设备上删除指定员工信息
+     * 根据终端编号和用户编号,从所有的设备中删除指定员工的信息。
+     *
+      * @param userId 用户编号,用于标识需要删除的员工信息
+     *
+     * @return 响应信息主体,包含操作结果的状态码、消息内容以及可能的附加数据
+     */
+    @GetMapping("/emp/del/all/{userId}")
+    public R<Void> deleteAllEmpByFromDevice(@PathVariable("userId") Long userId) {
+        return sendDeviceService.deleteOneEmpFromDevice(userId);
+    }
+
+    /**
+     * 删除所有设备上的所有员工
+     *
+     * @return 响应信息主体,包含操作结果的状态码、消息内容以及可能的附加数据
+     */
+    @GetMapping("/emp/del/all")
+    public R<Void> deleteAllEmpByFromDevice() {
+        return sendDeviceService.deleteEmpFromDevice();
+    }
+    //endregion
+
+    //region 人员信息上传有关
+    /**
+     * 上传员工信息到指定设备。
+     * <p>
+     * 该方法接收包含员工信息和设备信息的数据传输对象,将员工信息上传至指定设备。
+     *
+     * @param uploadEmpDto 包含员工信息和设备信息的数据传输对象
+     * @return 响应信息主体,包含操作结果的状态码、消息内容以及可能的附加数据
+     */
+    @PostMapping("/emp/upload")
+    public R<Void> uploadEmpToDevice(@RequestBody UploadEmpDto uploadEmpDto) {
+        return sendDeviceService.upLoadEmpToDevice(uploadEmpDto);
+    }
+
+    /**
+     * 上传指定员工信息到指定设备。
+     * <p>
+     * 根据终端编号和用户编号,将对应的员工信息上传至指定设备。
+     *
+     * @param termNo 终端编号,用于标识目标设备
+     * @param userId 用户编号,用于标识需要上传的员工信息
+     * @return 响应信息主体,包含操作结果的状态码、消息内容以及可能的附加数据
+     */
+    @GetMapping("/emp/upload/{termNo}/{userId}")
+    public R<Void> upLoadEmpToDevice(@PathVariable("termNo") Long termNo,@PathVariable("userId") Long userId){
+        return sendDeviceService.upLoadEmpToDevice(termNo, userId);
+    }
+
+    /**
+     * 上传所有员工信息到指定设备。
+     * <p>
+     * 根据终端编号,将员工信息上传至指定设备。该方法通过HTTP GET请求触发,
+     * 并将终端编号作为路径变量传递。
+     *
+     * @param termNo 终端编号,用于标识目标设备
+     * @return 响应信息主体,包含操作结果的状态码、消息内容以及可能的附加数据
+     */
+    @GetMapping("/emp/upload/all/{termNo}")
+    public R<Void> upLoadAllEmpToDevice(@PathVariable("termNo") Long termNo){
+        return sendDeviceService.upLoadEmpToDevice(termNo);
+    }
+
+    /**
+     * 上传指定员工信息到所有设备。
+     * <p>
+     * 根据用户编号,将对应的员工信息上传至设备。该方法通过HTTP GET请求触发,
+     * 并将用户编号作为路径变量传递。
+     *
+     * @param userId 用户编号,用于标识需要上传的员工信息
+     * @return 响应信息主体,包含操作结果的状态码、消息内容以及可能的附加数据
+     */
+    @GetMapping("/emp/upload/{userId}")
+    public R<Void> upLoadEmpToDevice(@PathVariable("userId") Long userId){
+        return sendDeviceService.upLoadEmpToDevice(userId);
+    }
+
+    /**
+     * 上传所有员工信息到所有设备。
+     * <p>
+     * 该方法通过HTTP GET请求触发,将系统中的所有员工信息上传至所有关联的设备。
+     * 具体上传逻辑由服务层实现,返回操作结果的状态信息。
+     *
+     * @return 响应信息主体,包含操作结果的状态码、消息内容以及可能的附加数据
+     */
+    @GetMapping("/emp/upload/all")
+    public R<Void> upLoadAllEmpToAllDevice(){
+        return sendDeviceService.upLoadEmpToDevice();
+    }
+    //endregion
+    /**
+     * 删除指定设备上某个用户的所有卡片信息。
+     * <p>
+     * 根据用户编号和设备信息,删除该用户在指定设备上的所有关联卡片数据。
+     *
+     * @param userNo 用户编号,用于标识需要删除卡片信息的用户
+     * @param device 设备信息,包含设备的详细配置数据
+     * @return 响应信息主体,包含操作结果的状态码、消息内容以及可能的附加数据
+     */
+    @PostMapping("/card/deleteAll/{userNo}")
+    public R<Void> deleteAllCardByUserNo(@PathVariable("userNo") String userNo, @RequestBody DeviceDto device) {
+        return sendDeviceService.deleteAllCardByUserNo(device,userNo);
+    }
+
 }

+ 8 - 2
ruoyi-server/ruoyi-server-hik/src/main/java/org/dromara/server/hik/domain/dto/DeviceDto.java

@@ -1,5 +1,7 @@
 package org.dromara.server.hik.domain.dto;
 
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
 import lombok.Data;
 
 import java.io.Serial;
@@ -27,23 +29,27 @@ public class DeviceDto implements Serializable {
     private Integer termNo;
 
     /**
-     * 管理账号
+     * 管理账号
      */
+    @NotBlank(message = "管理账号不能为空")
     private String adminName;
 
     /**
-     * 管理密码
+     * 管理密码
      */
+    @NotBlank(message = "管理密码不能为空")
     private String adminPwd;
 
     /**
      * 通讯IP
      */
+    @NotBlank(message = "设备通讯IP不能为空")
     private String deviceIp;
 
     /**
      * 通讯端口
      */
+    @NotNull(message = "设备通讯端口不能为空")
     private Integer devicePort;
 
     /**

+ 54 - 0
ruoyi-server/ruoyi-server-hik/src/main/java/org/dromara/server/hik/domain/dto/UploadEmpDto.java

@@ -0,0 +1,54 @@
+package org.dromara.server.hik.domain.dto;
+
+import lombok.Data;
+import lombok.experimental.Accessors;
+import org.dromara.server.hik.domain.dto.base.CardDto;
+import org.dromara.server.hik.domain.dto.base.EmpInfoDto;
+import org.dromara.server.hik.domain.dto.base.FaceDto;
+import org.dromara.server.hik.domain.dto.base.ValidDto;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 下发人员信息数据传输对象
+ * <p>
+ * 向设备下发的人员信息数据传输对象定义。数据格式包括了下发的设备信息、待下发的人员信息
+ *
+ * @author luoyibo
+ * @version 2.2.0
+ * @date 2025-05-23
+ * @since JDK17
+ */
+@Data
+@Accessors(chain = true)
+public class UploadEmpDto implements Serializable {
+    @Serial
+    private static final long serialVersionUID = 8195137760207081790L;
+
+    /**
+     * 下发设备信息
+     */
+    private DeviceDto device;
+
+    /**
+     * 下发人员信息
+     */
+    private EmpInfoDto employee;
+
+    /**
+     * 下发人员有效期
+     */
+    private ValidDto valid;
+
+    /**
+     * 下发卡片信息
+     */
+    private CardDto card;
+
+    /**
+     * 下发人脸信息
+     */
+    private FaceDto face;
+
+}

+ 2 - 0
ruoyi-server/ruoyi-server-hik/src/main/java/org/dromara/server/hik/domain/dto/UserInfoDto.java

@@ -2,6 +2,7 @@ package org.dromara.server.hik.domain.dto;
 
 import lombok.Data;
 import lombok.experimental.Accessors;
+import org.dromara.server.hik.domain.dto.base.EmpInfoDto;
 
 import java.io.Serial;
 import java.io.Serializable;
@@ -23,6 +24,7 @@ public class UserInfoDto implements Serializable {
 
     @Serial
     private static final long serialVersionUID = -2638389177956051530L;
+
     /**
      * 下发到至设备的人员信息对象
      */

+ 1 - 1
ruoyi-server/ruoyi-server-hik/src/main/java/org/dromara/server/hik/domain/dto/base/CardDto.java

@@ -27,7 +27,7 @@ public class CardDto implements Serializable {
     /**
      * 物理卡号
      */
-    private Long cardNo;
+    private String cardNo;
 
     /**
      * 是否删除卡片 为true时删除设备上员工此物理卡号对应的卡片

+ 4 - 6
ruoyi-server/ruoyi-server-hik/src/main/java/org/dromara/server/hik/domain/dto/EmpInfoDto.java → ruoyi-server/ruoyi-server-hik/src/main/java/org/dromara/server/hik/domain/dto/base/EmpInfoDto.java

@@ -1,10 +1,7 @@
-package org.dromara.server.hik.domain.dto;
+package org.dromara.server.hik.domain.dto.base;
 
 import lombok.Data;
 import lombok.experimental.Accessors;
-import org.dromara.server.hik.domain.dto.base.CardListDto;
-import org.dromara.server.hik.domain.dto.base.FaceListDto;
-import org.dromara.server.hik.domain.dto.base.ValidDto;
 import org.dromara.server.hik.enums.AuthenticationEnum;
 import org.dromara.server.hik.enums.EmpTypeEnum;
 
@@ -30,7 +27,8 @@ public class EmpInfoDto implements Serializable {
     /**
      * 人员编号,对应一卡通系统中人员流水号
      */
-    private Long employeeNo;
+    private String employeeNo;
+
     /**
      * 人员姓名
      */
@@ -53,7 +51,7 @@ public class EmpInfoDto implements Serializable {
      *
      * @see AuthenticationEnum
      */
-    private String userVerifyMode;
+    private String userVerifyMode = AuthenticationEnum.FACE_OR_FP_OR_CARD_OR_PW.getCode();
 
     /**
      * 人员的人脸图片信息

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

@@ -3,6 +3,7 @@ package org.dromara.server.hik.service;
 import org.dromara.common.core.domain.R;
 import org.dromara.server.hik.domain.dto.DeviceDto;
 import org.dromara.server.hik.domain.dto.QueryDto;
+import org.dromara.server.hik.domain.dto.UploadEmpDto;
 
 
 /**
@@ -19,6 +20,7 @@ public interface ISendDeviceService {
 
     /**
      * 给指定设务设置监听服务地址
+     *
      * @param dto 设备信息
      * @return 设置结果
      */
@@ -26,10 +28,19 @@ public interface ISendDeviceService {
 
     /**
      * 给所有设备设置监听服务地址
+     *
      * @return 设置结果
      */
     R<Void> setHttpHostAll();
 
+    /**
+     * 根据设备编号设置监听服务地址。
+     *
+     * @param termNo 设备编号,用于标识需要设置监听服务地址的设备
+     * @return 响应信息主体,包含操作结果的状态码、消息内容以及可能的附加数据
+     */
+    R<Void> setHttpHostByTermNo(Long termNo);
+
     /**
      * 批量查询设备上人员信息。
      * <p>
@@ -40,4 +51,139 @@ public interface ISendDeviceService {
      */
     R<Void> queryBatchEmpFormDevice(QueryDto dto);
 
+    /**
+     * 删除指定设备上的员工信息。
+     * <p>
+     * 根据用户编号从指定设备中删除对应的员工信息。
+     *
+     * @param device 设备信息,包含设备的详细配置数据
+     * @param userNo 用户编号,用于标识需要删除的员工信息
+     * @return 响应信息主体,包含操作结果的状态码、消息内容以及可能的附加数据
+     */
+    R<Void> deleteEmpByUserNo(DeviceDto device, String userNo);
+
+    /**
+     * 删除指定设备上的指定员工信息。
+     *
+     * @param termNo 设备编号,用于标识需要删除员工信息的设备
+     * @param userId 用户ID,用于标识需要删除的员工信息
+     * @return 响应信息主体,包含操作结果的状态码、消息内容以及可能的附加数据
+     */
+    R<Void> deleteEmpFromDevice(Long termNo, Long userId);
+
+    /**
+     * 删除指定设备上的所有员工信息。
+     *
+     * @param termNo 设备编号,用于标识需要删除员工信息的设备
+     * @return 响应信息主体,包含操作结果的状态码、消息内容以及可能的附加数据
+     */
+    R<Void> deleteEmpFromDevice(Long termNo);
+
+    /**
+     * 删除所有设备上的所有员工信息。
+     * <p>
+     * 该方法用于从设备中删除员工信息,具体删除逻辑由实现类定义。
+     *
+     * @return 响应信息主体,包含操作结果的状态码、消息内容以及可能的附加数据
+     */
+    R<Void> deleteEmpFromDevice();
+
+    /**
+     * 删除所有设备上的指定员工信息。
+     *
+     * @param userId 用户ID,用于标识需要删除的员工信息
+     * @return 响应信息主体,包含操作结果的状态码、消息内容以及可能的附加数据
+     */
+    R<Void> deleteOneEmpFromDevice(Long userId);
+
+    /**
+     * 删除指定用户在设备上的所有卡片信息。
+     * <p>
+     * 根据用户编号从指定设备中删除该用户的所有卡片信息。
+     *
+     * @param device 设备信息,包含设备的详细配置数据
+     * @param userNo 用户编号,用于标识需要删除卡片信息的用户
+     * @return 响应信息主体,包含操作结果的状态码、消息内容以及可能的附加数据
+     */
+    R<Void> deleteAllCardByUserNo(DeviceDto device, String userNo);
+
+    /**
+     * 删除指定用户在设备上的指定卡片信息。
+     * <p>
+     * 根据用户编号和物理卡号从指定设备中删除该用户的卡片信息。
+     *
+     * @param device    设备信息,包含设备的详细配置数据
+     * @param userNo    用户编号,用于标识需要删除卡片信息的用户
+     * @param factoryId 物理卡号,用于标识需要删除卡片的物理卡号
+     * @return 响应信息主体,包含操作结果的状态码、消息内容以及可能的附加数据
+     */
+    R<Void> deleteCardByUserNo(DeviceDto device, String userNo, String factoryId);
+
+    /**
+     * 删除指定用户在设备上的所有人脸数据。
+     *
+     * @param device 设备信息
+     * @param userNo 用户编号
+     * @return 操作结果
+     */
+    R<Void> deleteAllFaceByUserNo(DeviceDto device, String userNo);
+
+    /**
+     * 删除指定用户在设备上的指定的人脸数据。
+     *
+     * @param device 设备信息
+     * @param userNo 用户编号
+     * @param faceId 人脸ID
+     * @return 操作结果
+     */
+    R<Void> deleteFaceByUserNo(DeviceDto device, String userNo, String faceId);
+
+    /**
+     * 上传员工信息到指定设备。
+     * <p>
+     * 该方法用于将员工信息传输到指定的设备,基于提供的设备信息和员工信息数据传输对象。
+     *
+     * @param uploadEmpDto@return 响应信息主体,表示上传操作的结果状态及可能的附加信息
+     */
+    R<Void> upLoadEmpToDevice(UploadEmpDto uploadEmpDto);
+
+    /**
+     * 上传所有员工信息到指定设备。
+     * <p>
+     * 该方法用于将员工信息传输到指定的设备,基于提供的设备编号。
+     *
+     * @param termNo 设备编号,用于标识需要上传员工信息的目标设备
+     * @return 响应信息主体,表示上传操作的结果状态及可能的附加信息
+     */
+    R<Void> upLoadEmpToDevice(Long termNo);
+
+    /**
+     * 上传指定员工信息到指定设备。
+     * <p>
+     * 该方法用于将特定员工的信息传输到指定的设备,基于提供的设备编号和用户编号。
+     *
+     * @param termNo 设备编号,用于标识需要上传员工信息的目标设备
+     * @param userId 用户编号,用于标识需要上传的员工信息
+     * @return 响应信息主体,表示上传操作的结果状态及可能的附加信息
+     */
+    R<Void> upLoadEmpToDevice(Long termNo, Long userId);
+
+    /**
+     * 上传员工信息到设备。
+     * <p>
+     * 该方法用于将所有员工信息传输到所有设备,具体设备和员工信息管理后台接口提供。
+     *
+     * @return 响应信息主体,表示上传操作的结果状态及可能的附加信息
+     */
+    R<Void> upLoadEmpToDevice();
+
+    /**
+     * 上传指定员工信息到所有设备。
+     * <p>
+     * 该方法用于将特定员工的信息传输到所有设备,基于提供的用户编号。
+     *
+     * @param userId 用户编号,用于标识需要上传的员工信息
+     * @return 响应信息主体,表示上传操作的结果状态及可能的附加信息
+     */
+    R<Void> upLoadEmpToAllDevice(Long userId);
 }

+ 534 - 78
ruoyi-server/ruoyi-server-hik/src/main/java/org/dromara/server/hik/service/impl/SendDeviceServiceImpl.java

@@ -1,31 +1,44 @@
 package org.dromara.server.hik.service.impl;
 
+import cn.hutool.core.collection.CollectionUtil;
+import cn.hutool.core.date.DateUtil;
 import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.json.JSONObject;
 import cn.hutool.json.JSONUtil;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+import org.apache.dubbo.config.annotation.DubboReference;
+import org.dromara.backstage.api.RemotePtXfTermService;
+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.DefaultConstants;
 import org.dromara.common.core.domain.R;
+import org.dromara.common.core.enums.DeviceBrandEnum;
+import org.dromara.common.core.exception.ServiceException;
 import org.dromara.common.core.utils.StringUtils;
-import org.dromara.common.json.utils.JsonUtils;
+import org.dromara.common.core.utils.ValidatorUtils;
 import org.dromara.server.hik.constant.ErrCodeConstants;
 import org.dromara.server.hik.constant.HikApiConstants;
+import org.dromara.server.hik.constant.HikDefaultConstants;
 import org.dromara.server.hik.domain.dto.DeviceDto;
 import org.dromara.server.hik.domain.dto.QueryDto;
+import org.dromara.server.hik.domain.dto.UploadEmpDto;
+import org.dromara.server.hik.domain.dto.UserInfoDto;
+import org.dromara.server.hik.domain.dto.base.*;
 import org.dromara.server.hik.domain.dto.query.QueryEmpResultDto;
 import org.dromara.server.hik.enums.ContentTypeEnum;
 import org.dromara.server.hik.enums.StatusCodeEnum;
 import org.dromara.server.hik.service.ISendDeviceService;
 import org.dromara.server.hik.utils.DigestHttpUtil;
+import org.dromara.server.hik.utils.JsonConfig;
 import org.jetbrains.annotations.NotNull;
 import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
 
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Future;
+import java.text.MessageFormat;
+import java.util.*;
 import java.util.concurrent.ScheduledExecutorService;
 
 /**
@@ -44,94 +57,113 @@ import java.util.concurrent.ScheduledExecutorService;
 public class SendDeviceServiceImpl implements ISendDeviceService {
     private final DigestHttpUtil digestHttpUtil;
     private final ScheduledExecutorService scheduledExecutorService;
+    private final DefaultConfig defaultConfig;
+
+    @DubboReference
+    private final RemotePtXfTermService remotePtXfTermService;
+    @DubboReference
+    private final RemoteUserAccountService remoteUserAccountService;
+
+    // region 私有方法
 
     /**
-     * 给指定设务设置监听服务地址
+     * 根据提供的 RemoteUserAccountVo 对象和删除标志,构造并返回一个 EmpInfoDto 对象。
      *
-     * @param dto 设备信息
-     * @return 设置结果
+     * @param accountVo     包含用户账户信息的 RemoteUserAccountVo 对象
+     * @param deleteUser    布尔标志,指示是否应将用户标记为删除
+     * @param deleteAllFace 布尔标志,指示是否应删除与用户相关的所有人脸数据
+     * @param deleteFace    布尔标志,指示是否应删除特定的人脸数据
+     * @param deleteAllCard 布尔标志,指示是否应删除与用户相关的所有卡片数据
+     * @param deleteCard    布尔标志,指示是否应删除特定的卡片数据
+     * @return 一个填充了用户信息、有效性详情、卡片信息和人脸数据的 EmpInfoDto 对象
      */
-    @Override
-    public R<Void> setHttpHostByDto(DeviceDto dto) {
-        String setData = this.createHostXml(dto);
+    @NotNull
+    private static EmpInfoDto getEmpInfoDto(@NotNull RemoteUserAccountVo accountVo, Boolean deleteUser, Boolean deleteAllFace, Boolean deleteFace,
+                                            Boolean deleteAllCard, Boolean deleteCard) {
+        EmpInfoDto empDto = new EmpInfoDto();
+        // 设置用户基本信息
+        empDto.setEmployeeNo(accountVo.getUserNo().toString()).setName(accountVo.getRealName());
+        if (deleteUser) {
+            empDto.setDeleteUser(Boolean.TRUE);
+        }
+        // 设置有效期,海康设备支持有效期最大2037-12-31 23:59:59,所以要和系统的有效期比较取较小值
+        Date endTime = accountVo.getLifespan();
+        Date hikEndTime = DateUtil.parse(HikDefaultConstants.EMP_END_TIME);
+        if (endTime.compareTo(hikEndTime) > 0) {
+            endTime = hikEndTime;
+        }
+        ValidDto validDto = new ValidDto().setBeginTime(getBeginTime()).setEndTime(DateUtil.date(endTime));
+        empDto.setValid(validDto);
 
-        JSONObject sendResult = digestHttpUtil.sendPost(dto, setData, HikApiConstants.SET_HTTP_HOSTS, ContentTypeEnum.XML.getCode());
-        R<Void> doResult = this.doSetHostReturnData(sendResult);
-        if (R.isError(doResult)) {
-            return R.fail(StringUtils.format("[IP:{}的设备设置失败,原因:{}]", dto.getDeviceIp(), doResult.getMsg()));
+        // 设置用户卡片信息
+        if (ObjectUtil.isNotEmpty(accountVo.getFactoryId())) {
+            CardDto cardDto = new CardDto().setCardNo(accountVo.getFactoryId().toString());
+            if (deleteCard) {
+                cardDto.setDeleteCard(Boolean.TRUE);
+            }
+            List<CardDto> cardList = new ArrayList<>();
+            cardList.add(cardDto);
+
+            CardListDto cardListDto = new CardListDto();
+            cardListDto.setList(cardList);
+            if (deleteAllCard) {
+                cardListDto.setDeleteAllCard(Boolean.TRUE);
+            }
+            empDto.setCardInfo(cardListDto);
         }
-        return R.ok(StringUtils.format("[IP:{}的设备设置成功]", dto.getDeviceIp()));
+
+        // TODO 2025-05-24 因为人员照片原因,暂时不将人脸照片上传到消费机
+        // 设置用户人脸图片信息
+        // String photo = accountVo.getFacePicUrl();
+        // if (ObjectUtil.isNotEmpty(photo)) {
+        //     FaceDto faceDto = new FaceDto().setFDID("1").setFaceID(1L).setFacePicURL(photo);
+        //     if (deleteFace) {
+        //         faceDto.setDeleteFace(Boolean.TRUE);
+        //     }
+        //
+        //     List<FaceDto> faceList = new ArrayList<>();
+        //     faceList.add(faceDto);
+        //     FaceListDto faceListDto = new FaceListDto().setList(faceList);
+        //     if (deleteAllFace) {
+        //         faceListDto.setDeleteAllFace(Boolean.TRUE);
+        //     }
+        //     empDto.setFaceInfo(faceListDto);
+        // }
+
+        return empDto;
+
     }
 
     /**
-     * 给所有设备设置监听服务地址
+     * 将 RemoteXfTermVo 对象转换为 DeviceDto 对象。
+     * 它提取了终端号、管理员名称、管理员密码、设备 IP、设备端口、服务器 IP 和服务器端口等信息,并将其设置到 DeviceDto 实例中。
      *
-     * @return 设置结果
+     * @param termVo 包含终端信息的 RemoteXfTermVo 对象
+     * @return 转换后的 DeviceDto 对象
      */
-    @Override
-    public R<Void> setHttpHostAll() {
-        // TODO 2025-05-21 luoyibo 所有的设备需要通过远程调用获取
-
-        List<DeviceDto> list = new ArrayList<>();
+    @NotNull
+    private static DeviceDto getDeviceDto(@NotNull RemoteXfTermVo termVo) {
         DeviceDto dto = new DeviceDto();
-        dto.setAdminName("admin");
-        dto.setAdminPwd("Dt20250512");
-        dto.setDeviceIp("192.168.1.15");
-        dto.setDevicePort(8080);
-        dto.setServerIp("192.168.1.8");
-        dto.setServerPort(8080);
-        list.add(dto);
-
-        dto = new DeviceDto();
-        dto.setAdminName("admin");
-        dto.setAdminPwd("Dt20250512");
-        dto.setDeviceIp("192.168.1.14");
-        dto.setDevicePort(80);
-        dto.setServerIp("192.168.1.8");
-        dto.setServerPort(9000);
-        list.add(dto);
-
-        List<Future<String>> rlist = new ArrayList<>();
-        List<String> msgList = new ArrayList<>();
-        list.parallelStream().forEach(p -> {
-            Future<String> result = scheduledExecutorService.submit(() -> setHttpHostByDto(p).getMsg());
-            rlist.add(result);
-        });
-        for (Future<String> f : rlist) {
-            try {
-                msgList.add(f.get());
-            } catch (ExecutionException | InterruptedException e) {
-                log.error(e.getCause().getMessage());
-            }
-        }
-        msgList.forEach(System.out::println);
-        return R.ok("处理完成,详情见处理日志");
+        dto.setTermNo(termVo.getTermNo().intValue());
+        dto.setAdminName(termVo.getAdminName());
+        dto.setAdminPwd(termVo.getAdminPwd());
+        dto.setDeviceIp(termVo.getTermIp());
+        dto.setDevicePort(termVo.getCommPort().intValue());
+        dto.setServerIp(termVo.getServerIp());
+        dto.setServerPort(termVo.getServerPort().intValue());
+
+        return dto;
     }
 
     /**
-     * 批量查询设备上人员信息。
-     * <p>
-     * 该方法用于向指定设备发起批量查询人员信息的请求,基于提供的查询条件传输对象。
+     * 返回当前日期的开始时间,格式化为当天 00:00:00,
      *
-     * @param dto 查询条件传输对象,包含查询所需的设备信息及分页参数等
-     * @return 响应信息主体,表示查询操作的结果状态及可能的附加信息
+     * @return 当前日期的开始时间
      */
-    @Override
-    public R<Void> queryBatchEmpFormDevice(@NotNull QueryDto dto) {
-        DeviceDto device = dto.getDevice();
-        Map<String, Object> params = new HashMap<>();
-        params.put("searchID", dto.getSearchID());
-        params.put("searchResultPosition", (dto.getPageNo() - 1) * dto.getPageSize());
-        params.put("maxResults", dto.getPageSize());
-
-        String setData = JsonUtils.toJsonString(params);
-        JSONObject sendResult = digestHttpUtil.sendPost(dto.getDevice(), setData, HikApiConstants.QUERY_EMP_ALL, ContentTypeEnum.JSON.getCode());
-
-        R<Void> doResult = this.doQueryEmpReturnData(sendResult);
-        if (R.isError(doResult)) {
-            return R.fail(StringUtils.format("[IP:{}的设备查询失败,原因:{}]", device.getDeviceIp(), doResult.getMsg()));
-        }
-        return R.ok(StringUtils.format("[IP:{}的设备查询成功],结果:{}", device.getDeviceIp(),doResult.getMsg()));
+    private static Date getBeginTime() {
+        String temp = DateUtil.format(DateUtil.date(), DefaultConstants.DATE_FORMAT);
+        temp = temp + " 00:00:00";
+        return DateUtil.parse(temp);
     }
 
     /**
@@ -230,4 +262,428 @@ public class SendDeviceServiceImpl implements ISendDeviceService {
             return R.ok(msg);
         }
     }
+
+    /**
+     * 根据提供的 JSON 对象验证用户检查异常。
+     * 该方法检查给定 JSON 对象中的 "userCheck" 字段,以确定状态值是否表示错误条件。
+     * 如果检测到错误,则返回带有适当消息的失败响应。
+     *
+     * @param obj 一个非空的 JSON 对象,包含要验证的 "userCheck" 字段
+     * @return 如果未发现错误则返回成功响应;如果状态值表示错误或发生未知异常,则返回带错误消息的失败响应
+     */
+    private R<Void> validUserCheckException(@NotNull JSONObject obj) {
+        if (ObjectUtil.isNotEmpty(obj.getObj("userCheck"))) {
+            JSONObject responseStatus = obj.getJSONObject("userCheck");
+            Integer statusCode = responseStatus.getInt("statusValue");
+            if (ObjectUtil.notEqual(statusCode, ErrCodeConstants.OK) && ObjectUtil.notEqual(statusCode, ErrCodeConstants.YES)) {
+                return R.fail(StatusCodeEnum.getMessage("userCheck_" + statusCode.toString()));
+            } else {
+                return R.fail("未知异常");
+            }
+        }
+        return R.ok();
+    }
+
+    /**
+     * 根据提供的 JSON 对象验证状态码异常。
+     * 如果 JSON 对象中的 "errorMsg" 字段不为空,则返回带有错误消息的失败响应。
+     * 否则,返回成功响应。
+     *
+     * @param obj 包含要验证的错误消息的 JSON 对象;不能为空
+     * @return 一个响应对象,根据验证结果指示成功或失败
+     */
+    private R<Void> validStatusCodeException(@NotNull JSONObject obj) {
+        String errorMsg = obj.getStr("errorMsg");
+        if (ObjectUtil.isNotEmpty(errorMsg)) {
+            return R.fail(errorMsg);
+        }
+
+        return R.ok();
+    }
+
+    /**
+     * 验证提供的 JSON 对象中包含的详细信息。
+     * 检查 "DetailInfo" 对象是否存在,并评估其错误码。
+     * 如果错误码与可接受的值(ErrCodeConstants.OK 或 ErrCodeConstants.YES)不匹配,
+     * 则返回失败响应以及相关的用户错误消息。
+     * 否则,返回成功响应。
+     *
+     * @param obj 包含要验证的 "DetailInfo" 的 JSON 对象;不能为空
+     * @return 如果验证通过则返回成功响应,如果验证失败则返回包含错误消息的失败响应
+     */
+    private R<Void> validDetailInfo(@NotNull JSONObject obj) {
+        if (ObjectUtil.isNotEmpty(obj.getObj("DetailInfo"))) {
+            JSONObject responseStatus = obj.getJSONObject("DetailInfo");
+            Integer statusCode = responseStatus.getInt("errorCode");
+            if (ObjectUtil.notEqual(statusCode, ErrCodeConstants.OK) && ObjectUtil.notEqual(statusCode, ErrCodeConstants.YES)) {
+                return R.fail(responseStatus.getStr("userErrorMsg"));
+            }
+        }
+        return R.ok();
+    }
+
+    /**
+     * 创建并发送需要操作的员工信息到指定设备
+     *
+     * @param device  员工信息将被发送到的设备
+     * @param empInfo 要创建并传输的员工信息
+     * @return 一个表示操作结果的 R 实例,封装了任何可能的异常或验证结果
+     */
+    private R<Void> createOperatorEmpInfo(DeviceDto device, EmpInfoDto empInfo) {
+        UserInfoDto sendDto = new UserInfoDto();
+        sendDto.setUserInfoAndRight(empInfo);
+        String setData = JSONUtil.toJsonStr(sendDto, JsonConfig.getConfig());
+
+        JSONObject sendResult = digestHttpUtil.sendPost(device, setData, HikApiConstants.SEND_EMP_INFO, ContentTypeEnum.JSON.getCode());
+        R<Void> check = this.validUserCheckException(sendResult);
+        if (R.isError(check)) {
+            return R.fail(
+                MessageFormat.format("[上传人员信息失败]-[设备IP:{0}, 人员信息:{1}, 错误信息:{2}", device.getDeviceIp(), empInfo,
+                                     check.getMsg()));
+        }
+        check = this.validStatusCodeException(sendResult);
+        if (R.isError(check)) {
+            return R.fail(
+                MessageFormat.format("[上传人员信息失败]-[设备IP:{0}, 人员信息:{1}, 错误信息:{2}", device.getDeviceIp(), empInfo,
+                                     check.getMsg()));
+        }
+        check = this.validDetailInfo(sendResult);
+        if (R.isError(check)) {
+            return R.fail(
+                MessageFormat.format("[上传人员信息失败]-[设备IP:{0}, 人员信息:{1}, 错误信息:{2}", device.getDeviceIp(), empInfo,
+                                     check.getMsg()));
+        }
+        return R.ok(MessageFormat.format("[上传人员信息成功]-[设备IP:{0}, 人员信息:{1}]", device.getDeviceIp(), empInfo));
+    }
+
+    /**
+     * 根据提供的终端编号获取一个 DeviceDto 对象。
+     *
+     * @param termNo 用于查询和检索设备信息的终端编号
+     * @return 与查询的终端编号对应的 DeviceDto 对象
+     * @throws ServiceException 如果设备不存在,或者设备不是海康威视(Hikvision)品牌的设备
+     */
+    @NotNull
+    private DeviceDto getDeviceDto(Long termNo) {
+        RemoteXfTermVo termVo = remotePtXfTermService.queryByNo(termNo, defaultConfig.getTenantId());
+        if (ObjectUtil.isEmpty(termVo)) {
+            throw new ServiceException(MessageFormat.format("设备不存在,设备编号:{0}", termNo));
+        }
+        if (ObjectUtil.notEqual(DeviceBrandEnum.HK.getCode(), termVo.getBrand())) {
+            throw new ServiceException(MessageFormat.format("无法处理非海康设备,设备编号:{0}", termNo));
+        }
+        return getDeviceDto(termVo);
+    }
+    // endregion
+
+    // region 设置监听相关
+    @Override
+    public R<Void> setHttpHostByDto(@Validated DeviceDto dto) {
+        ValidatorUtils.validate(dto);
+
+        if (ObjectUtil.isEmpty(dto.getServerIp())) {
+            return R.fail("监听IP不能为空");
+        }
+        if (ObjectUtil.isEmpty(dto.getServerPort())) {
+            return R.fail("监听端口不能为空");
+        }
+        String setData = this.createHostXml(dto);
+
+        JSONObject sendResult = digestHttpUtil.sendPost(dto, setData, HikApiConstants.SET_HTTP_HOSTS, ContentTypeEnum.XML.getCode());
+        R<Void> doResult = this.doSetHostReturnData(sendResult);
+        if (R.isError(doResult)) {
+            return R.fail(MessageFormat.format("[IP:{0}的设备设置失败,原因:{1}]", dto.getDeviceIp(), doResult.getMsg()));
+        }
+        return R.ok(MessageFormat.format("[IP:{0}的设备设置成功]", dto.getDeviceIp()));
+    }
+
+    @Override
+    public R<Void> setHttpHostByTermNo(Long termNo) {
+        DeviceDto dto = getDeviceDto(termNo);
+
+        return this.setHttpHostByDto(dto);
+    }
+
+    @Override
+    public R<Void> setHttpHostAll() {
+        List<RemoteXfTermVo> termList = remotePtXfTermService.queryListByBrand("hk");
+        if (CollectionUtil.isEmpty(termList)) {
+            return R.warn("没有要配置的设备");
+        }
+
+        termList.parallelStream().forEach(p -> {
+            scheduledExecutorService.execute(() -> {
+                DeviceDto dto = getDeviceDto(p);
+                R<Void> result = setHttpHostByDto(dto);
+                log.info(result.getMsg());
+            });
+        });
+        return R.ok("处理完成,详情见处理日志");
+    }
+    // endregion
+
+    // region 从消费机删除人员相关
+    @Override
+    public R<Void> deleteEmpByUserNo(DeviceDto device, String userNo) {
+        EmpInfoDto delEmpDto = new EmpInfoDto();
+        delEmpDto.setDeleteUser(Boolean.TRUE);
+        delEmpDto.setEmployeeNo(userNo);
+
+        R<Void> delResult = this.createOperatorEmpInfo(device, delEmpDto);
+        if (R.isError(delResult)) {
+            return R.fail(
+                MessageFormat.format("[处理人员失败]-[设备IP:{0}, 人员编号:{1}, 错误信息:{2}", device.getDeviceIp(), userNo, delResult.getMsg()));
+        }
+        return R.ok(MessageFormat.format("[处理人员成功]-[设备IP:{0}, 人员编号:{1}]", device.getDeviceIp(), userNo));
+    }
+
+    @Override
+    public R<Void> deleteEmpFromDevice(Long termNo, Long userId) {
+        DeviceDto device = getDeviceDto(termNo);
+        RemoteUserAccountVo accountVo = remoteUserAccountService.getUserAccountVoBy(userId);
+        if (ObjectUtil.isEmpty(accountVo)) {
+            return R.fail(
+                MessageFormat.format("[处理人员失败]-[设备IP:{0}, 人员Id:{1}, 错误信息:无此Id对应的人员信息", device.getDeviceIp(), userId));
+        }
+        EmpInfoDto empInfo = getEmpInfoDto(accountVo, true, false, false, false, false);
+        R<Void> result = createOperatorEmpInfo(device, empInfo);
+
+        log.info(result.getMsg());
+
+        return result;
+    }
+
+    @Override
+    public R<Void> deleteEmpFromDevice(Long termNo) {
+        DeviceDto deviceDto = getDeviceDto(termNo);
+        List<RemoteUserAccountVo> accountVoList = remoteUserAccountService.getUserAccountVoList();
+        accountVoList.parallelStream().forEach(p -> {
+            scheduledExecutorService.execute(() -> {
+                EmpInfoDto empDto = getEmpInfoDto(p, true, false, false, false, false);
+                R<Void> result = createOperatorEmpInfo(deviceDto, empDto);
+                log.info(result.getMsg());
+            });
+        });
+
+        return R.ok();
+    }
+
+    public R<Void> deleteOneEmpFromDevice(Long userId) {
+        RemoteUserAccountVo accountVo = remoteUserAccountService.getUserAccountVoBy(userId);
+        if (ObjectUtil.isEmpty(accountVo)) {
+            return R.fail(
+                MessageFormat.format("[处理人员失败]-[人员Id:{0}, 错误信息:无此Id对应的人员信息", userId));
+        }
+        EmpInfoDto empInfo = getEmpInfoDto(accountVo, true, false, false, false, false);
+
+        List<RemoteXfTermVo> termList = remotePtXfTermService.queryListByBrand("hk");
+        if (CollectionUtil.isEmpty(termList)) {
+            return R.warn("没有要处理人员的设备");
+        }
+
+        termList.parallelStream().forEach(p -> {
+            scheduledExecutorService.execute(() -> {
+                DeviceDto device = getDeviceDto(p);
+                R<Void> result = createOperatorEmpInfo(device, empInfo);
+                log.info(result.getMsg());
+            });
+        });
+        return R.ok();
+    }
+
+    @Override
+    public R<Void> deleteEmpFromDevice() {
+        // 获取所有设备
+        List<RemoteXfTermVo> termList = remotePtXfTermService.queryListByBrand("hk");
+        if (CollectionUtil.isEmpty(termList)) {
+            return R.warn("没有要处理人员的设备");
+        }
+        // 获取所有人员
+        List<RemoteUserAccountVo> accountVoList = remoteUserAccountService.getUserAccountVoList();
+        if (CollectionUtil.isEmpty(accountVoList)) {
+            return R.warn("没有要处理的人员");
+        }
+
+        // 并行处理
+        termList.parallelStream().forEach(p -> {
+            scheduledExecutorService.execute(() -> {
+                DeviceDto device = getDeviceDto(p);
+                accountVoList.parallelStream().forEach(t -> {
+                    EmpInfoDto empInfo = getEmpInfoDto(t, true, false, false, false, false);
+                    R<Void> result = createOperatorEmpInfo(device, empInfo);
+                    log.info(result.getMsg());
+                });
+            });
+        });
+        return R.ok();
+    }
+    // endregion
+
+
+    @Override
+    public R<Void> deleteAllCardByUserNo(DeviceDto device, String userNo) {
+        CardListDto cardList = new CardListDto();
+        cardList.setDeleteAllCard(Boolean.TRUE);
+
+        EmpInfoDto delEmpDto = new EmpInfoDto();
+        delEmpDto.setEmployeeNo(userNo);
+        delEmpDto.setName("胡哲");
+        delEmpDto.setCardInfo(cardList);
+
+        R<Void> check = this.createOperatorEmpInfo(device, delEmpDto);
+        if (R.isError(check)) {
+            return R.fail(
+                MessageFormat.format("[处理人员所有卡片失败]-[设备IP:{0}, 人员编号:{1}, 错误信息:{2}", device.getDeviceIp(), userNo, check.getMsg()));
+        }
+        return R.ok(MessageFormat.format("[处理人员所有卡片成功]-[设备IP:{0}, 人员编号:{1}]", device.getDeviceIp(), userNo));
+    }
+
+    @Override
+    public R<Void> deleteCardByUserNo(DeviceDto device, String userNo, String factoryId) {
+        return null;
+    }
+
+    @Override
+    public R<Void> deleteAllFaceByUserNo(DeviceDto device, String userNo) {
+        return null;
+    }
+
+    @Override
+    public R<Void> deleteFaceByUserNo(DeviceDto device, String userNo, String faceId) {
+        return null;
+    }
+
+
+    // region 向消费机上传人员相关
+    @Override
+    public R<Void> upLoadEmpToDevice(UploadEmpDto uploadEmpDto) {
+        DeviceDto device = uploadEmpDto.getDevice();
+        EmpInfoDto empDto = uploadEmpDto.getEmployee();
+        ValidDto validDto = uploadEmpDto.getValid();
+        CardDto cardDto = uploadEmpDto.getCard();
+        FaceDto faceDto = uploadEmpDto.getFace();
+        if (ObjectUtil.isNotEmpty(cardDto)) {
+            List<CardDto> cardList = new ArrayList<>();
+            cardList.add(cardDto);
+
+            CardListDto cardListDto = new CardListDto();
+            cardListDto.setList(cardList);
+            empDto.setCardInfo(cardListDto);
+        }
+        if (ObjectUtil.isNotEmpty(faceDto)) {
+            List<FaceDto> faceList = new ArrayList<>();
+            faceList.add(faceDto);
+            FaceListDto faceListDto = new FaceListDto();
+            faceListDto.setList(faceList);
+            empDto.setFaceInfo(faceListDto);
+        }
+        if (ObjectUtil.isNotEmpty(validDto)) {
+            empDto.setValid(validDto);
+        }
+        String strEmpInfo = JSONUtil.toJsonStr(empDto, JsonConfig.getConfig());
+        R<Void> check = this.createOperatorEmpInfo(device, empDto);
+        if (R.isError(check)) {
+            return R.fail(
+                MessageFormat.format("[上传人员信息失败]-[设备IP:{0}, 人员信息:{1}, 错误信息:{2}", device.getDeviceIp(), strEmpInfo, check.getMsg()));
+        }
+        return R.ok(MessageFormat.format("[上传人员信息成功]-[设备IP:{0}, 人员信息:{1}]", device.getDeviceIp(), strEmpInfo));
+    }
+
+    @Override
+    public R<Void> upLoadEmpToDevice(Long termNo) {
+        DeviceDto deviceDto = getDeviceDto(termNo);
+        List<RemoteUserAccountVo> accountVoList = remoteUserAccountService.getUserAccountVoList();
+        if (CollectionUtil.isEmpty(accountVoList)) {
+            return R.warn("没有要处理的人员");
+        }
+         // 并行处理人员列表
+        accountVoList.parallelStream().forEach(p -> {
+            scheduledExecutorService.execute(() -> {
+                EmpInfoDto empDto = getEmpInfoDto(p, false, false, false, false, false);
+                R<Void> result = createOperatorEmpInfo(deviceDto, empDto);
+                log.info(result.getMsg());
+            });
+        });
+        return R.ok();
+    }
+
+    @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);
+
+        return this.createOperatorEmpInfo(deviceDto, empDto);
+    }
+
+    @Override
+    public R<Void> upLoadEmpToDevice() {
+        // 获取所有设备
+        List<RemoteXfTermVo> termList = remotePtXfTermService.queryListByBrand("hk");
+        if (CollectionUtil.isEmpty(termList)) {
+            return R.warn("没有要处理人员的设备");
+        }
+        // 获取所有人员
+        List<RemoteUserAccountVo> accountVoList = remoteUserAccountService.getUserAccountVoList();
+        if (CollectionUtil.isEmpty(accountVoList)) {
+            return R.warn("没有要处理的人员");
+        }
+
+        // 并行处理
+        termList.parallelStream().forEach(p -> {
+            scheduledExecutorService.execute(() -> {
+                DeviceDto device  = getDeviceDto(p);
+                accountVoList.parallelStream().forEach(t -> {
+                    EmpInfoDto empInfo = getEmpInfoDto(t, false, false, false, false, false);
+                    R<Void> result = createOperatorEmpInfo(device, empInfo);
+                    log.info(result.getMsg());
+                });
+            });
+        });
+        return R.ok();
+    }
+
+    @Override
+    public R<Void> upLoadEmpToAllDevice(Long userId) {
+        RemoteUserAccountVo accountVo = remoteUserAccountService.getUserAccountVoBy(userId);
+        if (ObjectUtil.isEmpty(accountVo)) {
+            return R.warn(MessageFormat.format("没有要处理的人员信息,userId:{0}", userId));
+        }
+        EmpInfoDto empDto = getEmpInfoDto(accountVo, false, false, false, false, false);
+
+        List<RemoteXfTermVo> termList = remotePtXfTermService.queryListByBrand("hk");
+        if (CollectionUtil.isEmpty(termList)) {
+            return R.warn("没有要处理的设备");
+        }
+        termList.parallelStream().forEach(p -> {
+            scheduledExecutorService.execute(() -> {
+                DeviceDto deviceDto = getDeviceDto(p);
+                R<Void> result = this.createOperatorEmpInfo(deviceDto, empDto);
+                log.info(result.getMsg());
+            });
+        });
+
+        return R.ok();
+    }
+    // endregion
+
+    @Override
+    public R<Void> queryBatchEmpFormDevice(@NotNull QueryDto dto) {
+        DeviceDto device = dto.getDevice();
+        Map<String, Object> params = new HashMap<>();
+        params.put("searchID", dto.getSearchID());
+        params.put("searchResultPosition", (dto.getPageNo() - 1) * dto.getPageSize());
+        params.put("maxResults", dto.getPageSize());
+
+        String setData = JSONUtil.toJsonStr(params);
+        JSONObject sendResult = digestHttpUtil.sendPost(dto.getDevice(), setData, HikApiConstants.QUERY_EMP_ALL, ContentTypeEnum.JSON.getCode());
+
+        R<Void> doResult = this.doQueryEmpReturnData(sendResult);
+        if (R.isError(doResult)) {
+            return R.fail(StringUtils.format("[IP:{}的设备查询失败,原因:{}]", device.getDeviceIp(), doResult.getMsg()));
+        }
+        return R.ok(StringUtils.format("[IP:{}的设备查询成功],结果:{}", device.getDeviceIp(), doResult.getMsg()));
+    }
 }

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

@@ -77,7 +77,7 @@ public class DigestHttpUtil {
                 jsonObject = JSONUtil.parseObj(str);
             }
             // TODO 2025-05-21 luoyibo 开发过程中打印,正式发布时不再打印
-            log.info("str={}", jsonObject);
+            // log.info("str={}", jsonObject);
 
             return jsonObject;
         } catch (Exception e) {

+ 24 - 0
ruoyi-server/ruoyi-server-hik/src/main/java/org/dromara/server/hik/utils/JsonConfig.java

@@ -0,0 +1,24 @@
+package org.dromara.server.hik.utils;
+
+import cn.hutool.json.JSONConfig;
+
+/**
+ * Json序列化配置
+ * <p>
+ * Hutool工具Json序列化配置定义
+ *
+ * @author luoyibo
+ * @version 2.2.0
+ * @date 2025-05-23
+ * @since JDK17
+ */
+public class JsonConfig {
+
+    public static JSONConfig getConfig() {
+        JSONConfig jsonConfig = new JSONConfig();
+        jsonConfig.setIgnoreNullValue(true);
+        jsonConfig.setDateFormat("yyyy-MM-dd HH:mm:ss");
+        return jsonConfig;
+    }
+
+}