|
|
@@ -2,8 +2,13 @@ package cn.iocoder.yudao.module.pms.controller.admin.hikvision;
|
|
|
|
|
|
import cn.hutool.core.util.StrUtil;
|
|
|
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
|
|
+import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
|
|
|
import cn.iocoder.yudao.module.pms.controller.admin.hikvision.vo.HikvisionPtzControlReqVO;
|
|
|
import cn.iocoder.yudao.module.pms.controller.admin.hikvision.vo.HikvisionTreeNodeRespVO;
|
|
|
+import cn.iocoder.yudao.module.system.api.dept.DeptApi;
|
|
|
+import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
|
|
|
+import cn.iocoder.yudao.module.system.api.permission.PermissionApi;
|
|
|
+import cn.iocoder.yudao.module.system.enums.permission.RoleCodeEnum;
|
|
|
import com.alibaba.fastjson.JSON;
|
|
|
import com.alibaba.fastjson.JSONArray;
|
|
|
import com.alibaba.fastjson.JSONObject;
|
|
|
@@ -22,13 +27,21 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
|
|
import org.springframework.web.bind.annotation.RequestParam;
|
|
|
import org.springframework.web.bind.annotation.RestController;
|
|
|
|
|
|
-import javax.validation.Valid;
|
|
|
-import javax.validation.constraints.NotBlank;
|
|
|
+import java.util.ArrayDeque;
|
|
|
import java.util.ArrayList;
|
|
|
import java.util.Collections;
|
|
|
+import java.util.Deque;
|
|
|
import java.util.HashMap;
|
|
|
+import java.util.HashSet;
|
|
|
import java.util.List;
|
|
|
+import java.util.Locale;
|
|
|
+import java.util.LinkedHashMap;
|
|
|
+import java.util.LinkedHashSet;
|
|
|
+import java.nio.charset.StandardCharsets;
|
|
|
import java.util.Map;
|
|
|
+import java.util.Set;
|
|
|
+import javax.validation.Valid;
|
|
|
+import javax.validation.constraints.NotBlank;
|
|
|
|
|
|
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.error;
|
|
|
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
|
|
@@ -37,7 +50,7 @@ import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
|
|
* 海康 Artemis 区域和监控点接口。
|
|
|
*
|
|
|
* <p>
|
|
|
- * 树形列表采用懒加载方式:前端首次传入 {@code -1},后端先获取根区域,再加载根区域的下一层节点;
|
|
|
+ * 树形列表采用懒加载方式:前端首次传入 {@code -1},管理员获取根区域,普通用户获取所属公司区域;
|
|
|
* 用户继续展开某个区域时,传入该区域的 {@code indexCode}。后端分别查询该区域的直接子区域和直属监控点,
|
|
|
* 并将两种资源转换成统一的 {@link HikvisionTreeNodeRespVO} 返回给前端。
|
|
|
* </p>
|
|
|
@@ -56,6 +69,10 @@ public class HikvisionController {
|
|
|
private static final String SUB_REGIONS_ADDRESS = "/api/resource/v2/regions/subRegions";
|
|
|
/** 根据区域编号查询直属资源,不会查询子区域中的资源。 */
|
|
|
private static final String SUB_RESOURCES_ADDRESS = "/api/irds/v2/resource/subResources";
|
|
|
+ /** 按名称及区域(含子孙区域)搜索监控点。 */
|
|
|
+ private static final String CAMERA_SEARCH_ADDRESS = "/api/resource/v2/camera/search";
|
|
|
+ /** 批量获取区域详情,用 parentIndexCode 还原监控点的完整目录。 */
|
|
|
+ private static final String REGION_INFO_ADDRESS = "/api/resource/v1/region/regionCatalog/regionInfo";
|
|
|
/** 获取监控点实时预览地址。 */
|
|
|
private static final String PREVIEW_URL_ADDRESS = "/api/video/v2/cameras/previewURLs";
|
|
|
/** 获取监控点对讲地址。 */
|
|
|
@@ -75,6 +92,8 @@ public class HikvisionController {
|
|
|
|
|
|
/** 包含 host、appKey、appSecret 的海康 SDK 单例配置。 */
|
|
|
private final ArtemisConfig artemisConfig;
|
|
|
+ private final DeptApi deptApi;
|
|
|
+ private final PermissionApi permissionApi;
|
|
|
/** 负责将环境配置的 artemisPath 与具体接口地址拼接成 SDK 请求路径。 */
|
|
|
private final HikvisionArtemisConfiguration artemisConfiguration;
|
|
|
|
|
|
@@ -83,11 +102,13 @@ public class HikvisionController {
|
|
|
*
|
|
|
* <p>
|
|
|
* 当 {@code regionIndexCode=-1} 时,先调用根区域接口获得真实的根区域 {@code indexCode},
|
|
|
- * 再查询根区域的直接子区域和直属监控点,最终返回一个带有 {@code children} 的根节点。
|
|
|
+ * 管理员返回带有 {@code children} 的根节点;普通用户仅返回名称匹配的公司节点及其下一层,
|
|
|
+ * 未匹配到授权公司时返回空列表。
|
|
|
* </p>
|
|
|
*
|
|
|
* <p>
|
|
|
- * 当传入真实区域编号时,只返回该区域的下一层节点,供前端在用户展开区域时追加到对应节点。
|
|
|
+ * 当传入真实区域编号时,先校验该区域位于授权公司子树内,再返回下一层节点。
|
|
|
+ * 超级管理员不受公司范围限制。
|
|
|
* </p>
|
|
|
*
|
|
|
* @param regionIndexCode 当前展开区域的唯一标识;传 {@code -1} 表示加载根区域
|
|
|
@@ -98,6 +119,20 @@ public class HikvisionController {
|
|
|
public CommonResult<List<HikvisionTreeNodeRespVO>> treeChildren(
|
|
|
@Parameter(description = "区域唯一标识;传 -1 加载根节点及其下一层", required = true) @RequestParam("regionIndexCode") @NotBlank(message = "区域编号不能为空") String regionIndexCode) {
|
|
|
try {
|
|
|
+ if (!isSuperAdmin()) {
|
|
|
+ HikvisionTreeNodeRespVO company = queryAllowedCompany();
|
|
|
+ if (company == null) {
|
|
|
+ return "-1".equals(regionIndexCode) ? success(Collections.emptyList())
|
|
|
+ : error(403, "无权查看该监控目录");
|
|
|
+ }
|
|
|
+ if ("-1".equals(regionIndexCode)) {
|
|
|
+ company.setChildren(queryTreeChildren(company.getIndexCode()));
|
|
|
+ return success(Collections.singletonList(company));
|
|
|
+ }
|
|
|
+ if (!containsResource(company.getIndexCode(), regionIndexCode, false)) {
|
|
|
+ return error(403, "无权查看该监控目录");
|
|
|
+ }
|
|
|
+ }
|
|
|
if ("-1".equals(regionIndexCode)) {
|
|
|
// 首次加载时保留根节点,并提前填充根节点的下一层 children。
|
|
|
return success(Collections.singletonList(queryRootWithChildren()));
|
|
|
@@ -109,6 +144,129 @@ public class HikvisionController {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 搜索监控点并返回完整的匹配树,不依赖前端已经展开的节点。
|
|
|
+ * 仅匹配监控点名称;目录只用于保留路径,公共目录合并,不带无关监控点。
|
|
|
+ * 搜索结果已填充全部 children,前端应直接展示,清空关键字后恢复普通懒加载树。
|
|
|
+ */
|
|
|
+ @GetMapping("/tree/search")
|
|
|
+ @Operation(summary = "按监控点名称搜索并返回所属目录树")
|
|
|
+ public CommonResult<List<HikvisionTreeNodeRespVO>> searchTree(
|
|
|
+ @Parameter(description = "监控点名称,模糊匹配,最多32个UTF-8字节;空值恢复初始树")
|
|
|
+ @RequestParam(value = "keyword", required = false) String keyword) {
|
|
|
+ String name = StrUtil.trim(keyword);
|
|
|
+ if (StrUtil.isBlank(name)) {
|
|
|
+ return treeChildren("-1");
|
|
|
+ }
|
|
|
+ if (name.getBytes(StandardCharsets.UTF_8).length > 32) {
|
|
|
+ return error(400, "监控点名称不能超过32个UTF-8字节");
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ HikvisionTreeNodeRespVO root = isSuperAdmin() ? queryRoot() : queryAllowedCompany();
|
|
|
+ if (root == null) {
|
|
|
+ return success(Collections.emptyList());
|
|
|
+ }
|
|
|
+ Map<String, Object> params = new HashMap<>();
|
|
|
+ params.put("name", name);
|
|
|
+ params.put("regionIndexCodes", Collections.singletonList(root.getIndexCode()));
|
|
|
+ params.put("isSubRegion", true);
|
|
|
+ List<JSONObject> cameras = queryAllPages(CAMERA_SEARCH_ADDRESS, params);
|
|
|
+ // 海康返回结果仍需按显示名称核对,避免平台模糊搜索返回无关监控点。
|
|
|
+ // 在全部分页读取完成后过滤,不能因为某一页没有命中而漏掉后续页。
|
|
|
+ String keywordLowerCase = name.toLowerCase(Locale.ROOT);
|
|
|
+ cameras.removeIf(camera -> camera == null || camera.getString("name") == null
|
|
|
+ || !camera.getString("name").toLowerCase(Locale.ROOT).contains(keywordLowerCase));
|
|
|
+ return success(buildSearchTree(root, cameras));
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("搜索海康监控点目录树失败", e);
|
|
|
+ return error(500, "搜索海康监控点目录树失败");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 批量补齐祖先区域,并仅挂载最终能追溯到授权根节点的匹配监控点。 */
|
|
|
+ private List<HikvisionTreeNodeRespVO> buildSearchTree(HikvisionTreeNodeRespVO root,
|
|
|
+ List<JSONObject> cameras) throws Exception {
|
|
|
+ Map<String, HikvisionTreeNodeRespVO> regions = new LinkedHashMap<>();
|
|
|
+ root.setChildren(new ArrayList<>());
|
|
|
+ regions.put(root.getIndexCode(), root);
|
|
|
+ Set<String> pending = new LinkedHashSet<>();
|
|
|
+ for (JSONObject camera : cameras) {
|
|
|
+ String regionCode = camera.getString("regionIndexCode");
|
|
|
+ if (StrUtil.isNotBlank(regionCode)) {
|
|
|
+ pending.add(regionCode);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ Set<String> queried = new HashSet<>();
|
|
|
+ queried.add(root.getIndexCode());
|
|
|
+ // 每轮批量查询一层祖先。已查询集合同时阻止缺失节点重试和异常目录环路。
|
|
|
+ while (!pending.isEmpty()) {
|
|
|
+ pending.removeAll(queried);
|
|
|
+ if (pending.isEmpty()) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ List<String> ids = new ArrayList<>(pending);
|
|
|
+ pending.clear();
|
|
|
+ queried.addAll(ids);
|
|
|
+ for (int start = 0; start < ids.size(); start += MAX_PAGE_SIZE) {
|
|
|
+ List<String> batch = ids.subList(start, Math.min(start + MAX_PAGE_SIZE, ids.size()));
|
|
|
+ JSONObject data = parseArtemisData(invokeArtemis(REGION_INFO_ADDRESS,
|
|
|
+ Collections.singletonMap("indexCodes", batch)));
|
|
|
+ JSONArray list = data.getJSONArray("list");
|
|
|
+ if (list == null) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ for (int i = 0; i < list.size(); i++) {
|
|
|
+ JSONObject region = list.getJSONObject(i);
|
|
|
+ String code = region.getString("indexCode");
|
|
|
+ if (!batch.contains(code) || regions.containsKey(code)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ String parentCode = region.getString("parentIndexCode");
|
|
|
+ regions.put(code, HikvisionTreeNodeRespVO.builder()
|
|
|
+ .indexCode(code).name(region.getString("name"))
|
|
|
+ .nodeType(NODE_TYPE_REGION).parentIndexCode(parentCode)
|
|
|
+ .available(getBooleanOrDefault(region, "available", true))
|
|
|
+ .children(new ArrayList<>()).build());
|
|
|
+ if (StrUtil.isNotBlank(parentCode) && !"-1".equals(parentCode)) {
|
|
|
+ pending.add(parentCode);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ Set<String> attachedRegions = new HashSet<>();
|
|
|
+ Set<String> attachedCameras = new HashSet<>();
|
|
|
+ for (JSONObject camera : cameras) {
|
|
|
+ String cameraCode = camera.getString("indexCode");
|
|
|
+ if (StrUtil.isBlank(cameraCode) || attachedCameras.contains(cameraCode)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ HikvisionTreeNodeRespVO region = regions.get(camera.getString("regionIndexCode"));
|
|
|
+ List<HikvisionTreeNodeRespVO> path = new ArrayList<>();
|
|
|
+ Set<String> visited = new HashSet<>();
|
|
|
+ while (region != null && !root.getIndexCode().equals(region.getIndexCode())
|
|
|
+ && visited.add(region.getIndexCode())) {
|
|
|
+ path.add(region);
|
|
|
+ region = regions.get(region.getParentIndexCode());
|
|
|
+ }
|
|
|
+ if (region == null || !root.getIndexCode().equals(region.getIndexCode())) {
|
|
|
+ continue; // 缺失路径、环路或其他公司的监控点均不得出现在响应里。
|
|
|
+ }
|
|
|
+ HikvisionTreeNodeRespVO parent = root;
|
|
|
+ for (int i = path.size() - 1; i >= 0; i--) {
|
|
|
+ HikvisionTreeNodeRespVO child = path.get(i);
|
|
|
+ if (attachedRegions.add(child.getIndexCode())) {
|
|
|
+ parent.getChildren().add(child);
|
|
|
+ parent.setLeaf(false);
|
|
|
+ }
|
|
|
+ parent = child;
|
|
|
+ }
|
|
|
+ parent.getChildren().add(toCameraNode(camera));
|
|
|
+ parent.setLeaf(false);
|
|
|
+ attachedCameras.add(cameraCode);
|
|
|
+ }
|
|
|
+ return root.getChildren().isEmpty() ? Collections.emptyList() : Collections.singletonList(root);
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* 获取监控点的 WSS 实时预览地址。
|
|
|
*
|
|
|
@@ -127,6 +285,9 @@ public class HikvisionController {
|
|
|
@Parameter(description = "监控点唯一标识", required = true) @RequestParam("cameraIndexCode") @NotBlank(message = "监控点编号不能为空") String cameraIndexCode,
|
|
|
@Parameter(description = "码流类型:0 主码流,1 子码流", example = "0") @RequestParam(value = "streamType", defaultValue = "0") Integer streamType) {
|
|
|
try {
|
|
|
+ if (!canAccessCamera(cameraIndexCode)) {
|
|
|
+ return error(403, "无权访问该监控点");
|
|
|
+ }
|
|
|
Map<String, Object> params = new HashMap<>();
|
|
|
params.put("cameraIndexCode", cameraIndexCode);
|
|
|
params.put("streamType", streamType);
|
|
|
@@ -160,6 +321,9 @@ public class HikvisionController {
|
|
|
public CommonResult<String> getTalkUrl(
|
|
|
@Parameter(description = "监控点唯一标识", required = true) @RequestParam("cameraIndexCode") @NotBlank(message = "监控点编号不能为空") String cameraIndexCode) {
|
|
|
try {
|
|
|
+ if (!canAccessCamera(cameraIndexCode)) {
|
|
|
+ return error(403, "无权访问该监控点");
|
|
|
+ }
|
|
|
Map<String, Object> params = new HashMap<>();
|
|
|
params.put("cameraIndexCode", cameraIndexCode);
|
|
|
params.put("protocol", H5PLAYER_PROTOCOL);
|
|
|
@@ -190,6 +354,9 @@ public class HikvisionController {
|
|
|
@Operation(summary = "控制海康监控点云台")
|
|
|
public CommonResult<Boolean> controlPtz(@Valid @RequestBody HikvisionPtzControlReqVO reqVO) {
|
|
|
try {
|
|
|
+ if (!canAccessCamera(reqVO.getCameraIndexCode())) {
|
|
|
+ return error(403, "无权访问该监控点");
|
|
|
+ }
|
|
|
Map<String, Object> params = new HashMap<>();
|
|
|
params.put("cameraIndexCode", reqVO.getCameraIndexCode());
|
|
|
params.put("action", reqVO.getAction());
|
|
|
@@ -209,6 +376,89 @@ public class HikvisionController {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ /** 超级管理员以系统角色判断,不能通过组织名称获得管理员权限。 */
|
|
|
+ private boolean isSuperAdmin() {
|
|
|
+ Long userId = SecurityFrameworkUtils.getLoginUserId();
|
|
|
+ return userId != null && permissionApi.hasAnyRoles(userId, RoleCodeEnum.SUPER_ADMIN.getCode());
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 从登录组织向上找到最近的公司;不匹配、组织缺失或组织成环时拒绝授权。 */
|
|
|
+ private HikvisionTreeNodeRespVO queryAllowedCompany() throws Exception {
|
|
|
+ if (SecurityFrameworkUtils.getLoginUserId() == null) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ Long deptId = SecurityFrameworkUtils.getLoginUserDeptId();
|
|
|
+ Set<Long> visited = new HashSet<>();
|
|
|
+ while (deptId != null && deptId != 0L && visited.add(deptId)) {
|
|
|
+ DeptRespDTO dept = deptApi.getDeptNoPermission(deptId);
|
|
|
+ if (dept == null) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ if ("1".equals(dept.getType())) {
|
|
|
+ String companyName = dept.getName();
|
|
|
+ if (!"四川瑞都".equals(companyName) && !"瑞恒兴域".equals(companyName)) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ JSONObject root = parseArtemisData(invokeArtemis(ROOT_ADDRESS, Collections.emptyMap()));
|
|
|
+ String rootCode = root.getString("indexCode");
|
|
|
+ if (StrUtil.isBlank(rootCode)) {
|
|
|
+ throw new IllegalStateException("海康根区域响应缺少 indexCode");
|
|
|
+ }
|
|
|
+ for (HikvisionTreeNodeRespVO region : querySubRegions(rootCode)) {
|
|
|
+ if (companyName.equals(region.getName()) && StrUtil.isNotBlank(region.getIndexCode())) {
|
|
|
+ return region;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ deptId = dept.getParentId();
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ private boolean canAccessCamera(String cameraIndexCode) throws Exception {
|
|
|
+ if (isSuperAdmin()) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ HikvisionTreeNodeRespVO company = queryAllowedCompany();
|
|
|
+ return company != null && containsResource(company.getIndexCode(), cameraIndexCode, true);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 只遍历已授权公司的子树,校验客户端传入的区域或监控点编号。
|
|
|
+ * 每次从海康读取归属,避免目录移动后继续使用过期授权;visited 防止异常环路。
|
|
|
+ */
|
|
|
+ private boolean containsResource(String companyCode, String targetCode, boolean camera) throws Exception {
|
|
|
+ if (StrUtil.isBlank(targetCode)) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ Deque<String> pending = new ArrayDeque<>();
|
|
|
+ Set<String> visited = new HashSet<>();
|
|
|
+ pending.add(companyCode);
|
|
|
+ while (!pending.isEmpty()) {
|
|
|
+ String regionCode = pending.removeFirst();
|
|
|
+ if (!visited.add(regionCode)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ if (!camera && regionCode.equals(targetCode)) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ if (camera) {
|
|
|
+ for (HikvisionTreeNodeRespVO node : querySubCameras(regionCode)) {
|
|
|
+ if (targetCode.equals(node.getIndexCode())) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ for (HikvisionTreeNodeRespVO region : querySubRegions(regionCode)) {
|
|
|
+ if (StrUtil.isNotBlank(region.getIndexCode())) {
|
|
|
+ pending.addLast(region.getIndexCode());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* 获取根区域,并将根区域的下一层节点放入 children。
|
|
|
*
|
|
|
@@ -216,22 +466,30 @@ public class HikvisionController {
|
|
|
* @throws Exception Artemis SDK 请求异常,或根区域响应缺少 indexCode
|
|
|
*/
|
|
|
private HikvisionTreeNodeRespVO queryRootWithChildren() throws Exception {
|
|
|
+ HikvisionTreeNodeRespVO root = queryRoot();
|
|
|
+ List<HikvisionTreeNodeRespVO> children = queryTreeChildren(root.getIndexCode());
|
|
|
+ root.setChildren(children);
|
|
|
+ if (root.getLeaf() == null) {
|
|
|
+ root.setLeaf(children.isEmpty());
|
|
|
+ }
|
|
|
+ return root;
|
|
|
+ }
|
|
|
+
|
|
|
+ private HikvisionTreeNodeRespVO queryRoot() throws Exception {
|
|
|
JSONObject root = parseArtemisData(invokeArtemis(ROOT_ADDRESS, Collections.emptyMap()));
|
|
|
String rootIndexCode = root.getString("indexCode");
|
|
|
if (StrUtil.isBlank(rootIndexCode)) {
|
|
|
throw new IllegalStateException("海康根区域响应缺少 indexCode");
|
|
|
}
|
|
|
- List<HikvisionTreeNodeRespVO> children = queryTreeChildren(rootIndexCode);
|
|
|
return HikvisionTreeNodeRespVO.builder()
|
|
|
.indexCode(rootIndexCode)
|
|
|
.name(root.getString("name"))
|
|
|
.nodeType(NODE_TYPE_REGION)
|
|
|
// 根区域接口在部分版本中不返回 leaf,此时根据实际 children 判断。
|
|
|
- .leaf(getBooleanOrDefault(root, "leaf", children.isEmpty()))
|
|
|
+ .leaf(root.getBoolean("leaf"))
|
|
|
.parentIndexCode(root.getString("parentIndexCode"))
|
|
|
// 根区域接口在部分版本中不返回 available;能够成功查询到根区域时默认可用。
|
|
|
.available(getBooleanOrDefault(root, "available", true))
|
|
|
- .children(children)
|
|
|
.build();
|
|
|
}
|
|
|
|
|
|
@@ -295,22 +553,24 @@ public class HikvisionController {
|
|
|
List<JSONObject> cameras = queryAllPages(SUB_RESOURCES_ADDRESS, paramMap);
|
|
|
List<HikvisionTreeNodeRespVO> nodes = new ArrayList<>(cameras.size());
|
|
|
for (JSONObject camera : cameras) {
|
|
|
- nodes.add(HikvisionTreeNodeRespVO.builder()
|
|
|
- .indexCode(camera.getString("indexCode"))
|
|
|
- .name(camera.getString("name"))
|
|
|
- .nodeType(NODE_TYPE_CAMERA)
|
|
|
- .parentIndexCode(camera.getString("regionIndexCode"))
|
|
|
- // 某些平台版本会扩展返回 available;没有该字段时,已查询到的监控点默认可用。
|
|
|
- .available(getBooleanOrDefault(camera, "available", true))
|
|
|
- // 保留平台实际返回的在线状态;未返回时保持 null,不伪造在线或离线状态。
|
|
|
- .onlineStatus(camera.getBoolean("onlineStatus"))
|
|
|
- // camera 不允许继续懒加载,响应中不需要 children 字段。
|
|
|
- .children(null)
|
|
|
- .build());
|
|
|
+ nodes.add(toCameraNode(camera));
|
|
|
}
|
|
|
return nodes;
|
|
|
}
|
|
|
|
|
|
+ private HikvisionTreeNodeRespVO toCameraNode(JSONObject camera) {
|
|
|
+ return HikvisionTreeNodeRespVO.builder()
|
|
|
+ .indexCode(camera.getString("indexCode"))
|
|
|
+ .name(camera.getString("name"))
|
|
|
+ .nodeType(NODE_TYPE_CAMERA)
|
|
|
+ .parentIndexCode(camera.getString("regionIndexCode"))
|
|
|
+ .available(getBooleanOrDefault(camera, "available", true))
|
|
|
+ // 搜索接口未提供在线状态时保持 null。
|
|
|
+ .onlineStatus(camera.getBoolean("onlineStatus"))
|
|
|
+ .children(null)
|
|
|
+ .build();
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* 自动分页查询海康列表接口,确保返回当前层的全部数据。
|
|
|
*
|
|
|
@@ -400,7 +660,7 @@ public class HikvisionController {
|
|
|
* @return 海康接口原始响应字符串
|
|
|
* @throws Exception Artemis SDK 请求异常
|
|
|
*/
|
|
|
- private String invokeArtemis(String address, Object body) throws Exception {
|
|
|
+ String invokeArtemis(String address, Object body) throws Exception {
|
|
|
return ArtemisHttpUtil.doPostStringArtemis(
|
|
|
artemisConfig,
|
|
|
artemisConfiguration.buildArtemisPath(address),
|