最近在做微信的企业服务号,刚开始通过个人的测试平台进行开发,使用了自定义菜单,自定义菜单包含两个功能:1、扫一扫,通过扫描我们账单的二维码,绑定账户和账单的关系;2、打开我们系统的账单查询页面,查询账户的账单信息。
我的使用环境是:JSP+SpringMVC。
开发之前需要首先配置服务好的JS接口安全域名:“公众号设置”-“功能设置”-“JS接口安全域名”。
在测试号下测试运行没问题,实现方式是:扫一扫使用“scancode_waitmsg”类型,通过微信测试平台提供的“接口配置信息”配置的url回传扫描出的二维码信息,而打开网页采用自定义菜单的‘view’类型,通过menu配置的url打开网页。详细配置菜单如下:
{ "button": [ { "type": "scancode_waitmsg", "name": "扫一扫", "key": "MENU_BIND_CASE" }, { "type": "view", "name": "我的账单", "url": "https://open.weixin.qq.com/connect/oauth2/authorize?appid=#{appid}&redirect_uri=#{url}&response_type=code&scope=snsapi_base#wechat_redirect" }, ] }
其中#{appid}和#{url}是我自己定制的参数占位符,发布的时候会通过代码的替换这两个占位符。
当发布到正式服务号时,问题发生了,表现出的现象是:点击扫一扫菜单也能启动扫描二维码功能,扫描完成后也能看到访问等待的提示,但是服务器端拿不到微信端发送的任何二维码的信息。经过各种尝试后发现,服务号里边有个设置:“开发”-》“基本配置”-》“服务器配置”,这里如果配置后启用,则通过服务号的输入框输入的信息就能再次调用这个url来通知服务器输入的信息,但是带来的问题是自定义菜单没了!!!要想再次启用自定义菜单,就必须关掉这个配置!!!这个鱼与熊掌不能兼得的问题我没找到好的配置方式来解决,如果读者有好的方式希望能够分享给我,邮箱:cqu2005@163.com
我解决这个问题的办法是:放弃使用菜单的“scancode_waitmsg”类型,统一改成“view”类型的菜单,然后通过自定义的界面中调用微信的JS-SDK的方式来完成扫一扫的功能。
{ "button": [ { "type": "view", "name": "扫一扫", "url": "https://open.weixin.qq.com/connect/oauth2/authorize?appid=#{appid}&redirect_uri=#{url.scan}&response_type=code&scope=snsapi_base#wechat_redirect" }, { "type": "view", "name": "我的账单", "url": "https://open.weixin.qq.com/connect/oauth2/authorize?appid=#{appid}&redirect_uri=#{url}&response_type=code&scope=snsapi_base#wechat_redirect" }, ] }
具体的过程:
,如果你需要返回提交的结果给用户,可以通过页面跳转的方式返回给用户,具体方法是在jsp中添加一个form,通过form提交(不再使用ajax异步提交)的方式完成页面的跳转。你也可以不通过页面提示给用户,而选择通过服务号对话框,发送消息给客户,类似于银行的信用卡消费提醒,实现这个功能你需要去申请自定义消息模版(防止企业号随意的给客户发广告骚扰客户),具体可以去看微信的帮助文档。
下面就来具体的讲如何实现第二步的JS-SDK调用。
首先、调用JS-SDK是需要通过微信验证的,建议您首先去查看微信的帮助文档;服务端首要要通过access_token获得jsapi_ticket,获取的方式参考access_token。以下主要说明一下wx.config()中的四个参数的由来:timestamp、nonceStr、signature、jsApiList;除了jsApiList外,其它的三个参数都是来自服务器端。
- timestamp:服务器端当前的时间,是一个long类型的数,时间单位精确到秒,如果是java下,通过System.currentTimeMillis()获得时间精确到毫秒,这时你需要除以1000才能使用;js端和服务端要保持一致;
- nonceStr:一个随机字符串,你可以每次都固定使用一个,也可以随机一个uuid使用,js和后端的值保持一致就行;
- singature:一个通过计算获得的加密字符串,加密算法为sha1,加密的文本为一个包含4部分的字符串(timestamp、noncestr、url、jsapi_ticket),其中noncestr需要注意名字大小写,与js的大小写不同!加密过程如下:a、获得当前的timestamp、noncestr、url、jsapi_ticket,然后将他们的变量名和值连接成“name=value”的字符串;(其中url为通过request请求获得字符串,具体参照实例代码,另外在jsp中通过alert这个属性也可以拿到url:location.href.split('#')[0])b、对连接后的四个字符串进行按照asc码升序排列,排列完成后将4个字符串通过连接符“&”连接为一个字符串;c、对连接后的字符串通过sha1加密并转换成16进制的字符串;d、将timestamp、noncestr、url、jsapi_ticket返回给jsp,传入给wx.config(),以备微信服务端验证。
- jsApiList:一个string类型的数组,内容为微信提供的功能识别关键字。
然后、修改JSP页面,完成wx的config配置方法;
最后、验证通过后系统会调用wx.ready()方法,通过本方法调用我们的扫一扫接口:wx.scanQRCode()完成扫描后通过ajax或者form提交的方式提交数据到指定的处理数据URL上,完成功能。
实例代码如下:
SpringMVC-Controller-字符串加密部分:
String timestamp = String.valueOf(System.currentTimeMillis() / 1000); String noncestr = UUID.randomUUID().toString(); String url = request.getScheme() + "://" + request.getServerName() + request.getServletPath(); if (request.getQueryString() != null) { url += "?" + request.getQueryString(); } String[] tempArr = { "jsapi_ticket=" + jsapi_ticket, "timestamp=" + timestamp, "noncestr=" + noncestr, "url=" + url }; Arrays.sort(tempArr); String concatStr = tempArr[0] + "&" + tempArr[1] + "&" + tempArr[2] + "&" + tempArr[3]; String signature = DigestUtils.sha1Hex(concatStr);
JSP-wx.config:
wx.config({ debug: false, appId: '${appId}', timestamp: '${timestamp}', nonceStr: '${noncestr}', signature: '${signature}', jsApiList: ['scanQRCode'] });
JSP-wx.ready:
wx.ready(function() { wx.scanQRCode({ needResult: 1, scanType: ["qrCode", "barCode"], success: function(res) { $('#content').val(res.resultStr); $('#form1').submit(); } }); });
SpringMVC-Controller-获得二维码信息部分为一个普通的controller方法,这里不再描述。
如果总是提示验证不同的问题,微信也提供了验证测试接口。
具体开发中你可能发现,整个过程并没有关于如何获得当前微信用户和我们自有网站账户的关联关系逻辑。其实这个问题的关键就是生成singature时的url,细心的人会发现这个url中有个code,可以通过这个code到微信服务器中拿到openid,再通过openid获得我们系统的userid,由于篇幅原因不再细说,具体可以参考我的另一篇文章(如何通过用户点击自定义菜单的view类型标签获得所绑定的系统用户)。