在使用RestTemplate請求三方接口時:三方接口通常都要求在url後面拼接上固定的幾個參數,通常如
accessToken
進行權限校驗。而咱們在開發時,請求這些地址,如何避免在url拼接accessToken
這種重複固定的編碼操做呢。web
方法固然有不少,本文提供一種經過反射偷樑換柱的寫法來實現。redis
-
以微信小程序服務端接口請求做爲請求對象。 -
微信小程序要求在請求時帶上 ?accesss_token=ACCESS_TOKEN
如何實現..?小程序
# 基礎配置
-
微信小程序配置類
/**
* 微信小程序配置類
*
* @author futao
* @date 2020/10/29
*/
@ConfigurationProperties(prefix = WxMiniProgramProperties.PROPERTY_PREFIX)
public class WxMiniProgramProperties {
/**
* 微信小程序配置前綴
*/
public static final String PROPERTY_PREFIX = Consts.System.FRAMEWORK_BASE_NAME + "." + Consts.WxMiniProgram.WX_MINI_PROGRAM_BASE_NAME;
/**
* AppID(小程序ID)
*/
private String appId;
/**
* AppSecret(小程序密鑰)
*/
private String appSecret;
public String getAppId() {
if (StringUtils.isBlank(appId)) {
throw new WxMiniProgramException("微信小程序AppId未設置");
}
return appId;
}
public void setAppId(String appId) {
this.appId = appId;
}
public String getAppSecret() {
if (StringUtils.isBlank(appSecret)) {
throw new WxMiniProgramException("微信小程序AppSecret未設置");
}
return appSecret;
}
public void setAppSecret(String appSecret) {
this.appSecret = appSecret;
}
}
-
獲取微信小程序accessToken
/**
* 微信小程序AccessToken
*
* @author futao
* @date 2020/10/29
*/
@Slf4j
@Service
public class AccessTokenServiceImpl implements AccessTokenService {
@Autowired
private StringRedisTemplate redisTemplate;
@Autowired
private WxMiniProgramProperties wxMiniProgramProperties;
/**
* 獲取token
*
* @return token
*/
@Override
public String get() {
String redisAccessToken = redisTemplate.opsForValue().get(RedisKeyConsts.WxMiniProgram.WX_ACCESS_TOKEN);
if (StringUtils.isBlank(redisAccessToken)) {
//無緩存
String url = UriComponentsBuilder
.fromHttpUrl(Consts.WxMiniProgram.WX_API_DOMAIN + "/cgi-bin/token")
.queryParam("grant_type", "client_credential")
.queryParam("appid", wxMiniProgramProperties.getAppId())
.queryParam("secret", wxMiniProgramProperties.getAppSecret())
.build()
.encode()
.toString();
ResponseEntity<AccessToken> accessTokenResponseEntity = WxMiniProgramConfig.REST_TEMPLATE.getForEntity(url, AccessToken.class);
AccessToken accessToken = accessTokenResponseEntity.getBody();
String token = accessToken.getAccessToken();
redisTemplate.opsForValue().set(RedisKeyConsts.WxMiniProgram.WX_ACCESS_TOKEN, token, accessToken.getExpiresIn() - 5, TimeUnit.SECONDS);
return token;
} else {
// 緩存命中
log.info("cache hint");
return redisAccessToken;
}
}
}
-
想要請求的接口: GET https://api.weixin.qq.com/cgi-bin/message/wxopen/activityid/create?access_token=ACCESS_TOKEN&unionid=UNIONID
1、 每一個接口都手動拼上accessToken
/**
* 動態消息
*
* @author futao
* @date 2020/10/30
*/
@Service
public class DynamicMessageServiceImpl implements DynamicMessageService {
@Autowired
private AccessTokenService accessTokenService;
/**
* 建立被分享動態消息或私密消息的 activity_id
*
* @return
*/
@Override
public DynamicMessageCreateResult createActivityId() {
String url = UriComponentsBuilder
.fromHttpUrl(Consts.WxMiniProgram.WX_API_DOMAIN + "/cgi-bin/message/wxopen/activityid/create")
// 手動加上請求參數accessToken
.queryParam("access_token", accessTokenService.get())
.build()
.encode()
.toString();
ResponseEntity<DynamicMessageCreateResult> messageCreateResultResponseEntity = WxMiniProgramConfig.REST_TEMPLATE.getForEntity(url, DynamicMessageCreateResult.class);
DynamicMessageCreateResult createResult = messageCreateResultResponseEntity.getBody();
return createResult;
}
}
-
測試
/**
* @author futao
* @date 2020/10/30
*/
@RequestMapping("/wx/mini")
@RestController
public class WxMiniController {
@Autowired
private DynamicMessageService dynamicMessageService;
@GetMapping("/createDynamicMessage")
public DynamicMessageCreateResult createDynamicMessage() {
return dynamicMessageService.createActivityId();
}
}
-
測試結果 -
功能是實現了,可是很是繁瑣。
編碼時,
1.在每一個調用微信小程序接口的地方,都加上accessToken參數
,因爲該參數又依賴於AccessTokenService
,因此又須要先注入AccessTokenService
,比較繁瑣。且,2.若是固定的請求參數不止一個而有不少個
,3.且來源比較複雜
,將極大地增長開發的繁瑣程度。且,4.若是後續參數有調整
,有增減,那散落在各處的請求地址,每一個都須要改,想一想均可怕😨。微信小程序
-
綜合以上四點問題,迫切須要統一處理這些請求參數。
2、 攔截RestTemplate請求地址,給請求地址添加參數並替換原有地址
-
RestTemplate攔截器
/**
* @author futao
* @date 2020/10/29
*/
@Slf4j
@Configuration
public class WxMiniProgramConfig {
private static AccessTokenService ACCESS_TOKEN_SERVICE;
/**
* 忽略的Path的集合
*/
private static final Set<String> IGNORE_PATH_SET = new HashSet<>();
@Autowired
private AccessTokenService accessTokenService;
/**
* PostConstruct註解的方法將會在依賴注入完成後被自動調用
*/
@PostConstruct
public void setWxMiniProgramProperties() {
WxMiniProgramConfig.ACCESS_TOKEN_SERVICE = accessTokenService;
}
/**
* 加強過的RestTemplate
*/
public static final RestTemplate REST_TEMPLATE = new RestTemplate();
static {
//兼容text/plain
WxMiniProgramConfig.REST_TEMPLATE.getMessageConverters()
.add(new TextPlainHttpMessageConverter());
//須要忽略的地址: 請求token
IGNORE_PATH_SET.add("/cgi-bin/token");
// 添加攔截器
WxMiniProgramConfig.REST_TEMPLATE.getInterceptors().add(new ClientHttpRequestInterceptor() {
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
URI uri = request.getURI();
// 原始請求參數
String rawQueryString = uri.getRawQuery();
if (!IGNORE_PATH_SET.contains(uri.getRawPath())) {
String queryStringToAppend = "access_token=" + WxMiniProgramConfig.ACCESS_TOKEN_SERVICE.get();
//追加以後的請求參數
String qsAfterAppend = StringUtils.isBlank(rawQueryString) ? queryStringToAppend : rawQueryString + "&" + queryStringToAppend;
try {
Field stringField = URI.class.getDeclaredField("string");
stringField.setAccessible(true);
// 完整請求路徑
String completeUrl = uri.getScheme() + "://" + uri.getHost() + uri.getPath() + "?" + qsAfterAppend;
// 從新設置完整請求路徑
stringField.set(uri, completeUrl);
log.debug("request complete url:{}", completeUrl);
} catch (NoSuchFieldException | IllegalAccessException e) {
log.error("反射異常", e);
throw new WxMiniProgramException("反射異常", e);
}
} else {
log.debug("ignore path :{}", uri.getPath());
}
ClientHttpResponse httpResponse = execution.execute(request, body);
if (!httpResponse.getStatusCode().is2xxSuccessful()) {
throw new WxMiniProgramException("訪問微信小程序服務器失敗:" + httpResponse.getStatusText());
}
return httpResponse;
}
});
}
/**
* 兼容text/plain
*/
static class TextPlainHttpMessageConverter extends MappingJackson2HttpMessageConverter {
public TextPlainHttpMessageConverter() {
ArrayList<MediaType> supportedMediaTypes = new ArrayList<>(1);
supportedMediaTypes.add(MediaType.TEXT_PLAIN);
this.setSupportedMediaTypes(supportedMediaTypes);
}
}
}
-
service再也不須要手動拼接參數
-
替換字段 string
的值,而不是字段query
,是由於debug後發現,最終請求的地址是string
這個字段的值。
-
測試 -
已自動加上了access_token
-
能夠愉快地CRUD惹
3、 其餘
-
將攔截器封裝成通用的方法
/**
* 追加請求參數queryString的攔截器
*
* @param paramsToAppend 須要追加的參數
* @param ignorePathSet 忽略的path的集合
* @return 攔截器
*/
public static ClientHttpRequestInterceptor appendUrlQueryStringInterceptor(Map<String, Object> paramsToAppend, Set<String> ignorePathSet) {
return (httpRequest, bytes, clientHttpRequestExecution) -> {
if (paramsToAppend != null && paramsToAppend.size() > 0) {
URI uri = httpRequest.getURI();
// 未忽略
if (ignorePathSet == null || (!ignorePathSet.contains(uri.getPath()))) {
//當前查詢字符串
String rawQueryString = uri.getRawQuery();
StringBuffer sb = new StringBuffer();
paramsToAppend.forEach((k, v) -> sb.append(k)
.append("=")
.append(v)
.append("&"));
// 須要追加的queryString
String queryStringToAppend = sb.toString();
if (queryStringToAppend.endsWith("&")) {
queryStringToAppend = queryStringToAppend.substring(0, queryStringToAppend.lastIndexOf("&"));
}
//追加以後的請求參數
String qsAfterAppend = StringUtils.isBlank(rawQueryString) ? queryStringToAppend : rawQueryString + "&" + queryStringToAppend;
try {
Field stringField = URI.class.getDeclaredField("string");
stringField.setAccessible(true);
// 完整請求路徑
String completeUrl = uri.getScheme() + "://" + uri.getHost() + uri.getPath() + "?" + qsAfterAppend;
stringField.set(uri, completeUrl);
log.debug("request complete url:{}", completeUrl);
} catch (NoSuchFieldException | IllegalAccessException e) {
log.error("反射異常", e);
throw new WxMiniProgramException("反射異常", e);
}
}
}
ClientHttpResponse httpResponse = clientHttpRequestExecution.execute(httpRequest, bytes);
if (!httpResponse.getStatusCode().is2xxSuccessful()) {
throw new WxMiniProgramException("訪問微信小程序服務器失敗:" + httpResponse.getStatusText());
}
return httpResponse;
};
}
-
使用 setDefaultUriVariables()
歡迎在評論區留下你看文章時的思考,及時說出,有助於加深記憶和理解,還能和像你同樣也喜歡這個話題的讀者相遇~api
本文分享自微信公衆號 - 喜歡天文(AllUnderControl)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。緩存