微信公众平台-第三方平台开发(四)被动回复消息

这个问题卡了我两天的时间,一直没有头绪,文档都快翻烂了,还是没有解决,最后直接调用的客服发送消息的接口回复的。(有可能是因为natapp网速的原因,导致的回复超时,毕竟免费的内网穿透总会有它的缺点,的确是很慢)

话不多说,直接上代码

消息与事件接收配置

@ApiOperation(value = "消息与事件接收配置")
    @RequestMapping(value = "/platform/{APPID}/callback",
            method = RequestMethod.POST,consumes = "text/xml",produces = "text/xml;charset=utf-8")
    public String wechatPlatformEvent(@PathVariable String APPID,
                                    HttpServletRequest request,
                                    HttpServletResponse response
    ) throws Exception {
        String nonce = request.getParameter("nonce");
        String timestamp = request.getParameter("timestamp");
        String signature = request.getParameter("signature");
        String msgSignature = request.getParameter("msg_signature");
        String encType =  request.getParameter("encrypt_type");
        String xml =   IOUtils.toString(request.getReader());
        new Thread(new Runnable() {
            public void run() {
                    weChatService.processMessageAndEvent(APPID, nonce, timestamp,signature,msgSignature,encType,xml,response);
            }
        }).start();
        return "SUCCESS";
    }

 Service处理

 /**
     * 处理公众号的消息和事件
     *
     * @param response
     * @throws DocumentException
     */
    public void processMessageAndEvent(String appId,  String nonce,String timestamp,String signature,String msgSignature,String encType,String xml,HttpServletResponse response )  {
        try{


        String token = PLATFORM_COMPONENT_TOKEN;
        if (StringUtils.isBlank(msgSignature)) {
            // 微信推送给第三方开放平台的消息一定是加过密的,无消息加密无法解密消息
            return;
        }
        boolean isValid = WxMessageUtil.checkSignature(token, signature, timestamp, nonce);
        if (isValid) {
            Map<String, String> map = WxMessageUtil.decryptMsgToMap(msgSignature, timestamp, nonce, xml,PLATFORM_COMPONENT_TOKEN,PLATFORM_AES_KEY,PLATFORM_APP_ID);
            logger.error("--->>> processMessageAndEvent: {}", map.toString());
            String toUserName = map.get("ToUserName");
            String fromUserName = map.get("FromUserName");
            // 微信全网测试: gh_3c884a361561 微信专用测试公众号;  gh_8dad206e9538 微信专用测试小程序
            if (StringUtils.equals(toUserName, Constants.MP_USERNAME) || StringUtils.equals(toUserName, Constants.MINI_USERNAME)) {
                // 微信全网发布检测的
                this.wxAllNetworkCheck(map, response,appId);
            } else {
                // 公众号消息事件处理
                this.mpMessageAndEvent(appId, map, response);
            }
        }
        }catch (Exception e){
                e.printStackTrace();
        }
    }

 微信全网发布检测

/**
     * 微信全网发布检测
     */
    private void wxAllNetworkCheck(Map<String, String> map, HttpServletResponse response,String appid) throws Exception {
        String msgType = map.get("MsgType");
        String toUserName = map.get("ToUserName");
        String fromUserName = map.get("FromUserName");

        if ("event".equalsIgnoreCase(msgType)) {

            // 全网检测的模拟粉丝点击事件,只要返回文本消息:事件名称+"from_callback"
            String event = map.get("Event");
            replyTextMessage(response, WxMessageUtil.generateTextXML(fromUserName, toUserName, event + "from_callback"));

        } else if ("text".equalsIgnoreCase(msgType)) {
            String content = map.get("Content");
            if ("TESTCOMPONENT_MSG_TYPE_TEXT".equalsIgnoreCase(content)) {
                //全网检测的模拟模板消息,只要返回文本消息: 内容 + "_callback"
                replyTextMessage(response, WxMessageUtil.generateTextXML(fromUserName, toUserName, content + "_callback"));
            } else if (StringUtils.startsWithIgnoreCase(content, "QUERY_AUTH_CODE")) {

                // 模拟粉丝发送文本消息给专用测试公众号,第三方平台方需在5秒内返回空串表明暂时不回复,然后再立即使用客服消息接口发送消息回复粉丝
                this.output(response, "");
                String msg = content.split(":")[1];
                // 调用客服接口回复消息
                this.postMessage(fromUserName,msg, appid);
            }
        }
    }



/**
     * 发送消息文本
     *
     * @param response
     * @param message
     * @throws IOException
     */
    private void replyTextMessage(HttpServletResponse response, String message) throws IOException, AesException {
        // 加密要发送的消息
        String encryptXml = WxMessageUtil.encryptMsg(message,PLATFORM_COMPONENT_TOKEN,PLATFORM_AES_KEY,PLATFORM_APP_ID);
        this.output(response, encryptXml);
    }


/**
     * 发送客服消息
     * @param openid
     * @param content
     * @param authorizerAppid
     * @return
     * @throws Exception
     */
    public String postMessage(String openid,String content,String authorizerAppid) throws Exception {
        String authorizer_access_token  = "";
        VtWechatAccount vtWechatAccount = vtWechatAccountMapper.selectVtWechatAccountByAuthorizerAppid(authorizerAppid);
        authorizer_access_token =  reFreshAuthorizerAccessTokenNoDeptId(vtWechatAccount);
        //消息推送接口
        String path = "https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=" + authorizer_access_token;
        JSONObject jsonData = new JSONObject();
        jsonData.put("touser", openid);
        jsonData.put("msgtype", "text");
        JSONObject text = new JSONObject();
        text.put("content",content);
        jsonData.put("text",text);
        System.out.println(jsonData);
        System.out.println(path);
        //HttpUtil.doPostJson(path, jsonData.toJSONString());
        HttpRequest.sendPost(path, jsonData.toString());
        return "SUCCESS";
        //return null;
    }

 公众号消息事件处理

/**
     * 公众号事件处理
     */
    private void mpMessageAndEvent(String appId, Map<String, String> map, HttpServletResponse response) throws Exception {
        String msgType = map.get("MsgType");
        String toUserName = map.get("ToUserName");
        String fromUserName = map.get("FromUserName");
        TextMessage clickReturn = new TextMessage();
        if(map.get("Event")!=null&&map.get("EventKey")!=null){
                clickReturn.setContent("你好");
                clickReturn.setToUserName(fromUserName);
                clickReturn.setFromUserName(toUserName);
                postMessage(fromUserName,clickReturn.getContent(),appId);
          
        }
//        this.output(response, "");
    }

相关utils类

 WXBizMsgCrypt.java


import com.alibaba.fastjson.JSONObject;
import org.apache.commons.codec.binary.Base64;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;

import java.io.StringReader;
import java.nio.charset.Charset;
import java.security.MessageDigest;
import java.util.Arrays;
import java.util.Map;
import java.util.Random;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

/**
 * 提供接收和推送给公众平台消息的加解密接口(UTF8编码的字符串).
 * <ol>  * <li>第三方回复加密消息给公众平台</li>  * <li>第三方收到公众平台发送的消息,验证消息的安全性,并对消息进行解密。</li>
 * </ol>
 * 说明:异常java.security.InvalidKeyException:illegal Key Size的解决方案
 * <ol>
 * <li>在官方网站下载JCE无限制权限策略文件(JDK7的下载地址:  *
 * http://www.oracle.com/technetwork/java/javase/downloads/jce-7-download-432124.html</li>
 * <li>下载后解压,可以看到local_policy.jar和US_export_policy.jar以及readme.txt</li>
 * <li>如果安装了JRE,将两个jar文件放到%JRE_HOME%\lib\security目录下覆盖原来的文件</li>
 * <li>如果安装了JDK,将两个jar文件放到%JDK_HOME%\jre\lib\security目录下覆盖原来文件</li>
 *
 * </ol>
 */
public class WXBizMsgCrypt {
    static Charset CHARSET = Charset.forName("utf-8");
    Base64 base64 = new Base64();
    byte[] aesKey;
    String token;
    String appId;

    /**
     * 构造函数
     * @param token 公众平台上,开发者设置的token
     * @param encodingAesKey 公众平台上,开发者设置的EncodingAESKey
     * @param appId 公众平台appid
     *
     * @throws AesException 执行失败,请查看该异常的错误码和具体的错误信息
     */
    public WXBizMsgCrypt(String token, String encodingAesKey, String appId) throws AesException {
        if (encodingAesKey.length() != 43) {
            throw new AesException(AesException.IllegalAesKey);
        }

        this.token = token;
        this.appId = appId;
        aesKey = Base64.decodeBase64(encodingAesKey + "=");
    }

    // 还原4个字节的网络字节序
    int recoverNetworkBytesOrder(byte[] orderBytes) {
        int sourceNumber = 0;
        for (int i = 0; i < 4; i++) {
            sourceNumber <<= 8;
            sourceNumber |= orderBytes[i] & 0xff;
        }
        return sourceNumber;
    }

    /**
     * 对密文进行解密.
     * @param text 需要解密的密文
     * @return 解密得到的明文
     * @throws AesException aes解密失败
     */
    String decrypt(String text) throws AesException {
        byte[] original;
        try {
            // 设置解密模式为AES的CBC模式
            Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
            SecretKeySpec key_spec = new SecretKeySpec(aesKey, "AES");
            IvParameterSpec iv = new IvParameterSpec(Arrays.copyOfRange(aesKey, 0, 16));
            cipher.init(Cipher.DECRYPT_MODE, key_spec, iv);

            // 使用BASE64对密文进行解码
            byte[] encrypted = Base64.decodeBase64(text);

            // 解密
            original = cipher.doFinal(encrypted);
        } catch (Exception e) {
            e.printStackTrace();
            throw new AesException(AesException.DecryptAESError);
        }

        String xmlContent, from_appid;
        try {
            // 去除补位字符
            byte[] bytes = PKCS7Encoder.decode(original);

            // 分离16位随机字符串,网络字节序和AppId
            byte[] networkOrder = Arrays.copyOfRange(bytes, 16, 20);

            int xmlLength = recoverNetworkBytesOrder(networkOrder);

            xmlContent = new String(Arrays.copyOfRange(bytes, 20, 20 + xmlLength), CHARSET);
            from_appid =
                    new String(Arrays.copyOfRange(bytes, 20 + xmlLength, bytes.length), CHARSET);
        } catch (Exception e) {
            e.printStackTrace();
            throw new AesException(AesException.IllegalBuffer);
        }

        // appid不相同的情况
        if (!from_appid.equals(appId)) {
            throw new AesException(AesException.ValidateSignatureError);
        }
        return xmlContent;

    }

    /**
     *  * 检验消息的真实性,并且获取解密后的明文.
     * <ol>
     * <li>利用收到的密文生成安全签名,进行签名验证</li>
     * <li>若验证通过,则提取xml中的加密消息</li>
     * <li>对消息进行解密</li>
     * </ol>
     *
     * @param msgSignature 签名串,对应URL参数的msg_signature
     * @param timeStamp 时间戳,对应URL参数的timestamp
     * @param nonce 随机串,对应URL参数的nonce
     * @param postData 密文,对应POST请求的数据
     * @return 解密后的原文
     * @throws AesException 执行失败,请查看该异常的错误码和具体的错误信息
     */
    public String decryptMsg(String msgSignature, String timeStamp, String nonce, String postData)
            throws AesException {

        // 密钥,公众账号的app secret
        // 提取密文
        Object[] encrypt = extract(postData);

        // 验证安全签名
        String signature = getSHA1(token, timeStamp, nonce, encrypt[1].toString());

        // 和URL中的签名比较是否相等
        // System.out.println("第三方收到URL中的签名:" + msg_sign);
        // System.out.println("第三方校验签名:" + signature);
        if (!signature.equals(msgSignature)) {
            throw new AesException(AesException.ValidateSignatureError);
        }

        // 解密
        String result = decrypt(encrypt[1].toString());
        return result;
    }

    /**
     * 提取出xml数据包中的加密消息
     * @param xmltext 待提取的xml字符串
     * @return 提取出的加密消息字符串
     * @throws AesException
     */
    public static Object[] extract(String xmltext) throws AesException {
        Object[] result = new Object[3];
        try {
            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
            dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
            dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
            dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
            dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
            dbf.setXIncludeAware(false);
            dbf.setExpandEntityReferences(false);
            DocumentBuilder db = dbf.newDocumentBuilder();
            StringReader sr = new StringReader(xmltext);
            InputSource is = new InputSource(sr);
            Document document = db.parse(is);

            Element root = document.getDocumentElement();
            NodeList nodelist1 = root.getElementsByTagName("Encrypt");
            NodeList nodelist2 = root.getElementsByTagName("ToUserName");
            result[0] = 0;
            result[1] = nodelist1.item(0).getTextContent();

            //注意这里,获取ticket中的xml里面没有ToUserName这个元素,官网原示例代码在这里会报空
            //空指针,所以需要处理一下
            if (nodelist2 != null) {
                if (nodelist2.item(0) != null) {
                    result[2] = nodelist2.item(0).getTextContent();
                }
            }
            return result;
        } catch (Exception e) {
            e.printStackTrace();
            throw new AesException(AesException.ParseXmlError);
        }
    }

    /**
     * 用SHA1算法生成安全签名
     * @param token 票据
     * @param timestamp 时间戳
     * @param nonce 随机字符串
     * @param encrypt 密文
     * @return 安全签名
     * @throws
     * AesException
     */
    public static String getSHA1(String token, String timestamp, String nonce, String encrypt)
            throws AesException {
        try {
            String[] array = new String[]{token, timestamp, nonce, encrypt};
            StringBuffer sb = new StringBuffer();
            // 字符串排序
            Arrays.sort(array);
            for (int i = 0; i < 4; i++) {
                sb.append(array[i]);
            }
            String str = sb.toString();
            // SHA1签名生成
            MessageDigest md = MessageDigest.getInstance("SHA-1");
            md.update(str.getBytes());
            byte[] digest = md.digest();

            StringBuffer hexstr = new StringBuffer();
            String shaHex = "";
            for (int i = 0; i < digest.length; i++) {
                shaHex = Integer.toHexString(digest[i] & 0xFF);
                if (shaHex.length() < 2) {
                    hexstr.append(0);
                }
                hexstr.append(shaHex);
            }
            return hexstr.toString();
        } catch (Exception e) {
            e.printStackTrace();
            throw new AesException(AesException.ComputeSignatureError);
        }
    }


    /**
     * 用SHA1算法生成安全签名
     * @param token 票据
     * @param timestamp 时间戳
     * @param nonce 随机字符串
     * @return 安全签名
     * @throws
     * AesException
     */
    public static String getSHA2(String jsapi_ticket, String timestamp, String noncestr,String url)
            throws AesException {
        try {
            String[] array = new String[]{jsapi_ticket, timestamp, noncestr,url};
            StringBuffer sb = new StringBuffer();
            // 字符串排序
            Arrays.sort(array);
            for (int i = 0; i < 4; i++) {
                sb.append(array[i]);
            }
            String str = sb.toString();
            // SHA1签名生成
            MessageDigest md = MessageDigest.getInstance("SHA-1");
            md.update(str.getBytes());
            byte[] digest = md.digest();

            StringBuffer hexstr = new StringBuffer();
            String shaHex = "";
            for (int i = 0; i < digest.length; i++) {
                shaHex = Integer.toHexString(digest[i] & 0xFF);
                if (shaHex.length() < 2) {
                    hexstr.append(0);
                }
                hexstr.append(shaHex);
            }
            return hexstr.toString();
        } catch (Exception e) {
            e.printStackTrace();
            throw new AesException(AesException.ComputeSignatureError);
        }
    }

    public static String shaEncode(String inStr) throws Exception {
        MessageDigest sha = null;
        try {
            sha = MessageDigest.getInstance("SHA");
        } catch (Exception e) {
            System.out.println(e.toString());
            e.printStackTrace();
            return "";
        }

        byte[] byteArray = inStr.getBytes("UTF-8");
        byte[] md5Bytes = sha.digest(byteArray);
        StringBuffer hexValue = new StringBuffer();
        for (int i = 0; i < md5Bytes.length; i++) {
            int val = ((int) md5Bytes[i]) & 0xff;
            if (val < 16) {
                hexValue.append("0");
            }
            hexValue.append(Integer.toHexString(val));
        }
        return hexValue.toString();
    }

    /**
     * 对明文进行加密.
     *
     * @param text 需要加密的明文
     * @return 加密后base64编码的字符串
     * @throws AesException aes加密失败
     */
    String encrypt(String randomStr, String text) throws AesException {
        ByteGroup byteCollector = new ByteGroup();
        byte[] randomStrBytes = randomStr.getBytes(CHARSET);
        byte[] textBytes = text.getBytes(CHARSET);
        byte[] networkBytesOrder = getNetworkBytesOrder(textBytes.length);
        byte[] appidBytes = appId.getBytes(CHARSET);

        // randomStr + networkBytesOrder + text + appid
        byteCollector.addBytes(randomStrBytes);
        byteCollector.addBytes(networkBytesOrder);
        byteCollector.addBytes(textBytes);
        byteCollector.addBytes(appidBytes);

        // ... + pad: 使用自定义的填充方式对明文进行补位填充
        byte[] padBytes = PKCS7Encoder.encode(byteCollector.size());
        byteCollector.addBytes(padBytes);

        // 获得最终的字节流, 未加密
        byte[] unencrypted = byteCollector.toBytes();

        try {
            // 设置加密模式为AES的CBC模式
            Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
            SecretKeySpec keySpec = new SecretKeySpec(aesKey, "AES");
            IvParameterSpec iv = new IvParameterSpec(aesKey, 0, 16);
            cipher.init(Cipher.ENCRYPT_MODE, keySpec, iv);

            // 加密
            byte[] encrypted = cipher.doFinal(unencrypted);

            // 使用BASE64对加密后的字符串进行编码
            String base64Encrypted = base64.encodeToString(encrypted);

            return base64Encrypted;
        } catch (Exception e) {
            e.printStackTrace();
            throw new AesException(AesException.EncryptAESError);
        }
    }

    /**
     * 将公众平台回复用户的消息加密打包.
     * <ol>
     * 	<li>对要发送的消息进行AES-CBC加密</li>
     * 	<li>生成安全签名</li>
     * 	<li>将消息密文和安全签名打包成xml格式</li>
     * </ol>
     *
     * @param replyMsg 公众平台待回复用户的消息,xml格式的字符串
     * @param timeStamp 时间戳,可以自己生成,也可以用URL参数的timestamp
     * @param nonce 随机串,可以自己生成,也可以用URL参数的nonce
     *
     * @return 加密后的可以直接回复用户的密文,包括msg_signature, timestamp, nonce, encrypt的xml格式的字符串
     * @throws AesException 执行失败,请查看该异常的错误码和具体的错误信息
     */
    public String encryptMsg(String replyMsg, String timeStamp, String nonce) throws AesException {
        // 加密
        String encrypt = encrypt(getRandomStr(), replyMsg);

        // 生成安全签名
        if (timeStamp == "") {
            timeStamp = Long.toString(System.currentTimeMillis());
        }

        String signature = getSHA1(token, timeStamp, nonce, encrypt);

        // 生成发送的xml
        String result = this.generateResp(encrypt, signature, timeStamp, nonce);
        return result;
    }



    /**
     * @Description: 生成密文需要参数JSON
     * @exception
     * @author mazhq
     * @date 2019/9/11 17:59
     */
    public String generateResp(String encrypt, String signature, String timestamp, String nonce) {
        JSONObject encryptJson = new JSONObject();
        encryptJson.put("encrypt", encrypt);
        encryptJson.put("signature", signature);
        encryptJson.put("timestamp", timestamp);
        encryptJson.put("nonce", nonce);
        return encryptJson.toJSONString();
    }

    // 随机生成16位字符串
    String getRandomStr() {
        String base = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
        Random random = new Random();
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < 16; i++) {
            int number = random.nextInt(base.length());
            sb.append(base.charAt(number));
        }
        return sb.toString();
    }

    public static String getRandom() {
        String base = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
        Random random = new Random();
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < 16; i++) {
            int number = random.nextInt(base.length());
            sb.append(base.charAt(number));
        }
        return sb.toString();
    }

    // 生成4个字节的网络字节序
    byte[] getNetworkBytesOrder(int sourceNumber) {
        byte[] orderBytes = new byte[4];
        orderBytes[3] = (byte) (sourceNumber & 0xFF);
        orderBytes[2] = (byte) (sourceNumber >> 8 & 0xFF);
        orderBytes[1] = (byte) (sourceNumber >> 16 & 0xFF);
        orderBytes[0] = (byte) (sourceNumber >> 24 & 0xFF);
        return orderBytes;
    }


}

 WxMessageUtil.java



import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.xml.DomDriver;
import org.apache.commons.lang.RandomStringUtils;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import javax.servlet.http.HttpServletRequest;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


/**
 * @Description: 消息工具类
 * @Author: xxl
 * @Date 2023-05-24
 */
public class WxMessageUtil {


    /**
     * 返回消息类型:文本
     */
    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 RESP_MESSAGE_TYPE_Image = "image";

    /**
     * 返回消息类型:语音
     */
    public static final String RESP_MESSAGE_TYPE_Voice = "voice";

    /**
     * 返回消息类型:视频
     */
    public static final String RESP_MESSAGE_TYPE_Video = "video";

    /**
     * 请求消息类型:文本
     */
    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_VIDEO = "video";

    /**
     * 请求消息类型:推送
     */
    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";

    /**
     * 事件类型:VIEW(自定义菜单URl视图)
     */
    public static final String EVENT_TYPE_VIEW = "VIEW";

    /**
     * 事件类型:LOCATION(上报地理位置事件)
     */
    public static final String EVENT_TYPE_LOCATION = "LOCATION";

    /**
     * 事件类型:LOCATION(上报地理位置事件)
     */
    public static final String EVENT_TYPE_SCAN = "SCAN";

    /**
     * @Description: 解析微信发来的请求(XML)
     * @param @param request
     * @param @return
     * @param @throws Exception
     * @author dapengniao
     * @date 2016年3月7日 上午10:04:02
     */
    public static Map<String, String> parseXml(HttpServletRequest request) {
        // 将解析结果存储在HashMap中
        Map<String, String> map = new HashMap<String, String>();

        // 读取输入流
        SAXReader reader = new SAXReader();
        Document document = null;
        InputStream inputStream = null;
        try {
            // 从request中取得输入流
            inputStream = request.getInputStream();
            document = reader.read(inputStream);
            // 得到xml根元素
            Element root = document.getRootElement();
            // 得到根元素的所有子节点
            List<Element> elementList = root.elements();

            // 遍历所有子节点
            elementList.stream().forEach(element -> {
                map.put(element.getName(), element.getStringValue());
            });
        } catch (DocumentException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            // 释放资源
            if(null != inputStream){
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        return map;
    }

    /**
     * @Description: 文本消息对象转换成xml
     * @param @param textMessage
     * @param @return
     * @author dapengniao
     * @date 2016年3月8日 下午4:13:22
     */
    public static String textMessageToXml(WxMessageImg textMessage) {
        XStream xStream = new XStream(new DomDriver("UTF-8"));
        //XStream xStream = new XStream();
        xStream.alias("xml", textMessage.getClass());
        return xStream.toXML(textMessage);
    }

    /**
     * 将xml转换为JSON对象
     * @param xml xml字符串
     * @return
     * @throws Exception
     */
    public static JSONObject xmltoJson(String xml) throws Exception {
        JSONObject jsonObject = new JSONObject();
        Document document = DocumentHelper.parseText(xml);
        //获取根节点元素对象
        Element root = document.getRootElement();
        iterateNodes(root, jsonObject);
        return jsonObject;
    }

    /**
     * 遍历元素
     * @param node 元素
     * @param json 将元素遍历完成之后放的JSON对象
     */
    @SuppressWarnings("unchecked")
    public static void iterateNodes(Element node,JSONObject json){
        //获取当前元素的名称
        String nodeName = node.getName();
        //判断已遍历的JSON中是否已经有了该元素的名称
        if(json.containsKey(nodeName)){
            //该元素在同级下有多个
            Object Object = json.get(nodeName);
            JSONArray array = null;
            if(Object instanceof JSONArray){
                array = (JSONArray) Object;
            }else {
                array = new JSONArray();
                array.add(Object);
            }
            //获取该元素下所有子元素
            List<Element> listElement = node.elements();
            if(listElement.isEmpty()){
                //该元素无子元素,获取元素的值
                //用这个字符串中的空格就都没了,导致换行失败,不要加
                //String nodeValue = node.getTextTrim();
                String nodeValue = node.getText();
                array.add(nodeValue);
                json.put(nodeName, array);
                return ;
            }
            //有子元素
            JSONObject newJson = new JSONObject();
            //遍历所有子元素
            for(Element e:listElement){
                //递归
                iterateNodes(e,newJson);
            }
            array.add(newJson);
            json.put(nodeName, array);
            return ;
        }
        //该元素同级下第一次遍历
        //获取该元素下所有子元素
        List<Element> listElement = node.elements();
        if(listElement.isEmpty()){
            //该元素无子元素,获取元素的值
            //String nodeValue = node.getTextTrim();
            String nodeValue = node.getText();
            json.put(nodeName, nodeValue);
            return ;
        }
        //有子节点,新建一个JSONObject来存储该节点下子节点的值
        JSONObject object = new JSONObject();
        //遍历所有一级子节点
        for(Element e:listElement){
            //递归
            iterateNodes(e,object);
        }
        json.put(nodeName, object);
        return ;
    }

    /**
     * 处理 subscribe 类型的event
     *
     * @param map
     * @param userOpenId
     * @return
     */
    public static String handleEventSubscribe(Map<String, String> map, String userOpenId) {
        String resXmlStr = getReturnMsgSubscribe(map);
        return resXmlStr;
    }

    public static String handleEventClick(Map<String, String> map, String text) {
        String resXmlStr = getReturnMsgClick(map,text);
        return resXmlStr;
    }

    public static String getReturnMsgClick(Map<String, String> decryptMap,String text) {
        WxMessageImg textMessage = new WxMessageImg();
        textMessage.setToUserName(decryptMap.get("FromUserName").toString());
        textMessage.setFromUserName(decryptMap.get("ToUserName").toString());
        textMessage.setCreateTime(System.currentTimeMillis());
        textMessage.setMsgType("text");
        textMessage.setContent(text);
        return getXmlString(textMessage);
    }

    public static String getReturnMsgSubscribe(Map<String, String> decryptMap) {
        WxMessageImg textMessage = new WxMessageImg();
        textMessage.setToUserName(decryptMap.get("FromUserName").toString());
        textMessage.setFromUserName(decryptMap.get("ToUserName").toString());
        textMessage.setCreateTime(System.currentTimeMillis());
        textMessage.setMsgType("text");
        textMessage.setContent("你好,感谢关注!");
        return getXmlString(textMessage);
    }


    public static String getXmlString(WxMessageImg textMessage) {
        String xml = "";
        if (textMessage != null) {
            xml = "<xml>";
            xml += "<ToUserName><![CDATA[";
            xml += textMessage.getToUserName();
            xml += "]]></ToUserName>";
            xml += "<FromUserName><![CDATA[";
            xml += textMessage.getFromUserName();
            xml += "]]></FromUserName>";
            xml += "<CreateTime>";
            xml += textMessage.getCreateTime();
            xml += "</CreateTime>";
            xml += "<MsgType><![CDATA[";
            xml += textMessage.getMsgType();
            xml += "]]></MsgType>";
            xml += "<Content><![CDATA[";
            xml += textMessage.getContent();
            xml += "]]></Content>";
            xml += "</xml>";
        }
        return xml;
    }

    /**
     * 校验微信消息签名是否有效
     *
     * @param token
     * @param signature
     * @param timestamp
     * @param nonce
     * @return
     */
    public static boolean checkSignature(String token, String signature, String timestamp, String nonce) {
        String[] arr = new String[]{token, timestamp, nonce};
        Arrays.sort(arr);

        StringBuilder content = new StringBuilder();
        for (String s : arr) {
            content.append(s);
        }

        MessageDigest md = null;
        String tmpStr = null;

        try {
            md = MessageDigest.getInstance("SHA-1");
            byte[] digest = md.digest(content.toString().getBytes());
            tmpStr = byteToStr(digest);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }

        return tmpStr != null && tmpStr.equalsIgnoreCase(signature);
    }

    private static String byteToStr(byte[] byteArray) {
        StringBuilder strDigest = new StringBuilder();
        for (byte item : byteArray) {
            strDigest.append(byteToHexStr(item));
        }
        return strDigest.toString();
    }

    private static String byteToHexStr(byte mByte) {
        char[] Digit = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
        char[] tempArr = new char[2];
        tempArr[0] = Digit[(mByte >>> 4) & 0X0F];
        tempArr[1] = Digit[mByte & 0X0F];
        return new String(tempArr);
    }

    /**
     * 解密微信消息,返回map
     *
     * @param msgSignature
     * @param timestamp
     * @param nonce
     * @param encryptData
     * @return
     * @throws AesException
     * @throws DocumentException
     */
    public static Map<String, String> decryptMsgToMap(String msgSignature, String timestamp, String nonce, String encryptData,String token,String aesKey,String appid) throws AesException, DocumentException {
        WXBizMsgCrypt wxBizMsgCrypt = new WXBizMsgCrypt(token, aesKey, appid);
        String xml = wxBizMsgCrypt.decryptMsg(msgSignature, timestamp, nonce, encryptData);
        return xmlToMap(xml);
    }

    /**
     * 将xml转换成map
     *
     * @param xml
     * @return
     * @throws DocumentException
     */
    public static Map<String, String> xmlToMap(String xml) throws DocumentException {
        Map<String, String> map = new HashMap<>();
        SAXReader reader = new SAXReader();
        Document document = reader.read(new ByteArrayInputStream(xml.getBytes(Charset.forName("utf-8"))));
        Element root = document.getRootElement();
        List<Element> list = root.elements();
        for (Element element : list) {
            map.put(element.getName(), element.getText());
        }
        return map;
    }

    /**
     * 加密消息
     *
     * @param xml
     * @return
     * @throws AesException
     */
    public static String encryptMsg(String xml,String token,String aesKey,String appid) throws AesException {
        WXBizMsgCrypt wxBizMsgCrypt = new WXBizMsgCrypt(token, aesKey, appid);
        String timestamp = System.currentTimeMillis() + "";
        String nonceStr = RandomStringUtils.randomAlphanumeric(32);
        return wxBizMsgCrypt.encryptMsg(xml, timestamp, nonceStr);
    }

    /**
     * 生成 文本消息的xml格式
     *
     * @param toUserName
     * @param fromUserName
     * @param content
     * @return
     */
    public static String generateTextXML(String toUserName, String fromUserName, String content) {
        StringBuilder sb = new StringBuilder();
        sb.append("<xml>");
        sb.append("<ToUserName><![CDATA[").append(toUserName).append("]]></ToUserName>");
        sb.append("<FromUserName><![CDATA[").append(fromUserName).append("]]></FromUserName>");
        sb.append("<CreateTime>").append(System.currentTimeMillis()).append("</CreateTime>");
        sb.append("<MsgType><![CDATA[text]]></MsgType>");
        sb.append("<Content><![CDATA[").append(content).append("]]></Content>)");
        sb.append("</xml>");
        return sb.toString();
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值