package com.ruoyi.http.service.impl; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONArray; import com.alibaba.fastjson2.JSONObject; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.http.HttpUtils; import com.ruoyi.http.service.RealTimeEnergyConsumptionService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.TimeUnit; /** * @author :yys * @date : 2026/1/27 16:02 */ @Service @Slf4j public class RealTimeEnergyConsumptionServiceImpl implements RealTimeEnergyConsumptionService { private static final long REMOTE_CACHE_TTL_SECONDS_30 = 30L; private static final String URL = "https://new.e-elitech.cn/api/data-api"; private static final String TOKEN_URL = "/elitechAccess/getToken"; private static final String REAL_TIME_URL = "/elitechAccess/v2/getRealTimeData"; private static final String REAL_HISTORY_URL = URL + "/elitechAccess/v2/getHistoryData"; private static final String KET_ID = "75804708"; private static final String KEY_SECRET = "xTUGToozKpYgUPqTsZzB"; private static final String USER_NAME = "\u7528\u623730773662"; private static final String PASS_WORD = "y17775163675"; private static final String REAL_TIME_CACHE_PREFIX = "JCLY:REAL_TIME:"; private static final String HISTORY_CACHE_PREFIX = "JCLY:HISTORY:"; private static final String TOKEN_CACHE_KEY = "JCLY_TOKEN:"; private static final String STATUS_KEY = "status"; private static final String STATUS_MESSAGE_KEY = "statusMessage"; private static final String STATUS_ONLINE = "在线"; private static final String STATUS_OFFLINE = "offline"; private static final String STATUS_ERROR = "error"; @Autowired private RedisTemplate redisTemplate; private static JSONObject getProbeParam(JSONArray paramList, String targetCode) { if (paramList == null) { return new JSONObject(); } for (int i = 0; i < paramList.size(); i++) { JSONObject paramObj = paramList.getJSONObject(i); if (targetCode.equals(paramObj.getString("paramCode"))) { return paramObj; } } return new JSONObject(); } public List> getHistoryData(String guid, long startTime, long endTime) { List> resultList = new ArrayList<>(); String token = getToken(); try { String historyData = requestHistoryData(token, guid, startTime, endTime); JSONObject resultObj = JSON.parseObject(historyData); if (resultObj == null) { resultList.add(buildStatusItem(guid, STATUS_ERROR, "history response is empty")); return resultList; } Integer code = resultObj.getInteger("code"); if (!Integer.valueOf(0).equals(code)) { resultList.add(buildStatusItem(guid, resolveStatusByCodeOrMessage(code, resultObj.getString("msg")), resultObj.getString("msg"))); return resultList; } JSONArray historyList = resultObj.getJSONArray("data"); if (historyList == null || historyList.isEmpty()) { resultList.add(buildStatusItem(guid, STATUS_OFFLINE, "no history data")); return resultList; } for (int i = 0; i < historyList.size(); i++) { JSONObject historyObj = historyList.getJSONObject(i); Map historyItem = new HashMap<>(); historyItem.put("guid", firstNonBlank( historyObj.getString("deviceGuid"), historyObj.getString("guid"), guid )); historyItem.put("subUId", stringValue(historyObj.get("subUid"))); historyItem.put("monitorTimeStamp", stringValue(historyObj.get("monitorTimeStamp"))); historyItem.put("monitorTimeStr", historyObj.getString("monitorTimeStr")); historyItem.put("position", historyObj.getString("position")); historyItem.put("address", historyObj.getString("address")); historyItem.put(STATUS_KEY, resolveStatusByAlarmState(historyObj.get("alarmState"), STATUS_ONLINE)); JSONArray paramList = historyObj.getJSONArray("data"); if (paramList != null && !paramList.isEmpty()) { for (int j = 0; j < paramList.size(); j++) { JSONObject paramObj = paramList.getJSONObject(j); String paramCode = paramObj.getString("paramCode"); String value = paramObj.getString("value"); String unitCode = paramObj.getString("unitCode"); String fullValue = concatValueWithUnit(value, unitCode); if ("0100".equals(paramCode)) { historyItem.put("light", fullValue); } else if ("0110".equals(paramCode)) { historyItem.put("temperature", fullValue); } else if ("0120".equals(paramCode)) { historyItem.put("humidity", fullValue); } else if ("0130".equals(paramCode)) { historyItem.put("co2", fullValue); } else if ("0042".equals(paramCode)) { historyItem.put("battery", fullValue); } else if (paramCode != null) { historyItem.put(paramCode, fullValue); } } } resultList.add(historyItem); } return resultList; } catch (Exception ex) { log.error("history data parse/request failed, guid={}", guid, ex); resultList.add(buildStatusItem(guid, STATUS_ERROR, ex.getMessage())); return resultList; } } public List> getRealData(List guidList) { log.info("start get real data"); List> listMaps = new ArrayList<>(); if (guidList == null || guidList.isEmpty()) { return listMaps; } String token = getToken(); try { String realTimeData = getRealTimeData(token, guidList); JSONObject batchResp = JSON.parseObject(realTimeData); Integer code = batchResp == null ? null : batchResp.getInteger("code"); if (Integer.valueOf(0).equals(code)) { parseRealDataResponse(batchResp, guidList, listMaps); return listMaps; } log.warn("batch getRealData failed, fallback one by one. code={}, msg={}", code, batchResp == null ? null : batchResp.getString("msg")); } catch (Exception ex) { log.error("batch getRealData exception, fallback one by one", ex); } for (String guid : guidList) { listMaps.add(fetchSingleDeviceRealData(token, guid)); } return listMaps; } public static void main(String[] args) { System.out.println(); } public String getToken() { String cachedToken = sanitizeToken(redisTemplate.opsForValue().get(TOKEN_CACHE_KEY)); if (cachedToken != null) { return cachedToken; } Map param = new HashMap<>(); param.put("keyId", KET_ID); param.put("keySecret", KEY_SECRET); param.put("userName", USER_NAME); param.put("password", PASS_WORD); log.info("request token payload: {}", JSON.toJSONString(param)); String result = HttpUtils.sendPostJson(URL + TOKEN_URL, JSON.toJSONString(param)); log.info("request token response: {}", result); Map map = JSON.parseObject(result, Map.class); if (Integer.valueOf(0).equals(map.get("code"))) { Object token = map.get("data"); log.info("token:{}", token); redisTemplate.opsForValue().set(TOKEN_CACHE_KEY, token.toString(), 60 * 60 * 12); return token.toString(); } log.error("get token failed, response={}", result); return null; } private String sanitizeToken(String token) { if (token == null) { return null; } String cleanedToken = token.replace("\0", "").trim(); return cleanedToken.isEmpty() ? null : cleanedToken; } private String firstNonBlank(String... values) { for (String value : values) { if (value != null && !value.trim().isEmpty()) { return value; } } return null; } private String stringValue(Object value) { return value == null ? null : String.valueOf(value); } private String refreshToken() { redisTemplate.delete(TOKEN_CACHE_KEY); return getToken(); } private boolean isUnauthorizedException(Exception ex) { if (ex == null || ex.getMessage() == null) { return false; } String msg = ex.getMessage().toLowerCase(); return msg.contains("401") || msg.contains("unauthorized"); } private boolean isUnauthorizedResponse(String result) { if (result == null || result.trim().isEmpty()) { return false; } try { JSONObject obj = JSON.parseObject(result); if (obj == null) { return false; } Integer code = obj.getInteger("code"); if (code != null && (code == 401 || code == 403)) { return true; } String msg = obj.getString("msg"); return msg != null && msg.toLowerCase().contains("unauthorized"); } catch (Exception ignore) { return result.contains("401"); } } private String requestWithTokenRetry(String url, String payload, String token, String scene) { String usedToken = sanitizeToken(token); if (usedToken == null) { usedToken = refreshToken(); } try { String result = HttpUtils.sendPostJson(url, payload, usedToken); if (!isUnauthorizedResponse(result) && StringUtils.isNotEmpty(result)) { return result; } log.warn("{} got unauthorized response, refresh token and retry once", scene); } catch (Exception ex) { if (!isUnauthorizedException(ex)) { throw ex; } log.warn("{} got 401 exception, refresh token and retry once", scene); } String newToken = refreshToken(); if (newToken == null) { throw new RuntimeException("refresh token failed"); } return HttpUtils.sendPostJson(url, payload, newToken); } private String concatValueWithUnit(String value, String unitCode) { if (value == null) { return null; } return value + (unitCode == null ? "" : unitCode); } private Map buildStatusItem(String guid, String status, String statusMessage) { Map result = new HashMap<>(); result.put("guid", guid); result.put(STATUS_KEY, status); if (statusMessage != null && !statusMessage.trim().isEmpty()) { result.put(STATUS_MESSAGE_KEY, statusMessage); } return result; } private String resolveStatusByCodeOrMessage(Integer code, String message) { if (code != null && (code == 5 || code == 1003 || code == 1004)) { return STATUS_OFFLINE; } if (message == null) { return STATUS_ERROR; } String lowered = message.toLowerCase(); if (lowered.contains("offline") || message.contains("离线")) { return STATUS_OFFLINE; } return STATUS_ERROR; } private void parseRealDataResponse(JSONObject responseObj, List requestedGuids, List> output) { JSONArray deviceList = responseObj.getJSONArray("data"); Set returnedGuids = new HashSet<>(); if (deviceList != null) { for (int deviceIndex = 0; deviceIndex < deviceList.size(); deviceIndex++) { JSONObject deviceObj = deviceList.getJSONObject(deviceIndex); Map deviceData = parseSingleDevice(deviceObj, null); String guid = deviceData.get("guid"); if (guid != null) { returnedGuids.add(guid); } output.add(deviceData); } } for (String requestGuid : requestedGuids) { if (!returnedGuids.contains(requestGuid)) { output.add(buildStatusItem(requestGuid, STATUS_OFFLINE, "device not returned by remote API")); } } } private Map fetchSingleDeviceRealData(String token, String guid) { try { String singleResult = getRealTimeData(token, Collections.singletonList(guid)); JSONObject singleObj = JSON.parseObject(singleResult); if (singleObj == null) { return buildStatusItem(guid, STATUS_ERROR, "single device response is empty"); } Integer code = singleObj.getInteger("code"); if (!Integer.valueOf(0).equals(code)) { return buildStatusItem(guid, resolveStatusByCodeOrMessage(code, singleObj.getString("msg")), singleObj.getString("msg")); } JSONArray dataList = singleObj.getJSONArray("data"); if (dataList == null || dataList.isEmpty()) { return buildStatusItem(guid, STATUS_OFFLINE, "single device response has no data"); } return parseSingleDevice(dataList.getJSONObject(0), guid); } catch (Exception ex) { log.error("single getRealData failed, guid={}", guid, ex); return buildStatusItem(guid, STATUS_ERROR, ex.getMessage()); } } private Map parseSingleDevice(JSONObject deviceObj, String fallbackGuid) { Map deviceData = new HashMap<>(); String deviceGuid = firstNonBlank( deviceObj.getString("deviceGuid"), deviceObj.getString("guid"), deviceObj.getString("devGuid"), deviceObj.getString("sn"), fallbackGuid ); if (deviceGuid != null) { deviceData.put("guid", deviceGuid); } JSONArray paramList = deviceObj.getJSONArray("data"); if (paramList == null || paramList.isEmpty()) { deviceData.put(STATUS_KEY, STATUS_OFFLINE); deviceData.put(STATUS_MESSAGE_KEY, "device data is empty"); return deviceData; } deviceData.put(STATUS_KEY, resolveStatusByAlarmState(deviceObj.get("alarmState"), STATUS_ONLINE)); for (int i = 0; i < paramList.size(); i++) { JSONObject paramObj = paramList.getJSONObject(i); String code = paramObj.getString("paramCode"); String value = paramObj.getString("value"); String unitCode = paramObj.getString("unitCode"); String fullValue = concatValueWithUnit(value, unitCode); if ("0100".equals(code)) { deviceData.put("light", fullValue); } else if ("0110".equals(code)) { deviceData.put("temperature", fullValue); } else if ("0120".equals(code)) { deviceData.put("humidity", fullValue); } else if ("0130".equals(code)) { deviceData.put("co2", fullValue); } else if ("0042".equals(code)) { deviceData.put("battery", fullValue); } } return deviceData; } private String resolveStatusByAlarmState(Object alarmState, String defaultStatus) { if (alarmState == null) { return defaultStatus; } if (alarmState instanceof Boolean) { return (Boolean) alarmState ? STATUS_OFFLINE : STATUS_ONLINE; } String normalized = String.valueOf(alarmState).trim().toLowerCase(); if ("true".equals(normalized) || "1".equals(normalized)) { return STATUS_OFFLINE; } if ("false".equals(normalized) || "0".equals(normalized)) { return STATUS_ONLINE; } return defaultStatus; } public String getRealTimeData(String token, List guidList) { Map param = new HashMap<>(); param.put("keyId", KET_ID); param.put("keySecret", KEY_SECRET); param.put("deviceGuids", guidList); log.info("request realtime payload: {}", JSON.toJSONString(param)); String cacheKey = REAL_TIME_CACHE_PREFIX + JSON.toJSONString(param); String cachedResult = sanitizeToken(redisTemplate.opsForValue().get(cacheKey)); if (cachedResult != null) { log.info("hit realtime cache: {}", cacheKey); return cachedResult; } String result = requestWithTokenRetry(URL + REAL_TIME_URL, JSON.toJSONString(param), token, "getRealTimeData"); log.info("request realtime response: {}", result); cacheRemoteResponse(cacheKey, result); return result; } public String requestHistoryData(String token, String guid, long startTime, long endTime) { Map param = new HashMap<>(); param.put("keyId", KET_ID); param.put("keySecret", KEY_SECRET); param.put("deviceGuid", guid); param.put("startTime", startTime); param.put("endTime", endTime); log.info("request history payload: {}", JSON.toJSONString(param)); String cacheKey = HISTORY_CACHE_PREFIX + JSON.toJSONString(param); String cachedResult = sanitizeToken(redisTemplate.opsForValue().get(cacheKey)); if (cachedResult != null) { log.info("hit history cache: {}", cacheKey); return cachedResult; } String result = requestWithTokenRetry(REAL_HISTORY_URL, JSON.toJSONString(param), token, "getHistoryData"); log.info("request history response: {}", result); cacheRemoteResponse(cacheKey, result); return result; } private void cacheRemoteResponse(String cacheKey, String result) { if (result == null || result.trim().isEmpty()) { return; } redisTemplate.opsForValue().set(cacheKey, result, REMOTE_CACHE_TTL_SECONDS_30, TimeUnit.SECONDS); } }