美文网首页
RestTemplate设置固定的url参数

RestTemplate设置固定的url参数

作者: 垃圾简书_吃枣药丸 | 来源:发表于2020-10-30 16:39 被阅读0次
    image.png

    在使用RestTemplate请求三方接口时:三方接口一般都要求在url后面拼接上固定的几个参数,一般如accessToken进行权限校验。而我们在开发时,请求这些地址,如何避免在url拼接accessToken这种重复固定的编码操作呢。

    方法当然有很多,本文提供一种通过反射偷梁换柱的写法来实现。

    • 以微信小程序服务端接口请求作为请求对象。
      • 微信小程序要求在请求时带上?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;
            }
        }
    }
    

    一、 每个接口都手动拼上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.如果后续参数有调整,有增减,那散落在各处的请求地址,每个都需要改,想想都可怕😨。

    • 综合以上四点问题,迫切需要统一处理这些请求参数。

    二、 拦截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这个字段的值。
      image.png
    image.png
    image.png
    • 测试
      • 已自动加上了access_token
    image.png
    • 可以愉快地CRUD惹

    三、 其他

    • 将拦截器封装成通用的方法
        /**
         * 追加请求参数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()

    相关文章

      网友评论

          本文标题:RestTemplate设置固定的url参数

          本文链接:https://www.haomeiwen.com/subject/mvorvktx.html