package com.ruoyi.web.controller.tool; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import com.ruoyi.common.core.domain.entity.SysDept; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.system.dto.SysDoorDevicePageRequest; import com.ruoyi.system.dto.SysEmployeePageRequest; import com.ruoyi.system.dto.SysVisitorMachinePageRequest; import com.ruoyi.system.dto.SysVisitAreaTreeRequest; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; /** * 集群版对接中台时,将基础信息中台查询结果适配成当前页面字段。 * *

目的:前端页面已绑定本系统字段名,直接透传中台字段会导致表格空列。

*

影响范围:部门、区域、员工、访客机、门禁设备的查询和详情接口。

*

注意:访客记录、入园承诺、版本更新暂无明确基础信息中台接口映射,仍由只读拦截保证不能写。

*/ public final class MiddlePlatformDataQuery { /** * 目的:工具类只提供静态中台查询适配方法,禁止实例化。 */ private MiddlePlatformDataQuery() { } public static String login(String username, String password) throws IOException { Map body = new LinkedHashMap<>(); body.put("username", username); body.put("password", password); JSONObject root = JSON.parseObject(BaseDataApiUtil.requestJson(BaseDataApiUtil.Api.POST_AUTH_USER_LOGIN, body)); if (root == null) { throw new IOException("middle platform login returned empty response"); } if (Boolean.FALSE.equals(root.getBoolean("success"))) { if (isNotFoundResponse(root)) { return null; } throw new IOException(StringUtils.defaultIfEmpty(root.getString("description"), "middle platform login failed")); } String token = root.getString("data"); if (!StringUtils.isNotBlank(token)) { throw new IOException("middle platform login returned empty token"); } return token; } public static void logout(String token) throws IOException { if (!StringUtils.isNotBlank(token)) { return; } Map headers = new LinkedHashMap<>(); headers.put("Authorization", "Bearer " + token); BaseDataApiUtil.request(BaseDataApiUtil.Api.POST_AUTH_USER_LOGOUT, null, Collections.emptyMap(), headers); } /** * 目的:查询中台区域树,并转换为访问区域页面使用的 areaId/areaName/children 字段。 */ public static Object areaTree(SysVisitAreaTreeRequest request) throws IOException { Map params = new LinkedHashMap<>(); Object data = data(BaseDataApiUtil.request(BaseDataApiUtil.Api.GET_REGION_TREE, params)); String keyword = request == null ? null : request.getAreaName(); return mapRegionTree(data, keyword); } /** * 目的:按区域 ID 查询中台区域详情,供区域详情弹窗复用。 */ public static Object areaDetail(Object areaId) throws IOException { Map params = new LinkedHashMap<>(); params.put("id", areaId); return mapRegion(data(BaseDataApiUtil.request(BaseDataApiUtil.Api.GET_REGION_DETAILS, params)), null); } /** * 目的:查询中台组织树并扁平化为若依部门列表,兼容前端 handleTree。 */ public static Object deptList(SysDept dept) throws IOException { Object data = data(BaseDataApiUtil.request(BaseDataApiUtil.Api.GET_GROUP_TREE)); List> rows = new ArrayList<>(); flattenGroup(data, rows, null, dept == null ? null : dept.getDeptName()); return rows; } /** * 目的:按组织 ID 查询中台组织详情,并适配为 SysDept 页面字段。 */ public static Object deptDetail(Object deptId) throws IOException { Map params = new LinkedHashMap<>(); params.put("id", deptId); return mapGroup(data(BaseDataApiUtil.request(BaseDataApiUtil.Api.GET_GROUP_GET, params)), null); } /** * 目的:查询中台人员分页数据,并转换为员工管理页面字段。 */ public static Object employeePage(SysEmployeePageRequest request) throws IOException { SysEmployeePageRequest query = request == null ? new SysEmployeePageRequest() : request; Map params = new LinkedHashMap<>(); params.put("current", defaultPageNum(query.getPageNum())); params.put("size", defaultPageSize(query.getPageSize())); // 中台人员列表只有 keyword 模糊查询,合并本地姓名、编号、电话、岗位等查询条件。 putIfNotBlank(params, "keyword", firstNotBlank(query.getEmployeeName(), query.getEmployeeNo(), query.getContactPhone(), query.getPosition())); if (query.getDeptId() != null) { params.put("orgId", query.getDeptId()); } return mapPage(data(BaseDataApiUtil.request(BaseDataApiUtil.Api.GET_PERSON_LIST, params)), true); } /** * 目的:按人员 ID 查询中台人员详情,保持员工列表和详情数据源一致。 */ public static Object employeeDetail(Object employeeId) throws IOException { Map params = new LinkedHashMap<>(); params.put("id", employeeId); return mapPerson(data(BaseDataApiUtil.request(BaseDataApiUtil.Api.GET_PERSON, params))); } /** * 目的:查询中台设备分页数据,并按访客机页面字段返回。 */ public static Object visitorMachinePage(SysVisitorMachinePageRequest request) throws IOException { SysVisitorMachinePageRequest query = request == null ? new SysVisitorMachinePageRequest() : request; return devicePage(query.getPageNum(), query.getPageSize(), query.getAreaId(), query.getDeviceName(), query.getDeviceId(), query.getOnlineStatus(), "machine"); } /** * 目的:查询中台设备分页数据,并按门禁设备页面字段返回。 */ public static Object doorDevicePage(SysDoorDevicePageRequest request) throws IOException { SysDoorDevicePageRequest query = request == null ? new SysDoorDevicePageRequest() : request; return devicePage(query.getPageNum(), query.getPageSize(), query.getAreaId(), query.getDeviceName(), query.getDeviceId(), query.getOnlineStatus(), "door"); } /** * 目的:按设备标识查询访客机详情,并补齐 machineId 字段。 */ public static Object visitorMachineDetail(Object machineId) throws IOException { return deviceDetail(machineId, "machine"); } /** * 目的:按设备标识查询门禁设备详情,并补齐 doorId 字段。 */ public static Object doorDeviceDetail(Object doorId) throws IOException { return deviceDetail(doorId, "door"); } /** * 目的:组装中台设备分页查询参数,并按设备类型映射主键字段名。 */ private static Object devicePage(Integer pageNum, Integer pageSize, Object areaId, String deviceName, String deviceId, String onlineStatus, String idName) throws IOException { Map params = new LinkedHashMap<>(); params.put("current", defaultPageNum(pageNum)); params.put("size", defaultPageSize(pageSize)); putIfNotBlank(params, "name", firstNotBlank(deviceName, deviceId)); putIfNotBlank(params, "onlineStatus", toMiddleOnlineStatus(onlineStatus)); if (areaId != null) { // 中台设备接口按 mapIds 过滤区域,本地页面只传单个 areaId。 params.put("mapIds", Collections.singletonList(areaId)); } return mapPage(data(BaseDataApiUtil.request(BaseDataApiUtil.Api.POST_HIK_DEVICE_LIST, params)), false, idName); } /** * 目的:调用中台设备详情接口,并按页面类型映射 machineId 或 doorId。 */ private static Object deviceDetail(Object deviceId, String idName) throws IOException { Map params = new LinkedHashMap<>(); params.put("id", deviceId); return mapDevice(data(BaseDataApiUtil.request(BaseDataApiUtil.Api.GET_HIK_DEVICE, params)), idName); } /** * 目的:分页转换入口,默认不需要设备主键字段名。 */ private static Map mapPage(Object data, boolean person) { return mapPage(data, person, null); } /** * 目的:把中台分页结构转换为前端 getPageRows/getPageTotal 可识别的结构。 */ private static Map mapPage(Object data, boolean person, String deviceIdName) { JSONObject page = asObject(data); JSONArray records = toArray(page.get("records")); List> rows = new ArrayList<>(); for (Object record : records) { rows.add(person ? mapPerson(record) : mapDevice(record, deviceIdName)); } // 保持 records/total/current/size 结构,兼容前端 getPageRows/getPageTotal。 Map result = new LinkedHashMap<>(); result.put("records", rows); result.put("total", firstValue(page, "total", "totalCount", "dataSize")); result.put("current", page.get("current")); result.put("size", page.get("size")); return result; } /** * 目的:递归转换中台区域树,并支持区域名称关键字过滤。 */ private static List> mapRegionTree(Object data, String keyword) { List> result = new ArrayList<>(); for (Object item : toArray(data)) { Map region = mapRegion(item, keyword); if (region != null) { result.add(region); } } return result; } /** * 目的:转换单个中台区域节点为本地访问区域节点。 */ private static Map mapRegion(Object source, String keyword) { JSONObject item = asObject(source); List> children = mapRegionTree(item.get("children"), keyword); String areaName = item.getString("name"); boolean matched = !StringUtils.isNotBlank(keyword) || StringUtils.contains(areaName, keyword); // 搜索区域时保留命中的父级节点,避免树结构断层。 if (!matched && children.isEmpty()) { return null; } Map result = new LinkedHashMap<>(); result.put("areaId", item.get("id")); result.put("parentId", defaultParentId(item.get("parentId"))); result.put("parentName", item.get("parentName")); result.put("areaCode", item.get("id")); result.put("areaName", areaName); result.put("areaDesc", firstValue(item, "remark", "type")); result.put("orderNum", item.get("level")); result.put("createTime", item.get("createTime")); result.put("children", children); return result; } /** * 目的:将中台组织树拍平,供若依部门 handleTree 重新组装层级。 */ private static void flattenGroup(Object data, List> rows, Object parentId, String keyword) { for (Object item : toArray(data)) { JSONObject group = asObject(item); Map mapped = mapGroup(group, parentId); if (!StringUtils.isNotBlank(keyword) || StringUtils.contains(String.valueOf(mapped.get("deptName")), keyword)) { rows.add(mapped); } // 中台组织树可能没有 parentId,递归时用父节点 id 补齐,供前端 handleTree 还原层级。 flattenGroup(group.get("children"), rows, mapped.get("deptId"), keyword); } } /** * 目的:转换单个中台组织节点为本地部门字段。 */ private static Map mapGroup(Object source, Object fallbackParentId) { JSONObject item = asObject(source); Map result = new LinkedHashMap<>(); Object id = item.get("id"); Object parentId = firstValue(item, "parentId", "pid", "parentID", "parent"); if (parentId == null) { parentId = fallbackParentId; } result.put("deptId", id); result.put("parentId", defaultParentId(parentId)); result.put("deptName", firstValue(item, "name", "orgName")); result.put("orderNum", firstValue(item, "sortNumber", "sort", "orderNum")); result.put("status", "0"); result.put("children", Collections.emptyList()); return result; } /** * 目的:转换单个中台人员记录为员工管理页面字段。 */ private static Map mapPerson(Object source) { JSONObject item = asObject(source); Map result = new LinkedHashMap<>(); Object id = item.get("id"); result.put("employeeId", id); result.put("deptId", item.get("orgId")); result.put("deptName", item.get("orgName")); result.put("employeeName", item.get("name")); result.put("employeeNo", firstValue(item, "personNumber", "jobCode")); result.put("contactPhone", item.get("phone")); result.put("position", item.get("position")); result.put("positionName", item.get("position")); result.put("cardNo", item.get("certNo")); result.put("faceImage", item.get("faceId")); result.put("enabled", !"1".equals(String.valueOf(item.get("isDelete")))); result.put("createTime", item.get("createTime")); return result; } /** * 目的:转换单个中台设备记录为访客机或门禁设备页面字段。 */ private static Map mapDevice(Object source, String idName) { JSONObject item = asObject(source); Map result = new LinkedHashMap<>(); Object id = firstValue(item, "indexCode", "id"); result.put(idName == null ? "deviceKey" : idName + "Id", id); result.put("areaId", item.get("mapId")); result.put("areaName", firstValue(item, "regionName", "mapName")); result.put("deviceName", item.get("name")); result.put("brand", item.get("deviceType")); result.put("deviceId", id); result.put("enabled", !"1".equals(String.valueOf(item.get("isDelete")))); result.put("onlineStatus", toLocalOnlineStatus(item.get("onlineStatus"))); result.put("remark", firstValue(item, "remark", "ipAddress")); result.put("createTime", item.get("createTime")); return result; } /** * 目的:解析中台标准响应,失败时统一转成 IOException。 */ private static Object data(String content) throws IOException { JSONObject root = JSON.parseObject(content); if (root == null) { return null; } // 中台标准响应 success=false 时转成 IOException,由 controller 统一返回业务错误。 if (Boolean.FALSE.equals(root.getBoolean("success"))) { throw new IOException(StringUtils.defaultIfEmpty(root.getString("description"), "中台接口返回失败")); } return root.get("data"); } private static boolean isNotFoundResponse(JSONObject root) { Object code = firstValue(root, "errorCode", "code"); String codeText = code == null ? "" : String.valueOf(code); String description = StringUtils.defaultString(root.getString("description")).toLowerCase(); return "404".equals(codeText) || description.contains("not found") || description.contains("\u4e0d\u5b58\u5728") || description.contains("\u672a\u67e5\u8be2") || description.contains("\u672a\u627e\u5230"); } /** * 目的:把任意响应片段安全转换为 JSONObject,减少空值判断分散。 */ private static JSONObject asObject(Object value) { if (value instanceof JSONObject) { return (JSONObject) value; } if (value == null) { return new JSONObject(); } return JSON.parseObject(JSON.toJSONString(value)); } /** * 目的:把中台可能返回的数组、集合或单对象统一转换为 JSONArray。 */ private static JSONArray toArray(Object value) { if (value instanceof JSONArray) { return (JSONArray) value; } JSONArray array = new JSONArray(); if (value == null) { return array; } if (value instanceof Iterable) { for (Object item : (Iterable) value) { array.add(item); } return array; } array.add(value); return array; } /** * 目的:规范页码默认值,避免中台接口收到无效页码。 */ private static int defaultPageNum(Integer pageNum) { return pageNum == null || pageNum < 1 ? 1 : pageNum; } /** * 目的:规范分页大小默认值,避免中台接口收到无效分页大小。 */ private static int defaultPageSize(Integer pageSize) { return pageSize == null || pageSize < 1 ? 10 : pageSize; } /** * 目的:将空父节点统一转换为 0,兼容若依根部门和根区域规则。 */ private static Object defaultParentId(Object parentId) { return parentId == null || "".equals(String.valueOf(parentId)) ? 0 : parentId; } /** * 目的:只写入非空查询参数,避免空字符串影响中台筛选。 */ private static void putIfNotBlank(Map params, String key, String value) { if (StringUtils.isNotBlank(value)) { params.put(key, value); } } /** * 目的:从多个本地查询条件中取第一个有效值映射到中台 keyword/name。 */ private static String firstNotBlank(String... values) { if (values == null) { return null; } for (String value : values) { if (StringUtils.isNotBlank(value)) { return value; } } return null; } /** * 目的:按候选字段顺序读取中台响应,兼容不同接口字段命名。 */ private static Object firstValue(JSONObject item, String... keys) { for (String key : keys) { Object value = item.get(key); if (value != null && !"".equals(String.valueOf(value))) { return value; } } return null; } /** * 目的:将本地 online/offline 查询值转换为中台 1/0 查询值。 */ private static String toMiddleOnlineStatus(String value) { if (!StringUtils.isNotBlank(value)) { return null; } if ("online".equalsIgnoreCase(value)) { return "1"; } if ("offline".equalsIgnoreCase(value)) { return "0"; } return value; } /** * 目的:将中台 1/0 在线状态转换为本地数据库使用的在线状态枚举。 */ private static String toLocalOnlineStatus(Object value) { return "1".equals(String.valueOf(value)) || "online".equalsIgnoreCase(String.valueOf(value)) ? "ONLINE" : "OFFLINE"; } }