白羽
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。那么这个动作都做了些什么呢?

这样当设置一个URLFactory的domain参数时,代码就会自动刷新对象内部所有private final修饰的URLBean的绝对路径。下面的例子是利用Spring生成tokenAPI实例的配置方法:
让代码风格统一化
当调用微信接口时,可以预见的情况分为:返回为空(null);返回有数据,但调用失败;返回有数据,调用成功。分解的流程如下图所示:

我们来看一下调用失败时,微信给我们返回什么内容:
{"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
您输入的评论内容中包含违禁敏感词
我知道了

请输入正确的手机号码
请输入正确的验证码
您今天的短信下发次数太多了,明天再试试吧!
我们会在第一时间安排职业规划师联系您!
您也可以联系我们的职业规划师咨询:
版权所有 职坐标-一站式AI+学习就业服务平台 沪ICP备13042190号-4
上海海同信息科技有限公司 Copyright ©2015 www.zhizuobiao.com,All Rights Reserved.
沪公网安备 31011502005948号