【Java】微信公众号被动回复用户消息开发者后台实现

目录

前期准备

 代码实现

业务类实现

工具类


前期准备

微信开放文档——消息加解密说明

https://res.wx.qq.com/op_res/-serEQ6xSDVIjfoOHcX78T1JAYX-pM_fghzfiNYoD8uHVd3fOeC0PC_pvlg4-kmP微信公众平台提供的 c++, php, java, python, c# 5 种语言的消息加解密示例代码下载:https://res.wx.qq.com/op_res/-serEQ6xSDVIjfoOHcX78T1JAYX-pM_fghzfiNYoD8uHVd3fOeC0PC_pvlg4-kmP

回复文本消息 | 微信开放文档

回复文本消息

<xml>
  <ToUserName><![CDATA[toUser]]></ToUserName>
  <FromUserName><![CDATA[fromUser]]></FromUserName>
  <CreateTime>12345678</CreateTime>
  <MsgType><![CDATA[text]]></MsgType>
  <Content><![CDATA[你好]]></Content>
</xml>

回复图片消息

<xml>
  <ToUserName><![CDATA[toUser]]></ToUserName>
  <FromUserName><![CDATA[fromUser]]></FromUserName>
  <CreateTime>12345678</CreateTime>
  <MsgType><![CDATA[image]]></MsgType>
  <Image>
    <MediaId><![CDATA[media_id]]></MediaId>
  </Image>
</xml>

服务器配置时需要校验token,所有先写好get接口配置到服务器

    @GetMapping(value = "/api/test")
    public void checkToken(HttpServletRequest request, HttpServletResponse response)  {
        //一、校验URL
        // 准备校验参数
        // 微信加密签名
        String msgSignature = request.getParameter("signature");
        // 时间戳
        String timeStamp = request.getParameter("timestamp");
        // 随机数
        String nonce = request.getParameter("nonce");
        // 随机字符串
        String echoStr = request.getParameter("echostr");
        PrintWriter out=null;
        try {
            // 校验url
            // 创建加解密类
            WXBizMsgCrypt wxcpt = new WXBizMsgCrypt(Env.TOKEN,Env.ENCODING_AES_KEY,Env.APP_ID);
            // 进行url校验
            //不抛异常就说明校验成功
            String sEchoStr= wxcpt.verifyUrl_WXGZ(msgSignature, Env.TOKEN, timeStamp, nonce, echoStr);
            // 若校验成功,则原样返回 echoStr
            out = response.getWriter();
            out.print(sEchoStr);
        } catch (AesException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (out != null) {
                out.close();
                out = null;
            }
        }
    }
/**
 * @desc :微信公众号  验证url
 *
 * @param msgSignature  签名串,对应URL参数的msg_signature
 * @param token 公众平台上,开发者设置的token
 * @param timeStamp   时间戳,对应URL参数的timestamp
 * @param nonce 随机数,对应URL参数的nonce
 * @param echoStr 随机串,对应URL参数的echostr,在微信公众号中是明文的,直接原样返回给微信公众平台官方服务器
 * @return String  验证成功后,原样返回echoStr
 * @throws AesException   执行失败,请查看该异常的错误码和具体的错误信息
 */
public String verifyUrl_WXGZ(String msgSignature, String token , String timeStamp, String nonce,String echoStr) throws AesException {
	// 进行SHA1加密
	String signature = SHA1.getSHA1_WXGZ(token, timeStamp, nonce);
	// 验证 token、timestamp、nonce进行SHA1加密生成的signature 是否与url传过来msgSignature相同
	if (!signature.equals(msgSignature)) {
		throw new AesException(AesException.ValidateSignatureError);
	}
	// 若不抛异常,则url验证成功,原样返回echoStr
	String result = echoStr;
	return result;
}
/**
 * 微信公众号SHA1加密算法
 * 用SHA1算法生成安全签名
 * @param token 票据
 * @param timestamp 时间戳
 * @param nonce 随机字符串
 * @return 安全签名
 * @throws AesException
 */
public static String getSHA1_WXGZ(String token, String timestamp, String nonce) throws AesException
{
	try {
		String[] array = new String[] { token, timestamp, nonce };
		StringBuffer sb = new StringBuffer();
		// 将token、timestamp、nonce三个参数进行字典序排序
		Arrays.sort(array);
		// 将三个参数字符串拼接成一个字符串进行sha1加密
		for (int i = 0; i < 3; 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);
	}
}

下一步,进行服务器配置,公众号管理页面——设置与开发——基本配置——服务器配置

 代码实现

接口回调post请求,接收用户消息,并回复。

get,post两个的请求路径一致,就是服务器配置填写的URL。

@PostMapping("/api/test")
public void reply(HttpServletRequest request, HttpServletResponse response) throws IOException {
	// 将请求、响应的编码均设置为UTF-8(防止中文乱码)
	request.setCharacterEncoding("UTF-8");
	response.setCharacterEncoding("UTF-8");

	// 调用消息业务类接收消息、处理消息
	String respMessage = replyMessageService.reply(request);

	// 响应消息
	PrintWriter out = response.getWriter();
	out.print(respMessage);
	out.close();
}

业务类实现

ReplyMessageService

/**
 * @desc : 回复消息
 *
 * @param request
 * @return String 回复消息的加密xml字符串
 */
public String reply( HttpServletRequest request ) {
	// 解密:从request中获取消息明文
	String xmlMsg = decryptMsg(request);

	// 获取回复消息(明文)
	String replyMsg = getReplyMsg( xmlMsg);
	logger.info("replyMsg:" + replyMsg);

	// 根据消息加密方式判断是否加密
	String timeStamp = request.getParameter("timestamp");   // 时间戳    
	String nonce = request.getParameter("nonce");          // 随机数  
	String encryptType = request.getParameter("encrypt_type");

	// 安全模式-加密:将回复消息加密
	if(null != encryptType) {
		WXBizMsgCrypt wxcpt;
		try {
			wxcpt = new WXBizMsgCrypt(Env.TOKEN,Env.ENCODING_AES_KEY,Env.APP_ID);
			replyMsg=wxcpt.encryptMsg(replyMsg, timeStamp, nonce);
		} catch (AesException e) {
			e.printStackTrace();
		}
	}
	return replyMsg;
}

/**
 * @desc : 从request中获取消息明文
 *  从request中获取加密消息,将其解密并返回
 * @param request
 * @return String   消息明文
 */
public static String decryptMsg(HttpServletRequest request) {
	String postData="";   // 密文,对应POST请求的数据
	String result="";     // 明文,解密之后的结果

	String msgSignature = request.getParameter("msg_signature"); // 微信加密签名  
	String timeStamp = request.getParameter("timestamp");   // 时间戳    
	String nonce = request.getParameter("nonce");          // 随机数  
	String encryptType = request.getParameter("encrypt_type");

	try {
		// 获取加密的请求消息:使用输入流获得加密请求消息postData
		ServletInputStream in = request.getInputStream();
		BufferedReader reader = new BufferedReader(new InputStreamReader(in));

		String tempStr = "";   //作为输出字符串的临时串,用于判断是否读取完毕
		while(null != (tempStr = reader.readLine())){
			postData += tempStr;
		}

		// 获取消息明文:对加密的请求消息进行解密获得明文 
		if(null!=encryptType) {
			logger.info("安全模式:消息被加密");
			WXBizMsgCrypt wxcpt = new WXBizMsgCrypt(Env.TOKEN,Env.ENCODING_AES_KEY,Env.APP_ID);
			result = wxcpt.decryptMsg(msgSignature, timeStamp, nonce, postData);
		}else {
			logger.info("明文模式");
			result = postData;
		}
	} catch (IOException e) {
		e.printStackTrace();
	} catch (AesException e) {
		e.printStackTrace();
	}
	return result;
}

/**
 * @desc :获取回复消息
 *
 * @param xmlMsg
 * @return String  返回加密后的回复消息
 */
public String getReplyMsg(String xmlMsg){
	String replyMsg = "success";
	try {
		// 解析微信发来的请求,解析xml字符串
		Map<String, String> requestMap= WxMsgUtil.parseXml(xmlMsg);
		// 获取请求参数
		String fromUserName = requestMap.get("FromUserName");
		String toUserName = requestMap.get("ToUserName");
		// 消息类型与事件 
		String msgType = requestMap.get("MsgType");
		String eventType = requestMap.get("Event");
		String eventKey = requestMap.get("EventKey");
		// 微信服务器在五秒内收不到响应会断掉连接,并重新发起请求,共重试三次,三次请求MsgId不变
		String msgId = requestMap.get("MsgId");
		
		String content = null;
		String picUrl = null;
		
		if(msgType.equals(WxMsgUtil.REQ_MESSAGE_TYPE_TEXT)){
			content = requestMap.get("Content");
		} else if (msgType.equals(WxMsgUtil.REQ_MESSAGE_TYPE_IMAGE)) {
			picUrl = requestMap.get("PicUrl");
		}

		if(null != picUrl) {
			// 用户发送的图片,消息处理
		} else {
			// 回复文本消息
			ReplyTextMsg message = new ReplyTextMsg();
			message.setToUserName(fromUserName);
			message.setFromUserName(toUserName);
			message.setCreateTime(new Date().getTime());
			message.setMsgType(WxMsgUtil.RESP_MESSAGE_TYPE_TEXT);
			// 获取回复消息的内容 :消息的分类处理
			String replyContent = getReplyContentByMsgType(msgType, eventType, eventKey, msgUser, content, picUrl, null);
			message.setContent(replyContent);
			// 获取xml字符串: 将(被动回复消息型的)文本消息对象转成xml字符串
			replyMsg = WxMsgUtil.textMessageToXml(message);
		}
	} catch (Exception e) {
		e.printStackTrace();
	}
	return replyMsg;
}


/**
 * @desc : 处理消息:根据消息类型获取回复内容
 *
 * @param msgType 消息类型
 * @return String 回复内容
 */
public String getReplyContentByMsgType(String msgType,String eventType,String eventKey, WxMsgUser msgUser, String content, String picUrl, String msgId) throws Exception {
	String replyContent = null;
	//1.文本消息
	if (msgType.equals(WxMsgUtil.REQ_MESSAGE_TYPE_TEXT)) {
		// TODO 回复内容处理
		replyContent = "文本消息";
	}
	//2.图片消息
	else if (msgType.equals(WxMsgUtil.REQ_MESSAGE_TYPE_IMAGE)) {
		replyContent = "图片消息";
	}
	//3.地理位置消息
	else if (msgType.equals(WxMsgUtil.REQ_MESSAGE_TYPE_LOCATION)) {
		replyContent = "地理位置消息";
	}
	//4.链接消息
	else if (msgType.equals(WxMsgUtil.REQ_MESSAGE_TYPE_LINK)) {
		replyContent = "链接消息";
	}
	//5.音频消息
	else if (msgType.equals(WxMsgUtil.REQ_MESSAGE_TYPE_VOICE)) {
		replyContent = "音频消息";
	}
	//6.事件推送
	else if (msgType.equals(WxMsgUtil.REQ_MESSAGE_TYPE_EVENT)) {
		replyContent = getReplyContentByEventType(eventType, eventKey);
	}
	//7.请求异常
	else {
		replyContent = "请求异常";
	}
	return replyContent;
}

/**
 * @desc : 事件推送消息处理
 *
 * @param eventType  事件类型
 * @param eventKey  事件key值
 * @return String
 */
public String getReplyContentByEventType(String eventType,String eventKey){
	String respContent = null;
	// 订阅
	if (eventType.equals(WxMsgUtil.EVENT_TYPE_SUBSCRIBE)) {
		respContent = "欢迎订阅";
	}
	// 取消订阅  
	else if (eventType.equals(WxMsgUtil.EVENT_TYPE_UNSUBSCRIBE)) {
		// 取消订阅后用户再收不到公众号发送的消息,因此不需要回复消息
	}
	//上报地理位置事件
	else if(eventType.equals("LOCATION")){

	}
	// 自定义菜单点击事件
	else if (eventType.equals(WxMsgUtil.EVENT_TYPE_CLICK)) {

	}
	return respContent;
}

返回图片消息,需要先将图片上传到临时素材,获取media_id

工具类


public class WxMsgUtil {
    //返回消息类型:文本
    public static final String RESP_MESSAGE_TYPE_TEXT = "text";
    //返回消息类型:图片
    public static final String RESP_MESSAGE_TYPE_IMAGE = "image";
    //返回消息类型:音乐
    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_CUSTOM = "transfer_customer_service";


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

    /**
     * @desc : 解析微信发来的请求(XML),获取请求参数
     *
     * @param request
     * @return
     * @throws Exception Map<String,String>
     */
    public static Map<String, String> parseXml(HttpServletRequest request) throws Exception {
        // 将解析结果存储在HashMap中
        Map<String, String> map = new HashMap<String, String>();
        // 从request中取得输入流
        InputStream inputStream = request.getInputStream();
        // 读取输入流
        SAXReader reader = new SAXReader();
        Document document = reader.read(inputStream);
        // 得到xml根元素
        Element root = document.getRootElement();
        // 得到根元素的所有子节点
        List<Element> elementList = root.elements();
        // 遍历所有子节点
        for (Element e : elementList) {
            map.put(e.getName(), e.getText());
        }
        // 释放资源
        inputStream.close();
        inputStream = null;
        return map;
    }

    /**
     * @desc : 解析微信发来的请求(xmlStr),获取请求参数
     *
     * @param xmlStr
     * @return
     * @throws Exception Map<String,String>
     */
    public static Map<String, String> parseXml(String xmlStr) throws Exception {
        // 将解析结果存储在HashMap中
        Map<String, String> map = new HashMap<String, String>();

        // 将字符串转为Document
        Document document = DocumentHelper.parseText(xmlStr);

        // 获取根元素的所有子节点
        // 得到xml根元素
        Element root = document.getRootElement();
        // 得到根元素的所有子节点
        List<Element> elementList = root.elements();
        // 遍历所有子节点
        for (Element e : elementList) {
            map.put(e.getName(), e.getText());
		}
        return map;
    }

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

	/**
     *  图片消息对象转换成xml
     *
     * @param textMessage 文本消息对象
     * @return xml
     */
    public static String imageMessageToXml(ReplyImageMsg imageMessage) {
        String msg = "<xml>\n" +
                "  <ToUserName><![CDATA["+imageMessage.getToUserName()+"]]></ToUserName>\n" +
                "  <FromUserName><![CDATA["+imageMessage.getFromUserName()+"]]></FromUserName>\n" +
                "  <CreateTime>"+new Date().getTime()+"</CreateTime>\n" +
                "  <MsgType><![CDATA[image]]></MsgType>\n" +
                "  <Image>\n" +
                "    <MediaId><![CDATA["+imageMessage.getImage().getMediaId()+"]]></MediaId>\n" +
                "  </Image>\n" +
                "</xml>";
        return msg;
    }

    /**
     *  转发客服消息对象转换成xml
     *
     * @param customMessage 文本消息对象
     * @return xml
     */
    public static String customMessageToXml(CustomMessage customMessage) {
        xstream.alias("xml", customMessage.getClass());
        return xstream.toXML(customMessage);
    }
	
    /**
     * 音乐消息对象转换成xml
     *
     * @param musicMessage 音乐消息对象
     * @return xml
     */
    public static String musicMessageToXml(MusicMessage musicMessage) {
        xstream.alias("xml", musicMessage.getClass());
        return xstream.toXML(musicMessage);
    }

    /**
     * 图文消息对象转换成xml
     *
     * @param newsMessage 图文消息对象
     * @return xml
     */
    public static String newsMessageToXml(NewsMessage newsMessage) {
        xstream.alias("xml", newsMessage.getClass());
        xstream.alias("item", new Article().getClass());
        return xstream.toXML(newsMessage);
    }

    /**
     * 扩展xstream,使其支持CDATA块
     *
     * @date 2013-05-19
     */
    private static final XStream xstream = new XStream(new XppDriver() {
        public HierarchicalStreamWriter createWriter(Writer out) {
            return new PrettyPrintWriter(out) {
                // 对所有xml节点的转换都增加CDATA标记
                final boolean cdata = true;

                @SuppressWarnings("unchecked")
                public void startNode(String name, Class clazz) {
                    super.startNode(name, clazz);
                }

                protected void writeText(QuickWriter writer, String text) {
                    if (cdata) {
                        writer.write("<![CDATA[");
                        writer.write(text);
                        writer.write("]]>");
                    } else {
                        writer.write(text);
                    }
                }
            };
        }
    });
}

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值