JAVA实现 springMVC方式的微信接入、实现消息自动回复实例

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

1.思路

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

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

1.公众平台配置

2.Controller

@Controller
@RequestMapping("/wechat")
publicclass WechatController {
  @Value("${DNBX_TOKEN}")
  private String DNBX_TOKEN;

  private static final Logger LOGGER = LoggerFactory.getLogger(WechatController.class);

  @Resource
  WechatService wechatService;

  /**
   * 微信接入
   * @param wc
   * @return
   * @throws IOException
   */
  @RequestMapping(value="/connect",method = {RequestMethod.GET, RequestMethod.POST})
  @ResponseBody
  publicvoid connectWeixin(HttpServletRequest request, HttpServletResponse response) throws IOException{
    // 将请求、响应的编码均设置为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");//随机字符串 

        // 通过检验signature对请求进行校验,若校验成功则原样返回echostr,表示接入成功,否则接入失败 if (SignUtil.checkSignature(DNBX_TOKEN, signature, timestamp, nonce)) {
          LOGGER.info("Connect the weixin server is successful.");
          response.getWriter().write(echostr);
        } else {
          LOGGER.error("Failed to verify the signature!");
        }
      }else{
        String respMessage = "异常消息!";

        try {
          respMessage = wechatService.weixinPost(request);
          out.write(respMessage);
          LOGGER.info("The request completed successfully");
          LOGGER.info("to weixin server "+respMessage);
        } catch (Exception e) {
          LOGGER.error("Failed to convert the message from weixin!");
        }

      }
    } catch (Exception e) {
      LOGGER.error("Connect the weixin server is error.");
    }finally{
      out.close();
    }
  }
}

3.签名验证 checkSignature

从上面的controller我们可以看到,我封装了一个工具类SignUtil,调用了里面的一个叫checkSignature,传入了四个值,DNBX_TOKEN, signature, timestamp, nonce。这个过程非常重要,其实我们可以理解为将微信传过来的值进行一个加解密的过程,很多大型的项目所有的接口为保证安全性都会有这样一个验证的过程。DNBX_TOKEN我们在微信公众平台配置的一个token字符串,主意保密哦!其他三个都是微信服务器发送get请求传过来的参数,我们进行一层sha1加密:

public class SignUtil { 

  /**
   * 验证签名
   *
   * @param token 微信服务器token,在env.properties文件中配置的和在开发者中心配置的必须一致
   * @param signature 微信服务器传过来sha1加密的证书签名
   * @param timestamp 时间戳
   * @param nonce 随机数
   * @return
   */
  public static boolean checkSignature(String token,String signature, String timestamp, String nonce) {
    String[] arr = new String[] { token, timestamp, nonce };
    // 将token、timestamp、nonce三个参数进行字典序排序
    Arrays.sort(arr); 

    // 将三个参数字符串拼接成一个字符串进行sha1加密
    String tmpStr = SHA1.encode(arr[0] + arr[1] + arr[2]); 

    // 将sha1加密后的字符串可与signature对比,标识该请求来源于微信
    return tmpStr != null ? tmpStr.equals(signature.toUpperCase()) : false;
  } 

}

SHA1:

/**
 * 微信公众平台(JAVA) SDK
 *
 * SHA1算法
 *
 * @author helijun 2016/06/15 19:49
 */
public final class SHA1 { 

  private static final char[] HEX_DIGITS = {'0', '1', '2', '3', '4', '5',
              '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; 

  /**
   * Takes the raw bytes from the digest and formats them correct.
   *
   * @param bytes the raw bytes from the digest.
   * @return the formatted bytes.
   */
  private static String getFormattedText(byte[] bytes) {
    int len = bytes.length;
    StringBuilder buf = new StringBuilder(len * 2);
    // 把密文转换成十六进制的字符串形式
    for (int j = 0; j < len; j++) {
      buf.append(HEX_DIGITS[(bytes[j] >> 4) & 0x0f]);
      buf.append(HEX_DIGITS[bytes[j] & 0x0f]);
    }
    return buf.toString();
  } 

  public static String encode(String str) {
    if (str == null) {
      return null;
    }
    try {
      MessageDigest messageDigest = MessageDigest.getInstance("SHA1");
      messageDigest.update(str.getBytes());
      return getFormattedText(messageDigest.digest());
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }
}

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

4. 实现消息自动回复service

/**
   * 处理微信发来的请求
   *
   * @param request
   * @return
   */
  public String weixinPost(HttpServletRequest request) {
    String respMessage = null;
    try {

      // xml请求解析
      Map<String, String> requestMap = MessageUtil.xmlToMap(request);

      // 发送方帐号(open_id)
      String fromUserName = requestMap.get("FromUserName");
      // 公众帐号
      String toUserName = requestMap.get("ToUserName");
      // 消息类型
      String msgType = requestMap.get("MsgType");
      // 消息内容
      String content = requestMap.get("Content");

      LOGGER.info("FromUserName is:" + fromUserName + ", ToUserName is:" + toUserName + ", MsgType is:" + msgType);

      // 文本消息
      if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_TEXT)) {
        //这里根据关键字执行相应的逻辑,只有你想不到的,没有做不到的
        if(content.equals("xxx")){

        }

        //自动回复
        TextMessage text = new TextMessage();
        text.setContent("the text is" + content);
        text.setToUserName(fromUserName);
        text.setFromUserName(toUserName);
        text.setCreateTime(new Date().getTime() + "");
        text.setMsgType(msgType);

        respMessage = MessageUtil.textMessageToXml(text);

      } /*else if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_EVENT)) {// 事件推送
        String eventType = requestMap.get("Event");// 事件类型

        if (eventType.equals(MessageUtil.EVENT_TYPE_SUBSCRIBE)) {// 订阅
          respContent = "欢迎关注xxx公众号!";
          return MessageResponse.getTextMessage(fromUserName , toUserName , respContent);
        } else if (eventType.equals(MessageUtil.EVENT_TYPE_CLICK)) {// 自定义菜单点击事件
          String eventKey = requestMap.get("EventKey");// 事件KEY值,与创建自定义菜单时指定的KEY值对应
          logger.info("eventKey is:" +eventKey);
          return xxx;
        }
      }
      //开启微信声音识别测试 2015-3-30
      else if(msgType.equals("voice"))
      {
        String recvMessage = requestMap.get("Recognition");
        //respContent = "收到的语音解析结果:"+recvMessage;
        if(recvMessage!=null){
          respContent = TulingApiProcess.getTulingResult(recvMessage);
        }else{
          respContent = "您说的太模糊了,能不能重新说下呢?";
        }
        return MessageResponse.getTextMessage(fromUserName , toUserName , respContent);
      }
      //拍照功能
      else if(msgType.equals("pic_sysphoto"))
      {

      }
      else
      {
        return MessageResponse.getTextMessage(fromUserName , toUserName , "返回为空");
      }*/
      // 事件推送
      else if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_EVENT)) {
        String eventType = requestMap.get("Event");// 事件类型
        // 订阅
        if (eventType.equals(MessageUtil.EVENT_TYPE_SUBSCRIBE)) {

          TextMessage text = new TextMessage();
          text.setContent("欢迎关注,xxx");
          text.setToUserName(fromUserName);
          text.setFromUserName(toUserName);
          text.setCreateTime(new Date().getTime() + "");
          text.setMsgType(MessageUtil.RESP_MESSAGE_TYPE_TEXT);

          respMessage = MessageUtil.textMessageToXml(text);
        }
        // TODO 取消订阅后用户再收不到公众号发送的消息,因此不需要回复消息
        else if (eventType.equals(MessageUtil.EVENT_TYPE_UNSUBSCRIBE)) {// 取消订阅

        }
        // 自定义菜单点击事件
        else if (eventType.equals(MessageUtil.EVENT_TYPE_CLICK)) {
          String eventKey = requestMap.get("EventKey");// 事件KEY值,与创建自定义菜单时指定的KEY值对应
          if (eventKey.equals("customer_telephone")) {
            TextMessage text = new TextMessage();
            text.setContent("0755-86671980");
            text.setToUserName(fromUserName);
            text.setFromUserName(toUserName);
            text.setCreateTime(new Date().getTime() + "");
            text.setMsgType(MessageUtil.RESP_MESSAGE_TYPE_TEXT);

            respMessage = MessageUtil.textMessageToXml(text);
          }
        }
      }
    }
    catch (Exception e) {
      Logger.error("error......")
    }
    return respMessage;
  }

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

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

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

5.MessageUtil

public class MessageUtil {

  /**
   * 返回消息类型:文本
   */
  public static final String RESP_MESSAGE_TYPE_TEXT = "text"; 

  /**
   * 返回消息类型:音乐
   */
  public static final String RESP_MESSAGE_TYPE_MUSIC = "music"; 

  /**
   * 返回消息类型:图文
   */
  public static final String RESP_MESSAGE_TYPE_NEWS = "news"; 

  /**
   * 请求消息类型:文本
   */
  public static final String REQ_MESSAGE_TYPE_TEXT = "text"; 

  /**
   * 请求消息类型:图片
   */
  public static final String REQ_MESSAGE_TYPE_IMAGE = "image"; 

  /**
   * 请求消息类型:链接
   */
  public static final String REQ_MESSAGE_TYPE_LINK = "link"; 

  /**
   * 请求消息类型:地理位置
   */
  public static final String REQ_MESSAGE_TYPE_LOCATION = "location"; 

  /**
   * 请求消息类型:音频
   */
  public static final String REQ_MESSAGE_TYPE_VOICE = "voice"; 

  /**
   * 请求消息类型:推送
   */
  public static final String REQ_MESSAGE_TYPE_EVENT = "event"; 

  /**
   * 事件类型:subscribe(订阅)
   */
  public static final String EVENT_TYPE_SUBSCRIBE = "subscribe"; 

  /**
   * 事件类型:unsubscribe(取消订阅)
   */
  public static final String EVENT_TYPE_UNSUBSCRIBE = "unsubscribe"; 

  /**
   * 事件类型:CLICK(自定义菜单点击事件)
   */
  public static final String EVENT_TYPE_CLICK = "CLICK";
}

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

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

1.引入jar包

<!-- 解析xml -->
    <dependency>
      <groupId>dom4j</groupId>
      <artifactId>dom4j</artifactId>
      <version>1.6.1</version>
    </dependency>

    <dependency>
      <groupId>com.thoughtworks.xstream</groupId>
      <artifactId>xstream</artifactId>
      <version>1.4.9</version>
    </dependency>

2.xml转map集合对象

/**
   * xml转换为map
   * @param request
   * @return
   * @throws IOException
   */
  @SuppressWarnings("unchecked")
  public static Map<String, String> xmlToMap(HttpServletRequest request) throws IOException{
    Map<String, String> map = new HashMap<String, String>();
    SAXReader reader = new SAXReader();

    InputStream ins = null;
    try {
      ins = request.getInputStream();
    } catch (IOException e1) {
      e1.printStackTrace();
    }
    Document doc = null;
    try {
      doc = reader.read(ins);
      Element root = doc.getRootElement();

      List<Element> list = root.elements();

      for (Element e : list) {
        map.put(e.getName(), e.getText());
      }

      return map;
    } catch (DocumentException e1) {
      e1.printStackTrace();
    }finally{
      ins.close();
    }

    return null;
  }

3.文本消息对象转换成xml

/**
   * 文本消息对象转换成xml
   *
   * @param textMessage 文本消息对象
   * @return xml
   */
  public static String textMessageToXml(TextMessage textMessage){
    XStream xstream = new XStream();
    xstream.alias("xml", textMessage.getClass());
    return xstream.toXML(textMessage);
  }

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

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • java 实现微信服务器下载图片到自己服务器

     java 实现微信服务器下载图片到自己服务器 此功能的实现需要注意java 中IO流的操作及网路开发, 实现代码: /** * @author why * */ public class PicDownload { /** * * 根据文件id下载文件 * * * * @param mediaId * * 媒体id * * @throws Exception */ public static InputStream getInputStream(String accessToken, Stri

  • Java编程调用微信接口实现图文信息推送功能

    本文实例讲述了Java编程调用微信接口实现图文信息等推送功能.分享给大家供大家参考,具体如下: Java调用微信接口工具类,包含素材上传.获取素材列表.上传图文消息内的图片获取URL.图文信息推送. 微信图文信息推送因注意html代码字符串中将双引号(")替换成单引号('),不然信息页面中包含图片将无法显示且图片后面的内容也不会显示 官方文档:http://mp.weixin.qq.com/wiki/home/ StringBuilder sb=new StringBuilder(); sb.a

  • Java编程调用微信支付功能的方法详解

    本文实例讲述了Java编程调用微信支付功能的方法.分享给大家供大家参考,具体如下: 微信开发文档地址:https://mp.weixin.qq.com/wiki/home/ 从调用处开始 我的流程: 1.点击"支付"按钮,去后台 --> 2.后台生成支付所需数据返回页面 --> 3.页面点击"确认支付"调用微信支付js.完成支付功能. 支付按钮 <div class="button" id="pay" onc

  • java微信开发第二步 获取消息和回复消息

    接着上一篇java微信开发API第一步 服务器接入进行学习,下面介绍java微信开发第二步:获取消息和回复消息,具体内容如下 * 本示例根据微信开发文档:http://mp.weixin.qq.com/wiki/home/index.html最新版(4/3/2016 5:34:36 PM )进行开发演示. * 编辑平台:myeclipse10.7+win32+jdk1.7+tomcat7.0  * 服务器:阿里云 windows server 2008 64bits * 平台要求:servlet

  • 微信java开发之实现微信主动推送消息

    1.拉取access_token2.拉取用户信息3.主动推送消息4.接口貌似要申请权限5.依赖httpclient4.2.3 和jackson 2.2.1 复制代码 代码如下: public class WeixinAPIHelper { /**  * 获取token接口  */ private String getTokenUrl = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=

  • 微信支付java版本之JSAPI支付+发送模板消息

    本文为大家分享了java版本之JSAPI支付+发送模板消息的相关资料,供大家参考,具体内容如下 1.工具类 工具类见:微信支付JAVA版本之Native付款 2.公众账号设置 3.代码实现 openId:openId为用户与该公众账号之间代表用户的唯一标示  以下类中涉及到生成token,关闭订单接口调用,获取配置文件信息,和工具类,在其他文章中有具体代码实现 package com.zhrd.bussinss.platform.controller.rest; import java.io.F

  • java微信企业号开发之发送消息(文本、图片、语音)

    上篇文章介绍了开启回调模式,开始回调模式后我们就要实现聊天功能了.平时使用微信聊天可以发送文本消息.语音.图片.视频等,这里只实现了其中的一些功能和大家分享. 一.与微信企业号建立连接 1.企业应用调用企业号提供的接口,管理或查询企业号后台所管理的资源.或给成员发送消息等,以下称主动调用模式. 2.企业号把用户发送的消息或用户触发的事件推送给企业应用,由企业应用处理,以下称回调模式. 3.用户在微信中阅读企业应用下发的H5页面,该页面可以调用微信提供的原生接口,使用微信开放的终端能力,以下称JS

  • Java开发微信公众号接收和被动回复普通消息

    上篇说完了如何接入微信公众号,本文说一下微信公众号的最基本功能:普通消息的接收和回复.说到普通消息,那么什么是微信公众号所定义的普通消息呢,微信开发者文档中提到的接收的普通消息包括如下几类: 1.文本消息 2.图片消息 3.语音消息 4.视频消息 5.小视频消息 6.地理位置消息 7.链接消息(被动回复的消息) 被动回复的普通消息包括: 1.回复文本消息 2.回复图片消息 3.回复语音消息 4.回复视频消息 5.回复音乐消息 6.回复图文消息 其实接收消息和被动回复消息这两个动作是不分家的,这本

  • Java编程调用微信分享功能示例

    本文实例讲述了Java编程调用微信分享功能.分享给大家供大家参考,具体如下: 这篇文章介绍如何使用java开发微信分享功能,因为工作,已经开发完成,可使用. 如果想要自定义微信的分享功能,首先在自己的页面内首先使用AJAX.下面我具体举例. 首先是在页面内写入请求后台的AJAX /** * 调用微信分享接口 * */ public void WXConfig(){ String url = getPara("href"); WXConfigController scan = new W

  • java微信扫描公众号二维码实现登陆功能

    本文实例为大家分享了java微信扫描公众号二维码实现登陆的具体代码,供大家参考,具体内容如下 前提条件: 1.微信公众平台为服务号, 2.服务号实现了账号绑定功能,即将open_id 与业务系统中的用户名有对应关系 具体实现原理: 1.用户访问业务系统登陆页时,调用二维码接口,获得二维码的ticketid,同时将sessionid,ticketid和二维码的seceneid保存 2.返回登陆页时,根据ticketid获得微信二维码 3.页面通过ajax发送请求,判断是否已经扫描成功. 4.公众平

  • Java微信公众号开发之通过微信公众号获取用户信息

    最近由于公司业务,就开始研究微信开发的流程,说实话,这东西刚开始看到时候和看天书的一样,总算,看了一天的文档,测试代码终于出来了. 1.首先需要到微信网站去设置一下,我是直接用的微信测试号. 接口配置信息必须要填写的,所以说必须能将自己的服务发布出去 到此微信配置完毕,接下来就是直接上代码了 2.获取用户信息的方式一共是两种,前提都是用户关注微信公众号,一种是静默获取(snsapi_base,这种方式只能获取openid),另一种是授权获取(snsapi_userinfo,可以获取用户的详细信息

随机推荐