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

时间: 2024-08-04 10:16:47

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

JAVA实现 springMVC方式的微信接入、实现简单的自动回复

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

Java实现JsApi方式的微信支付

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

微信公众平台,微信接入及消息回复

<?php /* 方倍工作室 http://www.fangbei.org/ CopyRight 2016 All Rights Reserved */ header('Content-type:text'); define("TOKEN", "weixin"); $wechatObj = new wechatCallbackapiTest(); if (!isset($_GET['echostr'])) { $wechatObj->responseMs

微信接入机器人实现对别人消息和群at消息的自动回复

微信接入机器人实现对别人消息和群at消息的自动回复 有时候,我们想让我们的微信号对别人发出的各种消息做出回复.我们可以通过接入图灵机器人的方式实现. IDLE编写py文件并保存,命名为wxbot. #!/usr/bin/env python # coding: utf-8 import os import sys import webbrowser import pyqrcode import requests import json import xml.dom.minidom import

微信接入服务器配置

填写服务器配置 打开https://mp.weixin.qq.com,登录到相应的公众号.在 开发->基本配置里填写相关信息 URL:是与微信进行接入认证的地址,由于我的默认页面是 Token:由开发者自定义填写,用作生成签名 EncodingAESKey:随机生成即可 消息加密方式:明文模式 服务端程序验证 可参考微信开发接入指南 1)获取微信通过GET传过来的参数 2)对token.timestamp.nonce三个参数进行字典排序 3)将上一步中的三个字符串参数进行拼接,并sha1加密 4

Java基于springMVC的验证码案例

1 ``` 2 Java验证码案例(基于springMVC方式) 3 4 验证码工具类 5 package com.ekyb.common.util; 6 7 import java.awt.Color; 8 import java.awt.Font; 9 import java.awt.Graphics; 10 11 import java.awt.image.BufferedImage; 12 import java.util.ArrayList; 13 import java.util.A

java微信接口之五—消息分组群发

一.微信消息分组群发接口简介 1.请求:该请求是使用post提交地址为: https://api.weixin.qq.com/cgi-bin/message/mass/sendall?access_token=ACCESS_TOKEN   其中ACCESS_TOKEN是我们动态获取的.   发送的数据:(这里使用图文消息示例) { "filter":{ "group_id":"2" }, "mpnews":{ "me

第三方微信接入登录流程整理

准备工作 1.在微信开放平台https://open.weixin.qq.com/注册成为开发者. 2.在“管理中心”中创建一个移动应用,需“应用名称.简介.及28*28和108*108的PNG图片各一张,且大小不超过300k”,点击下一步,需“应用官网地址,应用签名及包名”等信息,然后即可提交审核. 说明: 应用签名:可在微信开发平台的资源中心>>资源下载>>中下载“签名生成工具”,用户获取已经安装到手机的第三方应用的签名.输入应用包名,即可获得该应用的签名值. 3.提交审核后,

**微信接入探秘(一)——从零认识微信接口(主动接口和被动接口)

本文出处:http://blog.csdn.net/chaijunkun/article/details/53385088,转载请注明.由于本人不定期会整理相关博文,会对相应内容作出完善.因此强烈建议在原始出处查看此文 写在前面 回想起来今年还没有在CSDN上发过文章,草稿箱里积攒了很多话题,可由于时间关系最终也没发出来.不能再拖了,打算写个系列专题,聊聊微信接入. 随着微信用户量的不断增加,越来越多的企业也将自己的服务加入到了微信当中.微信本身提供的公众号.服务号的基础功能难以满足日趋增长的需