RestTemplate设置固定的url参数

本文涉及的产品
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
简介: RestTemplate设置固定的url参数

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;
        }
    }
}



image.png


一、 每个接口都手动拼上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()
相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
18天前
|
JavaScript
vue截取URL中的参数
vue截取URL中的参数
13 0
|
2月前
|
前端开发
[牛客网-前端大挑战QD2] 获取url参数
[牛客网-前端大挑战QD2] 获取url参数
19 0
|
2月前
BurpSuite8.2 -- 查找包含id参数的URL
BurpSuite8.2 -- 查找包含id参数的URL
19 1
|
2月前
|
分布式计算 DataWorks 关系型数据库
DataWorks数据源问题之参数“连接Url”如何解决
DataWorks数据源是指DataWorks中配置的用于数据集成的外部数据源;本合集将讲解如何在DataWorks中配置和管理数据源,以及处理数据源连接和集成过程中的问题。
29 5
|
3月前
|
测试技术
Cypress如何设置全局URL?
Cypress如何设置全局URL?
|
3月前
Copy网页中F12里的请求url到postman,并且把所有参数都带过来
Copy网页中F12里的请求url到postman,并且把所有参数都带过来
29 0
|
3月前
|
JavaScript
如何在JS中实现修改URL参数而不刷新页面
如何在JS中实现修改URL参数而不刷新页面
61 1
|
2月前
feign使用url参数传参@SpringQueryMap使用
feign使用url参数传参@SpringQueryMap使用
34 0
|
4月前
|
JavaScript
如何在JS中实现修改URL参数而不刷新页面
如何在JS中实现修改URL参数而不刷新页面
|
4月前
【超实用】Angular如何修改当前页面网页浏览器url后面?param1=xxx&param2=xxx参数(多用于通过浏览器地址参数保存用户当前操作状态的需求),实现监听url路由切换、状态变化。
【超实用】Angular如何修改当前页面网页浏览器url后面?param1=xxx&param2=xxx参数(多用于通过浏览器地址参数保存用户当前操作状态的需求),实现监听url路由切换、状态变化。
【超实用】Angular如何修改当前页面网页浏览器url后面?param1=xxx&param2=xxx参数(多用于通过浏览器地址参数保存用户当前操作状态的需求),实现监听url路由切换、状态变化。