微信开放平台开发(二)

当我创建第三方应用并审核通过后,发现自己不知道该做什么,当时确切的感受就是这样的,感觉微信开放平台的文档并没有微信公众平台的清晰,微信开放平台还在很多地方引用微信公众平台的链接,但是开发者万一没有开发过微信公众平台呢?牢骚归牢骚,但事情还是要做的,于是只好求助于互联网,还好找到了一个系列的博客(已在本系列第一篇博客中说明),让我一步步了解,最终并实现了功能。

框架实现

因为我比较熟练使用Java,所以框架也就使用了SpringMVC+Spring,演示并不牵涉到数据库,所以不使用数据库和orm框架。

如果看过我的微信公众号网页授权代码优化过程,可能知道我又用在码云上创建一个演示项目。演示项目的地址是https://gitee.com/valuetodays/open-wx-demo,还和之前一样,每个功能点完成后会打一个标签(Tag)。

框架已经搭建完成,项目采用通用的MVC框架,但由于该演示项目不需连接数据库,所以就只有controller和service两层,下面将说明一下。

  • 基础包名为com.billy.demo.open,感觉这名字也不合适,凑和着用吧^_^。
  • constants包下存放一些常量类
  • controller包下存放所有控制层类,目前只有一个类AuthController,用于接收并处理微信发过来的component_verify_ticket
  • service是与controller对应的类
  • task是任务调度类,要动态处理component_access_token,当然也可以使用redis而非定时器,演示项目主要是为了更少地使用依赖和配置
  • entity包下存放了一些POJO,封装了从微信接收过来的参数或向微信发送的参数
  • util是系统中使用的工具类,util包下的wx包是从微信那边弄过来的。
  • RequestParameterInterceptor,因为微信发过来的全是接口操作,所以我们添加了一个打印参数的拦截器,来打印每个接口的参数,更方便调试。

另外要着重说明的是,代码中一定要多打LOG,多打LOG,多打LOG!尤其是改动一处就得往阿里云服务器上发布的情况,(使用花生壳可以调试就比较方便了,^_^),开发时我就是因为少打LOG得重新往阿里云上部署,同时还要再等几分钟等微信的component_verify_ticket推送(刚开始)。

接收component_verify_ticket

编码工作将要开始。

当第三方应用之后,等待审核通过后,微信会每10分钟往“授权事件接收URL”发送component_verify_ticket,它是获取component_access_token的一个重要参数。 为了接收这个参数,我们会在微信开放平台配置一下我们的授权事件接收URL。 本项目中的src/main/filters/filter-dev-env.properties中的配置稍微说明一下

APP_NAME=open-wx-demo
LOG_HOME=c:/tmp/logs/${APP_NAME}
appId=开放平台的appId
_secret=开放平台的appsecret
_encoding_aes_key=开放平台对应的aes_key
base_url=http://k16048m998.imwork.net/open-wx-demo

其中APP_NAME是logback打印日志的文件名,即项目名称,LOG_HOME是日志文件的存放目录;_appId、_secret、_encoding_aes_key是第三方平台的appid、appsecret和消息加解密Key,其中一点要注意的是消息校验Token写在了src/main/resources/server.properties中

目前项目中有一个AuthController类、一个WxVerifyMsg、一堆util、一个RequestParameterInterceptor,再依次说明一下。

  • AuthController,接收微信授权及取消授权、授权更新消息,也用来接收component_verify_ticket,我们目前就为了获取这个值。
  • WxVerifyMsg,搞过微信公众号开发的都知道,微信发给你的数据有两部分,一部分以key-value的形式发送,一部分从request.getInputStream()中获取,前一部分的值就可以使用WxVerifyMsg来直接接收(当然,这是SpringMVC提供的功能)。
  • util,解密加密微信发过来的数据,还有HttpClientUitl用于发送http/https请求。
  • RequestParameterInterceptor,仅用于调试使用,打印出请求中的key-value参数,不包括equest.getInputStream()中的数据。

项目运行后,可以看到正常运行,具体请参见代码执行。下面我们分析一下接口参数。 到第十分钟时,微信发过来请求,数据如下:

signature=edd055c66bc3255bf5e91f3ff32f18cf30b03c14
timestamp=1521524413
nonce=474592478	encrypt_type=aes
msg_signature=715514b4e74a4ec72ce7f79650442f015b2321a7

<xml>
    <AppId><![CDATA[wx95bab1ed8125031a]]></AppId>
    <Encrypt><![CDATA[B5f1x5Df41ijdYYrkGhAc9U2F1Ts3Ifk/otmXgY0w7OziphEzQNwkElI6+NDX7Nb3fe4Ef8b1JbgIThNmBwIEaES/wksGdxI7qU882p++29K0Z8aIBnBtHfVsDmqTbqvzRZgm2g3AcEY6w9f+241x6WsFdb4TuMqGPNbvYyfZBFM8gypAX7DqMkGXV5WaPJBkT9dxCWMh9g/n8K1oM1iDyk0rLq0NWIghh83rbVEvA+iDzSjy7NLB0UCKzO8xmav6mJ6DpmmRv0ilQVlKU49vyNldKLBSEDELb3piQb9uAdegRlK+TTA2fosPpBnNjY33xu0ueoqzYhlXaM+3LKhdM89jWTziXM4ollxV2r9MXDtVwIY9BCKIo6+sAECYIHdqH4rWDNX0jWmVBg5CK8ALezzv0+t18kM0p2QCZsyo5PLHPquqxIsIouX/wSNmwY6Mrr25iTZbX3uEj6CZsJzug==]]></Encrypt>
</xml>

其中第一部分数据是key-value形式,第二部分数据是从request.getInputStream()中获取的,还是加密的。 使用微信提供的解密方法即可解密成明文,此处解密后的明文如下(其中我已把AppId的值处理了一下^_^)

<xml><AppId><![CDATA[aaaaaaaaaaaaaaaaaaa]]></AppId>
<CreateTime>1521524413</CreateTime>
<InfoType><![CDATA[component_verify_ticket]]></InfoType>
<ComponentVerifyTicket><![CDATA[ticket@@@zJSa-304dQiQinvAvwWPNz6QpN5c7ZI9b1F8Fla4E51m9EIvZLk5TAHtZravdU2e-eXmCTJPqTNVRoaoae-GRA]]></ComponentVerifyTicket>
</xml>

可以看到明文中有两个参数比较重要,第一个是InfoType,第二个是ComponentVerifyTicket。在获取ComponentVerifyTicket之前一定要判断InfoType是component_verify_ticket,不然不要直接获取ComponentVerifyTicket的值。我遇到过这个问题,如下

    if (StringUtils.isNotEmpty(infoType)) {
        if (OpenConstants.INFOTYPE_COMPONENT_VERIFY_TICKET.equals(infoType)) {
            componentVerifyTicket = xmlMap.get("componentVerifyTicket");
            LOG.debug("componentVerifyTicket was flushed into redis.");
        }
    }

第一次写的是

    componentVerifyTicket = xmlMap.get("componentVerifyTicket");
    LOG.debug("componentVerifyTicket was flushed into redis.");

因为授权与取消授权都会往这个接口发送数据,目前我所知的InfoType的值可能为unauthorized、authorized、component_verify_ticket三种。暂时没有在官网找到InfoType的所有值列表。

不出意外的话,程序就能正确打印出component_verify_ticket的值了。需要注意的是,开发者要把该值保存起来(如Redis中),本示例把它存放在了变量中,但是在重启后得等待微信下一次推送。

可能遇到的问题

InvalidKeyException: Illegal key size

出现该问题,请自行了解jce,并安装jce文件即可,注意要与你的jdk或jre匹配。

解密微信的密文不成功

微信提供的加解密类及demo的下载位置在这里,如不能下载请访问https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1419318479&token=&lang=zh_CN在页面内搜索“点击下载”即可。

要注意XMLParse类中的第45行代码有问题(大坑)

NodeList nodelist2 = root.getElementsByTagName("ToUserName");

仔细看上面微信发过来的密文会发现是xml接点下有Encrypt和AppId两个接点,导致解密不成功,解决方案是把上面那句修改为

NodeList nodelist2 = root.getElementsByTagName(root.getChildNodes().item(1).getNodeName());

即可,示例代码中已做修改。顺便说一句,上面ToUserName其实是处理微信公众号的消息的,这样改了之后就能同时处理公众号和开放平台两处的解密功能。

服务端接收不到微信每10分钟的推送

这个时候要是使用了nginx,就检查一下nginx的日志。我在这里出现的问题是https与http的问题,直接把地址输入到浏览器中访问是正常的,但就是接收不了微信的请求。

猜你喜欢

转载自my.oschina.net/valuetodays/blog/1784897
今日推荐