RestTemplate設置固定的url參數

在使用RestTemplate請求三方接口時:三方接口通常都要求在url後面拼接上固定的幾個參數,通常如accessToken進行權限校驗。而咱們在開發時,請求這些地址,如何避免在url拼接accessToken這種重複固定的編碼操做呢。web

方法固然有不少,本文提供一種經過反射偷樑換柱的寫法來實現。redis

  • 以微信小程序服務端接口請求做爲請求對象。
    • 微信小程序要求在請求時帶上 ?accesss_token=ACCESS_TOKEN
image.png

如何實現..?小程序

# 基礎配置

  • 微信小程序配置類

/**
 * 微信小程序配置類
 *
 * @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
image.png

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();
    }
}
  • 測試結果
    • 功能是實現了,可是很是繁瑣。
image.png

編碼時,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再也不須要手動拼接參數
image.png
  • 替換字段 string的值,而不是字段 query,是由於debug後發現,最終請求的地址是 string這個字段的值。

  • 測試
    • 已自動加上了access_token
image.png
  • 能夠愉快地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源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。緩存

相關文章
相關標籤/搜索