lm-book
  1. 微信公众号是一个非常轻便的项目平台,我主要是做web应用,在我看来,公众号就是一个web项目存放平台,通过view类型的菜单,可以存放多个web应用。
  2. 公众号开发入门:

配置服务器地址————即为你的公众号token验证的controller(我用的SpringMVC),这个controller比较简单,但是极为重要,这是整个项目的入口,关于微信公众号的最基本操作都放在里面,比如token验证,消息回复,其中也有一定的代码规范,比如异常处理,又比如在五秒内必须做出对微信消息的响应,如果代码不规范就会造成你的控制太明明打印的是验证成功,微信公众平台却显示配置失败的情况。

令牌(Token)————这个和你检验程序中的token一致就可以。
消息加解秘钥————我选择随机生成,(43位太麻烦,建议大家还是随机生成吧)。
消息加解密方式————如果要追求安全系数可以选择安全模式,但要自己写加密程序。
wechatcontroller:
@Autowired
private WechatPostService wechatPostService;

@RequestMapping(value = "/wechat/connect", method = { RequestMethod.GET, RequestMethod.POST })
@ResponseBody
public void ConnectWeixin(HttpServletRequest request, HttpServletResponse response) throws IOException, DocumentException {
	// 将请求、响应的编码均设置为UTF-8(防止中文乱码)
	request.setCharacterEncoding("UTF-8"); // 微信服务器POST消息时用的是UTF-8编码,在接收时也要用同样的编码,否则中文会乱码;
	response.setCharacterEncoding("UTF-8"); // 在响应消息(回复消息给用户)时,也将编码方式设置为UTF-8,原理同上;
	boolean isGet = request.getMethod().toLowerCase().equals("get");

	PrintWriter out = response.getWriter();
	try {
		if (isGet) {
			String signature = request.getParameter("signature");
			String timestamp = request.getParameter("timestamp");
			String nonce = request.getParameter("nonce");
			String echostr = request.getParameter("echostr");
			if (CheckUtils.checkSignature(signature, timestamp, nonce)) {
				System.out.println("验证成功!");
				out.write(echostr);
			} else {
				System.out.println("验证失败!");
			}
		} else {
			String respMessage = "异常消息!";
			respMessage = wechatPostService.weixinPost(request);
			out.write(respMessage);
			System.out.println(respMessage);
		}
	} catch (Exception e) {
		System.out.println("链接失败!");
	}finally {
		out.close();
	}
}

chickUtil:
/**
* 开发者模式-开发者自己填写的 token (令牌)
*/
private static final String token = "lm03";

/**
 * 功能:验证消息的确来自微信服务器
 */
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; i<arr.length; i++) {
		content.append(arr[i]);
	}
	//sha1加密
	String shaString = getSha1(content.toString());
	return shaString.equals(signature);
}
	/**
	* sha1加密
	* @param str
	* @return
	*/
	public static String getSha1(String str){
	if (null == str || 0 == str.length()){
		return null;
	}
	char[] hexDigits = { \'0\', \'1\', \'2\', \'3\', \'4\', \'5\', \'6\', \'7\', \'8\', \'9\',
	\'a\', \'b\', \'c\', \'d\', \'e\', \'f\'};
	try {
    	MessageDigest mdTemp = MessageDigest.getInstance("SHA1");
    	mdTemp.update(str.getBytes("UTF-8"));
    	byte[] md = mdTemp.digest();
    	int j = md.length;
    	char[] buf = new char[j * 2];
    	int k = 0;
    	for (int i = 0; i < j; i++) {
    	byte byte0 = md[i];
    	buf[k++] = hexDigits[byte0 >>> 4 & 0xf];
    	buf[k++] = hexDigits[byte0 & 0xf];
	}
    	return new String(buf);
	} catch (Exception e) {
		return null;
	}
}

WechatPostService:
@SuppressWarnings("null")
public String weixinPost(HttpServletRequest request) {

	String reqMessage = null;

	try {
		Map<String, String> map = MessageUtils.xmlToMap(request);
		String fromUserName = map.get("FromUserName");
		String toUserName = map.get("ToUserName");
		String msgType = map.get("MsgType");
		String content = map.get("Content");
		if (MessageUtils.MESSAGE_TEXT.equals(msgType)) {
			if ("1".equals(content)) {
				System.out.println(fromUserName);
				reqMessage = MessageUtils.initText(toUserName, fromUserName, MessageUtils.firstMenu());
			} else if ("2".equals(content)) {
				reqMessage = MessageUtils.initText(toUserName, fromUserName, MessageUtils.secondMenu());
			} else {
				reqMessage = MessageUtils.initText(toUserName, fromUserName, MessageUtils.menuText());
			}
		} else {
			String eventType = map.get("Event");
			if (MessageUtils.MESSAGE_SUBSCRIBE.equals(eventType)) {
				// 关注时创建菜单
				AccessToken accessToken = TokenUtils.getAccessToken();
				/*System.out.println("票据:" + accessToken.getToken());
				System.out.println("时效:" + accessToken.getExpiresIn());*/

				String menu = JSONObject.fromObject(TokenUtils.InitMenu()).toString();
				System.out.println(menu);
				int result = TokenUtils.CreateMenu(accessToken.getToken(), menu);
				if (result == 0) {
					System.out.println("创建菜单成功");
				} else {
					System.out.println("错误码:" + result);
				}
				reqMessage = MessageUtils.initText(toUserName, fromUserName, MessageUtils.menuText());
			} 
			
			else if (MessageUtils.MESSAGE_CLICK.equals(eventType)) {
				reqMessage = MessageUtils.initText(toUserName, fromUserName, MessageUtils.menuText());
			} else if (MessageUtils.MESSAGE_VIEW.equals(eventType)) {
				String url = map.get("EventKey");
				reqMessage = MessageUtils.initText(toUserName, fromUserName, url);
			} else if (MessageUtils.MESSAGE_UNSUBSCRIBE.equals(eventType)) {
				reqMessage = MessageUtils.initText(toUserName, fromUserName, "取消关注");
			} else {
				String key = map.get("EventKey");
				reqMessage = MessageUtils.initText(toUserName, fromUserName, key);
			}
		}
	} catch (Exception e) {
		e.printStackTrace();
	}

	return reqMessage;
}
  1. 消息回复:
    微信公众号的消息都是以XML的格式发送的,不管是客户发送给微信公众号还是微信公众号发给客户,都是如此,除文本消息外,其他的都要获取相应的数据进行封装。
    MessageUtils:
    public static final String MESSAGE_TEXT = "text";
    public static final String MESSAGE_IMAGE = "image";
    public static final String MESSAGE_VOICE = "voice";
    public static final String MESSAGE_VIDEO = "video";
    public static final String MESSAGE_LINK = "link";
    public static final String MESSAGE_LOCATION = "location";
    public static final String MESSAGE_EVENT = "event";
    public static final String MESSAGE_SUBSCRIBE = "subscribe";
    public static final String MESSAGE_UNSUBSCRIBE = "unsubscribe";
    public static final String MESSAGE_CLICK = "CLICK";
    public static final String MESSAGE_VIEW = "VIEW";
    /*

    • 封装响应
      */
      public static String initText(String toUserName,String fromUserName,String content){
      TextMessage text = new TextMessage();
      text.setFromUserName(toUserName);
      text.setToUserName(fromUserName);
      text.setMsgType(MessageUtils.MESSAGE_TEXT);
      text.setCreateTime(new Date().getTime());
      text.setContent(content);
      return textMessageToxml(text);
      }

    /**

    • 主菜单
      /
      public static String menuText(){
      StringBuffer sb = new StringBuffer();
      sb.append("欢迎您的关注,请按照菜单提示进行操作:\n\n");
      sb.append("1、家学通介绍\n");
      sb.append("2、学校风采\n\n");
      sb.append("回复任意调出此菜单。");
      return sb.toString();
      }
      /
    • 关键字1
      /
      public static String firstMenu(){
      StringBuffer sb = new StringBuffer();
      sb.append("此公众号主要用于家长能够及时了解学生在校学习情况");
      return sb.toString();
      }
      /
    • 关键字2
      /
      public static String secondMenu(){
      StringBuffer sb = new StringBuffer();
      sb.append("学校在近期的活动,如运动会,演讲,竞赛等");
      return sb.toString();
      }
      /
    • 消息请求为XML格式,将XML转为map集合
      */
      public static Map<String, String> xmlToMap(HttpServletRequest request) throws IOException, DocumentException{
      Map<String, String> map = new HashMap<String ,String>();
      SAXReader Reader = new SAXReader();
      InputStream inputStream = request.getInputStream();
      Document document = Reader.read(inputStream);
      Element root = document.getRootElement();
      List list = root.elements();
      for (Element element : list) {
      map.put(element.getName(), element.getText());
      }
      inputStream.close();
      return map;

    }
    /*

    • 响应为XML格式,将响应转为XML格式
      */
      public static String textMessageToxml(TextMessage textmessage) {
      XStream stream = new XStream();
      stream.alias("xml", textmessage.getClass());
      return stream.toXML(textmessage);
      }
  2. 菜单创建:
    我这里就不说菜单有什么类型了,对于一个web应用来说,一个view类型的菜单就是一个web项目的入口。在入口中又很多事情可以做,比如网页开发权限的获取,用户信息的获取。还有就是菜单创建的位置,当时我学的时候为这个东西苦恼了很久,最后觉得在用户关注时进行菜单创建比较好,当然这里只是提供我的建议而已,还有很多其他途径。
    代码中替换的都是微信提供的接口
    /**

    • 发起GET请求
      */
      public static JSONObject doGetStr(String url) {
      HttpClientBuilder builder = HttpClientBuilder.create();
      HttpGet httpGet = new HttpGet(url);
      JSONObject object = null;

      try {
      HttpResponse response = builder.build().execute(httpGet);
      HttpEntity entity = response.getEntity();
      if (null != entity) {
      String result = EntityUtils.toString(entity, CHARSET_FORMAT);
      object = JSONObject.fromObject(result);
      }
      } catch (ClientProtocolException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
      } catch (IOException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
      }
      return object;
      }

    /*

    • 发起post请求
      */
      public static JSONObject doPostStr(String url, String outstr) {
      HttpClientBuilder builder = HttpClientBuilder.create();
      HttpPost httpPost = new HttpPost(url);
      JSONObject object = null;
      httpPost.setEntity(new StringEntity(outstr, CHARSET_FORMAT));
      try {
      HttpResponse response = builder.build().execute(httpPost);
      HttpEntity entity = response.getEntity();
      if (null != entity) {
      String result = EntityUtils.toString(entity, CHARSET_FORMAT);
      object = JSONObject.fromObject(result);
      }
      } catch (IOException e) {

       e.printStackTrace();
      

      }
      return object;
      }

    /*

    • 获取 access_token
      */
      public static AccessToken getAccessToken() {
      AccessToken token = new AccessToken();
      String url = ACCESS_TOKEN_URL.replace("APPID", APPID).replace("APPSECRET", APPSECRET);
      JSONObject json = doGetStr(url);

      if (null != json && json.containsKey("access_token")) {
      token.setToken(json.getString("access_token"));
      token.setExpiresIn(json.getInt("expires_in"));
      }
      return token;
      }

    /*

    • 初始化菜单
      */
      public static Menu InitMenu() throws UnsupportedEncodingException {
      Menu menu = new Menu();

      ViewButton button1 = new ViewButton();
      button1.setName("家学通");
      button1.setType("view");
      button1.setUrl(getCodeUrl());

      ClickButton button2 = new ClickButton();
      button2.setName("更多");
      button2.setType("click");
      button2.setKey("11");

      ClickButton button31 = new ClickButton();
      button31.setName("扫码事件");
      button31.setType("scancode_push");
      button31.setKey("31");

      ClickButton button32 = new ClickButton();
      button32.setName("地理位置");
      button32.setType("location_select");
      button32.setKey("32");

      Button button3 = new Button();
      button3.setName("菜单");
      button3.setSub_button(new Button[] { button31, button32 });

      menu.setButton(new Button[] { button1, button2, button3 });
      return menu;
      }

    /*

    • 创建菜单
      */
      public static int CreateMenu(String token, String menu) {
      int result = 0;
      String url = CREATE_MENU_URL.replace("ACCESS_TOKEN", token);
      JSONObject jsonObject = TokenUtils.doPostStr(url, menu);
      if (jsonObject != null) {
      result = jsonObject.getInt("errcode");
      }
      return result;
      }
  3. 网页开发:
    开始我不得不说一句,微信的网页开发真的很坑,我学的时候真是痛不欲生。
    首先就是微信网页开发的配置,配置的是域名,前面不带http或者HTTPS
    其次就是微信客户信息的获取,首先获取code,由code获取acces_token,再由access_token获取用户信息,这里面有很多坑,第一个:code必须由请求重定向获取,至于为什么请求转发不行,我也不知道,你可以问下马化腾;第二:code的时效性,五分钟内你获取的只能用一次,有木有很坑;第三,这是最坑的一点,明明你只发送了一次请求,但是他却会发送多次,这就造成了我说的第二个问题,重复使用code造成获取不到用户信息,其实第一次是获取到的,但是被后面的覆盖了,最好的办法是只获取一次就存入session,后面想用的时候全部在session中获得,而且,如果你没有设置session时效,除非你退出web应用,否则session一直有效,至于微信发送多次请求的问题,我觉得除了我用映射工具的原因,一定还有其他因素,希望知道的同学可以告诉我。

  4. 最后,有关其他功能比如群发消息,支付接口,等等,虽然我没做过,但是我觉得无非是调用接口,另外,希望大家多看微信的开发文档,虽然微信的开发文档真的很烂。

分类:

技术点:

相关文章: