package com.ruoyi.web.controller.tool;
|
|
import cn.hutool.crypto.SecureUtil;
|
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;
|
|
/**
|
* 集群版对接中台时,将基础信息中台查询结果适配成当前页面字段。
|
*
|
* <p>目的:前端页面已绑定本系统字段名,直接透传中台字段会导致表格空列。</p>
|
* <p>影响范围:部门、区域、员工、访客机、门禁设备的查询和详情接口。</p>
|
* <p>注意:访客记录暂无明确中台接口映射,继续保持只读;入园承诺和版本更新属于本地维护模块。</p>
|
*/
|
public final class MiddlePlatformDataQuery {
|
|
/**
|
* 目的:工具类只提供静态中台查询适配方法,禁止实例化。
|
*/
|
private MiddlePlatformDataQuery() {
|
}
|
|
public static String login(String username, String password) throws IOException {
|
Map<String, Object> body = new LinkedHashMap<>();
|
body.put("username", username);
|
body.put("password", SecureUtil.sha256(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<String, String> 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<String, Object> params = new LinkedHashMap<>();
|
Object data = data(BaseDataApiUtil.request(BaseDataApiUtil.Api.GET_REGION_TREE, params));
|
String keyword = request == null ? null : request.getAreaName();
|
List<Map<String, Object>> list = mapRegionTree(data, keyword);
|
return list;
|
}
|
|
/**
|
* 目的:按区域 ID 查询中台区域详情,供区域详情弹窗复用。
|
*/
|
public static Object areaDetail(Object areaId) throws IOException {
|
Map<String, Object> 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<Map<String, Object>> 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<String, Object> 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<String, Object> 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 (StringUtils.isNotBlank(query.getDeptId())) {
|
params.put("orgId", query.getDeptId().trim());
|
}
|
return mapPage(data(BaseDataApiUtil.request(BaseDataApiUtil.Api.GET_PERSON_LIST, params)), true);
|
}
|
|
/**
|
* 目的:按人员 ID 查询中台人员详情,保持员工列表和详情数据源一致。
|
*/
|
public static Object employeeDetail(Object employeeId) throws IOException {
|
Map<String, Object> 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, String areaId, String deviceName,
|
String deviceId, String onlineStatus, String idName) throws IOException {
|
Map<String, Object> 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 (StringUtils.isNotBlank(areaId)) {
|
// 中台设备接口按 mapIds 过滤区域,本地页面只传单个 areaId。
|
params.put("mapIds", Collections.singletonList(areaId.trim()));
|
}
|
Map<String, Object> stringObjectMap = mapPage(data(BaseDataApiUtil.request(BaseDataApiUtil.Api.POST_HIK_DEVICE_LIST, params)), false, idName);
|
return stringObjectMap;
|
}
|
|
/**
|
* 目的:调用中台设备详情接口,并按页面类型映射 machineId 或 doorId。
|
*/
|
private static Object deviceDetail(Object deviceId, String idName) throws IOException {
|
Map<String, Object> params = new LinkedHashMap<>();
|
params.put("id", deviceId);
|
return mapDevice(data(BaseDataApiUtil.request(BaseDataApiUtil.Api.GET_HIK_DEVICE, params)), idName);
|
}
|
|
/**
|
* 目的:分页转换入口,默认不需要设备主键字段名。
|
*/
|
private static Map<String, Object> mapPage(Object data, boolean person) {
|
return mapPage(data, person, null);
|
}
|
|
/**
|
* 目的:把中台分页结构转换为前端 getPageRows/getPageTotal 可识别的结构。
|
*/
|
private static Map<String, Object> mapPage(Object data, boolean person, String deviceIdName) {
|
JSONObject page = asObject(data);
|
JSONArray records = toArray(page.get("records"));
|
List<Map<String, Object>> rows = new ArrayList<>();
|
for (Object record : records) {
|
rows.add(person ? mapPerson(record) : mapDevice(record, deviceIdName));
|
}
|
|
// 保持 records/total/current/size 结构,兼容前端 getPageRows/getPageTotal。
|
Map<String, Object> 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<Map<String, Object>> mapRegionTree(Object data, String keyword) {
|
List<Map<String, Object>> result = new ArrayList<>();
|
for (Object item : toArray(data)) {
|
Map<String, Object> region = mapRegion(item, keyword);
|
if (region != null) {
|
result.add(region);
|
}
|
}
|
return result;
|
}
|
|
/**
|
* 目的:转换单个中台区域节点为本地访问区域节点。
|
*/
|
private static Map<String, Object> mapRegion(Object source, String keyword) {
|
JSONObject item = asObject(source);
|
List<Map<String, Object>> 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<String, Object> 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<Map<String, Object>> rows, Object parentId, String keyword) {
|
for (Object item : toArray(data)) {
|
JSONObject group = asObject(item);
|
Map<String, Object> 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<String, Object> mapGroup(Object source, Object fallbackParentId) {
|
JSONObject item = asObject(source);
|
Map<String, Object> 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<String, Object> mapPerson(Object source) {
|
JSONObject item = asObject(source);
|
Map<String, Object> 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<String, Object> mapDevice(Object source, String idName) {
|
JSONObject item = asObject(source);
|
Map<String, Object> 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<String, Object> 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";
|
}
|
}
|