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

微信公众平台,简称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.   写个自己的服务器接收微信服务器推送消息的统一入口。

代码如下:

/**
	 * 微信公众号webservice主服务接口,提供与微信服务器的信息交互
	 * 
	 * @param request
	 * @param response
	 * @throws Exception
	 */
	@RequestMapping(value = "protal")
	public void wechatCore(HttpServletRequest request, HttpServletResponse response) throws Exception {
		response.setContentType("text/html;charset=utf-8");
		response.setStatus(HttpServletResponse.SC_OK);
		String signature = request.getParameter("signature");
		String nonce = request.getParameter("nonce");
		String timestamp = request.getParameter("timestamp");
		if (!wxMpService.checkSignature(timestamp, nonce, signature)) {
			// 消息签名不正确,说明不是公众平台发过来的消息
			response.getWriter().println("非法请求");
			return;
		}
		String echoStr = request.getParameter("echostr");
		if (StringUtils.isNotBlank(echoStr)) {
			// 说明是一个仅仅用来验证的请求,回显echostr
			String echoStrOut = String.copyValueOf(echoStr.toCharArray());
			response.getWriter().println(echoStrOut);
			return;
		}
		String encryptType = StringUtils.isBlank(request.getParameter("encrypt_type")) ? "raw"
				: request.getParameter("encrypt_type");

		if ("raw".equals(encryptType)) {
			// 明文传输的消息
			WxMpXmlMessage inMessage = WxMpXmlMessage.fromXml(request.getInputStream());
			// 微信消息路由 把相关类型的消息交给对应的handler去处理
			WxMpXmlOutMessage outMessage = this.weixinService.route(inMessage);
			if (null != outMessage) {
				response.getWriter().write(outMessage.toXml());
			}
			return;
		}
		if ("aes".equals(encryptType)) {
			// 是aes加密的消息
			String msgSignature = request.getParameter("msg_signature");
			WxMpXmlMessage inMessage = WxMpXmlMessage.fromEncryptedXml(request.getInputStream(), configStorage,
					timestamp, nonce, msgSignature);
			this.logger.debug("\n消息解密后内容为:\n{} ", inMessage.toString());
			WxMpXmlOutMessage outMessage = this.weixinService.route(inMessage);
			this.logger.info(response.toString());
			response.getWriter().write(outMessage.toEncryptedXml(configStorage));
			return;
		}
		response.getWriter().println("不可识别的加密类型");
		return;
	}

当我们填写好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()方法获得,

源码:

@Override
  public String getAccessToken(boolean forceRefresh) throws WxErrorException {
//获得锁
    Lock lock = this.wxMpConfigStorage.getAccessTokenLock();
    try {
      lock.lock();

//如果是强制刷新则强制将access token过期掉
      if (forceRefresh) {

        this.wxMpConfigStorage.expireAccessToken();
      }
//如果accesstoken已经过期,则重新请求并保存到wxMpconfigStorage,否则则从
//wxMpConfigStorage里取
      if (this.wxMpConfigStorage.isAccessTokenExpired()) {
        String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential" +
          "&appid=" + this.wxMpConfigStorage.getAppId() + "&secret="
          + this.wxMpConfigStorage.getSecret();
        try {
          HttpGet httpGet = new HttpGet(url);
          if (this.httpProxy != null) {
            RequestConfig config = RequestConfig.custom().setProxy(this.httpProxy).build();
            httpGet.setConfig(config);
          }
          try (CloseableHttpResponse response = getHttpclient().execute(httpGet)) {
            String resultContent = new BasicResponseHandler().handleResponse(response);
            WxError error = WxError.fromJson(resultContent);
            if (error.getErrorCode() != 0) {
              throw new WxErrorException(error);
            }
            WxAccessToken accessToken = WxAccessToken.fromJson(resultContent);
            this.wxMpConfigStorage.updateAccessToken(accessToken.getAccessToken(),
              accessToken.getExpiresIn());
          } finally {
            httpGet.releaseConnection();
          }
        } catch (IOException e) {
          throw new RuntimeException(e);
        }
      }
    } finally {
      lock.unlock();
    }
    return this.wxMpConfigStorage.getAccessToken();
  }

已经帮我们封装了很多方法,不用我们亲自去写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).

看看源码:

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

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

源码如下:

@Override
 public WxMpUser oauth2getUserInfo(WxMpOAuth2AccessTokenoAuth2AccessToken, String lang) throws WxErrorException {
   StringBuilder url = new StringBuilder();
   url.append("https://api.weixin.qq.com/sns/userinfo?");
   url.append("access_token=").append(oAuth2AccessToken.getAccessToken());
   url.append("&openid=").append(oAuth2AccessToken.getOpenId());
   if (lang == null) {
     url.append("&lang=zh_CN");
   } else {
     url.append("&lang=").append(lang);
   }
 
   try {
     RequestExecutor<String, String> executor = new SimpleGetRequestExecutor();
     String responseText = executor.execute(getHttpclient(), this.httpProxy, url.toString(), null);
     return WxMpUser.fromJson(responseText);
   } catch (IOException e) {
     throw new RuntimeException(e);
   }
  }


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

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

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

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

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

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

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

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

比如我的测试号:


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

/**
 * 自定义菜单
 * 
 * @author lilw
 *
 */
public class MenuConfig {
	private static final String wx_appid = "wxc5b2995ebfba4cXXXX";
	private static final String wx_appsecret = "61ef8021ff4d1376c096ade1a0XXXXX";
	private static final String wx_token = "20170502aaAAXXXX";
	public static final String prefix_url = "http://chenyuanxXXXXXX.tunnel.2bdata.com";

	protected static WxMenu getMenu() {
		WxMenu menu = new WxMenu();
		WxMenuButton button1 = new WxMenuButton();
		button1.setName("我要洗衣");
		button1.setType(WxConsts.BUTTON_VIEW);
		button1.setUrl(mainConfig.wxMpService().oauth2buildAuthorizationUrl(prefix_url + "/wx/weixin/home", "snsapi_userinfo", "123"));

		WxMenuButton button2 = new WxMenuButton();
		button2.setName("优惠活动");
		WxMenuButton button21 = new WxMenuButton();
		button21.setType(WxConsts.BUTTON_VIEW);
		button21.setName("分享有礼");
		button21.setUrl(prefix_url + "/mortgage/weixin/loan/needLoan");

		WxMenuButton button22 = new WxMenuButton();
		button22.setType(WxConsts.BUTTON_VIEW);
		button22.setName("我在抵扣券");
		button22.setUrl(prefix_url + "/mortgage/weixin/member/toLogin");
		button2.getSubButtons().add(button21);
		button2.getSubButtons().add(button22);

		WxMenuButton button3 = new WxMenuButton();
		button3.setName("我的");

		WxMenuButton button31 = new WxMenuButton();
		button31.setType(WxConsts.BUTTON_VIEW);
		button31.setName("我的订单");
		button31.setUrl(prefix_url + "/wx/weixin/myOrd");

		WxMenuButton button32 = new WxMenuButton();
		button32.setType(WxConsts.BUTTON_VIEW);
		button32.setName("我的帐户");
		button32.setUrl(prefix_url + "/mortgage/weixin/introduce");

		WxMenuButton button33 = new WxMenuButton();
		button33.setType(WxConsts.BUTTON_VIEW);
		button33.setName("联系我们");
		button33.setUrl(prefix_url + "/wx/page/joinUs.html");

		WxMenuButton button34 = new WxMenuButton();
		button34.setType(WxConsts.BUTTON_VIEW);
		button34.setName("司机管理");
		button34.setUrl("http://xd.sdaishu.com:6868/index.php?m=user&a=login");

		WxMenuButton button35 = new WxMenuButton();
		button35.setType(WxConsts.BUTTON_VIEW);
		button35.setName("帮助与投诉");
		button35.setUrl(prefix_url + "/mortgage/weixin/contactme");

		button3.getSubButtons().add(button31);
		button3.getSubButtons().add(button32);
		button3.getSubButtons().add(button33);
		button3.getSubButtons().add(button34);
		button3.getSubButtons().add(button35);

		menu.getButtons().add(button1);
		menu.getButtons().add(button2);
		menu.getButtons().add(button3);

		return menu;
	}

	private static MainConfig mainConfig;
	public static void main(String[] args) {
		mainConfig = new MainConfig(wx_appid, wx_appsecret, wx_token);
		WxMpService wxMpService = mainConfig.wxMpService(); // 获取微信创建订单的service
		try {
			wxMpService.getMenuService().menuCreate(getMenu());
			System.out.println("success");
		} catch (WxErrorException e) {
			e.printStackTrace();
		}
	}
}

MainConfig.java:

/**
 * 微信的主要配置信息   由wx.properties注入
 *
 */
@Configuration
public class MainConfig {

    @Value("${wx_appid}")
    public String appid;

    @Value("${wx_appsecret}")
    public String appsecret;

    @Value("${wx_token}")
    public String token;

    public static String  prefix_url;
    /**
     * 如果出现 org.springframework.beans.BeanInstantiationException
     * https://github.com/Wechat-Group/weixin-java-tools-springmvc/issues/7
     * 请添加以下默认无参构造函数
     */
     protected MainConfig(){}
    
    /**
     * 为了生成自定义菜单使用的构造函数,其他情况Spring框架可以直接注入
     *
     * @param appid
     * @param appsecret
     * @param token
     * @param aesKey
     */
    protected MainConfig(String appid, String appsecret, String token) {
        this.appid = appid;
        this.appsecret = appsecret;
        this.token = token;
    }

    @Bean
    public WxMpConfigStorage wxMpConfigStorage() {
        WxMpInMemoryConfigStorage configStorage = new WxMpInMemoryConfigStorage();
        configStorage.setAppId(this.appid);
        configStorage.setSecret(this.appsecret);
        configStorage.setToken(this.token);
        return configStorage;
    }

    @Bean
    public WxMpService wxMpService() {
        WxMpService wxMpService = new WxMpServiceImpl();
        wxMpService.setWxMpConfigStorage(wxMpConfigStorage());
        return wxMpService;
    }

}

执行main方法返回结果:

[URL]: https://api.weixin.qq.com/cgi-bin/menu/create
[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"}]}]}
[RESPONSE]:{"errcode":0,"errmsg":"ok"}
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"}
success


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

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

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



猜你喜欢

转载自blog.csdn.net/achenyuan/article/details/71333596