当前位置:   article > 正文

微信开发-初级接入微信公众平台MP_wxmpxmloutmessage

wxmpxmloutmessage

微信公众平台,简称weixinMP, 微信公众平台发布以前叫媒体平台,提供给合作方与用户互动,MP是media platform的简写。

说难也难,说容易也容易,看微信接入文档,会让人一头雾水,蒙逼的感觉,因为官方文档都是晦涩难懂的,显的逼格很高,下面用普通语言走一遍,让我们开始微信接入之旅吧。

1.   首先,微信服务器使用的是必需是80端口,而我们常常使用的是tomcat是8080端口,当然我们可以修改端口,而本机的80端口被浏览器占用,是不可能把tomcat改在80端口的,而且微信服务器会向我们自己的服务器以验证请求,需要内网穿透,所以大家可能看看我写的ngrok内网穿透的文章:微信开发-ngrok内网穿透部署 

2.   开发者未必有自己的微信公众号,微信官方考虑好这一点,所以提供了微信测试号,在测试号上有些功能限制,但接入以及走完部分流程是没问题的,首先当然是申请测试号了,百度“微信   测试号”或打开https://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login


用我信自己的微信号扫一扫后在微信界面点确认登录。


其中appID,appsecret是我们微信公众号接入的关键信息。





其中下方需要我们配置自己服务器的URl和JS接口安全域名。那URL填写注意:

1.   必需是80端口

2.   是自己的服务器的地址,其中该地址是微信服务器向我们自己服务器发送的数据统一入口,每次微信服务器给我们服务器推送消息时都推送到该url地址,根据消息类型的事件类型选择不同的handler去处理不同推送消息。稍后会写个统一入口,这里暂不写

js域名是我们服务器域名,注意不带http://等协议头。授权回调域名配置规范为全域名,意思是,比如我现在填chenyuanx.tunnel.2bdata.com,那么该域名下的所以请求都可以进行OAuth2.0授权。

3.   写个自己的服务器接收微信服务器推送消息的统一入口。

代码如下:

  1. /**
  2. * 微信公众号webservice主服务接口,提供与微信服务器的信息交互
  3. *
  4. * @param request
  5. * @param response
  6. * @throws Exception
  7. */
  8. @RequestMapping(value = "protal")
  9. public void wechatCore(HttpServletRequest request, HttpServletResponse response) throws Exception {
  10. response.setContentType("text/html;charset=utf-8");
  11. response.setStatus(HttpServletResponse.SC_OK);
  12. String signature = request.getParameter("signature");
  13. String nonce = request.getParameter("nonce");
  14. String timestamp = request.getParameter("timestamp");
  15. if (!wxMpService.checkSignature(timestamp, nonce, signature)) {
  16. // 消息签名不正确,说明不是公众平台发过来的消息
  17. response.getWriter().println("非法请求");
  18. return;
  19. }
  20. String echoStr = request.getParameter("echostr");
  21. if (StringUtils.isNotBlank(echoStr)) {
  22. // 说明是一个仅仅用来验证的请求,回显echostr
  23. String echoStrOut = String.copyValueOf(echoStr.toCharArray());
  24. response.getWriter().println(echoStrOut);
  25. return;
  26. }
  27. String encryptType = StringUtils.isBlank(request.getParameter("encrypt_type")) ? "raw"
  28. : request.getParameter("encrypt_type");
  29. if ("raw".equals(encryptType)) {
  30. // 明文传输的消息
  31. WxMpXmlMessage inMessage = WxMpXmlMessage.fromXml(request.getInputStream());
  32. // 微信消息路由 把相关类型的消息交给对应的handler去处理
  33. WxMpXmlOutMessage outMessage = this.weixinService.route(inMessage);
  34. if (null != outMessage) {
  35. response.getWriter().write(outMessage.toXml());
  36. }
  37. return;
  38. }
  39. if ("aes".equals(encryptType)) {
  40. // 是aes加密的消息
  41. String msgSignature = request.getParameter("msg_signature");
  42. WxMpXmlMessage inMessage = WxMpXmlMessage.fromEncryptedXml(request.getInputStream(), configStorage,
  43. timestamp, nonce, msgSignature);
  44. this.logger.debug("\n消息解密后内容为:\n{} ", inMessage.toString());
  45. WxMpXmlOutMessage outMessage = this.weixinService.route(inMessage);
  46. this.logger.info(response.toString());
  47. response.getWriter().write(outMessage.toEncryptedXml(configStorage));
  48. return;
  49. }
  50. response.getWriter().println("不可识别的加密类型");
  51. return;
  52. }

当我们填写好url和token(开发者自己随机写)点提交时,微信服务器就以get方式推送消息到填写的url上去,携带

参数

描述

signature

微信加密签名,signature结合了开发者填写的token参数和请求中的timestamp参数、nonce参数。

timestamp

时间戳

nonce

随机数

echostr

随机字符串

 

加密/校验流程如下:

1. tokentimestampnonce三个参数进行字典序排序

2. 将三个参数字符串拼接成一个字符串进行sha1加密

3. 开发者获得加密后的字符串可与signature对比,标识该请求来源于微信

若确认此次GET请求来自微信服务器,请原样返回echostr参数内容,则接入生效,成为开发者成功,否则接入失败。

我们不用自己去加密然后比较,有个weixin-java-mp.jar包提供了wxMpService.checkSignature(timestamp,nonce, signature)方法,只要传入参数就帮我们做好验证,以后还会介绍该jar包,它包装了很好url及方法供开发者使用。成功返回echostr则网页会提示“配置成功”,至此服务器配置成功,该url是核心接口,是接收微信服务器推送消息的总控。

1.   获取用户信息。

有2种access_token,一种是使用AppID和AppSecret获取的access_token,常常用在关注公众号,一种是OAuth2.0授权中产生的access_token,常用在打开页面需要用户点击授权时

4.1.全局票据access_token。

 access_token出现的原因是什么呢?appId和appSecret是定位我们公众号的2个关键数据,其实appid已经可以唯一确定一个公众号,但为了安全考虑加个appsecret,用于验证公众号相关权限信息。如果每次都用2个参数唯一定位公众号不仅麻烦,而且也不安全,容易将消息暴露在公开环境,故微信出于安全及方便考虑,让开发者用appid和appsecret去拿access_token,即用access_token就可定位一个公众号,不仅安全,而且方便,当然也可以有其它的深意,这里不做深入研究。获得access_token的方式:

用get方式请求:

https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=myappid&secret=mysappsecret

ps:微信相关的请求都是https而不是http,目的是为了安全)grant_type固定是client_credential,appid和appsecret是我们自己的消息。

比如我的测试号的请求返回的结果:


在jar里可以使用WxMpService.getAccessToken()方法获得,

源码:

  1. @Override
  2. public String getAccessToken(boolean forceRefresh) throws WxErrorException {
  3. //获得锁
  4. Lock lock = this.wxMpConfigStorage.getAccessTokenLock();
  5. try {
  6. lock.lock();
  7. //如果是强制刷新则强制将access token过期掉
  8. if (forceRefresh) {
  9. this.wxMpConfigStorage.expireAccessToken();
  10. }
  11. //如果accesstoken已经过期,则重新请求并保存到wxMpconfigStorage,否则则从
  12. //wxMpConfigStorage里取
  13. if (this.wxMpConfigStorage.isAccessTokenExpired()) {
  14. String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential" +
  15. "&appid=" + this.wxMpConfigStorage.getAppId() + "&secret="
  16. + this.wxMpConfigStorage.getSecret();
  17. try {
  18. HttpGet httpGet = new HttpGet(url);
  19. if (this.httpProxy != null) {
  20. RequestConfig config = RequestConfig.custom().setProxy(this.httpProxy).build();
  21. httpGet.setConfig(config);
  22. }
  23. try (CloseableHttpResponse response = getHttpclient().execute(httpGet)) {
  24. String resultContent = new BasicResponseHandler().handleResponse(response);
  25. WxError error = WxError.fromJson(resultContent);
  26. if (error.getErrorCode() != 0) {
  27. throw new WxErrorException(error);
  28. }
  29. WxAccessToken accessToken = WxAccessToken.fromJson(resultContent);
  30. this.wxMpConfigStorage.updateAccessToken(accessToken.getAccessToken(),
  31. accessToken.getExpiresIn());
  32. } finally {
  33. httpGet.releaseConnection();
  34. }
  35. } catch (IOException e) {
  36. throw new RuntimeException(e);
  37. }
  38. }
  39. } finally {
  40. lock.unlock();
  41. }
  42. return this.wxMpConfigStorage.getAccessToken();
  43. }

已经帮我们封装了很多方法,不用我们亲自去写url以及亲自使用httpClient去请求,如果缓存里有accessToken且没过期,否则刷新请求新的accessToken.(测试号一天可以请求2000)。

拿到accessToken就代表拿到了公众号,就可以拿到用户信息。

获得用户基本信息(包括昵称、头像、性别、所在城市、语言和关注时间。注意是拿不到用户的微信名的(openId只能算是用户标识符并不是用户名),只提供基本信息):

http请求方式:

GET https://api.weixin.qq.com/cgi-bin/user/info?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN

参数解释:access_token代表公众号,openid代表用户。lang代表语言,默认中文zh_CN。解释一下为什么要传递公众号,按正常理解传递用户号即可以拿到基本信息,和关注的公众号没关系,还是那句话,安全,安全,安全。微信服务器为了限制和管理公众号以及安全考虑,是不会无限制的提供服务的。



上图即是获得的用户信息。weixin-java-mp.jar提供了WxMpUserService.userinfo(openid,lang);获得用户基本信息。注意并没有传入access_token,因为access_token交给框架去管理生命周期,当缓存没有或已经过期再请求,如果存在则直接返回,还是那句话,微信服务器并发量很多,出支效率和安全考虑会限制token的请求次数。

1.2.       授权access_token。

当通过OAuth2.0方式弹出需要网页授权页面想获得用户基本信息时才需要用到该token,比如打开朋友圈分享的链接,该页面需要获得微信用户基本信息时使用,因为这时候用户并不有关注该公众号。

我还需要配置回调安全域名,否则会报出错了或未授权等错误提示:



找开微信 公众号后台管理,找到网页授权获取用户基本信息,如下图


点修改,添加安全域名,我的是


点确认,再弹出授权页面时就不会报相关错误了。

一般常见的OAuth2.0弹出授权页面出下:


 第一步:用户同意授权,获取code

https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect

appid:公众号唯一标识号,redirect_uri就是当用户点“确认登录”时回到的链接,response_type固定写code,scope有2种,snsapi_base (不弹出授权页面,直接跳转,只能获取用户openid),snsapi_userinfo (弹出授权页面,可通过openid拿到昵称、性别、所在地。并且,即使在未关注的情况下,只要用户授权,也能获取其信息),state值随便写。

当用户确认登录后,微信服务器会将包括code值的参数传到上面redirect_uri的地址上,如下:redirect_uri/?code=CODE&state=STATE。当然若用户禁止授权,则重定向后不会带上code参数,仅会带上state参数redirect_uri?state=STATE。

注意参数顺序必需一致。

比如我的请求:https://open.weixin.qq.com/connect/oauth2/authorize?appid=wxc5b2995ebfba4cf1& redirect_uri= http://chenyuanx.tunnel.2bdata.com /wx/weixin/home&response_type=code&scope=snsapi_userinfo &state=”11”#wechat_redirect.

回调打开链接地址:

http://chenyuanx.tunnel.2bdata.com /wx/weixin/home?code=061oz8Kb1DlFvt0eOcLb1piRJb1oz8Kg& state=”11”

 

第二步:通过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

注意这里需要填appsecret

第三步:拉取用户信息(需scope为 snsapi_userinfo)

http:GET(请使用https协议)

https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN

注意请求的地址和全局access_token请求地址是不一样的,

https://api.weixin.qq.com/cgi-bin/user/info

 

这些请求如果使用sdk是不用自己写的。

第二步可以用:wxMpserver. oauth2getAccessToken(cdoe).

看看源码:

 

  1. @Override
  2. public WxMpOAuth2AccessToken oauth2getAccessToken(Stringcode) throws WxErrorException {
  3. StringBuilder url = new StringBuilder();
  4. url.append("https://api.weixin.qq.com/sns/oauth2/access_token?");
  5. url.append("appid=").append(this.wxMpConfigStorage.getAppId());
  6. url.append("&secret=").append(this.wxMpConfigStorage.getSecret());
  7. url.append("&code=").append(code);
  8. url.append("&grant_type=authorization_code");
  9. return this.getOAuth2AccessToken(url);
  10. }

第三步可以用:wxMpServer.oauth2getUserInfo(WxMpOAuth2AccessToken oAuth2AccessToken, String lang);

源码如下:

  1. @Override
  2. public WxMpUser oauth2getUserInfo(WxMpOAuth2AccessTokenoAuth2AccessToken, String lang) throws WxErrorException {
  3. StringBuilder url = new StringBuilder();
  4. url.append("https://api.weixin.qq.com/sns/userinfo?");
  5. url.append("access_token=").append(oAuth2AccessToken.getAccessToken());
  6. url.append("&openid=").append(oAuth2AccessToken.getOpenId());
  7. if (lang == null) {
  8. url.append("&lang=zh_CN");
  9. } else {
  10. url.append("&lang=").append(lang);
  11. }
  12. try {
  13. RequestExecutor<String, String> executor = new SimpleGetRequestExecutor();
  14. String responseText = executor.execute(getHttpclient(), this.httpProxy, url.toString(), null);
  15. return WxMpUser.fromJson(responseText);
  16. } catch (IOException e) {
  17. throw new RuntimeException(e);
  18. }
  19. }


至此初步接入微信成功了。

第一, 向微信服务器配置了统一的推送消息入口。一切的推送消息都从该入口进去

第二, 填写了正确的JS回调安全域名,是不带http://等协议头的

第三, 拿到用户基本信息。分2种,一种是当我们关注了公众号,公众号即有权拿到用户信息,另一个是Oauth2.0方式的授权页面获得用户基本信息。

 

5.公众号自定义菜单生成。

微信公众号自定义菜单规则:

自定义菜单最多包括3个一级菜单,每个一级菜单最多包含5个二级菜单。

一级菜单最多4个汉字,二级菜单最多7个汉字,多出来的部分将会以“...”代替。

比如我的测试号:


那这么些自定义菜单是如何生成的呢,代码如下Main.java:

  1. /**
  2. * 自定义菜单
  3. *
  4. * @author lilw
  5. *
  6. */
  7. public class MenuConfig {
  8. private static final String wx_appid = "wxc5b2995ebfba4cXXXX";
  9. private static final String wx_appsecret = "61ef8021ff4d1376c096ade1a0XXXXX";
  10. private static final String wx_token = "20170502aaAAXXXX";
  11. public static final String prefix_url = "http://chenyuanxXXXXXX.tunnel.2bdata.com";
  12. protected static WxMenu getMenu() {
  13. WxMenu menu = new WxMenu();
  14. WxMenuButton button1 = new WxMenuButton();
  15. button1.setName("我要洗衣");
  16. button1.setType(WxConsts.BUTTON_VIEW);
  17. button1.setUrl(mainConfig.wxMpService().oauth2buildAuthorizationUrl(prefix_url + "/wx/weixin/home", "snsapi_userinfo", "123"));
  18. WxMenuButton button2 = new WxMenuButton();
  19. button2.setName("优惠活动");
  20. WxMenuButton button21 = new WxMenuButton();
  21. button21.setType(WxConsts.BUTTON_VIEW);
  22. button21.setName("分享有礼");
  23. button21.setUrl(prefix_url + "/mortgage/weixin/loan/needLoan");
  24. WxMenuButton button22 = new WxMenuButton();
  25. button22.setType(WxConsts.BUTTON_VIEW);
  26. button22.setName("我在抵扣券");
  27. button22.setUrl(prefix_url + "/mortgage/weixin/member/toLogin");
  28. button2.getSubButtons().add(button21);
  29. button2.getSubButtons().add(button22);
  30. WxMenuButton button3 = new WxMenuButton();
  31. button3.setName("我的");
  32. WxMenuButton button31 = new WxMenuButton();
  33. button31.setType(WxConsts.BUTTON_VIEW);
  34. button31.setName("我的订单");
  35. button31.setUrl(prefix_url + "/wx/weixin/myOrd");
  36. WxMenuButton button32 = new WxMenuButton();
  37. button32.setType(WxConsts.BUTTON_VIEW);
  38. button32.setName("我的帐户");
  39. button32.setUrl(prefix_url + "/mortgage/weixin/introduce");
  40. WxMenuButton button33 = new WxMenuButton();
  41. button33.setType(WxConsts.BUTTON_VIEW);
  42. button33.setName("联系我们");
  43. button33.setUrl(prefix_url + "/wx/page/joinUs.html");
  44. WxMenuButton button34 = new WxMenuButton();
  45. button34.setType(WxConsts.BUTTON_VIEW);
  46. button34.setName("司机管理");
  47. button34.setUrl("http://xd.sdaishu.com:6868/index.php?m=user&a=login");
  48. WxMenuButton button35 = new WxMenuButton();
  49. button35.setType(WxConsts.BUTTON_VIEW);
  50. button35.setName("帮助与投诉");
  51. button35.setUrl(prefix_url + "/mortgage/weixin/contactme");
  52. button3.getSubButtons().add(button31);
  53. button3.getSubButtons().add(button32);
  54. button3.getSubButtons().add(button33);
  55. button3.getSubButtons().add(button34);
  56. button3.getSubButtons().add(button35);
  57. menu.getButtons().add(button1);
  58. menu.getButtons().add(button2);
  59. menu.getButtons().add(button3);
  60. return menu;
  61. }
  62. private static MainConfig mainConfig;
  63. public static void main(String[] args) {
  64. mainConfig = new MainConfig(wx_appid, wx_appsecret, wx_token);
  65. WxMpService wxMpService = mainConfig.wxMpService(); // 获取微信创建订单的service
  66. try {
  67. wxMpService.getMenuService().menuCreate(getMenu());
  68. System.out.println("success");
  69. } catch (WxErrorException e) {
  70. e.printStackTrace();
  71. }
  72. }
  73. }

MainConfig.java:

  1. /**
  2. * 微信的主要配置信息 由wx.properties注入
  3. *
  4. */
  5. @Configuration
  6. public class MainConfig {
  7. @Value("${wx_appid}")
  8. public String appid;
  9. @Value("${wx_appsecret}")
  10. public String appsecret;
  11. @Value("${wx_token}")
  12. public String token;
  13. public static String prefix_url;
  14. /**
  15. * 如果出现 org.springframework.beans.BeanInstantiationException
  16. * https://github.com/Wechat-Group/weixin-java-tools-springmvc/issues/7
  17. * 请添加以下默认无参构造函数
  18. */
  19. protected MainConfig(){}
  20. /**
  21. * 为了生成自定义菜单使用的构造函数,其他情况Spring框架可以直接注入
  22. *
  23. * @param appid
  24. * @param appsecret
  25. * @param token
  26. * @param aesKey
  27. */
  28. protected MainConfig(String appid, String appsecret, String token) {
  29. this.appid = appid;
  30. this.appsecret = appsecret;
  31. this.token = token;
  32. }
  33. @Bean
  34. public WxMpConfigStorage wxMpConfigStorage() {
  35. WxMpInMemoryConfigStorage configStorage = new WxMpInMemoryConfigStorage();
  36. configStorage.setAppId(this.appid);
  37. configStorage.setSecret(this.appsecret);
  38. configStorage.setToken(this.token);
  39. return configStorage;
  40. }
  41. @Bean
  42. public WxMpService wxMpService() {
  43. WxMpService wxMpService = new WxMpServiceImpl();
  44. wxMpService.setWxMpConfigStorage(wxMpConfigStorage());
  45. return wxMpService;
  46. }
  47. }

执行main方法返回结果:

  1. [URL]: https://api.weixin.qq.com/cgi-bin/menu/create
  2. [PARAMS]:{"button":[{"type":"view","name":"我要洗衣","url":"https://open.weixin.qq.com/connect/oauth2/authorize?appid=wxc5b2995ebfba4cf1&redirect_uri=http%3A%2F%2Fchenyuanx.tunnel.2bdata.com%2Fwx%2Fweixin%2Fhome&response_type=code&scope=snsapi_userinfo&state=123#wechat_redirect"},{"name":"优惠活动","sub_button":[{"type":"view","name":"分享有礼","url":"http://chenyuanx.tunnel.2bdata.com/mortgage/weixin/loan/needLoan"},{"type":"view","name":"我在抵扣券","url":"http://chenyuanx.tunnel.2bdata.com/mortgage/weixin/member/toLogin"}]},{"name":"我的","sub_button":[{"type":"view","name":"我的订单","url":"http://chenyuanx.tunnel.2bdata.com/wx/weixin/myOrd"},{"type":"view","name":"我的帐户","url":"http://chenyuanx.tunnel.2bdata.com/mortgage/weixin/introduce"},{"type":"view","name":"联系我们","url":"http://chenyuanx.tunnel.2bdata.com/wx/page/joinUs.html"},{"type":"view","name":"司机管理","url":"http://xd.sdaishu.com:6868/index.php?m=user&a=login"},{"type":"view","name":"帮助与投诉","url":"http://chenyuanx.tunnel.2bdata.com/mortgage/weixin/contactme"}]}]}
  3. [RESPONSE]:{"errcode":0,"errmsg":"ok"}
  4. 15:42:01.473 [main] DEBUGme.chanjar.weixin.mp.api.impl.WxMpMenuServiceImpl - 创建菜单:{"button":[{"type":"view","name":"我要洗衣","url":"https://open.weixin.qq.com/connect/oauth2/authorize?appid=wxc5b2995ebfba4cf1&redirect_uri=http%3A%2F%2Fchenyuanx.tunnel.2bdata.com%2Fwx%2Fweixin%2Fhome&response_type=code&scope=snsapi_userinfo&state=123#wechat_redirect"},{"name":"优惠活动","sub_button":[{"type":"view","name":"分享有礼","url":"http://chenyuanx.tunnel.2bdata.com/mortgage/weixin/loan/needLoan"},{"type":"view","name":"我在抵扣券","url":"http://chenyuanx.tunnel.2bdata.com/mortgage/weixin/member/toLogin"}]},{"name":"我的","sub_button":[{"type":"view","name":"我的订单","url":"http://chenyuanx.tunnel.2bdata.com/wx/weixin/myOrd"},{"type":"view","name":"我的帐户","url":"http://chenyuanx.tunnel.2bdata.com/mortgage/weixin/introduce"},{"type":"view","name":"联系我们","url":"http://chenyuanx.tunnel.2bdata.com/wx/page/joinUs.html"},{"type":"view","name":"司机管理","url":"http://xd.sdaishu.com:6868/index.php?m=user&a=login"},{"type":"view","name":"帮助与投诉","url":"http://chenyuanx.tunnel.2bdata.com/mortgage/weixin/contactme"}]}]},结果:{"errcode":0,"errmsg":"ok"}
  5. success


说明生成菜单成功,可以取消公众号关注再关注即可看到最新结果。

自定义菜单官方API:https://mp.weixin.qq.com/wiki/10/0234e39a2025342c17a7d23595c6b40a.html

 

微信接入已经成功,当然这只是一小部分,还有很多功能需要开发,不过这只是相关API 的事了,至少,我们找到了入口。下一篇会详细介绍weixin-java-mp.jar工具包。



声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/很楠不爱3/article/detail/274148
推荐阅读
相关标签
  

闽ICP备14008679号