登录微信公众平台官网后,在公众平台官网的开发-基本设置页面,勾选协议成为开发者,点击“修改配置”按钮,填写服务器地址(URL)、Token和EncodingAESKey,其中URL是开发者用来接收微信消息和事件的接口URL。Token可由开发者可以任意填写,用作生成签名(该Token会和接口URL中包含的Token进行比对,从而验证安全性)。EncodingAESKey由开发者手动填写或随机生成,将用作消息体加解密密钥。同时,开发者可选择消息加解密方式:明文模式、兼容模式和安全模式。模式的选择与服务器配置在提交后都会立即生效,请开发者谨慎填写及选择。加解密方式的默认状态为明文模式,选择兼容模式和安全模式需要提前配置好相关加解密代码,详情请参考消息体签名及加解密部分的文档 。
@RequestMapping(value = "signature", method = RequestMethod.GET)
public void signature(HttpServletRequest request, HttpServletResponse response)throws ServletException,IOException{
String signature = request.getParameter("signature");
String timestamp = request.getParameter("timestamp");
String nonce = request.getParameter("nonce");
String echostr = request.getParameter("echostr");
PrintWriter out = response.getWriter();
if((signature!=null&×tamp!=null&&nonce!=null)&&CheckUtil.checkSignature(signature,timestamp,nonce)){
//如果校验成功,将得到的随机字符串原路返回
out.print(echostr);
}
}
public static boolean checkSignature(String signature,String timestamp,String nonce){ String[] arr = new String[]{token,timestamp,nonce}; //排序 Arrays.sort(arr); //生成字符串 StringBuffer content = new StringBuffer(); for (int i =0;ilength ;i++){ content.append(arr[i]); } //sha1加密 String temp = getSha1(content.toString()); return temp.equals(signature); }
如果校验成功将得到随机字符串原路返回,验证URL有效性成功后即接入生效,成为开发者,之后即可愉快的进行开发了。
进入正题
这次要做的功能是在用户点击自定义菜单后,跳转第三方活动页面,进入活动页面需要带上微信用户的id标识,这里我们使用的是微信的openId,简单的说下openId,一个微信号与一个公众号对应一个固定不变的openid。所以一个微信号在一个公众号下的openid是不变的,如果换了一个对应的公众号,那就是另一个openid了。且只有在微信自带浏览器中打开的项目才可获取到,那么第一步就是需要获取到用户的openId。
通过用户的点击跳转获取用户openid就必须在菜单中动态绑定用户的openid,或者在菜单的跳转URL中填写微信提供的链接,官方给了两个链接类型
一种是Scope为snsapi_base的链接
https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx520c15f417810387&redirect_uri=https%3A%2F%2Fchong.qq.com%2Fphp%2Findex.php%3Fd%3D%26c%3DwxAdapter%26m%3DmobileDeal%26showwxpaytitle%3D1%26vb2ctag%3D4_2030_5_1194_60&response_type=code&scope=snsapi_base&state=123#wechat_redirect
另一种是Scope为snsapi_userinfo的链接
https://open.weixin.qq.com/connect/oauth2/authorize?appid=wxf0e81c3bee622d60&redirect_uri=http%3A%2F%2Fnba.bluewebgame.com%2Foauth_response.php&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect
这两种链接的区别如下
应用授权作用域,snsapi_base (不弹出授权页面,直接跳转,只能获取用户openid),snsapi_userinfo (弹出授权页面,可通过openid拿到昵称、性别、所在地。并且,即使在未关注的情况下,只要用户授权,也能获取其信息)
点击菜单时微信不会主动将用户的openId发给你,而是通过回调机制,这里我们需要先在自定义菜单中填写自己的url,在填写的url中将用户重定向到snsapi_base的url中,然后再在snsapi_base中配置获取用户openid以及用户其他信息,最后跳转到一个页面
@GetMapping(value = "/redirect") public void WxRedirect(HttpServletResponse response)throws Exception{ String sUrl = serverUrl+"/WxRedirect/oauth"; String url = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx048aa1a561400011&redirect_uri="+URLEncoder.encode(sUrl,"UTF-8")+"?response_type=code&scope=snsapi_base&state=1&connect_redirect=1#wechat_redirect"; response.sendRedirect(url); }
@RequestMapping(value = "/oauth", method = RequestMethod.GET) public void weixinOAuth(HttpServletRequest request, HttpServletResponse response)throws Exception { //得到code String code = request.getParameter("code"); String redirectUrl = service.getRedirectUrl(code); response.sendRedirect(redirectUrl); }
在访问成功的情况下,后台/oauth 接口将会获得微信带来的code参数,然后通过code换取网页授权access_token
获取code后,请求以下链接获取access_token: https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code
//获取access_token String getTokenUrl = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code".replace("APPID", WXappId).replace("SECRET", WXsecret).replace("CODE", code); String result = urlConnectionUtils.urlOpen(getTokenUrl); JSONObject jsonObject = JSONObject.fromObject(result); String openId = (String) jsonObject.get("openid"); String access_token = (String)jsonObject.get("access_token");
拿到openId和access_token后我们就能做很多事了,这里我们要做的是用openId拼接活动跳转URL,拼好后返回给微信一个重定向到活动页面的地址,第一次尝试报错了
出现了redirect_url域名与后台配置不一致,网上查了资料,问题出在给微信的回调地址未在后台配置,
这里配上你的/aouth 接口地址即可。
但是!!!
以上方法只针对于服务号的操作,而我需要操作的是订阅号,订阅号并不支持通过自定义菜单获取用户openid,所以以上方案多次尝试都没能成功,唯一的途径获取用户openId就是通过引导用户来与公众号交互。微信会在用户回复的内容中带上openId,那么只能修改我们的代码了
首先看看微信消息机制
当普通微信用户向公众账号发消息时,微信服务器将POST消息的XML数据包到开发者填写的URL上,这里的url,就是之前我们配置的与微信服务器认证的url,只不过认证是用get请求,发送消息post请求,那么我们再写一个
@RequestMapping(value = "signature", method = RequestMethod.POST) public void retrunMessage(HttpServletRequest request, HttpServletResponse response)throws ServletException,IOException{ System.out.println("消息进来了"); response.setCharacterEncoding("UTF-8"); response.setCharacterEncoding("UTF-8"); PrintWriter out = response.getWriter(); try { Map, String> map = xmlToMapUtil.xmlToMap(request); String fromUserName = map.get("FromUserName"); String toUserName = map.get("ToUserName"); String msgType = map.get("MsgType"); String content = map.get("Content"); String message = null; if("text".equals(msgType)){ TextMessage text = new TextMessage(); text.setFromUserName(toUserName); //原来的信息发送者,将变成信息接受者 text.setToUserName(fromUserName); //原理的接受者,变成发送者 text.setMsgType("text"); //表示消息的类型是text类型 text.setCreateTime(new Date().getTime()); text.setContent("点击活动:" + "http://stage.vpclub.cn/vphonor/WxRedirect/userRedirect?fromUserName="+fromUserName); message = textMessageToXml.textMessageToXml(text); //装换成 xml 格式发送给微信解析 System.out.println(message); } out.print(message); } catch (DocumentException e) { // TODO Auto-generated catch block e.printStackTrace(); } }
微信消息是以xml格式进行的传递和接收,所以我们先要对其进行传参解析,工具类代码:
public class xmlToMapUtil { public static Map, String> xmlToMap(HttpServletRequest request) throws IOException, DocumentException { Map,String> map = new HashMap ,String>(); SAXReader reader = new SAXReader(); InputStream ins = request.getInputStream(); Document doc = reader.read(ins); //获取根节点 Element root = doc.getRootElement(); List list = root.elements(); for(Element e : list){ map.put(e.getName(), e.getText()); } ins.close(); return map; }
返回解析,工具类:
public static String textMessageToXml(TextMessage textMessage){
XStream xstream = new XStream();
xstream.alias("xml", textMessage.getClass());
return xstream.toXML(textMessage);
}
pom依赖
com.thoughtworks.xstream
xstream
1.4.9
然后看看效果
这里接收消息成功了,后台获取到了用户openId,然后拼接成了Url返回给用户,引导用户点击
可以看到跳转到了我们未配置任何信息的活动页面,也就是说跳转成功了,这种方法的弊端就是交互不太友好,暴露了用户openId,后期还得优化
多说一句
微信公众号开发的服务器必须是外网可访问的服务器地址,所以在本地调试时遇到的阻碍还是挺大的,每次都需要发到生产才能看到代码效果,推荐使用外网穿透工具(网上很多,可自行百度),将本地ip地址映射到公网ip上,微信填写映射ip地址就好,等测试完成后再换成正式生产地址