java 微信开发 回复消息乱码

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java 微信开发 回复消息乱码相关的知识,希望对你有一定的参考价值。

我在开发一个微信公众号项目,linux服务器 locale 命令查看 系统编码是 GB18030方式, 当用户点击微信号自定义菜单,我需要回复消息给用户,调用了回复客服消息接口,但是微信号收到的是乱码,试了好几种编码方式,最好的一次是 new String(消息内容.getBytes("UTF-8"),"GB18030")),收到的大部分是正确的,但还是有个别字是乱码。 这个问题该怎样解决啊

住在地球村上,应该用 UTF8。也就是说发出去 "汉字".getBytes(),发出去,收到也 new String(bytes, "UTF8"),当然你先确定微信对边有没有什么中间过程会复制或转换数据,如果它本身就是用 UTF8 那就最好了。Java 本身内部是 unicode 的,所以我们用 UTF8 是没问题的。凡是我们 String.getBytes(...) 和 new String(...,, ...) 时一定要带上字符集编码就不会出问题了。

用其它字符集会有些问题,免得像新闻上某公安局的官员得意地说解决了百姓多年的无法录入生僻姓名汉字的难题。
参考技术A 乱码问题要彻底设置好编码方式,你不妨尝试一下将数据库,后台,前台都设置为UTF-8试试 参考技术B 所有字符集统一成utf-8。

new String(消息内容.getBytes("UTF-8"),"GB18030"))这种方式是你能确认【消息内容】是utf-8的时候才能用的。本回答被提问者采纳

微信公众号开发java版-消息回复(普通文字消息和语音消息)

关于微信公众号的消息自动回复功能开发,本文将做一个系统介绍,开发语言为java

话不多说,直接上代码

1.控制器代码

package webapp.controller;

import org.springframework.context.annotation.Scope;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import webapp.ext.WechatSignUtil;
import webapp.ext.wechat.AuthProcess;
import webapp.ext.wechat.MessageUtil;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

@Scope("prototype")
@RestControllerpublic class NotifyController 

    @RequestMapping(value = "notify",method =  RequestMethod.GET, RequestMethod.POST )
    @ResponseBody
    public String wechatNotify(HttpServletRequest request, HttpServletResponse response, String signature, String timestamp,
                               String nonce, String echostr) throws IOException 

        boolean isGet = request.getMethod().toLowerCase().equals("get");
        if (isGet) 
            boolean isOk = WechatSignUtil.checkSignature(signature, timestamp, nonce);
            if (isOk)
                return echostr;
            else
                return null;
         else 
            String resp = "";
            request.setCharacterEncoding("UTF-8");
            response.setCharacterEncoding("UTF-8");
            try 
                Map<String,String> map = MessageUtil.xmlToMap(request);
                String xml = "<xml><ToUserName>"+map.get("ToUserName")+"</ToUserName><Encrypt>" +map.get("Encrypt")+
                        "</Encrypt></xml>";
                String result = AuthProcess.decryptMsg(request, xml);
                Map<String, Object> req = MessageUtil.xmlStr2Map(result);

                boolean hasEvent = req.containsKey("Event");

                String respXml = "";
                if (hasEvent) 
                    String event = req.get("Event").toString();
                    if ("subscribe".equals(event)) 
                        //用户订阅公众号行为
                        HashMap<String, Object> mapXml = new HashMap<>();
                        mapXml.put("ToUserName",req.get("FromUserName"));
                        mapXml.put("FromUserName",req.get("ToUserName"));
                        mapXml.put("CreateTime",req.get("CreateTime"));
                        mapXml.put("MsgType","text");
                        mapXml.put("Content","感谢关注");
                        respXml = MessageUtil.map2Xmlstring(mapXml);
                    
                 else 
                    HashMap<String, Object> mapXml = new HashMap<>();
                    mapXml.put("ToUserName",req.get("FromUserName"));
                    mapXml.put("FromUserName",req.get("ToUserName"));
                    mapXml.put("CreateTime",req.get("CreateTime"));
                    mapXml.put("MsgType","text");

                    String msgType = req.get("MsgType").toString();
                    if ("text".equals(msgType))
                        //普通文字消息
                        String content = req.get("Content").toString();
                        respXml = sendMsg(req);
                     else if ("voice".equals(msgType))
                        //语音消息
                        //注意:这里Recognition拿到的直接就是语音消息转化之后的文字内容!!!直接用就好了
                        String msg = req.get("Recognition").toString();
                        respXml = sendMsg(req);
                    
                
                resp = AuthProcess.encryptMsg(request, respXml.trim());
                return resp;
             catch (Exception e) 
                e.printStackTrace();
            
            return resp;
        
    

    private String sendMsg(Map<String, Object> req) 
        HashMap<String, Object> mapXml = new HashMap<>();

        mapXml.put("ToUserName",req.get("FromUserName"));
        mapXml.put("FromUserName",req.get("ToUserName"));
        mapXml.put("CreateTime",req.get("CreateTime"));
        mapXml.put("MsgType","text");
        mapXml.put("Content","消息内容");
        return MessageUtil.map2Xmlstring(mapXml);
    

 

2.其它杂七杂八的一些类(上面控制器中用到的)

package webapp.ext.wechat;

@SuppressWarnings("serial")
public class AesException extends Exception 

    public final static int OK = 0;
    public final static int ValidateSignatureError = -40001;
    public final static int ParseXmlError = -40002;
    public final static int ComputeSignatureError = -40003;
    public final static int IllegalAesKey = -40004;
    public final static int ValidateAppidError = -40005;
    public final static int EncryptAESError = -40006;
    public final static int DecryptAESError = -40007;
    public final static int IllegalBuffer = -40008;
    //public final static int EncodeBase64Error = -40009;
    //public final static int DecodeBase64Error = -40010;
    //public final static int GenReturnXmlError = -40011;

    private int code;

    private static String getMessage(int code) 
        switch (code) 
        case ValidateSignatureError:
            return "签名验证错误";
        case ParseXmlError:
            return "xml解析失败";
        case ComputeSignatureError:
            return "sha加密生成签名失败";
        case IllegalAesKey:
            return "SymmetricKey非法";
        case ValidateAppidError:
            return "appid校验失败";
        case EncryptAESError:
            return "aes加密失败";
        case DecryptAESError:
            return "aes解密失败";
        case IllegalBuffer:
            return "解密后得到的buffer非法";
//        case EncodeBase64Error:
//            return "base64加密错误";
//        case DecodeBase64Error:
//            return "base64解密错误";
//        case GenReturnXmlError:
//            return "xml生成失败";
        default:
            return null; // cannot be
        
    

    public int getCode() 
        return code;
    

    AesException(int code) 
        super(getMessage(code));
        this.code = code;
    

package webapp.ext.wechat;
import javax.servlet.http.HttpServletRequest;

public class AuthProcess 
    /**
     * 将加密后的原文进行解密重新封装
     * @param request
     * @param originalXml 原xml
     * @return    重新解密后的xml
     */
    public static String  decryptMsg(HttpServletRequest request,String originalXml) 
        // 微信加密签名
        //String sVerifyMsgSig = request.getParameter("signature");
        String msgSignature = request.getParameter("msg_signature");
        // 时间戳
        String timestamp = request.getParameter("timestamp");
        // 随机数
        String nonce = request.getParameter("nonce");
        try 
            WXBizMsgCrypt pc = new WXBizMsgCrypt("your token", "your EncodingAESKey", "your appid");
            return pc.decryptMsg(msgSignature, timestamp, nonce, originalXml);
         catch (AesException e) 
            // TODO Auto-generated catch block
            e.printStackTrace();
        
        return null;
    

    /**
     * 对需要回复的原文进行加密重新封装
     * @param request
     * @param replyXml 需要回复的xml
     * @return    重新加密后的xml
     */
    public static String  encryptMsg(HttpServletRequest request, String replyXml) 
        // 时间戳
        String timestamp = request.getParameter("timestamp");
        // 随机数
        String nonce = request.getParameter("nonce");
        try 
            WXBizMsgCrypt pc = new WXBizMsgCrypt("your token","your EncodingAESKey","your appid");
            return pc.encryptMsg(replyXml, timestamp, nonce);
         catch (AesException e) 
            // TODO Auto-generated catch block
            e.printStackTrace();
        
        return null;
    

package webapp.ext.wechat;

import java.util.ArrayList;

class ByteGroup 
    ArrayList<Byte> byteContainer = new ArrayList<Byte>();

    public byte[] toBytes() 
        byte[] bytes = new byte[byteContainer.size()];
        for (int i = 0; i < byteContainer.size(); i++) 
            bytes[i] = byteContainer.get(i);
        
        return bytes;
    

    public ByteGroup addBytes(byte[] bytes) 
        for (byte b : bytes) 
            byteContainer.add(b);
        
        return this;
    

    public int size() 
        return byteContainer.size();
    
package webapp.ext.wechat;

import com.thoughtworks.xstream.XStream;
import org.apache.http.util.TextUtils;
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.IOException;
import java.io.InputStream;
import java.util.*;

public class MessageUtil 

    public static final String MESSAGE_TEXT = "text";
    public static final String MESSAGE_IMAGE = "image";
    public static final String MESSAGE_VOICE = "voice";
    public static final String MESSAGE_VIDEO = "video";
    public static final String MESSAGE_LINK = "link";
    public static final String MESSAGE_LOCATION = "location";
    public static final String MESSAGE_EVENT = "event";

    public static final String EVENT_SUB = "subscribe";
    public static final String EVENT_UNSUB = "unsubscribe";
    public static final String EVENT_CLICK = "CLICK";
    public static final String EVENT_VIEW = "VIEW";

    /**
     * xml转为map
     * @param request
     * @return
     * @throws DocumentException
     * @throws IOException
     */
    public static Map<String, String> xmlToMap(HttpServletRequest request ) throws DocumentException, IOException
    
        Map<String,String> map = new HashMap<String, String>();

        SAXReader reader = new SAXReader();

        InputStream ins = request.getInputStream();
        Document doc = reader.read(ins);

        Element root = doc.getRootElement();
        List<Element> list = root.elements();
        for (Element e : list) 
            map.put(e.getName(), e.getText());
        
        ins.close();
        return map;
    

    public static String textMessageToXml(TextMessage textMessage)
        XStream xstream = new XStream();
        xstream.alias("xml", textMessage.getClass());
        return xstream.toXML(textMessage);

    

    public static Map<String,Object> xmlStr2Map(String xmlStr)
        Map<String,Object> map = new HashMap<String,Object>();
        Document doc;
        try 
            doc = DocumentHelper.parseText(xmlStr);
            Element root = doc.getRootElement();
            List children = root.elements();
            if(children != null && children.size() > 0) 
                for(int i = 0; i < children.size(); i++) 
                    Element child = (Element)children.get(i);
                    map.put(child.getName(), child.getTextTrim());
                
            
         catch (DocumentException e) 
            e.printStackTrace();
        
        return map;
    


    public static String map2Xmlstring(Map<String,Object> map)
        StringBuffer sb = new StringBuffer("");
        sb.append("<xml>");

        Set<String> set = map.keySet();
        for(Iterator<String> it = set.iterator(); it.hasNext();)
            String key = it.next();
            Object value = map.get(key);
            sb.append("<").append(key).append(">");
            sb.append(value);
            sb.append("</").append(key).append(">");
        
        sb.append("</xml>");
        return sb.toString();
    

    public static String map2XmlstringWithHead(Map<String,Object> map,String head)
        StringBuffer sb = new StringBuffer("");

        if (!TextUtils.isEmpty(head)) 
            sb.append("<"+head+">");
        
        Set<String> set = map.keySet();
        for(Iterator<String> it = set.iterator(); it.hasNext();)
            String key = it.next();
            Object value = map.get(key);
            sb.append("<").append(key).append(">");
            sb.append(value);
            sb.append("</").append(key).append(">");
        
        if (!TextUtils.isEmpty(head)) 
            sb.append("</"+head+">");
        
        return sb.toString();
    

/**
 * 对公众平台发送给公众账号的消息加解密示例代码.
 * 
 * @copyright Copyright (c) 1998-2014 Tencent Inc.
 */

// ------------------------------------------------------------------------

package webapp.ext.wechat;

import java.nio.charset.Charset;
import java.util.Arrays;

/**
 * 提供基于PKCS7算法的加解密接口.
 */
class PKCS7Encoder 
    static Charset CHARSET = Charset.forName("utf-8");
    static int BLOCK_SIZE = 32;

    /**
     * 获得对明文进行补位填充的字节.
     * 
     * @param count 需要进行填充补位操作的明文字节个数
     * @return 补齐用的字节数组
     */
    static byte[] encode(int count) 
        // 计算需要填充的位数
        int amountToPad = BLOCK_SIZE - (count % BLOCK_SIZE);
        if (amountToPad == 0) 
            amountToPad = BLOCK_SIZE;
        
        // 获得补位所用的字符
        char padChr = chr(amountToPad);
        String tmp = new String();
        for (int index = 0; index < amountToPad; index++) 
            tmp += padChr;
        
        return tmp.getBytes(CHARSET);
    

    /**
     * 删除解密后明文的补位字符
     * 
     * @param decrypted 解密后的明文
     * @return 删除补位字符后的明文
     */
    static byte[] decode(byte[] decrypted) 
        int pad = (int) decrypted[decrypted.length - 1];
        if (pad < 1 || pad > 32) 
            pad = 0;
        
        return Arrays.copyOfRange(decrypted, 0, decrypted.length - pad);
    

    /**
     * 将数字转化成ASCII码对应的字符,用于对明文进行补码
     * 
     * @param a 需要转化的数字
     * @return 转化得到的字符
     */
    static char chr(int a) 
        byte target = (byte) (a & 0xFF);
        return (char) target;
    

/**
 * 对公众平台发送给公众账号的消息加解密示例代码.
 * 
 * @copyright Copyright (c) 1998-2014 Tencent Inc.
 */

// ------------------------------------------------------------------------

package webapp.ext.wechat;

import java.security.MessageDigest;
import java.util.Arrays;

/**
 * SHA1 class
 *
 * 计算公众平台的消息签名接口.
 */
class SHA1 

    /**
     * 用SHA1算法生成安全签名
     * @param token 票据
     * @param timestamp 时间戳
     * @param nonce 随机字符串
     * @param encrypt 密文
     * @return 安全签名
     * @throws com.qq.weixin.mp.aes.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);
        
    
package webapp.ext.wechat;

public class TextMessage 

    public String ToUserName;
    public String FromUserName;
    public int CreateTime;
    public String MsgType;
    public String Content;

    public String getToUserName() 
        return ToUserName;
    

    public void setToUserName(String toUserName) 
        ToUserName = toUserName;
    

    public String getFromUserName() 
        return FromUserName;
    

    public void setFromUserName(String fromUserName) 
        FromUserName = fromUserName;
    

    public int getCreateTime() 
        return CreateTime;
    

    public void setCreateTime(int createTime) 
        CreateTime = createTime;
    

    public String getMsgType() 
        return MsgType;
    

    public void setMsgType(String msgType) 
        MsgType = msgType;
    

    public String getContent() 
        return Content;
    

    public void setContent(String content) 
        Content = content;
    
/**
 * 对公众平台发送给公众账号的消息加解密示例代码.
 * 
 * @copyright Copyright (c) 1998-2014 Tencent Inc.
 */

// ------------------------------------------------------------------------

/**
 * 针对org.apache.commons.codec.binary.Base64,
 * 需要导入架包commons-codec-1.9(或commons-codec-1.8等其他版本)
 * 官方下载地址:http://commons.apache.org/proper/commons-codec/download_codec.cgi
 */
package webapp.ext.wechat;

import org.apache.commons.codec.binary.Base64;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.Random;

/**
 * 提供接收和推送给公众平台消息的加解密接口(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个字节的网络字节序
    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;
    

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

    // 随机生成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();
    

    /**
     * 对明文进行加密.
     * 
     * @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);
        
    

    /**
     * 对密文进行解密.
     * 
     * @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.ValidateAppidError);
        
        return xmlContent;

    

    /**
     * 将公众平台回复用户的消息加密打包.
     * <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 = SHA1.getSHA1(token, timeStamp, nonce, encrypt);

        // System.out.println("发送给平台的签名是: " + signature[1].toString());
        // 生成发送的xml
        String result = XMLParse.generate(encrypt, signature, timeStamp, nonce);
        return result;
    

    /**
     * 检验消息的真实性,并且获取解密后的明文.
     * <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 = XMLParse.extract(postData);

        // 验证安全签名
        String signature = SHA1.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;
    

    /**
     * 验证URL
     * @param msgSignature 签名串,对应URL参数的msg_signature
     * @param timeStamp 时间戳,对应URL参数的timestamp
     * @param nonce 随机串,对应URL参数的nonce
     * @param echoStr 随机串,对应URL参数的echostr
     * 
     * @return 解密之后的echostr
     * @throws AesException 执行失败,请查看该异常的错误码和具体的错误信息
     */
    public String verifyUrl(String msgSignature, String timeStamp, String nonce, String echoStr)
            throws AesException 
        String signature = SHA1.getSHA1(token, timeStamp, nonce, echoStr);

        if (!signature.equals(msgSignature)) 
            throw new AesException(AesException.ValidateSignatureError);
        

        String result = decrypt(echoStr);
        return result;
    

/**
 * 对公众平台发送给公众账号的消息加解密示例代码.
 * 
 * @copyright Copyright (c) 1998-2014 Tencent Inc.
 */

// ------------------------------------------------------------------------

package webapp.ext.wechat;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.StringReader;

/**
 * XMLParse class
 *
 * 提供提取消息格式中的密文及生成回复消息格式的接口.
 */
class XMLParse 

    /**
     * 提取出xml数据包中的加密消息
     * @param xmltext 待提取的xml字符串
     * @return 提取出的加密消息字符串
     * @throws com.qq.weixin.mp.aes.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();
            result[2] = nodelist2.item(0).getTextContent();
            return result;
         catch (Exception e) 
            e.printStackTrace();
            throw new AesException(AesException.ParseXmlError);
        
    

    /**
     * 生成xml消息
     * @param encrypt 加密后的消息密文
     * @param signature 安全签名
     * @param timestamp 时间戳
     * @param nonce 随机字符串
     * @return 生成的xml字符串
     */
    public static String generate(String encrypt, String signature, String timestamp, String nonce) 

        String format = "<xml>\\n" + "<Encrypt><![CDATA[%1$s]]></Encrypt>\\n"
                + "<MsgSignature><![CDATA[%2$s]]></MsgSignature>\\n"
                + "<TimeStamp>%3$s</TimeStamp>\\n" + "<Nonce><![CDATA[%4$s]]></Nonce>\\n" + "</xml>";
        return String.format(format, encrypt, signature, timestamp, nonce);

    
package webapp.ext;


import webapp.config.Config;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;

/**
 * @Description:微信签名校验
 */
public class WechatSignUtil 


    /**
     * 校验签名
     * @param signature 微信加密签名.
     * @param timestamp 时间戳.
     * @param nonce 随机数.
     * @return
     */
    public static boolean checkSignature(String signature, String timestamp, String nonce) 
        // 对token、timestamp、和nonce按字典排序.
        String[] paramArr = new String[] "你的公众号token", timestamp, nonce;
        Arrays.sort(paramArr);

        // 将排序后的结果拼接成一个字符串.
        String content  = paramArr[0].concat(paramArr[1]).concat(paramArr[2]);

        String ciphertext = null;
        try 
            MessageDigest md = MessageDigest.getInstance("SHA-1");
            // 对拼接后的字符串进行sha1加密.
            byte[] digest = md.digest(content.toString().getBytes());
            ciphertext = byteToStr(digest);
         catch (NoSuchAlgorithmException e) 
            e.printStackTrace();
        

        // 将sha1加密后的字符串与signature进行对比.
        return ciphertext != null ? ciphertext.equals(signature.toUpperCase()) : false;
    

    /**
     * 将字节数组转换为十六进制字符串.
     * @param byteArray
     * @return
     */
    private static String byteToStr(byte[] byteArray) 
        String strDigest = "";
        for (int i = 0; i < byteArray.length; i++) 
            strDigest += byteToHexStr(byteArray[i]);
        
        return strDigest;
    

    /**
     * 将字节转换为十六进制字符串.
     * @param mByte
     * @return
     */
    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];

        String s = new String(tempArr);
        return s;
    

//    //获取token
//    public static String getToken() throws Exception
//        String token = DbGhostApi.tokenIsValid();
//        if (token != null) 
//            return token;
//         else 
//            Date now = new Date();
//            String rsp = HttpUtil.getString("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + Config.appid + "&secret=" + Config.secret);
//            JSONObject obj = JSONObject.parseObject(rsp);
//            String access_token = obj.getString("access_token");
//            int expires_in = obj.getIntValue("expires_in");
//            DbGhostApi.updateToken(access_token, DateUtil.dateAddSecond(now,expires_in));
//            return access_token;
//        
//    

 

注意:语音消息识别需要在公众号后台开启相应功能

技术图片

 

以上是关于java 微信开发 回复消息乱码的主要内容,如果未能解决你的问题,请参考以下文章

微信开发之消息接收与回复--weixin-java-tools

Java微信公众平台开发--文本及图文消息回复的实现

Java微信公众平台开发--回复消息的分类及实体的创建

微信公众号开发java版-消息回复(普通文字消息和语音消息)

Java微信公众平台开发_03_消息管理之被动回复消息

Java微信公众号开发----关键字自动回复消息