فهرست منبع

feat(hikiot): 新增海康开放平台接口调用功能- 新增多个海康开放平台接口调用方法,包括获取授权码、用户访问凭证、新增人员、设备添加等
- 新增 AppHikiotDevice 实体类和对应的 Mapper 接口
- 新增多个 DTO 类用于封装接口请求和响应数据
- 优化 HikiotConstant 类,添加新的常量定义
- 重构 HikiotTool 类,增加新的工具方法支持接口调用

SheepHy 2 روز پیش
والد
کامیت
a9342089b7

+ 35 - 0
national-motion-module-system/national-motion-system-biz/src/main/java/org/jeecg/modules/hikiot/HikiotConstant.java

@@ -10,6 +10,15 @@ public interface HikiotConstant {
     String APP_SECRET = "appSecret";
     String APP_ACCESS_TOKEN = "appAccessToken";
     String REFRESH_APP_TOKEN = "refreshAppToken";
+    String USER_ACCESS_TOKEN = "User-Access-Token";
+    String USER_NAME = "userName";
+    String PASSWORD = "password";
+    String USER_NAME_VALUE = "18908510411";
+    String PASSWORD_VALUE = "ziyan515.";
+    String APP_ACCESS_TOKEN_AUTH_HEADER = "App-Access-Token";
+    String REDIRECT_URL = "redirectUrl";
+    String VISITOR = "normal";
+    String REDIRECT_URL_VALUE = "http://o3878n6548.wicp.vip";
     /**APP KEY*/
     String APP_KEY_VALUE = "1954824074366873616";
     /**APP SECRET*/
@@ -18,4 +27,30 @@ public interface HikiotConstant {
     String GET_ACCESS_TOKEN_URL = "https://open-api.hikiot.com/auth/exchangeAppToken";
     /**刷新应用访问凭证URL*/
     String REFRESH_ACCESS_TOKEN_URL = "https://open-api.hikiot.com/auth/refreshAppToken";
+    /**授权码获取用户访问凭证*/
+    String GET_USER_ACCESS_TOKEN_URL = "https://open-api.hikiot.com/auth/third/code2Token?authCode=";
+    /**申请授权码*/
+    String GET_AUTH_CODE_URL = "https://open-api.hikiot.com/auth/third/applyAuthCode";
+    /**新增/修改人员*/
+    String ADD_USER_URL = "https://open-api.hikiot.com/device/direct/v1/userInfo/addOneRecord";
+    /**设备添加*/
+    String ADD_DEVICE_URL = "https://open-api.hikiot.com/device/mgr/v1/add";
+    /**设备/通道能力查询*/
+    String QUERY_DEVICE_ABILITY_URL = "https://open-api.hikiot.com/device/v1/getDeviceCapacities?deviceSerial=";
+    /**新增/修改人脸*/
+    String ADD_FACE_URL = "https://open-api.hikiot.com/device/direct/v1/faceAccess/addOneRecord";
+    /**人员信息查询*/
+    String QUERY_USER_INFO_URL = "https://open-api.hikiot.com/device/direct/v1/userInfo/search";
+    /**配置人员周计划*/
+    String CONFIG_USER_WEEK_PLAN_URL = "https://open-api.hikiot.com/device/direct/v1/timePlanAdd/userWeekPlan";
+    /**配置人员计划模板*/
+    String CONFIG_USER_PLAN_TEMPLATE_URL = "https://open-api.hikiot.com/device/direct/v1/timePlanAdd/userPlanTemplate";
+    /**配置门周计划*/
+    String CONFIG_DOOR_WEEK_PLAN_URL = "https://open-api.hikiot.com/device/direct/v1/timePlanAdd/weekPlan";
+    /**配置门关联计划模板*/
+    String CONFIG_DOOR_PLAN_TEMPLATE_URL = "https://open-api.hikiot.com/device/direct/v1/timePlanAdd/doorRightPlan";
+    /**配置门计划模板*/
+    String CONFIG_DOOR_PLAN_TEMPLATE_URL2 = "https://open-api.hikiot.com/device/direct/v1/timePlanAdd/planTemplate";
+
+
 }

+ 379 - 23
national-motion-module-system/national-motion-system-biz/src/main/java/org/jeecg/modules/hikiot/HikiotTool.java

@@ -1,14 +1,24 @@
 package org.jeecg.modules.hikiot;
 
+import cn.hutool.core.date.LocalDateTimeUtil;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
 import org.jeecg.common.exception.JeecgBootException;
+import org.jeecg.modules.hikiot.dto.AddDoorWeekPlanDTO;
+import org.jeecg.modules.hikiot.dto.AddFaceRequestDTO;
+import org.jeecg.modules.hikiot.dto.AddUserRequestDTO;
+import org.jeecg.modules.hikiot.dto.AddUserWeekPlanDTO;
 
+import java.io.IOException;
 import java.net.URI;
 import java.net.http.HttpClient;
 import java.net.http.HttpRequest;
 import java.net.http.HttpResponse;
 import java.nio.charset.StandardCharsets;
-import java.util.HashMap;
-import java.util.Map;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.temporal.ChronoUnit;
+import java.util.*;
 
 import static com.alibaba.dashscope.utils.JsonUtils.gson;
 import static org.jeecg.modules.hikiot.HikiotConstant.*;
@@ -23,18 +33,36 @@ public class HikiotTool {
      * @param jsonParam 请求参数(JSON格式)
      * @return 响应结果
      */
-    public static String sendPostRequest(String url, String jsonParam) {
+    public static String sendPostRequest(String url, String jsonParam, Map<String, String> variables) {
+        // 参数校验
+        Objects.requireNonNull(url, "请求URL不能为空");
+        Objects.requireNonNull(jsonParam, "JSON参数不能为空");
+
         try {
-            HttpRequest request = HttpRequest.newBuilder()
+            // 构建请求基础配置
+            HttpRequest.Builder requestBuilder = HttpRequest.newBuilder()
                     .uri(URI.create(url))
                     .header("Content-Type", "application/json; charset=UTF-8")
-                    .POST(HttpRequest.BodyPublishers.ofString(jsonParam, StandardCharsets.UTF_8))
-                    .build();
+                    .POST(HttpRequest.BodyPublishers.ofString(jsonParam, StandardCharsets.UTF_8));
+
+            // 添加自定义Header
+            if (variables != null) {
+                variables.forEach(requestBuilder::header);
+            }
 
+            // 构建最终请求
+            HttpRequest request = requestBuilder.build();
+
+            // 发送请求并返回结果
             HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
             return response.body();
-        } catch (Exception e) {
-            throw new JeecgBootException("请求海康开放平台接口异常", e);
+
+        } catch (IOException e) {
+            throw new JeecgBootException("请求海康开放平台接口IO异常", e);
+        } catch (InterruptedException e) {
+            // 恢复中断状态
+            Thread.currentThread().interrupt();
+            throw new JeecgBootException("请求海康开放平台接口被中断", e);
         }
     }
     /**
@@ -42,18 +70,33 @@ public class HikiotTool {
      * @param url 完整请求地址(包含查询参数)
      * @return 响应结果
      */
-    public static String sendGetRequest(String url) {
-        try {
-            HttpRequest request = HttpRequest.newBuilder()
-                    .uri(URI.create(url))
-                    .header("Accept", "application/json")
-                    .GET()
-                    .build();
-            HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
-            return response.body();
-        } catch (Exception e) {
-            throw new JeecgBootException("请求海康开放平台接口异常", e);
+    public static String sendGetRequest(String url, Map<String, String> headers) throws IOException, InterruptedException {
+        // 获取访问凭证
+        JsonObject appAccessToken = JsonParser.parseString(HikiotTool.getAppAccessToken()).getAsJsonObject();
+        if (appAccessToken.get("code").getAsInt() == 0) {
+            appAccessToken = appAccessToken.getAsJsonObject("data");
+        } else {
+            throw new JeecgBootException("海康API appAccessToken请求失败: " + appAccessToken.get("msg").getAsString());
+        }
+
+        // 构建请求基础配置
+        HttpRequest.Builder requestBuilder = HttpRequest.newBuilder()
+                .uri(URI.create(url))
+                .GET()
+                .header("Content-Type", "application/json")
+                .header(APP_ACCESS_TOKEN_AUTH_HEADER, appAccessToken.get("appAccessToken").getAsString());
+
+        // 添加动态header
+        if (headers != null) {
+            headers.forEach(requestBuilder::header);
         }
+
+        // 构建最终请求
+        HttpRequest request = requestBuilder.build();
+
+        // 发送请求
+        HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
+        return response.body();
     }
 
     /**
@@ -65,7 +108,7 @@ public class HikiotTool {
         Map<String, String> map = new HashMap<>();
         map.put(APP_KEY, APP_KEY_VALUE);
         map.put(APP_SECRET, APP_SECRET_VALUE);
-        return HikiotTool.sendPostRequest(GET_ACCESS_TOKEN_URL, gson.toJson(map));
+        return HikiotTool.sendPostRequest(GET_ACCESS_TOKEN_URL, gson.toJson(map), null);
     }
 
     /**
@@ -77,9 +120,322 @@ public class HikiotTool {
         Map<String, String> map = new HashMap<>();
         map.put(APP_ACCESS_TOKEN, appAccessToken);
         map.put(REFRESH_APP_TOKEN, refreshAppToken);
-        return HikiotTool.sendPostRequest(REFRESH_ACCESS_TOKEN_URL, gson.toJson(map));
+        return HikiotTool.sendPostRequest(REFRESH_ACCESS_TOKEN_URL, gson.toJson(map), null);
+    }
+
+    /**
+     * @Author SheepHy
+     * @Description 申请授权码
+     * @Date 14:04 2025/8/14
+     * @Param
+     * @return
+     **/
+    public static String getAuthCode() {
+        Map<String, String> map = new HashMap<>();
+        map.put(USER_NAME, USER_NAME_VALUE);
+        map.put(PASSWORD, PASSWORD_VALUE);
+        map.put(APP_KEY, APP_KEY_VALUE);
+        map.put(REDIRECT_URL, REDIRECT_URL_VALUE);
+        return HikiotTool.sendPostRequest(GET_AUTH_CODE_URL, gson.toJson(map), null);
+    }
+
+    /**
+     * @Author SheepHy
+     * @Description 授权码获取用户访问凭证
+     * @Date 11:36 2025/8/14
+     * @Param
+     * @return
+     **/
+    public static String getUserAccessToken() throws IOException, InterruptedException {
+        JsonObject dataAuthCode = JsonParser.parseString(HikiotTool.getAuthCode()).getAsJsonObject();
+        if (dataAuthCode.get("code").getAsInt() == 0) {
+            dataAuthCode = dataAuthCode.getAsJsonObject("data");
+        } else {
+            throw new JeecgBootException("海康API authCode请求失败: " + dataAuthCode.get("msg").getAsString());
+        }
+        return sendGetRequest(GET_USER_ACCESS_TOKEN_URL + dataAuthCode.get("authCode").getAsString(), null);
     }
-    public static void main(String[] args) {
-        System.out.println(HikiotTool.getAppAccessToken());
+
+    /** 
+     * @Author SheepHy
+     * @Description 新增/修改人员
+     * @Date 9:35 2025/8/15
+     **/
+    public static String addUser() throws IOException, InterruptedException {
+        AddUserRequestDTO addUserRequestDTO = new AddUserRequestDTO();
+        addUserRequestDTO.setDeviceSerial("FX0889961");
+        addUserRequestDTO.setPayload(new AddUserRequestDTO.Payload()
+                .setUserInfo(new AddUserRequestDTO.Payload.UserInfo()
+                        .setUserType(VISITOR)
+                        .setEmployeeNo("0002")
+                        .setName("Sheep2")
+                        .setPermanentValid(true)
+                        .setEnableBeginTime(LocalDateTimeUtil.format(LocalDateTime.now().with(LocalTime.MIN), "yyyy-MM-dd'T'HH:mm:ss"))
+                        .setEnableEndTime(LocalDateTimeUtil.format(LocalDateTime.now()
+                        .with(LocalTime.MAX)
+                        .truncatedTo(ChronoUnit.SECONDS), "yyyy-MM-dd'T'HH:mm:ss"))));
+        return sendPostRequest(ADD_USER_URL, gson.toJson(addUserRequestDTO),setHeaders());
+    }
+
+    /** 
+     * @Author SheepHy
+     * @Description 设备添加
+     * @Date 10:47 2025/8/15
+     **/
+    public static String addDevice() throws IOException, InterruptedException {
+        Map<String, String> device = new HashMap<>();
+        device.put("deviceSerial", "FX0889961");
+        device.put("validateCode", "zswl8812");
+        device.put("addMethod", "DeviceToken");
+        device.put("qrCode", "");
+        return sendPostRequest(ADD_DEVICE_URL, gson.toJson(device),setHeaders());
+    }
+
+    /**
+     * @Author SheepHy
+     * @Description 设置请求头
+     * @Date 10:56 2025/8/15
+     **/
+    private static Map<String, String> setHeaders() throws IOException, InterruptedException {
+        Map<String, String> variables = new HashMap<>();
+        JsonObject appAccessToken = JsonParser.parseString(HikiotTool.getAppAccessToken()).getAsJsonObject();
+        JsonObject userAccessToken = JsonParser.parseString(HikiotTool.getUserAccessToken()).getAsJsonObject();
+        if (appAccessToken.get("code").getAsInt() == 0) {
+            appAccessToken = appAccessToken.getAsJsonObject("data");
+        }
+        if (userAccessToken.get("code").getAsInt() == 0) {
+            userAccessToken = userAccessToken.getAsJsonObject("data");
+        }
+        variables.put(APP_ACCESS_TOKEN_AUTH_HEADER, appAccessToken.get("appAccessToken").getAsString());
+        variables.put(USER_ACCESS_TOKEN, userAccessToken.get("userAccessToken").getAsString());
+        return variables;
+    }
+
+    /**
+     * @Author SheepHy
+     * @Description 设备/通道能力查询
+     * @Date 12:00 2025/8/15
+     **/
+    public static String getDeviceAbility() throws IOException, InterruptedException {
+        Map<String, String> headers = new HashMap<>();
+        JsonObject userAccessToken = JsonParser.parseString(HikiotTool.getUserAccessToken()).getAsJsonObject();
+        if (userAccessToken.get("code").getAsInt() == 0) {
+            userAccessToken = userAccessToken.getAsJsonObject("data");
+        }
+        headers.put(USER_ACCESS_TOKEN, userAccessToken.get("userAccessToken").getAsString());
+        return sendGetRequest(QUERY_DEVICE_ABILITY_URL + "FX0889961",headers);
+    }
+
+    /** 
+     * @Author SheepHy
+     * @Description 新增/修改人脸
+     * @Date 14:28 2025/8/15
+     **/
+    public static String addFace() throws IOException, InterruptedException {
+        AddFaceRequestDTO addFaceRequestDTO = new AddFaceRequestDTO();
+        addFaceRequestDTO.setDeviceSerial("FX0889961");
+        addFaceRequestDTO.setPayload(new AddFaceRequestDTO.Payload()
+                .setFaceInfo(new AddFaceRequestDTO.Payload.FaceInfo().setEmployeeNo("0002")
+                        .setFaceURL("https://dst-health.oss-cn-chengdu.aliyuncs.com/20250815/99e0a736139b4c52a53365486c810452.jpg")));
+        return sendPostRequest(ADD_FACE_URL, gson.toJson(addFaceRequestDTO),setHeaders());
+    }
+
+    /**
+     * @Author SheepHy
+     * @Description 人员信息查询
+     * @Date 15:08 2025/8/15
+     **/
+    public static String queryUserInfo() throws IOException, InterruptedException {
+        Map<String, String> queryUserInfo = new HashMap<>();
+        queryUserInfo.put("deviceSerial", "FX0889961");
+        queryUserInfo.put("keyword", "0002");
+        return sendPostRequest(QUERY_USER_INFO_URL, gson.toJson(queryUserInfo),setHeaders());
+    }
+
+    /** 
+     * @Author SheepHy
+     * @Description 配置人员周计划
+     * @Date 16:12 2025/8/15
+     **/
+    public static String addUserWeekPlan() throws IOException, InterruptedException {
+        AddUserWeekPlanDTO dto = new AddUserWeekPlanDTO();
+
+        // 基础配置
+        dto.setDeviceSerial("FX0889961");
+
+        // 初始化payload
+        AddUserWeekPlanDTO.Payload payload = new AddUserWeekPlanDTO.Payload();
+        AddUserWeekPlanDTO.UserWeekPlan userWeekPlan = new AddUserWeekPlanDTO.UserWeekPlan();
+
+        userWeekPlan.setDoorWeekId(1);
+        userWeekPlan.setEnable(true);
+
+        // 生成时间配置
+        userWeekPlan.setTimeSegment(createTimeSegments());
+
+        payload.setUserWeekPlan(userWeekPlan);
+        dto.setPayload(payload);
+
+        return sendPostRequest(CONFIG_USER_WEEK_PLAN_URL, gson.toJson(dto), setHeaders());
+    }
+
+    // 生成时间片段
+    private static List<AddUserWeekPlanDTO.TimeSegment> createTimeSegments() {
+        List<AddUserWeekPlanDTO.TimeSegment> segments = new ArrayList<>();
+        // 默认时间片段配置
+        for (String day : Arrays.asList("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday")) {
+            for (int i = 1; i <= 8; i++) {
+                AddUserWeekPlanDTO.TimeSegment segment = new AddUserWeekPlanDTO.TimeSegment();
+                segment.setDoorWeekTimeSegmentId(i);
+                segment.setWeek(day);
+                segment.setDoorStatus("remainClosed");
+                segment.setBeginTime("00:00:00");
+                segment.setEndTime("00:00:00");
+
+                // 特殊配置
+                if (i == 1 && (day.equals("Monday") || day.equals("Tuesday"))) {
+                    segment.setEnable(true);
+                    segment.setEndTime("23:59:59");
+                } else {
+                    segment.setEnable(false);
+                }
+                segments.add(segment);
+            }
+        }
+        return segments;
+    }
+
+    /**
+     * @Author SheepHy
+     * @Description 配置人员计划模板
+     * @Date 17:09 2025/8/15
+     **/
+    public static String addUserPlanTemplate() throws IOException, InterruptedException {
+        String addUserPlanTemplate = "{\n" +
+                "    \"deviceSerial\": \"FX0889961\",\n" +
+                "    \"payload\": {\n" +
+                "        \"userPlanTemplate\": {\n" +
+                "            \"doorPlanTemplateId\": 1,\n" +
+                "            \"enable\": true,\n" +
+                "            \"name\": \"全天通行计划模板\",\n" +
+                "            \"doorWeekId\": 1,\n" +
+                "            \"holidayGroupId\": [\n" +
+                "                1\n" +
+                "            ]\n" +
+                "        }\n" +
+                "    }\n" +
+                "}";
+        return sendPostRequest(CONFIG_USER_PLAN_TEMPLATE_URL, addUserPlanTemplate, setHeaders());
+    }
+
+    /**
+     * @Author SheepHy
+     * @Description 配置门周计划
+     * @Date 16:42 2025/8/15
+     **/
+    public static String addDoorWeekPlan() throws IOException, InterruptedException {
+        AddDoorWeekPlanDTO dto = new AddDoorWeekPlanDTO();
+        // 基础配置
+        dto.setDeviceSerial("FX0889961");
+        // 初始化payload
+        AddDoorWeekPlanDTO.Payload payload = new AddDoorWeekPlanDTO.Payload();
+        AddDoorWeekPlanDTO.WeekPlan weekPlan = new AddDoorWeekPlanDTO.WeekPlan();
+        weekPlan.setDoorWeekId(1);
+        weekPlan.setEnable(true);
+        // 生成时间配置
+        weekPlan.setTimeSegment(createTimeSegments1());
+        payload.setWeekPlan(weekPlan);
+        dto.setPayload(payload);
+        return sendPostRequest(CONFIG_DOOR_WEEK_PLAN_URL, gson.toJson(dto), setHeaders());
+    }
+    private static List<AddDoorWeekPlanDTO.TimeSegment> createTimeSegments1() {
+        List<AddDoorWeekPlanDTO.TimeSegment> segments = new ArrayList<>();
+
+        List<String> days = Arrays.asList(
+                "Monday", "Tuesday", "Wednesday", "Thursday",
+                "Friday", "Saturday", "Sunday"
+        );
+
+        for (String day : days) {
+            for (int i = 1; i <= 8; i++) {
+                AddDoorWeekPlanDTO.TimeSegment segment = new AddDoorWeekPlanDTO.TimeSegment();
+                segment.setDoorWeekTimeSegmentId(i);
+                segment.setWeek(day);
+
+                // 设置默认值
+                segment.setBeginTime("00:00:00");
+                segment.setEndTime("00:00:00");
+
+                // 特殊配置
+                if (i == 1 && (day.equals("Monday") || day.equals("Tuesday")
+                        || day.equals("Wednesday") || day.equals("Thursday")
+                        || day.equals("Friday"))) {
+                    segment.setEnable(true);
+                    segment.setEndTime("23:59:59");
+                } else {
+                    segment.setEnable(false);
+                }
+
+                segments.add(segment);
+            }
+        }
+
+        return segments;
+    }
+
+    /**
+     * @Author SheepHy
+     * @Description 配置门关联计划模板
+     * @Date 16:48 2025/8/15
+     **/
+    public static String addDoorPlanTemplate() throws IOException, InterruptedException {
+        String str = "{\n" +
+                "    \"deviceSerial\": \"FX0889961\",\n" +
+                "    \"payload\": {\n" +
+                "        \"doorRightPlan\": {\n" +
+                "            \"doorNo\": 1,\n" +
+                "            \"planTemplateId\": 1\n" +
+                "        }\n" +
+                "    }\n" +
+                "}";
+        return sendPostRequest(CONFIG_DOOR_PLAN_TEMPLATE_URL, str, setHeaders());
+    }
+
+    /**
+     * @Author SheepHy
+     * @Description 配置门计划模板
+     * @Date 16:51 2025/8/15
+     **/
+    public static String addDoorPlanTemplate1() throws IOException, InterruptedException {
+        String str = "{\n" +
+                "    \"deviceSerial\": \"FX0889961\",\n" +
+                "    \"payload\": {\n" +
+                "        \"planTemplate\": {\n" +
+                "            \"doorPlanTemplateId\": 1,\n" +
+                "            \"enable\": true,\n" +
+                "            \"name\": \"全天常开计划模板\",\n" +
+                "            \"doorWeekId\": 1,\n" +
+                "            \"holidayGroupId\": [\n" +
+                "                1\n" +
+                "            ]\n" +
+                "        }\n" +
+                "    }\n" +
+                "}";
+        return sendPostRequest(CONFIG_DOOR_PLAN_TEMPLATE_URL2, str, setHeaders());
+    }
+
+    public static void main(String[] args) throws IOException, InterruptedException {
+        addUser();
+//        addUser();
+//        addFace();
+//        addUserWeekPlan();
+//        JsonObject root = JsonParser.parseString(HikiotTool.getAppAccessToken()).getAsJsonObject();
+//        JsonObject data;
+//        if (root.get("code").getAsInt() == 0) {
+//            data = root.getAsJsonObject("data");
+//            sendGetRequest(GET_USER_ACCESS_TOKEN_URL + AUTH_CODE + "FX0889961", data.get("appAccessToken").getAsString());
+//        } else {
+//            throw new JeecgBootException("海康API 请求失败: " + root.get("msg").getAsString());
+//        }
     }
 }

+ 46 - 0
national-motion-module-system/national-motion-system-biz/src/main/java/org/jeecg/modules/hikiot/dto/AddDoorWeekPlanDTO.java

@@ -0,0 +1,46 @@
+package org.jeecg.modules.hikiot.dto;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+import lombok.experimental.Accessors;
+
+import java.util.List;
+
+@Data
+@Accessors(chain = true)
+@EqualsAndHashCode(callSuper = false)
+@ToString
+public class AddDoorWeekPlanDTO {
+    private String deviceSerial;
+    private Payload payload;
+
+    @Data
+    @Accessors(chain = true)
+    @EqualsAndHashCode(callSuper = false)
+    @ToString
+    public static class Payload {
+        private WeekPlan weekPlan;
+    }
+    @Data
+    @Accessors(chain = true)
+    @EqualsAndHashCode(callSuper = false)
+    @ToString
+    public static class WeekPlan {
+        private int doorWeekId;
+        private boolean enable;
+        private List<TimeSegment> timeSegment;
+    }
+    @Data
+    @Accessors(chain = true)
+    @EqualsAndHashCode(callSuper = false)
+    @ToString
+    public static class TimeSegment {
+        private int doorWeekTimeSegmentId;
+        private String week;
+        private boolean enable;
+        private String doorStatus = "remainClosed";
+        private String beginTime;
+        private String endTime;
+    }
+}

+ 28 - 0
national-motion-module-system/national-motion-system-biz/src/main/java/org/jeecg/modules/hikiot/dto/AddFaceRequestDTO.java

@@ -0,0 +1,28 @@
+package org.jeecg.modules.hikiot.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+import lombok.experimental.Accessors;
+
+@Data
+@Accessors(chain = true)
+@EqualsAndHashCode(callSuper = false)
+@Schema(description="海康门禁新增/修改人脸DTO")
+@ToString
+public class AddFaceRequestDTO {
+    private String deviceSerial;
+    private Payload payload;
+    @Data
+    public static class Payload {
+        private FaceInfo faceInfo;
+        @Data
+        public static class FaceInfo {
+            private String employeeNo;
+            private String faceURL;
+            private String faceLibType = "blackFD";
+            private String faceDbId = "1";
+        }
+    }
+}

+ 58 - 0
national-motion-module-system/national-motion-system-biz/src/main/java/org/jeecg/modules/hikiot/dto/AddUserRequestDTO.java

@@ -0,0 +1,58 @@
+package org.jeecg.modules.hikiot.dto;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+import lombok.experimental.Accessors;
+
+import java.util.Collections;
+import java.util.List;
+
+@Data
+@Accessors(chain = true)
+@EqualsAndHashCode(callSuper = false)
+@Schema(description="海康门禁添加用户信息DTO")
+@ToString
+public class AddUserRequestDTO {
+    @Schema(description = "设备序列号")
+    private String deviceSerial;
+    @Schema(description = "载荷")
+    private Payload payload;
+    @Data
+    @Accessors(chain = true)
+    @EqualsAndHashCode(callSuper = false)
+    @ToString
+    public static class Payload {
+        @Schema(description = "用户信息")
+        private UserInfo userInfo;
+        @Data
+        @Accessors(chain = true)
+        @EqualsAndHashCode(callSuper = false)
+        @ToString
+        public static class UserInfo {
+            @Schema(description = "员工编号")
+            private String employeeNo;
+            @Schema(description = "姓名")
+            private String name;
+            @Schema(description = "用户类型")
+            private String userType;
+            @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")
+            @Schema(description = "启用开始时间")
+            private String enableBeginTime;
+            @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")
+            @Schema(description = "启用结束时间")
+            private String enableEndTime;
+            @Schema(description = "永是否久有效")
+            private boolean permanentValid;
+            private DoorRightPlan doorRightPlan;
+        }
+
+        @Data
+        public static class DoorRightPlan {
+            private int doorNo = 1;
+            private List<Integer> planTemplateId = Collections.singletonList(1);
+        }
+    }
+}

+ 46 - 0
national-motion-module-system/national-motion-system-biz/src/main/java/org/jeecg/modules/hikiot/dto/AddUserWeekPlanDTO.java

@@ -0,0 +1,46 @@
+package org.jeecg.modules.hikiot.dto;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+import lombok.experimental.Accessors;
+
+import java.util.List;
+
+@Data
+@Accessors(chain = true)
+@EqualsAndHashCode(callSuper = false)
+@ToString
+public class AddUserWeekPlanDTO {
+    private String deviceSerial;
+    private Payload payload;
+
+    @Data
+    @Accessors(chain = true)
+    @EqualsAndHashCode(callSuper = false)
+    @ToString
+    public static class Payload {
+        private UserWeekPlan userWeekPlan;
+    }
+    @Data
+    @Accessors(chain = true)
+    @EqualsAndHashCode(callSuper = false)
+    @ToString
+    public static class UserWeekPlan {
+        private int doorWeekId;
+        private boolean enable;
+        private List<TimeSegment> timeSegment;
+    }
+    @Data
+    @Accessors(chain = true)
+    @EqualsAndHashCode(callSuper = false)
+    @ToString
+    public static class TimeSegment {
+        private int doorWeekTimeSegmentId;
+        private String week;
+        private boolean enable;
+        private String doorStatus;
+        private String beginTime;
+        private String endTime;
+    }
+}

+ 73 - 0
national-motion-module-system/national-motion-system-biz/src/main/java/org/jeecg/modules/system/app/entity/AppHikiotDevice.java

@@ -0,0 +1,73 @@
+package org.jeecg.modules.system.app.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.experimental.Accessors;
+import org.jeecgframework.poi.excel.annotation.Excel;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * @Description: nm_hikiot_device
+ * @Author: jeecg-boot
+ * @Date:   2025-08-15
+ * @Version: V1.0
+ */
+@Data
+@TableName("nm_hikiot_device")
+@Accessors(chain = true)
+@EqualsAndHashCode(callSuper = false)
+@Schema(description="nm_hikiot_device")
+public class AppHikiotDevice implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+	/**主键ID*/
+	@TableId(type = IdType.ASSIGN_ID)
+    @Schema(description = "主键ID")
+    private String id;
+	/**设备序列号*/
+	@Excel(name = "设备序列号", width = 15)
+    @Schema(description = "设备序列号")
+    private String deviceSerial;
+	/**是否添加至海康互联 0、未添加 1、已添加*/
+	@Excel(name = "是否添加至海康互联 0、未添加 1、已添加", width = 15)
+    @Schema(description = "是否添加至海康互联 0、未添加 1、已添加")
+    private Integer isValid;
+	/**添加码*/
+	@Excel(name = "添加码", width = 15)
+    @Schema(description = "添加码")
+    private String validateCode;
+	/**siteId*/
+	@Excel(name = "siteId", width = 15)
+    @Schema(description = "siteId")
+    private String siteId;
+	/**删除标志;删除状态(0-正常,1-已删除)*/
+	@Excel(name = "删除标志;删除状态(0-正常,1-已删除)", width = 15)
+    @Schema(description = "删除标志;删除状态(0-正常,1-已删除)")
+    @TableLogic
+    private Integer delFlag;
+	/**创建人;创建人*/
+    @Schema(description = "创建人;创建人")
+    private String createBy;
+	/**创建时间;创建时间*/
+	@JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd HH:mm:ss")
+    @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
+    @Schema(description = "创建时间;创建时间")
+    private Date createTime;
+	/**更新人;更新人*/
+    @Schema(description = "更新人;更新人")
+    private String updateBy;
+	/**更新时间;更新时间*/
+	@JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd HH:mm:ss")
+    @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
+    @Schema(description = "更新时间;更新时间")
+    private Date updateTime;
+}

+ 14 - 0
national-motion-module-system/national-motion-system-biz/src/main/java/org/jeecg/modules/system/app/mapper/AppHikiotDeviceMapper.java

@@ -0,0 +1,14 @@
+package org.jeecg.modules.system.app.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.jeecg.modules.system.app.entity.AppHikiotDevice;
+
+/**
+ * @Description: nm_hikiot_device
+ * @Author: jeecg-boot
+ * @Date:   2025-08-15
+ * @Version: V1.0
+ */
+public interface AppHikiotDeviceMapper extends BaseMapper<AppHikiotDevice> {
+
+}