feat(biz): 实现动环系统HTTP客户端服务功能
- 新增园区、建筑及设备指标数据的获取方法 - 完善动环系统相关DTO类定义,包括分页数据结构 - 优化HTTP客户端的请求处理逻辑,增强错误处理机制 - 支持自动刷新访问令牌及请求重试机制 - 统一请求参数构建方式,提升代码可维护性
This commit is contained in:
@@ -1,7 +0,0 @@
|
||||
package com.jeelowcode.module.biz.dto;
|
||||
|
||||
// todo 待完善
|
||||
public class PowerEnvBuildingDataDTO {
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.jeelowcode.module.biz.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/**
|
||||
* 动环系统建筑数据
|
||||
*
|
||||
* @author yangchenjj
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@Schema(description = "动环系统建筑数据")
|
||||
public class PowerEnvBuildingItemDTO {
|
||||
|
||||
/**
|
||||
* 建筑ID
|
||||
*/
|
||||
@Schema(description = "建筑ID")
|
||||
private String buildingId;
|
||||
|
||||
/**
|
||||
* 建筑名称
|
||||
*/
|
||||
@Schema(description = "建筑名称")
|
||||
private String buildingName;
|
||||
|
||||
/**
|
||||
* 园区ID
|
||||
*/
|
||||
@Schema(description = "园区ID")
|
||||
private String campusId;
|
||||
|
||||
/**
|
||||
* 园区名称
|
||||
*/
|
||||
@Schema(description = "园区名称")
|
||||
private String campusName;
|
||||
|
||||
/**
|
||||
* 状态(0:停用,1:启用)
|
||||
*/
|
||||
@Schema(description = "状态(0:停用,1:启用)")
|
||||
private Integer status;
|
||||
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
package com.jeelowcode.module.biz.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
// todo 待完善
|
||||
@Data
|
||||
public class PowerEnvCampusDataDTO {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.jeelowcode.module.biz.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/**
|
||||
* 动环系统园区信息
|
||||
*
|
||||
* @author yangchenjj
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@Schema(description = "动环系统园区信息")
|
||||
public class PowerEnvCampusItemDTO {
|
||||
|
||||
/**
|
||||
* 园区编号
|
||||
*/
|
||||
@Schema(description = "园区编号")
|
||||
private String campusId;
|
||||
|
||||
/**
|
||||
* 园区名称
|
||||
*/
|
||||
@Schema(description = "园区名称")
|
||||
private String campusName;
|
||||
|
||||
/**
|
||||
* 状态(0:停用,1:启用)
|
||||
*/
|
||||
@Schema(description = "状态(0:停用,1:启用)")
|
||||
private Integer status;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package com.jeelowcode.module.biz.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 动环系统园区数据DTO
|
||||
*
|
||||
* @author yangchenjj
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@Schema(description = "动环系统园区数据DTO")
|
||||
public class PowerEnvPageDataDTO<T> {
|
||||
|
||||
/**
|
||||
* 总页数
|
||||
*/
|
||||
@Schema(description = "总页数")
|
||||
private Integer totalPage = 0;
|
||||
|
||||
/**
|
||||
* 每页数量
|
||||
*/
|
||||
@Schema(description = "每页数量")
|
||||
private Integer pageSize = 0;
|
||||
|
||||
/**
|
||||
* 当前页码
|
||||
*/
|
||||
@Schema(description = "当前页码")
|
||||
private Integer currentPage = 0;
|
||||
|
||||
/**
|
||||
* 总记录数
|
||||
*/
|
||||
@Schema(description = "总记录数")
|
||||
private Integer totalCount = 0;
|
||||
|
||||
/**
|
||||
* 数据项
|
||||
*/
|
||||
@Schema(description = "数据项")
|
||||
private List<T> items;
|
||||
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.jeelowcode.module.biz.http;
|
||||
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.jeelowcode.module.biz.dto.PowerEnvRequestParamsDTO;
|
||||
import com.jeelowcode.module.biz.dto.PowerEnvResponseDataDTO;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -9,6 +10,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.client.HttpClientErrorException;
|
||||
@@ -17,23 +19,35 @@ import org.springframework.web.client.RestTemplate;
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.Resource;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
/**
|
||||
* 可重试的HttpClient,支持自动登录和token刷新
|
||||
* 线程安全的实现,可以在并发环境下使用
|
||||
*
|
||||
* @author yangchenjj
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@ConditionalOnProperty(name = "jeelowcode.powerenv.baseurl")
|
||||
public class RetryableHttpClient {
|
||||
|
||||
/**
|
||||
* 动环系统基础URL
|
||||
*/
|
||||
@Value("${jeelowcode.powerenv.baseurl}")
|
||||
private String baseUrl;
|
||||
|
||||
/**
|
||||
* 动环系统应用Key
|
||||
*/
|
||||
@Value("${jeelowcode.powerenv.appKey:campusT0Yoh1U6tXD3ZVXy}")
|
||||
private String appKey;
|
||||
|
||||
/**
|
||||
* 动环系统应用密钥
|
||||
*/
|
||||
@Value("${jeelowcode.powerenv.appSecret:3d4d89b5-8a14-4b83-a9aa-715bdc8264a1}")
|
||||
private String appSecret;
|
||||
|
||||
@@ -62,12 +76,10 @@ public class RetryableHttpClient {
|
||||
/**
|
||||
* 发送GET请求并获取响应数据
|
||||
*
|
||||
* @param apiPath API接口路径
|
||||
* @param requestParams 请求参数
|
||||
* @return 响应数据
|
||||
*/
|
||||
public ResponseEntity<PowerEnvResponseDataDTO<?>> getData(
|
||||
String apiPath, PowerEnvRequestParamsDTO<?> requestParams) {
|
||||
public ResponseEntity<PowerEnvResponseDataDTO<?>> getData(PowerEnvRequestParamsDTO<?> requestParams) {
|
||||
// 检查令牌是否过期,如果过期则刷新
|
||||
if (isTokenExpired()) {
|
||||
refreshAccessToken();
|
||||
@@ -75,18 +87,49 @@ public class RetryableHttpClient {
|
||||
// 构建带access_token参数的URL
|
||||
String url = baseUrl + API_URL + "?access_token=" + accessToken;
|
||||
try {
|
||||
return restTemplate.exchange(url, HttpMethod.POST, new HttpEntity<>(requestParams),
|
||||
// 请求数据
|
||||
ResponseEntity<PowerEnvResponseDataDTO<?>> response = restTemplate
|
||||
.exchange(url, HttpMethod.POST, new HttpEntity<>(requestParams),
|
||||
new ParameterizedTypeReference<PowerEnvResponseDataDTO<?>>() {
|
||||
});
|
||||
} catch (HttpClientErrorException.Unauthorized e) {
|
||||
log.warn("API请求未授权,尝试刷新访问令牌后重试");
|
||||
// 如果是401未授权错误,则刷新令牌后重试
|
||||
|
||||
// 检查响应状态码
|
||||
if (!Objects.equals(response.getStatusCode(), HttpStatus.OK)) {
|
||||
// 如果不是200,则抛出异常
|
||||
throw new HttpClientErrorException(response.getStatusCode(), "API请求失败");
|
||||
}
|
||||
|
||||
// 如果请求返回200,则检查响应内容
|
||||
PowerEnvResponseDataDTO<?> responseData = response.getBody();
|
||||
if (!Objects.requireNonNull(responseData).getSuccess()) {
|
||||
// 如果响应消息为false,则还需要进一步查看错误信息
|
||||
if (StrUtil.equalsAny(responseData.getCode(), "OPEN_API_PARAM_ERROR", "ACCESS_TOKEN_ERROR")) {
|
||||
// 需要刷新Token并且重试
|
||||
refreshAccessToken();
|
||||
url = baseUrl + API_URL + "?access_token=" + accessToken;
|
||||
return restTemplate.exchange(url, HttpMethod.POST, new HttpEntity<>(requestParams),
|
||||
|
||||
// 获得重试结果
|
||||
ResponseEntity<PowerEnvResponseDataDTO<?>> retryResponse =
|
||||
restTemplate.exchange(url, HttpMethod.POST, new HttpEntity<>(requestParams),
|
||||
new ParameterizedTypeReference<PowerEnvResponseDataDTO<?>>() {
|
||||
});
|
||||
if (Objects.equals(retryResponse.getStatusCode(), HttpStatus.OK) &&
|
||||
Objects.requireNonNull(retryResponse.getBody()).getSuccess()) {
|
||||
// 只放请求成功的结果
|
||||
return retryResponse;
|
||||
} else {
|
||||
// 重试如果除了任何异常,则抛出异常
|
||||
throw new HttpClientErrorException(HttpStatus.BAD_REQUEST, responseData.getMsg());
|
||||
}
|
||||
} else {
|
||||
// 直接抛出异常
|
||||
throw new HttpClientErrorException(HttpStatus.BAD_REQUEST, responseData.getMsg());
|
||||
}
|
||||
}
|
||||
// 如果成功了,则直接返回响应数据
|
||||
return response;
|
||||
} catch (Exception e) {
|
||||
// 如果发生异常,则在日志中记录异常,并且抛出
|
||||
log.error("请求API时发生异常", e);
|
||||
throw e;
|
||||
}
|
||||
|
||||
@@ -1,11 +1,36 @@
|
||||
package com.jeelowcode.module.biz.service;
|
||||
|
||||
import com.jeelowcode.module.biz.dto.*;
|
||||
|
||||
/**
|
||||
* 业务HTTP客户端服务接口
|
||||
* 动环系统客户端服务接口
|
||||
*
|
||||
* @author lingma
|
||||
* @author yangchenjj
|
||||
*/
|
||||
public interface IBizHttpClientService {
|
||||
|
||||
/**
|
||||
* 获取园区列表
|
||||
*
|
||||
* @param params 园区查询参数
|
||||
* @return 园区数据列表
|
||||
*/
|
||||
PowerEnvPageDataDTO<PowerEnvCampusItemDTO> listCampus(PowerEnvCampusParamsDTO params);
|
||||
|
||||
/**
|
||||
* 获取建筑列表
|
||||
*
|
||||
* @param params 建筑查询参数
|
||||
* @return 建筑数据列表
|
||||
*/
|
||||
PowerEnvPageDataDTO<PowerEnvBuildingItemDTO> listBuilding(PowerEnvBuildingParamsDTO params);
|
||||
|
||||
/**
|
||||
* 获取设备指标数据列表
|
||||
*
|
||||
* @param deviceId 设备ID
|
||||
* @return 设备指标数据列表
|
||||
*/
|
||||
PowerEnvDeviceDataDTO listDeviceMetrics(String deviceId);
|
||||
|
||||
}
|
||||
@@ -1,11 +1,17 @@
|
||||
package com.jeelowcode.module.biz.service.impl;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.lang.TypeReference;
|
||||
import com.jeelowcode.module.biz.dto.*;
|
||||
import com.jeelowcode.module.biz.http.RetryableHttpClient;
|
||||
import com.jeelowcode.module.biz.service.IBizHttpClientService;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.Collections;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 业务HTTP客户端服务实现类
|
||||
@@ -19,4 +25,58 @@ public class BizHttpClientServiceImpl implements IBizHttpClientService {
|
||||
@Resource
|
||||
private RetryableHttpClient retryableHttpClient;
|
||||
|
||||
/**
|
||||
* 获取园区分页数据
|
||||
*
|
||||
* @param params 园区查询参数
|
||||
* @return 园区分页数据
|
||||
*/
|
||||
@Override
|
||||
public PowerEnvPageDataDTO<PowerEnvCampusItemDTO> listCampus(PowerEnvCampusParamsDTO params) {
|
||||
PowerEnvResponseDataDTO<?> response = retryableHttpClient.getData(
|
||||
PowerEnvRequestParamsDTO.buildCampusRequestParams(params)).getBody();
|
||||
return Optional.ofNullable(response)
|
||||
.map(PowerEnvResponseDataDTO::getContent)
|
||||
.map(content -> Convert.convert(
|
||||
new TypeReference<PowerEnvPageDataDTO<PowerEnvCampusItemDTO>>() {
|
||||
}, content))
|
||||
.orElse(new PowerEnvPageDataDTO<PowerEnvCampusItemDTO>().setItems(Collections.emptyList()));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取建筑分页数据
|
||||
*
|
||||
* @param params 建筑查询参数
|
||||
* @return 建筑分页数据
|
||||
*/
|
||||
@Override
|
||||
public PowerEnvPageDataDTO<PowerEnvBuildingItemDTO> listBuilding(PowerEnvBuildingParamsDTO params) {
|
||||
PowerEnvResponseDataDTO<?> response = retryableHttpClient.getData(
|
||||
PowerEnvRequestParamsDTO.buildBuildingRequestParams(params)).getBody();
|
||||
return Optional.ofNullable(response)
|
||||
.map(PowerEnvResponseDataDTO::getContent)
|
||||
.map(content -> Convert.convert(
|
||||
new TypeReference<PowerEnvPageDataDTO<PowerEnvBuildingItemDTO>>() {
|
||||
}, content))
|
||||
.orElse(new PowerEnvPageDataDTO<PowerEnvBuildingItemDTO>().setItems(Collections.emptyList()));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取设备指标数据
|
||||
*
|
||||
* @param deviceId 设备ID
|
||||
* @return 设备指标数据
|
||||
*/
|
||||
@Override
|
||||
public PowerEnvDeviceDataDTO listDeviceMetrics(String deviceId) {
|
||||
PowerEnvResponseDataDTO<?> response = retryableHttpClient.getData(
|
||||
PowerEnvRequestParamsDTO.buildDeviceRunDataRequestParams(deviceId)).getBody();
|
||||
return Optional.ofNullable(response)
|
||||
.map(PowerEnvResponseDataDTO::getContent)
|
||||
.map(content -> BeanUtil.toBean(content, PowerEnvDeviceDataDTO.class))
|
||||
.orElse(new PowerEnvDeviceDataDTO().setDeviceUid(deviceId).setPropertyRunDataList(Collections.emptyList()));
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user