前段时间小忙了一阵,微信公众号的开发,从零开始看文档,踩了不少坑,也算是熬过来了,最近考虑做一些总结,方便以后再开发的时候回顾,也给正在做相关项目的同学做个参考。

思路

微信接入:用户消息和开发者需要的事件推送都会通过微信方服务器发起一个请求,转发到你在公众平台配置的服务器url地址,微信方将带上signature,timestamp,nonce,echostr四个参数,我们自己服务器通过拼接公众平台配置的token,以及传上来的timestamp,nonce进行SHA1加密后匹配signature,返回ture说明接入成功。

消息回复:当用户给公众号发送消息时,微信服务器会将用户消息以xml格式通过POST请求到我们配置好的服务器对应的接口,而我们要做的事情就是根据消息类型等做相应的逻辑处理,并将最后的返回结果也通过xml格式return给微信服务器,微信方再传达给用户的这样一个过程。 

公众平台配置

Controller

  1. @Controller
  2. @RequestMapping("/wechat")
  3. publicclass WechatController {
  4. @Value("${DNBX_TOKEN}")
  5. private String DNBX_TOKEN;
  6.  
  7. private static final Logger LOGGER = LoggerFactory.getLogger(WechatController.class);
  8.  
  9. @Resource
  10. WechatService wechatService;
  11.  
  12. /**
  13. * 微信接入
  14. * @param wc
  15. * @return
  16. * @throws IOException
  17. */
  18. @RequestMapping(value="/connect",method = {RequestMethod.GET, RequestMethod.POST})
  19. @ResponseBody
  20. publicvoid connectWeixin(HttpServletRequest request, HttpServletResponse response) throws IOException{
  21. // 将请求、响应的编码均设置为UTF-8(防止中文乱码)
  22. request.setCharacterEncoding("UTF-8"); //微信服务器POST消息时用的是UTF-8编码,在接收时也要用同样的编码,否则中文会乱码;
  23. response.setCharacterEncoding("UTF-8"); //在响应消息(回复消息给用户)时,也将编码方式设置为UTF-8,原理同上;boolean isGet = request.getMethod().toLowerCase().equals("get");
  24.  
  25. PrintWriter out = response.getWriter();
  26.  
  27. try {
  28. if (isGet) {
  29. String signature = request.getParameter("signature");// 微信加密签名
  30. String timestamp = request.getParameter("timestamp");// 时间戳
  31. String nonce = request.getParameter("nonce");// 随机数
  32. String echostr = request.getParameter("echostr");//随机字符串
  33.  
  34. // 通过检验signature对请求进行校验,若校验成功则原样返回echostr,表示接入成功,否则接入失败 if (SignUtil.checkSignature(DNBX_TOKEN, signature, timestamp, nonce)) {
  35. LOGGER.info("Connect the weixin server is successful.");
  36. response.getWriter().write(echostr);
  37. } else {
  38. LOGGER.error("Failed to verify the signature!");
  39. }
  40. }else{
  41. String respMessage = "异常消息!";
  42.  
  43. try {
  44. respMessage = wechatService.weixinPost(request);
  45. out.write(respMessage);
  46. LOGGER.info("The request completed successfully");
  47. LOGGER.info("to weixin server "+respMessage);
  48. } catch (Exception e) {
  49. LOGGER.error("Failed to convert the message from weixin!");
  50. }
  51.  
  52. }
  53. } catch (Exception e) {
  54. LOGGER.error("Connect the weixin server is error.");
  55. }finally{
  56. out.close();
  57. }
  58. }
  59. }

>签名验证 checkSignature

从上面的controller我们可以看到,我封装了一个工具类SignUtil,调用了里面的一个叫checkSignature,传入了四个值,DNBX_TOKEN, signature, timestamp, nonce。这个过程非常重要,其实我们可以理解为将微信传过来的值进行一个加解密的过程,很多大型的项目所有的接口为保证安全性都会有这样一个验证的过程。DNBX_TOKEN我们在微信公众平台配置的一个token字符串,主意保密哦!其他三个都是微信服务器发送get请求传过来的参数,我们进行一层sha1加密:
  1. public class SignUtil {
  2.  
  3. /**
  4. * 验证签名
  5. *
  6. * @param token 微信服务器token,在env.properties文件中配置的和在开发者中心配置的必须一致
  7. * @param signature 微信服务器传过来sha1加密的证书签名
  8. * @param timestamp 时间戳
  9. * @param nonce 随机数
  10. * @return
  11. */
  12. public static boolean checkSignature(String token,String signature, String timestamp, String nonce) {
  13. String[] arr = new String[] { token, timestamp, nonce };
  14. // 将token、timestamp、nonce三个参数进行字典序排序
  15. Arrays.sort(arr);
  16.  
  17. // 将三个参数字符串拼接成一个字符串进行sha1加密
  18. String tmpStr = SHA1.encode(arr[0] + arr[1] + arr[2]);
  19.  
  20. // 将sha1加密后的字符串可与signature对比,标识该请求来源于微信
  21. return tmpStr != null ? tmpStr.equals(signature.toUpperCase()) : false;
  22. }
  23.  
  24. }

>SHA1:

  1. /**
  2. * 微信公众平台(JAVA) SDK
  3. *
  4. * SHA1算法
  5. *
  6. * @author helijun 2016/06/15 19:49
  7. */
  8. public final class SHA1 {
  9.  
  10. private static final char[] HEX_DIGITS = {'0', '1', '2', '3', '4', '5',
  11. '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
  12.  
  13. /**
  14. * Takes the raw bytes from the digest and formats them correct.
  15. *
  16. * @param bytes the raw bytes from the digest.
  17. * @return the formatted bytes.
  18. */
  19. private static String getFormattedText(byte[] bytes) {
  20. int len = bytes.length;
  21. StringBuilder buf = new StringBuilder(len * 2);
  22. // 把密文转换成十六进制的字符串形式
  23. for (int j = 0; j < len; j++) {
  24. buf.append(HEX_DIGITS[(bytes[j] >> 4) & 0x0f]);
  25. buf.append(HEX_DIGITS[bytes[j] & 0x0f]);
  26. }
  27. return buf.toString();
  28. }
  29.  
  30. public static String encode(String str) {
  31. if (str == null) {
  32. return null;
  33. }
  34. try {
  35. MessageDigest messageDigest = MessageDigest.getInstance("SHA1");
  36. messageDigest.update(str.getBytes());
  37. return getFormattedText(messageDigest.digest());
  38. } catch (Exception e) {
  39. throw new RuntimeException(e);
  40. }
  41. }
  42. }

当你在公众平台提交保存,并且看到绿色的提示“接入成功”之后,恭喜你已经完成微信接入。这个过程需要细心一点,注意加密算法里的大小写,如果接入不成功,大多数情况都是加密算法的问题,多检查检查。

实现消息自动回复service

  1. /**
  2. * 处理微信发来的请求
  3. *
  4. * @param request
  5. * @return
  6. */
  7. public String weixinPost(HttpServletRequest request) {
  8. String respMessage = null;
  9. try {
  10.  
  11. // xml请求解析
  12. Map<String, String> requestMap = MessageUtil.xmlToMap(request);
  13.  
  14. // 发送方帐号(open_id)
  15. String fromUserName = requestMap.get("FromUserName");
  16. // 公众帐号
  17. String toUserName = requestMap.get("ToUserName");
  18. // 消息类型
  19. String msgType = requestMap.get("MsgType");
  20. // 消息内容
  21. String content = requestMap.get("Content");
  22.  
  23. LOGGER.info("FromUserName is:" + fromUserName + ", ToUserName is:" + toUserName + ", MsgType is:" + msgType);
  24.  
  25. // 文本消息
  26. if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_TEXT)) {
  27. //这里根据关键字执行相应的逻辑,只有你想不到的,没有做不到的
  28. if(content.equals("xxx")){
  29.  
  30. }
  31.  
  32. //自动回复
  33. TextMessage text = new TextMessage();
  34. text.setContent("the text is" + content);
  35. text.setToUserName(fromUserName);
  36. text.setFromUserName(toUserName);
  37. text.setCreateTime(new Date().getTime() + "");
  38. text.setMsgType(msgType);
  39.  
  40. respMessage = MessageUtil.textMessageToXml(text);
  41.  
  42. } /*else if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_EVENT)) {// 事件推送
  43. String eventType = requestMap.get("Event");// 事件类型
  44.  
  45. if (eventType.equals(MessageUtil.EVENT_TYPE_SUBSCRIBE)) {// 订阅
  46. respContent = "欢迎关注xxx公众号!";
  47. return MessageResponse.getTextMessage(fromUserName , toUserName , respContent);
  48. } else if (eventType.equals(MessageUtil.EVENT_TYPE_CLICK)) {// 自定义菜单点击事件
  49. String eventKey = requestMap.get("EventKey");// 事件KEY值,与创建自定义菜单时指定的KEY值对应
  50. logger.info("eventKey is:" +eventKey);
  51. return xxx;
  52. }
  53. }
  54. //开启微信声音识别测试 2015-3-30
  55. else if(msgType.equals("voice"))
  56. {
  57. String recvMessage = requestMap.get("Recognition");
  58. //respContent = "收到的语音解析结果:"+recvMessage;
  59. if(recvMessage!=null){
  60. respContent = TulingApiProcess.getTulingResult(recvMessage);
  61. }else{
  62. respContent = "您说的太模糊了,能不能重新说下呢?";
  63. }
  64. return MessageResponse.getTextMessage(fromUserName , toUserName , respContent);
  65. }
  66. //拍照功能
  67. else if(msgType.equals("pic_sysphoto"))
  68. {
  69.  
  70. }
  71. else
  72. {
  73. return MessageResponse.getTextMessage(fromUserName , toUserName , "返回为空");
  74. }*/
  75. // 事件推送
  76. else if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_EVENT)) {
  77. String eventType = requestMap.get("Event");// 事件类型
  78. // 订阅
  79. if (eventType.equals(MessageUtil.EVENT_TYPE_SUBSCRIBE)) {
  80.  
  81. TextMessage text = new TextMessage();
  82. text.setContent("欢迎关注,xxx");
  83. text.setToUserName(fromUserName);
  84. text.setFromUserName(toUserName);
  85. text.setCreateTime(new Date().getTime() + "");
  86. text.setMsgType(MessageUtil.RESP_MESSAGE_TYPE_TEXT);
  87.  
  88. respMessage = MessageUtil.textMessageToXml(text);
  89. }
  90. // TODO 取消订阅后用户再收不到公众号发送的消息,因此不需要回复消息
  91. else if (eventType.equals(MessageUtil.EVENT_TYPE_UNSUBSCRIBE)) {// 取消订阅
  92.  
  93. }
  94. // 自定义菜单点击事件
  95. else if (eventType.equals(MessageUtil.EVENT_TYPE_CLICK)) {
  96. String eventKey = requestMap.get("EventKey");// 事件KEY值,与创建自定义菜单时指定的KEY值对应
  97. if (eventKey.equals("customer_telephone")) {
  98. TextMessage text = new TextMessage();
  99. text.setContent("0755-86671980");
  100. text.setToUserName(fromUserName);
  101. text.setFromUserName(toUserName);
  102. text.setCreateTime(new Date().getTime() + "");
  103. text.setMsgType(MessageUtil.RESP_MESSAGE_TYPE_TEXT);
  104.  
  105. respMessage = MessageUtil.textMessageToXml(text);
  106. }
  107. }
  108. }
  109. }
  110. catch (Exception e) {
  111. Logger.error("error......")
  112. }
  113. return respMessage;
  114. }

先贴代码如上,大多都有注释,读一遍基本语义也懂了不需要多解释。

有一个地方格外需要注意:

上面标红的fromUserName和toUserName刚好相反,这也是坑之一,还记得我当时调了很久,明明都没有问题就是不通,最后把这两个一换消息就收到了!其实回过头想也对,返回给微信服务器这时本身角色就变了,所以发送和接收方也肯定是相反的。

MessageUtil    

  1. public class MessageUtil {
  2.  
  3. /**
  4. * 返回消息类型:文本
  5. */
  6. public static final String RESP_MESSAGE_TYPE_TEXT = "text";
  7.  
  8. /**
  9. * 返回消息类型:音乐
  10. */
  11. public static final String RESP_MESSAGE_TYPE_MUSIC = "music";
  12.  
  13. /**
  14. * 返回消息类型:图文
  15. */
  16. public static final String RESP_MESSAGE_TYPE_NEWS = "news";
  17.  
  18. /**
  19. * 请求消息类型:文本
  20. */
  21. public static final String REQ_MESSAGE_TYPE_TEXT = "text";
  22.  
  23. /**
  24. * 请求消息类型:图片
  25. */
  26. public static final String REQ_MESSAGE_TYPE_IMAGE = "image";
  27.  
  28. /**
  29. * 请求消息类型:链接
  30. */
  31. public static final String REQ_MESSAGE_TYPE_LINK = "link";
  32.  
  33. /**
  34. * 请求消息类型:地理位置
  35. */
  36. public static final String REQ_MESSAGE_TYPE_LOCATION = "location";
  37.  
  38. /**
  39. * 请求消息类型:音频
  40. */
  41. public static final String REQ_MESSAGE_TYPE_VOICE = "voice";
  42.  
  43. /**
  44. * 请求消息类型:推送
  45. */
  46. public static final String REQ_MESSAGE_TYPE_EVENT = "event";
  47.  
  48. /**
  49. * 事件类型:subscribe(订阅)
  50. */
  51. public static final String EVENT_TYPE_SUBSCRIBE = "subscribe";
  52.  
  53. /**
  54. * 事件类型:unsubscribe(取消订阅)
  55. */
  56. public static final String EVENT_TYPE_UNSUBSCRIBE = "unsubscribe";
  57.  
  58. /**
  59. * 事件类型:CLICK(自定义菜单点击事件)
  60. */
  61. public static final String EVENT_TYPE_CLICK = "CLICK";
  62. }

这里为了程序可读性、扩展性更好一点,我做了一些封装,定义了几个常量,以及将微信传过来的一些参数封装成java bean持久化对象,核心代码如上。重点讲下xml和map之间的转换

其实这个问题要归咎于微信是用xml通讯,而我们平时一般是用json,所以可能短时间内会有点不适应

>引入jar包

  1. <!-- 解析xml -->
  2. <dependency>
  3. <groupId>dom4j</groupId>
  4. <artifactId>dom4j</artifactId>
  5. <version>1.6.1</version>
  6. </dependency>
  7.  
  8. <dependency>
  9. <groupId>com.thoughtworks.xstream</groupId>
  10. <artifactId>xstream</artifactId>
  11. <version>1.4.9</version>
  12. </dependency>

>xml转map集合对象

  1. /**
  2. * xml转换为map
  3. * @param request
  4. * @return
  5. * @throws IOException
  6. */
  7. @SuppressWarnings("unchecked")
  8. public static Map<String, String> xmlToMap(HttpServletRequest request) throws IOException{
  9. Map<String, String> map = new HashMap<String, String>();
  10. SAXReader reader = new SAXReader();
  11.  
  12. InputStream ins = null;
  13. try {
  14. ins = request.getInputStream();
  15. } catch (IOException e1) {
  16. e1.printStackTrace();
  17. }
  18. Document doc = null;
  19. try {
  20. doc = reader.read(ins);
  21. Element root = doc.getRootElement();
  22.  
  23. List<Element> list = root.elements();
  24.  
  25. for (Element e : list) {
  26. map.put(e.getName(), e.getText());
  27. }
  28.  
  29. return map;
  30. } catch (DocumentException e1) {
  31. e1.printStackTrace();
  32. }finally{
  33. ins.close();
  34. }
  35.  
  36. return null;
  37. }

>文本消息对象转换成xml

  1. /**
  2. * 文本消息对象转换成xml
  3. *
  4. * @param textMessage 文本消息对象
  5. * @return xml
  6. */
  7. public static String textMessageToXml(TextMessage textMessage){
  8. XStream xstream = new XStream();
  9. xstream.alias("xml", textMessage.getClass());
  10. return xstream.toXML(textMessage);
  11. }

到此为止已经大功告成了,这个时候可以在公众号里尝试发送“测试”,你会收到微信回复的“the text is 测试”,这也是上面代码里做的回复处理,当然你也可以发挥你的想象用他做所有你想做的事了,比如回复1查天气,2查违章等等....

JAVA实现 springMVC方式的微信接入、实现消息自动回复的更多相关文章

  1. Java实现JsApi方式的微信支付

    要使用JsApi进行微信支付,首先要从微信获得一个prepay_id,然后通过调用微信的jsapi完成支付,JS API的返回结果get_brand_wcpay_request:ok仅在用户成功完成支 ...

  2. 微信公众平台消息接口API指南

    简介 微信公众平台消息接口为开发者提供了一种新的消息处理方式.微信公众平台消息接口为开发者提供与用户进行消息交互的能力.对于成功接入消息接口的微信公众账号,当用户发消息给公众号,微信公众平台服务器会使 ...

  3. JAVA配置&注解方式搭建简单的SpringMVC前后台交互系统

    前面两篇文章介绍了 基于XML方式搭建SpringMVC前后台交互系统的方法,博文链接如下: http://www.cnblogs.com/hunterCecil/p/8252060.html htt ...

  4. 微信企业号接收消息(使用SpringMVC)

    微信企业号接收消息(使用SpringMVC) 微信企业号接收消息(使用SpringMVC) 将应用设置在回调模式时,企业可以通过回调URL接收员工回复的消息,以及员工关注.点击菜单.上报地理位置等事件 ...

  5. 第七篇 :微信公众平台开发实战Java版之如何获取微信用户基本信息

    在关注者与公众号产生消息交互后,公众号可获得关注者的OpenID(加密后的微信号,每个用户对每个公众号的OpenID是唯一的.对于不同公众号,同一用户的openid不同). 公众号可通过本接口来根据O ...

  6. 第六篇 :微信公众平台开发实战Java版之如何自定义微信公众号菜单

    我们来了解一下 自定义菜单创建接口: http请求方式:POST(请使用https协议) https://api.weixin.qq.com/cgi-bin/menu/create?access_to ...

  7. Java开发微信公众号(四)---微信服务器post消息体的接收及消息的处理

    在前几节文章中我们讲述了微信公众号环境的搭建.如何接入微信公众平台.以及微信服务器请求消息,响应消息,事件消息以及工具处理类的封装:接下来我们重点说一下-微信服务器post消息体的接收及消息的处理,这 ...

  8. Java企业微信开发_05_消息推送之发送消息(主动)

    一.本节要点 1.发送消息与被动回复消息 (1)流程不同:发送消息是第三方服务器主动通知微信服务器向用户发消息.而被动回复消息是 用户发送消息之后,微信服务器将消息传递给 第三方服务器,第三方服务器接 ...

  9. 微信公众平台开发教程(一)_微信接入校验以及token获取

    微信公众平台是运营者通过公众号为微信用户提供资讯和服务的平台,而公众平台开发接口则是提供服务的基础. 接入微信公众平台开发,开发者需要按照如下步骤完成: 1.填写服务器配置 2.验证服务器地址的有效性 ...

随机推荐

  1. MyBatis参数传入集合之foreach用法

    传入集合list // 账户类型包括门店和分公司 List<Object> scopeList = new ArrayList<Object>(); scopeList.add ...

  2. JQuery对id中含有特殊字符的转义处理

    转载--http://www.jb51.net/article/41192.htm <div id="a[]">kkkkkk</div> <scrip ...

  3. Java面试题总结(二)

    43.Java中的两种异常类型是什么?他们有什么区别? Java中有两种异常:受检查的(checked)异常和不受检查的(unchecked)异常.不受检查的异常不需要在方法或者是构造函数上声明,就算 ...

  4. Map工具系列-02-数据迁移工具使用说明

    所有cs端工具集成了一个工具面板 -打开(IE) Map工具系列-01-Map代码生成工具说明 Map工具系列-02-数据迁移工具使用说明 Map工具系列-03-代码生成BySQl工具使用说明 Map ...

  5. Beta阶段第四次Scrum Meeting

    情况简述 Beta阶段第四次Scrum Meeting 敏捷开发起始时间 2016/12/13 24:00 敏捷开发终止时间 2016/12/14 24:00 会议基本内容摘要 进度平稳推进,分配新任 ...

  6. php 月初,月末时间大统计

    //PHP获取指定月份的月初月尾时间 //获取上月月初月尾时间: $startday=strtotime(date("Y-m-d H:i:s",mktime(0,0,0,date( ...

  7. Android开发笔记之《Window下安装Ubuntu双系统,Grub无法显示Window选项》

    解决方法是: 在terminal里面输入: sudo update-grub 会找到windows的grub 重启电脑就可以了.

  8. python学习笔记-(十六)python操作mysql

    一. mysql安装 1. windows下安装mysql 1.1. 下载源: http://dev.mysql.com/downloads/installer/,请认准对应版本 Windows (x ...

  9. 一个ERP项目实施工程师的若干体会

    本人在多年的工作中,参与了ERP的研发和实施,对ERP有较深的认识.在这里,根据自已的实施过程中的一些经历,把自已在实践中的一些体会贡献出来和大家共享,由于时间和精力所限,内容难免有不当之处,挂一漏万 ...

  10. lua c api

    #include <stdio.h> #include <string.h> extern "C"{ #include <lua.h> #inc ...