KeLinOkHttpHelper.java 10.5 KB
package com.zteits.charge.config;

import com.alibaba.fastjson.JSONObject;
import com.clouds.common.cache.charge.ChargeCallZteitsTokenCacheUtil;
import com.clouds.common.cache.charge.ZteitsCallChargeTokenCacheUtil;
import com.clouds.common.utils.CryptoUtils;
import com.clouds.common.utils.SeqHelper;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Strings;
import com.google.common.hash.Hashing;
import com.zteits.charge.dao.OperatorInfoDao;
import com.zteits.charge.domain.OperatorInfo;
import com.zteits.clouds.api.apibase.bean.ErrorCode;
import com.zteits.clouds.api.apibase.constants.ErrorType;
import com.zteits.clouds.api.apibase.constants.charge.ChargeConst;
import com.zteits.clouds.api.apibase.constants.charge.enums.RetEnum;
import com.zteits.clouds.api.apibase.exception.BizException;
import com.zteits.clouds.api.dto.charge.base.ChargeBaseReq;
import com.zteits.clouds.api.dto.charge.base.ChargeBaseResult;
import com.zteits.clouds.api.dto.charge.post.QueryTokenPost;
import com.zteits.clouds.api.dto.charge.result.QueryTokenResult;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import okhttp3.*;
import okio.Buffer;
import org.apache.poi.ss.formula.functions.T;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl;

import javax.annotation.Resource;
import java.io.IOException;
import java.lang.reflect.Type;
import java.net.SocketTimeoutException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import static java.nio.charset.StandardCharsets.UTF_8;

@Slf4j
@Component
public class KeLinOkHttpHelper {
    protected final MediaType MEDIA_TYPE = MediaType.parse("application/json;charset=utf-8");
    private Lock lock = new ReentrantLock();
    // 调用科林的运营商ID
    @Value("${zteits.operatorId}")
    private String zteitsOperatorId;
    @Resource
    private OperatorInfoDao operatorInfoDao;
    @Resource
    private ObjectMapper objectMapper;
    private static final OkHttpClient client;

    static {
        // 构建 OkHttpClient 并添加拦截器
        client = new OkHttpClient.Builder()
                .addInterceptor(chain -> {
                    Request request = chain.request();
//                    log.info("httpClient {} {}", request.method(), request.url().url());
                    if(request.method().equalsIgnoreCase("POST")){
                        final Request copy = request.newBuilder().build();
                        final Buffer buffer = new Buffer();
                        copy.body().writeTo(buffer);
                        log.info("Body: {}", buffer.readUtf8());
                    }
                    Response response = chain.proceed(request);
                    if(response != null) {
                        log.info("Response {}: {}", response.code(), response.peekBody(Long.MAX_VALUE).string());
                        Headers headers = response.headers();
                        for (int i = 0; i < headers.size(); i++) {
                            log.info("Response Header {}: {}", headers.name(i), headers.get(headers.name(i)));
                        }
                    }else {
                        log.info("Response null");
                    }
                    return response;
                })
                //设置连接超时时间
                .connectTimeout(10, TimeUnit.SECONDS)
                //设置读取超时时间
                .readTimeout(10, TimeUnit.SECONDS)
                .build();
    }

    public <T> T post(String apiName, String operatorId, Object data, Class<T> type) throws IOException {
        // 根据运营商id查询相关信息
        OperatorInfo operatorInfo = operatorInfoDao.selectByOperatorId(operatorId);
        if (operatorInfo == null) {
            throw new BizException(ErrorType.BIZ_ERROR,"请先维护运营商:"+operatorId+"的信息");
        }
        String url = operatorInfo.getOperatorUrl().endsWith("/") ? operatorInfo.getOperatorUrl() + apiName : operatorInfo.getOperatorUrl() + "/" + apiName;
        Request.Builder requestBuilder = new Request.Builder().url(url);
        // 时间戳
        Date now = new Date();
        String timestamp = new SimpleDateFormat("yyyyMMddHHmmss").format(now);
        String seq = SeqHelper.getSequence(timestamp);
        ChargeBaseReq<String> newRequest = new ChargeBaseReq<>();
        // 写死的zteits,标识自己身份
        newRequest.setOperatorId(zteitsOperatorId);
        newRequest.setTimestamp(timestamp);
        newRequest.setSeq(seq);
        // 明文 string 串
        String body = data == null ? null : data instanceof String ? (String) data : objectMapper.writeValueAsString(data);
        log.info("请求url:{},请求参数:{}",url,body);
        // 密文
        String encodeData = CryptoUtils.encodeByAes128_CBC_PKCS5Padding(body, operatorInfo.getOperatorDataSecretIv(), operatorInfo.getOperatorDataSecret());
        newRequest.setData(encodeData);
        // 签名
        String content = newRequest.getOperatorId() + encodeData + timestamp + seq;
        String sig = Hashing.hmacMd5(operatorInfo.getOperatorSignSecret().getBytes(UTF_8)).hashString(content, UTF_8).toString().toUpperCase();
        newRequest.setSig(sig);

        final RequestBody requestBody = RequestBody.create(
                MEDIA_TYPE,
                objectMapper.writeValueAsBytes(newRequest)
        );
        requestBuilder.method("POST", requestBody);

        return  _post(apiName,operatorInfo,requestBuilder, type, 0);
    }

    /**
     * 获取token
     * @return
     */
    private String getToken(OperatorInfo operatorInfo) {
        String token = ZteitsCallChargeTokenCacheUtil.getToken(operatorInfo.getOperatorId());
        if(token == null){
            try {
                if(lock.tryLock(5, TimeUnit.SECONDS)){
                    token = ZteitsCallChargeTokenCacheUtil.getToken(operatorInfo.getOperatorId());
                    if(token == null){
                        token = this.queryToken(operatorInfo);
                    }
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            } finally {
                lock.unlock();
            }
        }
        return token;
    }

    @SneakyThrows
    private String queryToken(OperatorInfo operatorInfo){
        QueryTokenPost post = new QueryTokenPost();
        // 这里入参修改zteits的,标识自己身份
        post.setOperatorId(zteitsOperatorId);
        post.setOperatorSecret(operatorInfo.getOperatorSecret());
        QueryTokenResult data = this.post(ChargeConst.QUERY_TOKEN,operatorInfo.getOperatorId(), post, QueryTokenResult.class);
        // 少5秒,防止临界点过期
        int tokenAvailableTime = data.getTokenAvailableTime() - 5;
        ZteitsCallChargeTokenCacheUtil.setToken(operatorInfo.getOperatorId(),data.getAccessToken(),(long)tokenAvailableTime);
        return data.getAccessToken();
    }

    private <T> T _post(String apiName,OperatorInfo operatorInfo,Request.Builder requestBuilder, Class<T> type, int count) throws IOException {
        // 设置header
        requestBuilder.removeHeader("Content-Type");
        requestBuilder.addHeader("Content-Type", "application/json; charset=utf-8");
        if (ChargeConst.QUERY_TOKEN.equals(apiName)) {

        } else {
            requestBuilder.removeHeader("Authorization");
            requestBuilder.addHeader("Authorization", "Bearer " + getToken(operatorInfo));
        }

        Request request = requestBuilder.build();
        try{
            Response response = client.newCall(request).execute();
            if(response.code() != HttpStatus.OK.value()){
                log.error("请求科林接口响应非200");
                throw new BizException(ErrorType.REQUEST_STATUS_NOT_200);
            }
            ResponseBody responseBody = response.body();
            ChargeBaseResult<String> strResult = objectMapper.readValue(responseBody.bytes(), new TypeReference<ChargeBaseResult<String>>() {});
            if (strResult.getRet() == null) {
                log.info("请求科林接口返回参数Ret为空");
                throw new BizException(ErrorType.BIZ_ERROR,"启动充电异常");
            }
            if (RetEnum.TOKEN_ERROR.getCode() == strResult.getRet()) {
                // token 错误,重新获取token
                // 删除缓存
                ZteitsCallChargeTokenCacheUtil.delToken(operatorInfo.getOperatorId());
                if(count < operatorInfo.getOperatorRetryCount()){
                    log.info("请求科林接口报Token错误,重新获取Token,开始重试,URL:" + request.url().url().getPath());
                    return _post(apiName,operatorInfo,requestBuilder, type, count + 1);
                }else {
                    log.error("请求科林接口超过重试次数,抛异常,URL:" + request.url().url().getPath());
                    throw new BizException(ErrorType.BIZ_ERROR,"获取Token失败");
                }
            }
            if (RetEnum.REQUEST_SUCCESS.getCode() != strResult.getRet()) {
                throw new BizException(ErrorType.CHARGE_CALL_SERVICE_FAIL,strResult.getMsg());
            }
            if(!Strings.isNullOrEmpty(strResult.getData())){
                String data = strResult.getData();
                if(!data.startsWith("{") || data.startsWith("}")){
                    // 对返回数据做解密
                    data = CryptoUtils.decodeByAes128_CBC_PKCS5Padding(data, operatorInfo.getOperatorDataSecretIv(), operatorInfo.getOperatorDataSecret());
                    log.info("响应信息-解密后:{}",data);
                }
                return objectMapper.readValue(data,type);
            }
            return null;
        }catch (SocketTimeoutException e){
            if(count < operatorInfo.getOperatorRetryCount()){
                log.error("请求科林接口异常,开始重试,URL:" + request.url().url().getPath(),e);
                return _post(apiName,operatorInfo,requestBuilder, type, count + 1);
            }else {
                log.error("请求科林接口超过重试次数,抛异常,URL:" + request.url().url().getPath(),e);
                throw e;
            }
        }
    }
}