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 post(String apiName, String operatorId, Object data, Class 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 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 _post(String apiName,OperatorInfo operatorInfo,Request.Builder requestBuilder, Class 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 strResult = objectMapper.readValue(responseBody.bytes(), new TypeReference>() {}); 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; } } } }