记一次RestTemplate调用监控API

422 阅读2分钟

maven依赖

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>cn.hutool</groupId>
        <artifactId>hutool-all</artifactId>
        <version>5.8.5</version>
    </dependency>
    <!--Redis-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    <dependency>
        <groupId>org.apache.httpcomponents</groupId>
        <artifactId>httpclient</artifactId>
    </dependency>

</dependencies>

yaml配置

monitoring:
#api的url
  url: 
  appId: 
  secret: 
server:
  port: 8080
spring:
  redis:
    host: 127.0.0.1
    database: 9
    port: 6379

初始化连接池RestTemplateConfig

package com.jen.resttemplatedemo.resttemplate;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.springframework.beans.factory.annotation.Value;
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.web.client.RestTemplate;

/**
 * @author Rick Jen
 * @Description 初始化连接池
 * @date 2022/8/18 11:55
 */
@Component
public class RestTemplateConfig {


    @Bean
    public RestTemplate restTemplate(RestTemplateBuilder builder) {
        return builder.build();
    }

    @Bean
    public HttpClient httpClient() {
        Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
                .register("http", PlainConnectionSocketFactory.getSocketFactory())
                .register("https", SSLConnectionSocketFactory.getSocketFactory())
                .build();
        PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(registry);
        // 最大连接数
        connectionManager.setMaxTotal(100);
        //单个路由最大连接数
        connectionManager.setDefaultMaxPerRoute(20);
        // 最大空间时间
        connectionManager.setValidateAfterInactivity(3000000);
        RequestConfig requestConfig = RequestConfig.custom()
                //服务器返回数据(response)的时间,超过抛出read timeout
                .setSocketTimeout(10000)
                //连接上服务器(握手成功)的时间,超出抛出connect timeout
                .setConnectTimeout(1000)
                //从连接池中获取连接的超时时间,超时间未拿到可用连接,会抛出org.apache.http.conn.ConnectionPoolTimeoutException: Timeout waiting for connection from pool
                .setConnectionRequestTimeout(500)
                .build();
        return HttpClientBuilder.create()
                .setDefaultRequestConfig(requestConfig)
                .setConnectionManager(connectionManager)
                .build();
    }


}

调用监控API MonitoringService

@Component
@Slf4j
public class MonitoringService {


@Autowired
private MonitoringConfiguration monitoringConfiguration;

@Autowired
private RedisTemplate<String, String> stringRedisTemplate;

@Autowired
private RestTemplate restTemplate;


public String getToken() throws NoSuchAlgorithmException, InvalidKeySpecException, SignatureException, InvalidKeyException, UnsupportedEncodingException {
        // 获取redis中token信息
        String token = stringRedisTemplate.opsForValue().get(MONITORING_TOKEN_REDIS_KEY);
        if (StrUtil.isNotBlank(token)) {
            log.info("请求监控API从Redis中获取到Token数据:{}", JSONUtil.toJsonStr(token));
            return token;
        }
        MonitoringTokenParam param = MonitoringTokenParam.builder()
                .operatorType(1)
                .sig(SecureUtil.md5(monitoringConfiguration.getAppId() + monitoringConfiguration.getSecret()))
                .build();
        // 当前时间戳
        String currentTimestamp = String.valueOf(DateUtil.current());
        MonitoringCreateHeaderSignParam headerSignParam = new MonitoringCreateHeaderSignParam(monitoringConfiguration.getAppId(), SecureUtil.md5(JSONUtil.toJsonStr(param)), currentTimestamp);
        // 请求头设置,application/json格式的数据
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        headers.set("appid", monitoringConfiguration.getAppId());
        log.info("参数的json:{}", JSONUtil.toJsonStr(param));
        headers.set("md5", SecureUtil.md5(JSONUtil.toJsonStr(param)));
        // 时间戳 单位毫秒
        headers.set("timestamp", headerSignParam.getTimestamp());
        // 客户端版本号
        headers.set("version", headerSignParam.getVersion());
        // 签名参数的签名值
        headers.set("signature", getSign(headerSignParam));
        HttpEntity<MonitoringTokenParam> request = new HttpEntity<>(param, headers);
        String result = restTemplate.postForObject(monitoringConfiguration.getUrl() + "/token", request, String.class);
        assert result != null;
        log.info("请求监控API获取Token返回数据:{}", result);
        MonitoringTokenResult tokenResult = convertToBean(result, MonitoringTokenResult.class);
        assert tokenResult != null;
        // 将token传入缓存中 并设置用此token的有效为一天
        token = tokenResult.getToken();
        stringRedisTemplate.opsForValue().set(MONITORING_TOKEN_REDIS_KEY, token, 86400, TimeUnit.SECONDS);
        log.info("请求监控API获取到Token数据:{}", JSONUtil.toJsonStr(tokenResult));
        return tokenResult.getToken();
    }



    /**
     * @param param
     * @return null
     * @Description 生成签名
     * @author Rick Jen
     * @date 2022/8/11 16:18
     */
    public static String getSign(MonitoringCreateHeaderSignParam param) throws NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException, SignatureException, UnsupportedEncodingException {
        //header中定义需要参与签名或者公共的与具体业务无关的属性
        //将header中需要参与签名的参数按照字母ascii排列组成字符串如:"{"appid":"8c58593ad151","md5":"cb88af352e8871802ec4ce60162df99d","timestamp":"231545134555","token":"eyJhbGciJ9.eyJzdWIiOiJ24i.LVJnaW9uIiwiZXp8","version":"3.1.2"}",并对字符串按照RSA安全哈希算法签名方法进行签名
        //appid: 请在开发者控制台获取
        //RSA密钥:请在开发者控制台获取
        //md5:将body中的json字符串进行MD5计算,32位小写字符串
        //空字段不参与签名
        assert param != null;
        SortedMap<String, String> sortedMap = new TreeMap<>();
        sortedMap.put("appid", param.getAppid());
        sortedMap.put("md5", param.getMd5());
        sortedMap.put("timestamp", param.getTimestamp());
        sortedMap.put("version", param.getVersion());
        if (StrUtil.isNotBlank(param.getMsgSeq())) {
            sortedMap.put("msgSeq", param.getMsgSeq());
        }
        if (StrUtil.isNotBlank(param.getBundleId())) {
            sortedMap.put("bundleId", param.getBundleId());
        }
        if (StrUtil.isNotBlank(param.getToken())) {
            sortedMap.put("token", param.getToken());
        }
        String jsonString = JSONUtil.toJsonStr(sortedMap);
        log.info("监控头部数据转换成JSON:{}", jsonString);
        Base64.Encoder encoder = Base64.getEncoder();
        Base64.Decoder decoder = Base64.getDecoder();
//        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(decoder.decode(MonitoringConstant.MONITORING_RSA_PRIVATE_KEY.getBytes()));
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(decoder.decode(MonitoringConstant.MONITORING_RSA_PRIVATE_KEY.getBytes()));
        PrivateKey priKey = KeyFactory.getInstance("RSA").generatePrivate(keySpec);
        Signature signature = Signature.getInstance("SHA1WithRSA");
        signature.initSign(priKey);
        signature.update(jsonString.getBytes(StandardCharsets.UTF_8));
        String signatureString = encoder.encodeToString(signature.sign());
        log.info("监控生成签名:{}", signatureString);
        return signatureString;
    }


/**
 * @param result 返回的json数据
 * @return null
 * @Description 转换为指定的bean
 * @author Rick Jen
 * @date 2022/8/11 11:05
 */
public <T extends SuperResult> T convertToBean(String result, Class<T> clazz) throws NoSuchAlgorithmException, InvalidKeySpecException, SignatureException, InvalidKeyException, UnsupportedEncodingException {
    MonitoringBaseResult monitoringBaseResult = JSONUtil.toBean(result, MonitoringBaseResult.class);
    assert monitoringBaseResult != null;
    // 若状态码不为成功
    if (!Objects.equals(monitoringBaseResult.getResultCode(), SUCCESS.getCode())) {
        log.error("调用视频接口失败!错误码:{},错误码说明:{}", monitoringBaseResult.getResultCode(), monitoringBaseResult.getResultMsg());
        // 若token过期
        if (Objects.equals(monitoringBaseResult.getResultCode(), TOKEN_EXPR.getCode())) {
            log.error("监控token过期...");
            // 刷新token
            getToken();
            throw new RuntimeException("令牌失效,请重新获取!");
        }
        throw new RuntimeException("视频接口调用异常!原因:" + monitoringBaseResult.getResultCode() + "-" + monitoringBaseResult.getResultMsg());
    }
    return JSONUtil.toBean(monitoringBaseResult.getData(), clazz);
}

}

返回值封装MonitoringBaseResult MonitoringTokenResult

package com.jen.resttemplatedemo.result;

import lombok.Data;

import java.io.Serializable;

/**
 * @author Rick Jen
 * @Description 监控返回值基础对象
 * @date 2022/8/11 9:58
 */
@Data
public class MonitoringBaseResult implements Serializable {

    /** 返回状态码. */
    private String resultCode;

    /** 返回说明. */
    private String resultMsg;

    /** 返回数据. */
    private String data;

}
package com.jen.resttemplatedemo.result;

import lombok.Data;

/**
 * @author Rick Jen
 * @Description 监控的token
 * @date 2022/8/11 9:57
 */
@Data
public class MonitoringTokenResult extends SuperResult{

    /** token失效时间,单位:秒. */
    private Integer expires_in;

    /** token. */
    private String token;

    /** token号. */
    private String tokenNum;

}