Java調(diào)用第三方接口封裝實現(xiàn)
介紹
在Java項目中,會遇到很多調(diào)用第三方接口的地方,比如接入微信,其他公司的系統(tǒng),需要傳token或者簽名。由于接入調(diào)用接口很多,每個接口內(nèi)部都需要手動設置token或者其他數(shù)據(jù),這就顯得很麻煩。而且發(fā)送http請求還需要自己創(chuàng)建request對象。下面介紹2中封裝方式,
一、利用feign功能封裝請求,所有接口和服務之間調(diào)用是一樣的,只需要執(zhí)行后面的url,參數(shù)類,請求方式等。內(nèi)部需要傳輸?shù)膖oken信息,在自定的攔截器中設置,自定義的攔截器需要實現(xiàn)RequestInterceptor接口。在這里面設置公共的請求參數(shù)。比較推薦的。
二、自己封裝一個通用的請求類,里面自己創(chuàng)建Request對象,發(fā)送完http請求之后拿到返回的json對象在封裝返回類,借助于ObjectMapper類。
一、借助feign實現(xiàn)調(diào)用
寫一個feign的接口調(diào)用類,因為調(diào)用服務和接收服務不在一個注冊中心,所以需要指定url,并實現(xiàn)攔截器
自定義的feign接口
import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @FeignClient( url = "${e-sign.apiHost}", // 這個值在配置文件中配置好 value = "authFeign" // 這個是自定義的服務名稱,沒有用 ) @RequestMapping( headers = {"X-Tsign-Open-Auth-Mode=Signature"} // 設置請求的header ) public interface EAuthFeign { // 寫封裝的參數(shù)和請求路徑 @PostMapping({"/v3/oauth2/index"}) EResponse<String> applyAuth(@RequestBody EApplyAuthDTO params); }
配置文件內(nèi)容
e-sign: projectId: 111111 projectSecret: fasdfads apiHost: https://ss.sign.cn callbackHost: https://call.baidu.com redirectHost: https://web.baidu.com
自定義攔截器,這個是feign的攔截器,只需要標記被掃描到就行,會自動加載。寫了這個類后,這個類所在的項目所有的feign都會被這個攔截器控制
import common.exception.E; import esign.config.ESignConfig; import feign.RequestInterceptor; import feign.RequestTemplate; import java.net.URLDecoder; import java.nio.charset.StandardCharsets; import java.security.InvalidKeyException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Collection; import java.util.Map; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import sun.misc.BASE64Encoder; @Component public class EAuthFeignRequestInterceptor implements RequestInterceptor { private static final Logger log = LoggerFactory.getLogger(EAuthFeignRequestInterceptor.class); @Autowired private ESignConfig eSignConfig; private static final BASE64Encoder encodeBase64 = new BASE64Encoder(); public EAuthFeignRequestInterceptor() { } public void apply(RequestTemplate template) { String host = this.eSignConfig.getApiHost(); String url = template.url(); String target = template.feignTarget().url(); boolean b = target.equals(host); // 這個判斷就是為了防止所有的feign都走這個攔截器,限制只有第三方接口才能進入下面的邏輯 if (b) { template.header("X-Tsign-Open-App-Id", new String[]{this.eSignConfig.getProjectId()}); Map<String, Collection<String>> headers = template.headers(); Collection<String> authMode = (Collection)headers.get("X-Tsign-Open-Auth-Mode"); boolean isSign = authMode != null && authMode.contains("Signature"); if (isSign) { template.header("X-Tsign-Open-Ca-Timestamp", new String[]{String.valueOf(System.currentTimeMillis())}); template.header("Accept", new String[]{"*/*"}); template.header("Content-Type", new String[]{"application/json;charset=UTF-8"}); try { this.setMd5AndSignature(template); } catch (Exception var10) { log.error("發(fā)送請求失敗, url:{}, params:{}", url, new String(template.body())); throw new E("發(fā)送請求失敗"); } } else { template.header("X-Tsign-Open-Authorization-Version", new String[]{"v2"}); } log.info("發(fā)送請求, url:{}, params:{}", url, template.body() == null ? "" : new String(template.body())); } } public void setMd5AndSignature(RequestTemplate template) { byte[] body = template.body(); String url = template.url(); String method = template.method(); String contentMD5 = doContentMD5(body); if ("GET".equals(method) || "DELETE".equals(method)) { contentMD5 = ""; url = URLDecoder.decode(url); } String message = appendSignDataString(method, "*/*", contentMD5, "application/json;charset=UTF-8", "", "", url); log.info("接口,url:{}, 簽名字符串:{}", url, message); String reqSignature = doSignatureBase64(message, this.eSignConfig.getProjectSecret()); template.header("Content-MD5", new String[]{contentMD5}); template.header("X-Tsign-Open-Ca-Signature", new String[]{reqSignature}); } private static String doContentMD5(byte[] body) { if (body == null) { return ""; } else { byte[] md5Bytes = null; MessageDigest md5 = null; String contentMD5 = null; try { md5 = MessageDigest.getInstance("MD5"); md5.update(body); byte[] md5Bytes = md5.digest(); contentMD5 = encodeBase64.encode(md5Bytes); return contentMD5; } catch (NoSuchAlgorithmException var5) { log.error("不支持此算法", var5); throw new E("不支持此算法"); } } } private static String doSignatureBase64(String message, String secret) { String algorithm = "HmacSHA256"; String digestBase64 = null; try { Mac hmacSha256 = Mac.getInstance(algorithm); byte[] keyBytes = secret.getBytes(StandardCharsets.UTF_8); byte[] messageBytes = message.getBytes(StandardCharsets.UTF_8); hmacSha256.init(new SecretKeySpec(keyBytes, 0, keyBytes.length, algorithm)); byte[] digestBytes = hmacSha256.doFinal(messageBytes); digestBase64 = encodeBase64.encode(digestBytes); return digestBase64; } catch (NoSuchAlgorithmException var8) { log.error("不支持此算法", var8); throw new E("不支持此算法"); } catch (InvalidKeyException var9) { log.error("無效的密鑰規(guī)范", var9); throw new E("無效的密鑰規(guī)范"); } } private static String appendSignDataString(String method, String accept, String contentMD5, String contentType, String date, String headers, String url) { StringBuilder sb = new StringBuilder(); sb.append(method).append("\n").append(accept).append("\n").append(contentMD5).append("\n").append(contentType).append("\n").append(date).append("\n"); if ("".equals(headers)) { sb.append(headers).append(url); } else { sb.append(headers).append("\n").append(url); } return new String(sb); } }
配置類
@Configuration @ConfigurationProperties( prefix = "e-sign" ) public class ESignConfig { private static final Logger log = LoggerFactory.getLogger(ESignConfig.class); private String projectId; private String projectSecret; private String apiHost; private String callbackHost; private String redirectHost; }
上邊幾個類就可以封裝對外請求了。自己服務根據(jù)要求可以另外處理
二、自己封裝請求類
通用請求返回結(jié)果類
package com.service; import com.alibaba.fastjson.JSONObject; import com.common.dto.coop.HttpDTO; import com.common.vo.coop.ThirdResultVO; import com.cooperation.generator.ThirdTokenGenerator; import com.cooperation.template.TokenTemplate; import lombok.extern.slf4j.Slf4j; import okhttp3.*; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.util.Assert; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.util.Objects; import java.util.concurrent.TimeUnit; /** * 基于okhttp的請求客戶端的service * * @author aaa 2022-04-28 */ @Slf4j @Service public class HttpService2 { @Autowired private TokenTemplate tokenTemplate; /** * 獲取接口調(diào)用返回值 * * @param param 請求參數(shù) * @author aaa 2022-04-28 */ public ThirdResultVO getInterfaceResult(HttpDTO param) { // 參數(shù)轉(zhuǎn)換成json JSONObject json = Objects.isNull(param.getObj()) ? new JSONObject() : JSONObject.parseObject(JSONObject.toJSONString(param.getObj())); // 如果需要token,將token放在參數(shù)中 if (param.getNeedToken()) { String token = tokenTemplate.getToken(new ThirdTokenGenerator()); json.put("token", token); } // 發(fā)起請求,獲得響應 Response response = doRequest(param.getUrl(), json); // 處理返回值 ThirdResultVO ThirdResult = handleReturn(response); // 如果為null,響應有問題,不能轉(zhuǎn)成返回值類型 Assert.notNull(ThirdResult, "接口調(diào)用異常"); return ThirdResult; } /** * 處理返回值 * * @param response 請求響應 * @author aaa 2022-04-28 */ private ThirdResultVO handleReturn(Response response) { // 如果響應為空,返回null if (Objects.isNull(response)) { return null; } try { // 如果響應體為空,返回null if (Objects.isNull(response.body())) { return null; } // 響應體的字節(jié)數(shù)組 byte[] bytes = response.body().bytes(); log.info("調(diào)用接口返回數(shù)據(jù)--:{}", new String(bytes)); // 轉(zhuǎn)換成返回值類型 return JSONObject.parseObject(bytes, ThirdResultVO.class); } catch (Exception e) { log.info("處理返回值失敗:{}", e.getMessage(), e); } return null; } /** * 發(fā)送請求 * * @param url 請求地址 * @param jsonObject 參數(shù)DTO * @author aaa 2022-04-28 */ private Response doRequest(String url, JSONObject jsonObject) { // 獲得表單請求對象 Request formRequest = getFormRequest(url, jsonObject); // 調(diào)用接口 請求入?yún)? log.info("調(diào)用接口請求入?yún)?-:url:{} , jsonObject :{}",url, JSONObject.toJSONString(jsonObject)); // 獲得okhttp請求工具 OkHttpClient requestClient = getRequestClient(); // 獲得請求調(diào)用對象 Call call = requestClient.newCall(formRequest); // 發(fā)起請求,獲得響應 Response execute = null; try { execute = call.execute(); } catch (IOException e) { log.info("調(diào)用接口失敗:{}", e.getMessage(), e); } return execute; } /** * 獲得表單請求 * * @param url 請求地址 * @param jsonObject 參數(shù)DTO * @author aaa 2022-04-28 */ private Request getFormRequest(String url, JSONObject jsonObject) { // 獲得請求構(gòu)造器 Request.Builder requestBuilder = new Request.Builder(); // 設置表單請求的請求頭 try { requestBuilder.addHeader("content-type", "application/x-www-form-urlencoded").post(getFormBody(jsonObject)).url(new URL(url)); } catch (MalformedURLException e) { log.info("url轉(zhuǎn)換失?。簕}", e.getMessage(), e); } // 返回請求對象 return requestBuilder.build(); } /** * 獲取請求客戶端 * * @author aaa 2022-04-28 */ private OkHttpClient getRequestClient() { return new OkHttpClient.Builder() .readTimeout(2L, TimeUnit.MINUTES) .build(); } /** * 獲取form表單請求的body * * @param jsonObject 請求參數(shù)DTO * @author aaa 2022-04-28 */ private FormBody getFormBody(JSONObject jsonObject) { // 獲取表單請求構(gòu)造器 FormBody.Builder fb = new FormBody.Builder(); // 設置請求參數(shù) jsonObject.keySet().forEach(item -> fb.addEncoded(item, jsonObject.getString(item))); // 返回body return fb.build(); } }
通用請求參數(shù)類
@Data @Accessors(chain = true) @EqualsAndHashCode(callSuper = false) public class HttpDTO implements Serializable { private static final long serialVersionUID = 1L; @NotNull(message = "請求路徑不能為空") @NotBlank(message = "請求路徑不能為空") @ApiModelProperty(value = "請求路徑url") private String url; @ApiModelProperty(value = "參數(shù)") private Object obj; @NotNull(message = "是否需要token 不能為空") @ApiModelProperty(value = "是否需要token:true是,false否") private Boolean needToken; }
請求調(diào)用示例
public List<String> getSkuCode(String classifyCode) { ResultVO interfaceResult = httpService.getInterfaceResult(new HttpDTO() .setUrl(Constant.HOST.concat(Constant.GOODS_GET_SKUS_BY_CLASSID_URI)) .setNeedToken(true) .setObj(new DeLiGetSkusByClassId3DTO().setCode(classifyCode3).setIsAll(1))); log.info(interfaceResult.getResultMessage()); return JSONArray.parseArray(interfaceResult.getResult(), String.class); }
上邊的請求返回,從結(jié)果里取出result字符串,再轉(zhuǎn)一下list,這可以放在后面的請求方法中,傳一個返回的類class。使用objectMapper轉(zhuǎn)一下。
private ObjectMapper mapper = new ObjectMapper(); public <T, RR> Response<RR> api(ApiDTO<T> jsonParams, Class<RR> responseResult) { String json = JSONObject.toJSONString(jsonParams); log.info("三方api.do, requestParams:{}", json); String responseStr = this.thirdFileAgentFeign.api(json); // 構(gòu)建返回類型 JavaType javaType1 = this.mapper.getTypeFactory().constructParametricType(YZResponse.class, new Class[]{responseResult}); try { // 這個代碼塊就看這里返回帶泛型的類 return (Response)this.mapper.readValue(responseStr, javaType1); } catch (JsonProcessingException e) { log.error("反序列化返回值異常, responseStr:{}, errInfo:{}", new Object[]{responseStr, var7.getMessage(), e}); throw new E("反序列化返回值異常"); } }
第二種返回類型
import com.fasterxml.jackson.core.type.TypeReference; public static <T> T toObj(String json, Class<T> type) { try { return MAPPER.readValue(json, type); } catch (IOException e) { log.error("toObj error", e); throw E.of(BaseExceptionEnum.FORMAT_ERROR); } } public static <T> T toObj(String json, TypeReference<T> typeReference) { try { return mapper.readValue(json, typeReference); } catch (IOException e) { log.error("toObj error", e); throw E.of(BaseExceptionEnum.FORMAT_ERROR); } }
泛型返回處理可以參考RestTemplate里面的postForObject()
Class<?> deserializationView = ((MappingJacksonInputMessage)inputMessage).getDeserializationView(); if (deserializationView != null) { // 部分代碼,構(gòu)建一個reader ObjectReader objectReader = this.objectMapper.readerWithView(deserializationView).forType(javaType); if (isUnicode) { // 返回class類型的對象 return objectReader.readValue(inputMessage.getBody()); } Reader reader = new InputStreamReader(inputMessage.getBody(), charset); return objectReader.readValue(reader); }
具體可以學習一下ObjectMapper的用法
請求接口返回結(jié)果類
@Accessors(chain = true) @EqualsAndHashCode(callSuper = false) public class ResultVO implements Serializable { private static final long serialVersionUID = 1L; @ApiModelProperty(value = "接口返回成功與否") private Boolean success; @ApiModelProperty(value = "返回數(shù)據(jù)描述") private String resultMessage; @ApiModelProperty(value = "平臺業(yè)務代碼標記") private String resultCode; // 返回的json結(jié)果,再處理成返回對象 @ApiModelProperty(value = "業(yè)務數(shù)據(jù)") private String result; }
三、調(diào)用第三方生成token,可以使用策略類實現(xiàn)
策略接口類
public interface TokenGeneratorStrategy { /** * 獲取token * * @author aaa 2022-04-26 */ String getToken(); /** * 獲取token緩存key * * @author aaa 2022-04-26 */ String getTokenCacheKey(); /** * 獲取token過期時間,采用redis單位 * * @author aaa 2022-04-26 */ Duration getTokenExpireTime(); }
獲取token的實現(xiàn)類
package com.generator; import com.alibaba.fastjson.JSONObject; import com.common.constant.ThirdConstant; import com.common.constant.MallCacheKeyConstant; import com.common.constant.MallCommonConstant; import com.common.dto.coop.HttpDTO; import com.common.vo.coop.ThirdResultVO; import com.common.vo.coop.ThirdTokenVO; import com.cooperation.dto.ThirdTokenDTO; import com.cooperation.service.HttpService; import com.cooperation.strategy.TokenGeneratorStrategy; import com.common.exception.E; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.util.DigestUtils; import java.nio.charset.StandardCharsets; import java.time.Duration; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.format.DateTimeFormatter; import java.util.Objects; /** * 三方生成token * */ @Slf4j @Service public class ThirdTokenGenerator implements TokenGeneratorStrategy { @Autowired private HttpService httpService; @Override public String getToken() { // 獲取當前時間,轉(zhuǎn)換成yyyy/MM/dd格式 DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); String format = LocalDateTime.now().format(dateTimeFormatter); // 簽名規(guī)則:client_secret+timestamp+client_id+username+password+scope+client_secret將上面字符串MD5 加密后轉(zhuǎn)為小寫。 String sign = ThirdConstant.CLIENT_SECRET .concat(format) .concat(ThirdConstant.CLIENT_ID) .concat(ThirdConstant.USERNAME) .concat(ThirdConstant.PASSWORD) .concat(ThirdConstant.CLIENT_SECRET); // md5 加密 String signMd5 = DigestUtils.md5DigestAsHex(sign.getBytes(StandardCharsets.UTF_8)).toLowerCase(); ThirdTokenDTO ThirdTokenDto = new ThirdTokenDTO().setClient_id(ThirdConstant.CLIENT_ID).setClient_secret(ThirdConstant.CLIENT_SECRET) .setUsername(ThirdConstant.USERNAME).setPassword(ThirdConstant.PASSWORD) .setTimestamp(format).setSign(signMd5); // 接口調(diào)用 ThirdResultVO interfaceResult = httpService.getInterfaceResult( new HttpDTO() .setUrl(ThirdConstant.HOST.concat(ThirdConstant.TOKEN_URI)) .setObj(ThirdTokenDto) .setNeedToken(false)); ThirdTokenVO ThirdToken = new ThirdTokenVO(); try { ThirdToken = JSONObject.parseObject(interfaceResult.getResult(), ThirdTokenVO.class); } catch (Exception e) { log.info("token 返回值處理異常:{}", e.getMessage(), e); } if (Objects.isNull(ThirdToken)) { log.error("獲取三方token為空----{}", interfaceResult); throw new E("獲取三方token異常信息--" + interfaceResult.getResultMessage()); } return ThirdToken.getAccess_token(); } @Override public String getTokenCacheKey() { return MallCacheKeyConstant.COOP_Third_TOKEN + MallCommonConstant.DEFAULT_KEY; } /** * 三方有效時間一小時,這里的過期時間采用50分鐘 */ @Override public Duration getTokenExpireTime() { // 當前時間到12點的時間 Duration nowTo12 = Duration.between(LocalDateTime.now(), LocalDateTime.of(LocalDate.now(), LocalTime.MAX)); // 50分鐘 Duration fiftyMin = Duration.ofSeconds(50 * 60L); // 如果當前時間到12點的時間超過50分鐘,返回50分鐘 if (nowTo12.compareTo(fiftyMin) > 0) { return fiftyMin; } // 否則返回當前時間到12點的時間 return nowTo12; } }
總結(jié)
到此這篇關于Java調(diào)用第三方接口封裝實現(xiàn)的文章就介紹到這了,更多相關Java調(diào)用第三方接口封裝內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
SpringBoot整合Caffeine實現(xiàn)本地緩存的實踐分享
緩存是提升系統(tǒng)性能的一個不可或缺的工具,通過緩存可以避免大部分重復的請求到數(shù)據(jù)庫層,減少IO鏈接次數(shù),提升整體的響應速率,本地緩存中比較常見的比如 Caffeine 緩存,這篇文章將結(jié)合具體的 Springboot 項目搭配 Caffeine 實現(xiàn)本地緩存的各種使用方式2024-07-07Java?Cookie與Session實現(xiàn)會話跟蹤詳解
session的工作原理和cookie非常類似,在cookie中存放一個sessionID,真實的數(shù)據(jù)存放在服務器端,客戶端每次發(fā)送請求的時候帶上sessionID,服務端根據(jù)sessionID進行數(shù)據(jù)的響應2022-11-11