业务场景: 我们在开发过程中,除了后端开发接口,提供前端请求之外,有时也需要进行请求其他第三方接口数据,因为有些数据是其他服务器提供的一些公共服务,我们需要调用其他服务的第三方接口,获取数据,或者得到某个功能,这里我们在 【业务功能篇16】Springboot+mybatisplus+ShedLock框架根据一定的逻辑数据处理规则,定时任务生成告警单 这篇文章中分析的功能设计过程中,就不乏涉及需要调用第三方的接口,比如告警单需要给用户成员分别推送,这里就需要调用我们相应的推送服务功能,传递相应的参数,对应的员工信息,通过聊天社交软件进行推送, 也有需要给成员分别去生成一个 电子流待办信息,这个待办服务,也是需要调用第三方平台接口,然后通过页面展示当前用户的待办情况
【业务功能篇18】Springboot request请求第三方接口 JSONObject.parseObject 通用转换响应类-CSDN博客
@Slf4j
@Service
public class httpAdapter {
@Resource
RestTemplate restTemplate;
@Value("${interface.ms-url}")
private String baseUrl;
/**
* queryData
* 可以放在某个工具类下,通用化
* @param DataParam DataParam 请求第三方接口的参数VO
* @return ResponseVo
*/
public ResponseVo queryData(DataParam DataParam) {
String queryUrl = baseUrl + "queryData";
// 构造请求头
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
//如果有鉴权认证根据需求可以在请求头加Authorization
String token = "xxxxxxxx";
headers.add("Authorization", token);
String paramJsonString = JSON.toJSONString(DataParam);
// 查询
log.info("Start invoking, url = {}, query param = {}", queryUrl, paramJsonString);
HttpEntity<String> request = new HttpEntity<>(paramJsonString, headers);
ResponseEntity<JSONObject> response = null;
String errorMsg = "";
JSONObject body = new JSONObject();
long start = System.currentTimeMillis();
try {
response = restTemplate.postForEntity(queryUrl, request, JSONObject.class);
body = response.getBody();
} catch (RestClientException exception) {
errorMsg = StringUtils.abbreviate(exception.toString(), 2048);
log.error(errorMsg);
}
log.info("After invoking , cost {} s", (System.currentTimeMillis() - start) / 1000f);
// 解析结果
return changeResponse(body, errorMsg);
}
/**
* changeResponse
*
* @param body body
* @param errorMsg errorMsg
* @return ResponseVo
*/
private ResponseVo changeResponse(JSONObject body, String errorMsg) {
if (!"".equals(errorMsg) || body == null || body.get("result") == null) {
return ResponseUtils.errorResponse("query error! message = " + errorMsg);
}
//第三方接口返回的定义,通过result获取到数据 这里返回的是一个list 所以下面解析成数组
String jsonString = JSON.toJSONString(body.get("result"));
String msg = String.valueOf(body.get("message"));
//这里直接转换对应的数据封装类也可以得到这个对象集合,看整体开发封装需求决定
//List<ResData> DataParam = JSON.parseArray(jsonString, ResData.class);
List<JSONObject> listData = JSON.parseObject(jsonString, ArrayList.class);
log.info("AteBrain result: code = {}, message = {}, size = {}", body.get("code"), msg, listData.size());
return ResponseUtils.successResponse(listData, msg);
}
}
Service层进行调用 第三方请求接口
//这里直接@Autowired注入bean也可以
private final AteBrainAdapter httpAdapter= SpringBeanUtils.getBean(AteBrainAdapter.class);
//上层调用 第三方接口返回数据 这里可以写在上层service,跟上面写的调用http接口解耦开
public void getData() {
//请求第三方接口的参数实体VO 传递查询时间段内的数据
DataParam DataParam = DataParam.builder().startTime("2024-03-10").endTime("2024-03-15").build();
//调用第三方接口方法,传入参数,返回数据,这个封装类是我们自定义的
ResponseVo responseVo = httpAdapter.queryData(DataParam);
//获取对应的数据,此时还是JSON对象类型的list数据
List<JSONObject> jsonRsps = (List<JSONObject>) responseVo.getData();
//通过stream转换 JSONObject先转换string的json字符串 不过这里应该多转了一次 第一次的map可以不用,因为第二次map里面又转换了string
//parseObject("json字符串",对应转换的实体类),就可以吧json字符串转换成业务对应封装的实体类,
//这里也是需要基于第三方接口返回的json数据构建,比如{"count":1,"code": "机器码"} 那么对应的class类就包含这两个属性去接收转换,名字一样
List<ResData> DataParams = jsonRsps.stream().map(String::valueOf)
.map(e -> JSON.parseObject(String.valueOf(e), ResData.class))
.collect(Collectors.toList());
//到此拿到数据,转换好了 下面就根据具体业务情况去做数据的插入或者分析的动作
}
最后,需要自定义一个配置类:
import lombok.extern.slf4j.Slf4j;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.DefaultConnectionKeepAliveStrategy;
import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.ssl.SSLContextBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.web.client.RestTemplate;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit;
import javax.net.ssl.SSLContext;
/**
* restTemplate 配置类
*/
@Slf4j
@Configuration
public class RestTemplateConfig {
private static final int CONNECT_TIMEOUT = 60000; // 连接超时 60000ms
private static final int SOCKET_TIMEOUT = 180000; // socket超时 180000ms
private static final int READ_TIMEOUT = 180000; // 读超时 180000ms
private static final int REQUEST_TIMEOUT = 30000; // 请求超时 30000ms
private static final int MAX_CONN_TOTAL = 500; // 最大连接数500
private static final int MAX_CONN_ROUTE = 500; // 最大并发数500
private static final int RETRY_TIMES = 2; // 重试次数2
private static final int TIME_TO_LIVE = 30; // 长连接保持时长 30s
/**
* restTemplate
*
* @return restTemplate
*/
@Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate(simpleClientHttpRequestFactory());
restTemplate.getMessageConverters().add(new StringHttpMessageConverter(StandardCharsets.UTF_8));
return restTemplate;
}
/**
* 自定义ClientHttpRequestFactory配置
*
* @return ClientHttpRequestFactory
*/
@Bean
public static ClientHttpRequestFactory simpleClientHttpRequestFactory() {
// 长连接保持时长30秒
PoolingHttpClientConnectionManager pollingConnectionManager = new PoolingHttpClientConnectionManager(
TIME_TO_LIVE, TimeUnit.SECONDS);
// 最大连接数
pollingConnectionManager.setMaxTotal(MAX_CONN_TOTAL);
// 单路由的并发数
pollingConnectionManager.setDefaultMaxPerRoute(MAX_CONN_ROUTE);
HttpClientBuilder httpClientBuilder = HttpClients.custom();
httpClientBuilder.setConnectionManager(pollingConnectionManager);
// 重试次数2次,并开启
httpClientBuilder.setRetryHandler(new DefaultHttpRequestRetryHandler(RETRY_TIMES, true));
// 保持长连接配置,需要在头添加Keep-Alive
httpClientBuilder.setKeepAliveStrategy(new DefaultConnectionKeepAliveStrategy());
HttpClient httpClient = httpClientBuilder.build();
// httpClient连接底层配置clientHttpRequestFactory
HttpComponentsClientHttpRequestFactory clientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory(
httpClient);
clientHttpRequestFactory.setReadTimeout(READ_TIMEOUT); // ms
clientHttpRequestFactory.setConnectTimeout(CONNECT_TIMEOUT); // ms
return clientHttpRequestFactory;
}
/**
* 通过SSLContextBuilder绕过https安全验证
*
* @return restTemplateSSL
*/
@Bean
public RestTemplate restTemplateSsl() {
ClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
try {
HttpClient httpClient = httpClientCrossOverSsl(getDefaultReqConfig());
requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
} catch (Exception e) {
log.error("init ClientHttpRequestFactory error.", e);
}
return new RestTemplate(requestFactory);
}
/**
* Apache HttpClient 绕过ssl 验证
*
* @param requestConfig 请求配置
* @return httpClientCrossOverSSL
* @throws Exception 异常
*/
public HttpClient httpClientCrossOverSsl(RequestConfig requestConfig) throws Exception {
SSLContext sslContext = SSLContextBuilder.create()
.useProtocol(SSLConnectionSocketFactory.SSL)
.loadTrustMaterial((x, y) -> true)
.build();
return HttpClientBuilder.create()
.setDefaultRequestConfig(requestConfig)
.setSSLContext(sslContext)
.setSSLHostnameVerifier((x, y) -> true)
.setMaxConnTotal(MAX_CONN_TOTAL) // 最大连接数
.setMaxConnPerRoute(MAX_CONN_ROUTE) // 每个路由设置并发数
.build();
}
/**
* getDefaultReqConfig
*
* @return RequestConfig
*/
private RequestConfig getDefaultReqConfig() {
return RequestConfig.custom().setSocketTimeout(SOCKET_TIMEOUT) // 服务器返回数据(response)的时间,超时抛出read timeout
.setConnectTimeout(SOCKET_TIMEOUT) // 连接上服务器(握手成功)的时间,超时抛出connect timeout
.setConnectionRequestTimeout(REQUEST_TIMEOUT) // 从连接池中获取连接的超时时间,超时抛出ConnectionPoolTimeoutException
.build();
}
}