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

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

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查违章等等....

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

以上是小编为您精心准备的的内容,在的博客、问答、公众号、人物、课程等栏目也有的相关内容,欢迎继续使用右上角搜索按钮进行搜索spring
, mvc
, 自动回复
, 微信接入
springmvc微信接入
springmvc接入微信、springmvcmybatis实例、springmvc项目实例、spring mvc登录实例、netty springmvc实例,以便于您获取更多的相关知识。

时间: 2024-09-20 06:01:56

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

JAVA实现 SpringMVC方式的微信接入、实现简单的自动回复功能_java

前端时间小忙了一阵,微信公众号的开发,从零开始看文档,踩了不少坑,也算是熬过来了,最近考虑做一些总结,方便以后再开发的时候回顾,也给正在做相关项目的同学做个参考. 其实做过一遍之后会发现也不难,大致思路:用户消息和开发者需要的事件推送都会通过微信方服务器发起一个请求,转发到你在公众平台配置的服务器url地址,微信方将带上signature,timestamp,nonce,echostr四个参数,我们自己服务器通过拼接公众平台配置的token,以及传上来的timestamp,nonce进行SHA1

Java读取txt文件和写入txt文件的简单实例_java

写Java程序时经常碰到要读如txt或写入txt文件的情况,但是由于要定义好多变量,经常记不住,每次都要查,特此整理一下,简单易用,方便好懂! package edu.thu.keyword.test; import java.io.File; import java.io.InputStreamReader; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.FileInputStream;

Java用自定义的类作为HashMap的key值实例_java

这是Java中很经典的问题,在面试中也经常被问起.其实很多书或者文章都提到过要重载hashCode()和equals()两个方法才能实现自定义键在HashMap中的查找,但是为什么要这样以及如果不这样做会产生什么后果,好像很少有文章讲到,所以写这么一篇来说明下. 首先,如果我们直接用以下的Person类作为键,存入HashMap中,会发生发生什么情况呢? public class Person { private String id; public Person(String id) { thi

Java通过JsApi方式实现微信支付_java

要使用JsApi进行微信支付,首先要从微信获得一个prepay_id,然后通过调用微信的jsapi完成支付,JS API的返回结果get_brand_wcpay_request:ok仅在用户成功完成支付时返回.由于前端交互复杂,get_brand_wcpay_request:cancel或者get_brand_wcpay_request:fail可以统一处理为用户遇到错误或者主动放弃,不必细化区分. 示例代码如下: function onBridgeReady(){ WeixinJSBridge

java 微信随机红包算法代码实例_java

前几天的一个晚上突然想到微信红包应该有一个随机算法,就自己试着写了下,也不知道对不对,看了看网上的说法,好像到现在为止官方也没有给出一个确切的算法,只好在这里献丑了,先贴出代码: public static double [] getMoney(double money, int num){ Random r = new Random(); DecimalFormat format = new DecimalFormat(".##"); double middle = Double.p

Java网络编程之简单的服务端客户端应用实例_java

本文实例讲述了Java网络编程之简单的服务端客户端应用.分享给大家供大家参考.具体如下: 在Java中,我们使用java.net.Socket及其相关类来完成有关网络的相关功能.Socket类非常简单易用,因为Java技术隐藏了建立网络连接和通过连接发送数据的复杂过程.下面所说的内容只适用于TCP协议. 一.连接到服务器 我们可以使用Socket类的构造函数来打开一个套接字,如 Socket sk = new Socket("210.0.235.14",13); 其中,210.0.23

SpringMVC文件上传 多文件上传实例_java

必须明确告诉DispatcherServlet如何处理MultipartRequest.SpringMVC中提供了文件上传使用方式如下配置xxx-servlet.xml,添加如下代码: 复制代码 代码如下: <bean id="multipartResolver"  class="org.springframework.web.multipart.commons.CommonsMultipartResolver">          <!-- 设置

Java解压缩zip - 解压缩多个文件或文件夹实例_java

Java解压缩zip - 多个文件(包括文件夹),具体如下: 对多个文件和文件夹进行压缩,对复杂的文件目录进行解压. 压缩方法使用的是可变参数,可以压缩1到多个文件..可以写数组的方式或者一个个写到参数列表里面... ZipFiles(zip,"abc",new File("d:/English"),new File("d:/发放数据.xls")); 测试文件目录结构: 测试的压缩内容:English文件夹和同级的两个excel文件 File[]

Java添加事件监听的四种方法代码实例_java

Java添加事件的几种方式(转载了codebrother的文章,做了稍微的改动): /** * Java事件监听处理--自身类实现ActionListener接口,作为事件监听器 * * @author codebrother */ class EventListener1 extends JFrame implements ActionListener { private JButton btBlue, btDialog; public EventListener1() { setTitle(