微信接入之如何让HTTP接口地址拼接效率更高
白羽 2018-06-20 来源 :网络 阅读 1526 评论 0

摘要:本文将带你了解微信接入之如何让HTTP接口地址拼接效率更高,希望本文对大家学微信有所帮助。



让HTTP接口地址拼接效率更高

微信的主动调用接口使用HTTP方式实现,严格意义上来说是HTTPS,这样就保证了传输过程的安全性。让我们先从文档中随便看几个接口的地址:

获取access_token接口:

https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET

获取用户基本信息接口:

https://api.weixin.qq.com/cgi-bin/user/info?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN

在评估过其他接口后总结到结论:除去业务参数外,接口使用的协议和域名相同,路径不同。在笔者的工作中也涉及到了类似的开发实践:正式环境中使用域名DomainA;测试环境中使用域名DomainB,通过指定host来访问特定的测试环境。这样做的好处就是测试和线上隔离得非常彻底,避免因为使用相同域名导致访问混乱(尤其是带有写入功能的接口)。微信的接入相对于笔者上述的需求更加简单,因为不涉及到访问微信的测试环境,域名一直是线上域名,测试时使用的账号不同而已,这与接口地址无关。

当然,你可以在接入每个接口时都写入完整地址,但是这样就损失掉了代码的可维护性。举个最简单的例子,在微信官方文档中有如下描述:

开发者可以根据自己的服务器部署情况,选择最佳的接入点(延时更低,稳定性更高)。除此之外,可以将其他接入点用作容灾用途,当网络链路发生故障时,可以考虑选择备用接入点来接入。1. 通用域名(api.weixin.qq.com),使用该域名将访问官方指定就近的接入点;2. 上海域名(sh.api.weixin.qq.com),使用该域名将访问上海的接入点;3. 深圳域名(sz.api.weixin.qq.com),使用该域名将访问深圳的接入点;4. 香港域名(hk.api.weixin.qq.com),使用该域名将访问香港的接入点。

系统上线后你可能发现访问速度不甚理想,而选择了特定地区的域名后访问速度变得很快。如果你每个接口都写完整路径那就杯具了。于是我们有动机要实现一个需求:接口域名可配,一改全改。

你或许会想:那还不简单?把配置的域名拿到,每次调用接口的时候对地址进行拼接:”https://”+Domain+”/cgi-bin/….”。不可否认,这样做确实可以实现功能,然而这样做够高效吗?我们的服务启动后,拿到微信接口的域名配置,此后的启动-运行生命周期内几乎不会再对该值进行修改。每次都进行拼接是对计算和内存资源的浪费,最好是加载一次就生成一个固定的链接,每次都拿这个生成好的地址。

于是我们创建了一个这样的URL配置对象:

public class URLBean {

    /** 相对地址 */

    private final String relativeURL;

    /** 绝对地址 */

    private String absoluteURL;

    /**

     * 构建URL封装对象

     * @param relativeURL 相对路径,初始化后不可修改

     */

    public URLBean(final String relativeURL) {

        this.relativeURL = relativeURL;

        this.absoluteURL = relativeURL;

    }

    /**

     * 获取相对地址

     * @return 相对地址

     */

    public String getRelativeURL() {

        return relativeURL;

    }

    /**

     * 获取绝对地址

     * @return 绝对地址

     */

    public String getAbsoluteURL() {

        return absoluteURL;

    }

 

}

注意,这里的URLBean不是一个严格意义上的Bean,其中的相对地址relativeURL被修饰为final,对象的构造函数中对其进行初始化赋值,赋值后就不能被修改。绝对地址absoluteURL在表面上是一个只读属性,并且默认是和相对地址relativeURL一样的,没有被final修饰。先不急,它的作用一会儿介绍。我们先创建一个接口地址工厂,顾名思义,该工厂是用来生产对应接口的完整地址的。以获取access_token接口为例:

public class TokenAPIURLFactory extends AbstractURLFactory {

    /** 接入令牌接口URL */

    private final URLBean token = new URLBean("/cgi-bin/token");

    /**

     * 获取接入令牌接口URL

     * @return 接入令牌接口URL

     */

    public String getToken() {

        return token.getAbsoluteURL();

    }

}

相信你看完上面这段代码更让人一头雾水了。创建了一个被final修饰过的URLBean,只写了一个相对地址,然后就给出了一个获取完整地址的getToken()方法,怎么生成的完整地址?来,接着看它的父类AbstractURLFactory里面都写了什么:

public abstract class AbstractURLFactory {

    /** 是否使用https */

    private Boolean enableSSL;

    /** 域名 */

    private String domain;

    /**

     * 获取是否使用https

     * @return 是否使用https

     */

    public Boolean getEnableSSL() {

        return enableSSL;

    }

    /**

     * 获取域名

     * @return 域名

     */

    public String getDomain() {

        return domain;

    }

    /**

     * 递归设置domain

     * @param enableSSL

     * @param domain

     * @param clazz

     */

    private void recursiveSetDomain(Boolean enableSSL, String domain, Class clazz){

        if (null == clazz){

            return;

        }

        //获取所有字段

        Field[] declaredFields = clazz.getDeclaredFields();

        //特定修饰符字段筛选器

        int modifierFilter = Modifier.PRIVATE | Modifier.FINAL;

        boolean hasDomain = StringUtils.isNotBlank(domain);

        if (hasDomain){

            domain = domain.trim();

        }

        //默认开启SSL

        boolean useSSL = (null == enableSSL ? true : enableSSL);

        for (Field field : declaredFields) {

            //筛选特定字段

            if (modifierFilter != (modifierFilter & field.getModifiers())){

                continue;

            }

            //筛选指定类型类型

            if (URLBean.class != field.getType()){

                continue;

            }

            field.setAccessible(true);

            try{

                URLBean urlBean = (URLBean) field.get(this);

                Field relativeURL = urlBean.getClass().getDeclaredField("relativeURL");

                Field absoluteURLField = urlBean.getClass().getDeclaredField("absoluteURL");

                relativeURL.setAccessible(true);

                absoluteURLField.setAccessible(true);

                if (hasDomain){

                    //这里不使用String.format是考虑到有可能以后相对URL中存在%s通配符

                    if (useSSL){

                        absoluteURLField.set(urlBean, "https://".concat(domain).concat((String)relativeURL.get(urlBean)));

                    }else{

                        absoluteURLField.set(urlBean, "//".concat(domain).concat((String)relativeURL.get(urlBean)));

                    }

                }else{

                    absoluteURLField.set(urlBean, relativeURL.get(urlBean));

                }

            }catch(Exception e){

                //忽略错误

            }

        }

        recursiveSetDomain(enableSSL, domain, clazz.getSuperclass());

    }

    /**

     * 设置是否使用https

     * @param enableSSL 是否使用https

     */

    public void setEnableSSL(Boolean enableSSL) {

        this.enableSSL = enableSSL;

        //防止属性设置先后不同步的问题,每一次属性的改变都要刷新URL

        recursiveSetDomain(this.enableSSL, this.domain, getClass());

    }

    /**

     * 设置域名

     * @param domain 域名

     */

    public void setDomain(String domain){

        this.domain = domain;

        //防止属性设置先后不同步的问题,每一次属性的改变都要刷新URL

        recursiveSetDomain(this.enableSSL, this.domain, getClass());

    }

 

}

里面是一些通用的配置信息:

enableSSL:是否启用SSL(默认启用)

domain:接口使用的域名

与普通的Bean不同在于,这个抽象的URL工厂配置属性都是是只写(write-only)的,并且写入之后附加了递归设置域名的动作recursiveSetDomain。那么这个动作都做了些什么呢?

 微信接入之如何让HTTP接口地址拼接效率更高

这样当设置一个URLFactory的domain参数时,代码就会自动刷新对象内部所有private final修饰的URLBean的绝对路径。下面的例子是利用Spring生成tokenAPI实例的配置方法:

    

    

让代码风格统一化

当调用微信接口时,可以预见的情况分为:返回为空(null);返回有数据,但调用失败;返回有数据,调用成功。分解的流程如下图所示:

 微信接入之如何让HTTP接口地址拼接效率更高

我们来看一下调用失败时,微信给我们返回什么内容:

{"errcode":40013,"errmsg":"invalid appid"}

当调用成功时返回的内容(以获取access_token接口为例):

{"access_token":"ACCESS_TOKEN","expires_in":7200}

通读文档后发现:所有的调用失败返回数据格式都是一样的。根据业务不同,调用成功时的数据格式各自有很大的不同,但是调用任何一个接口都有失败的可能。因此我们把调用失败时的数据抽象成了所有返回对象的父类:

@JsonInclude(Include.NON_NULL)public abstract class WeChatAPIRet implements Serializable {

    private static final long serialVersionUID = 2422896542684235099L;

    /** 成功返回的代码 */

    public static final int CODE_OK = 0;

    /** 错误代码 */

    @JsonProperty(value = "errcode")

    private Integer errcode;

    /** 错误消息 */

    @JsonProperty(value = "errmsg")

    private String errmsg;

    /**

     * 判断是否是成功返回

     * @return

     */

    public boolean isSuccess(){

        if (null == errcode || errcode == CODE_OK){

            return true;

        }else{

            return false;

        }

    }

    //一些getters和setters,这里省略...

}

判断是否调用成功是个经常性的行为,因此为了简化判断逻辑,加入了一个isSuccess()方法,当返回结果中没有errcode字段,或者errcode字段等于0,则表示调用成功,其他情况认为调用失败。

然后定义一个接口调用正常返回时的数据结构映射(以获取access_token接口为例):

public class TokenResult extends WeChatAPIRet {

    private static final long serialVersionUID = -8242372755146179695L;

    /** 获取到的凭证 */

    @JsonProperty(value = "access_token")

    private String accessToken;

    /** 凭证有效时间,单位:秒 */

    @JsonProperty(value = "expires_in")

    private Integer expiresIn;

    //一些getters和setters,这里省略...

}

JSON转换组件会根据当时返回的数据进行字段匹配,无论成功还是失败都将生成一个TokenResult对象,在业务中直接调用其继承下来的isSuccess()方法即可判断是否成功,相关伪代码如下:

private void toDoSomething(TokenParam param) throws WeChatAPIException {

    TokenResult token = tokenAPI.getToken(param);

    if (null == token){

        throw new WeChatAPIException(APIErrEnum.SysErr, new IllegalStateException("获取到的token为空"));

    }

    if (!token.isSuccess()){

        throw new WeChatAPIException(token.getErrcode(), token.getErrmsg());

    }

    try {

        //TODO 业务方面的操作

    } catch (IOException e) {

        throw new WeChatAPIException(APIErrEnum.SysErr, e);

    }

}

简单来说,只要你的返回结果继承自WeChatAPIRet,在使用过程中的代码风格就会自然而然保持一致了。这也是Java作为工业化编程语言的一个特点。



本文由职坐标整理并发布,希望对同学们有所帮助。了解更多详情请关注职坐标移动开发之微信频道!


本文由 @白羽 发布于职坐标。未经许可,禁止转载。
喜欢 | 0 不喜欢 | 0
看完这篇文章有何感觉?已经有0人表态,0%的人喜欢 快给朋友分享吧~
评论(0)
后参与评论

您输入的评论内容中包含违禁敏感词

我知道了

助您圆梦职场 匹配合适岗位
验证码手机号,获得海同独家IT培训资料
选择就业方向:
人工智能物联网
大数据开发/分析
人工智能Python
Java全栈开发
WEB前端+H5

请输入正确的手机号码

请输入正确的验证码

获取验证码

您今天的短信下发次数太多了,明天再试试吧!

提交

我们会在第一时间安排职业规划师联系您!

您也可以联系我们的职业规划师咨询:

小职老师的微信号:z_zhizuobiao
小职老师的微信号:z_zhizuobiao

版权所有 职坐标-一站式AI+学习就业服务平台 沪ICP备13042190号-4
上海海同信息科技有限公司 Copyright ©2015 www.zhizuobiao.com,All Rights Reserved.
 沪公网安备 31011502005948号    

©2015 www.zhizuobiao.com All Rights Reserved