现在微信营销,真是炙手可热,各行各业都在利用微信这个社交平台,基于微信服务器这个服务网关,开发自己的应用服务,通过微信进行传播,真是方便快捷高效!大体的通信架构如下图所示。
由于工作的需要,我也着手研究微信公众平台的开发,在这过去的一周的时间,折腾了一个小实验环境,打通微信的两个基本验证关卡。
第一道验证,就是URL,Token填写完成后,点击提交的过程。(后面会详述)
第二道验证,就是公众号服务器上面的帖子进行分享等操作时,为了让别人看到友好的“标题”+“描述”+“缩略图”这种模式的帖子,必须在待分享的web页面中通过jssdk的使用,提供jsapi_ticket, timestamp, nocestr以及signature到微信服务器,让其验证,通过才会得到友好的转帖,否则就是没有缩略图,只有一个链接的丑陋格式的帖子。(后面详述)
为了将砖抛的好,有价值,我就通过微信公众号测试平台,如何获取appid及appsecret开始说吧。首先通过http://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login这个链接,用手机微信扫一扫,即可登录获得一个测试平台的appid以及appsecret秘钥。
完成了这步后,接下来,就是我们要开发后台web应用了,对应架构图中的business server部分的后台程序的开发了。这个开发,主要完成上面说的两步验证,第一步验证相对来说简单,基本不涉及实际的业务,第二步的验证,主要是帖子的转发,微信服务器为了确保帖子是基于jssdk来开发的(要通过微信服务器的验证),才能给予友好的转发格式。
先说第一步验证,主要目的是让微信服务器知道它即将把一个微信用户的信息转发到一个可信的后台开发者服务器(business server)。如下图,我的测试平台中,business server的URL,以及对应得Token。URL是告知微信服务器,这个business server的地址。Token,这个是用来生成签名用的,在这一步验证中非常重要。business server的验证程序中生成签名的函数中用到的token必须和这里填写的一样。【说白了,就是微信服务器通过下图中配置的token,再结合其他约定好的参数按照一定的算法生成签名,将签名及参与计算的其他参数(timestamp, nonce)发给business server, business server也用同样的方式生成签名,最后在business server端比较自己生成的签名和微信服务器发来的签名是不是一样】
如何才可信呢?微信服务器在第一次与后台开发者服务器(business server)通信时会将signature, timestamp,nonce,echostr者四个字符串通过一个http GET请求发给business server。business server在收到这四个信息时,通过一定的规则(token, timestamp, nonce这三个变量名全小写按照字典序排序后,拼接成一个字符串后通过SHA1算法生成签名)生成签名,和收到的签名比对,不相等,则拒绝响应请求(防止business server被恶意攻击)。签名相同后,将echostr字符串返回给微信服务器,则完成了第一步的验证。
验证通过后,你会看到下面的提示:
若验证没有通过,你会看到下面的提示:
失败的情况,若保证签名函数处理没有错的话,很可能是URL写的不对,一定是对应处理第一步签名这个controller的URL。另外一个,就是token的不一致。business server中计算签名用的Token一定是和这里配置的一致。
下面,说说第二步验证,主要是涉及到js页面操作的验证,也就是涉及到调用jssdk中api函数的验证了。这个验证过程和前面说的验证过程是反的,这里,是business server将签名(signature)计算出来后,配合appid, timestamp, noncestr, jsapi_ticket发给微信服务器。微信服务器同样会利用收到的jsapi_ticket, timestamp, noncestr,以及当前web页面对应的URL,按照全小写字母,且字典序排序后,以下面的格式组成一个字符串,并SHA1加密生成密钥,两个密钥相等,则验证通过,否则失败。
1 //注意这里参数名必须全部小写,且必须有字典序 2 string1 = "jsapi_ticket=" + jsapi_ticket + "&noncestr=" + nonce_str + "×tamp=" + timestamp + "&url=" + url;
对于第二步,还必须说明一定,business server计算签名的过程:
1> 通过appid以及appsecret,替换https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET中的APPID以及APPSECRET,然后通过http get发给微信服务器,得到Access Token。
2> 通过获取的Access Token,替换https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=ACCESS_TOKEN&type=jsapi中的ACCESS_TOKEN,然后通过http get发送给微信服务器,得到jsapi_ticket.
这两个步骤,一定要用https,得到的数据都是有时间期限的,都是7200秒。而微信的这两个接口每天是有调用次数限制的(2000),所以,一定要缓存这两个数据。不要每次都从微信服务器取。否则影响应用的可用性,好一点的情况下,web应用的性能也不会很好,比较慢啊!
缓存的工具很多,spring有cache及encache,不过我没有选择,我选择的是Google guava cache,用起来简单,主要是比较习惯和熟练吧。下面是基本的代码:
1 package com.tkiilab.utils; 2 3 import java.util.ArrayList; 4 import java.util.List; 5 import java.util.concurrent.ExecutionException; 6 import java.util.concurrent.TimeUnit; 7 8 import org.slf4j.Logger; 9 import org.slf4j.LoggerFactory; 10 11 import com.google.common.cache.CacheBuilder; 12 import com.google.common.cache.CacheLoader; 13 import com.google.common.cache.LoadingCache; 14 import com.tkiilab.model.AccessToken; 15 16 public class TokenJsapiHolder { 17 18 private final static Logger logger = LoggerFactory.getLogger(TokenJsapiHolder.class); 19 20 private static TokenJsapiHolder instance = new TokenJsapiHolder(); 21 22 private LoadingCache<String, List<String>> cache; 23 24 private TokenJsapiHolder(){ 25 cache = CacheBuilder.newBuilder().maximumSize(5) 26 .refreshAfterWrite(7000, TimeUnit.SECONDS) 27 .build(new CacheLoader<String, List<String>>() { 28 @Override 29 public List<String> load(String appid) throws Exception { 30 31 AccessToken token = WeixinUtil.getAccessToken(Constants.APPID, Constants.SECRET); 32 if(token != null){ 33 String jsapi_ticket = WeixinUtil.getJsApiTicket(token.getAccessToken()); 34 List<String> tokenJsapi = new ArrayList<String>(); 35 String access_token = token.getAccessToken(); 36 tokenJsapi.add(access_token); 37 tokenJsapi.add(jsapi_ticket); 38 39 logger.info("Access token: " + access_token); 40 logger.info("Jsapi ticket: " + jsapi_ticket); 41 return tokenJsapi; 42 } 43 return null; 44 } 45 46 }); 47 } 48 49 public static TokenJsapiHolder getInstance(){ 50 return instance; 51 } 52 53 private LoadingCache<String, List<String>> getCache(){ 54 return cache; 55 } 56 57 public String getTokenOrJsapi(CertWxCacheable type){ 58 try { 59 return instance.getCache().get("TOKEN_TICKET").get(type.ordinal()); 60 } catch (ExecutionException e) { 61 e.printStackTrace(); 62 } 63 return null; 64 } 65 66 }
其他的,没有什么好多说的了,个人觉得,微信开发,开发环境非常重要。公司内的网络,权限管理很死板。所以,测试的时候,将后台服务应用部署到sina app engine的云服务器上,因为微信的接口访问到我们的应用服务,必然要求我们的应用程序是能够被外网访问的。所以,debug起来就是个问题。。。
新浪的云服务器,个人觉得性能不行,甚至可靠性都存在问题,至少它的低配云服务器是如此的差。而且我的程序部署在其上,测试第二步的验证逻辑时,总是失败,Access Token总是无法获取。
后来,经过好友张龙的推荐,发现了一款反向代理软件ngork,真是牛逼到爆了。下面简单的说下,我用的这个款,http://natapp.cn/,可以在这个网站下载,也有使用说明,非常简单。下载解压后,只有三个文件,ngork.cfg, ngork.exe, run.bat。(我是在windows下使用的,当然根据自己的需求,选平台)。
下面是我在我本地电脑上测试微信公众号后台应用程序时配置的run.bat的内容:
1 ngrok -config ngrok.cfg -subdomain iismaster 8080
其他的,我们都不需要改,也不用关心,毕竟一个工具而已,只需要知道如何用即可。重点是要适配自己的应用程序名以及开发环境的端口。比如我得应用程序名称是iismaster,我的web应用部署在本地的tomcat上,默认的端口号是8080,所以这里,绿色的部分写8080,最终对外访问时,ngrok默认都是映射成80端口,这个符合微信接口的要求。
使用时,cmd到ngork软件解压缩后的目录,直接运行run.bat即可。
下面是我得run.bat运行的日志:
这里,对于如何使用ngrok,主要是在配置微信公众号平台时填写的URL,及js安全域名(若有需要时),一定要填写上面Forwarding对应的URL及应用名及path信息。因为这里的映射,比如上面的http://iismaster.ngrok.natapp.cn,其实对应在本地debug时是http://localhost:8080,所以,部署在tomcat中运行时,访问path为/hello对应的结果时,全URL是http://localhost:8080/iisMaster/hello, 在ngrok启用的情况下,URL为http://iismaster.ngrok.natapp.cn/iisMaster/hello.
注意,若这个URL的映射关系没弄清楚,404的错误是难免的哟,你懂的!
Ok,到此结束,微信开发的基本逻辑抛砖完成,其中有两个痛点,一个是对两个验证的理解,尤其是第二步验证,重点注意参与计算签名的url一定是当前要分享等jssdk涉及的页面的url。 另外的一个痛点,就是环境,特指debug,这里ngrok真是神器!