最近做微信公众号的功能,按钮菜单中有 type 为 click 的按钮,其功能是 若你配置好了服务器配置,设置后回调的url, 点击后微信会将该事件推送到配置好的地址,后台接受数据然后进行相应的处理。网上看了很多例子,学习了很多,我发现最好的还是这位博主写的文章,分享给大家:/tuposky/article/details/40589319
然后开始主题:
先做好前期准备:
首先做好服务器配置:
配置好url , 记录下token,和EncodingAESKey;
然后编写代码:
controller:
package com.modou.park.controller.app.user;import java.io.IOException;import java.io.PrintWriter;import java.util.Map;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import mons.logging.Log;import mons.logging.LogFactory;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;import com.alibaba.fastjson.JSON;import com.modou.park.service.impl.WxEventServiceImpl;import com.modou.park.util.wxevent.SignUtil;import io.swagger.annotations.Api;import io.swagger.annotations.ApiOperation;import springfox.documentation.annotations.ApiIgnore;/*** 微信相关接口* * @author cdj**/@Api("微信用户相关接口")@RestController("/WxUser")public class WxUserController {@Autowiredprivate WxEventServiceImpl wxEventServiceImpl;@Autowiredprivate static final Log LOG = LogFactory.getLog(WxUserController.class);@ApiOperation("微信公众号服务器配置接口")@ApiIgnore@RequestMapping("/HandleWxEvent")public void HandleWxEvent(HttpServletRequest req, HttpServletResponse resp) throws IOException {req.setCharacterEncoding("UTF-8");resp.setCharacterEncoding("UTF-8");Map<String, String[]> parameterMap = req.getParameterMap();System.out.println("requestData:" + JSON.toJSONString(parameterMap));LOG.info("requestData:" + JSON.toJSONString(parameterMap));// 微信加密签名String signature = req.getParameter("signature");// 时间戳String timestamp = req.getParameter("timestamp");// 随机数String nonce = req.getParameter("nonce");// 随机字符串String echostr = req.getParameter("echostr");System.out.println("signature=" + signature + ";timestamp=" + timestamp + ";nonce=" + nonce + ";echostr=" + echostr);LOG.info("signature=" + signature + ";timestamp=" + timestamp + ";nonce=" + nonce + ";echostr=" + echostr);PrintWriter out = resp.getWriter();// 通过检验signature对请求进行校验,若校验成功则原样返回echostr,表示接入成功,否则接入失败LOG.info("checkSignature:" + SignUtil.checkSignature(signature, timestamp, nonce));System.out.println("checkSignature:" + SignUtil.checkSignature(signature, timestamp, nonce));// 首次请求申请验证,返回echostrif (echostr != null) {out.println(echostr);if (out != null) {out.close();}return;}if (SignUtil.checkSignature(signature, timestamp, nonce)) {System.out.println("success");LOG.info("success");String message = "success";try {message = wxEventServiceImpl.processRequest(req);} catch (Exception e) {e.printStackTrace();} finally {out.println(message);if (out != null) {out.close();}}} else {System.out.println("The request signature is invalid");LOG.info("The request signature is invalid");}}}
WxEventServiceImpl:
import java.util.ArrayList;import java.util.Date;import java.util.List;import java.util.Map;import javax.servlet.http.HttpServletRequest;import mons.logging.Log;import mons.logging.LogFactory;import org.ponent;import com.alibaba.fastjson.JSON;import com.modou.park.entity.wxevent.resp.Article;import com.modou.park.entity.wxevent.resp.NewsMessage;import com.modou.park.service.WxEventService;import com.modou.park.util.wxevent.EventConstant;import com.modou.park.util.wxevent.MessageUtil;@Componentpublic class WxEventServiceImpl implements WxEventService {private static final Log log = LogFactory.getLog(WxEventServiceImpl.class);@Overridepublic String processRequest(HttpServletRequest req) {//String message = "success";String respMessage = null;try {// 把微信返回的xml信息转义成mapMap<String, String> map = MessageUtil.parseXml(req);//Map<String, String> map = XmlUtil.xmlToMap(req);System.out.println(JSON.toJSONString(map));boolean isreturn = false;if (map.get("MsgType") != null) {if ("event".equals(map.get("MsgType"))) {log.info("2.1解析消息内容为:事件推送");if ("subscribe".equals(map.get("Event")) || "CLICK".equals(map.get("Event"))) {log.info("2.2用户第一次关注 返回true哦");isreturn = true;}}}if(isreturn == true){//转换XMLString fromUserName = map.get("FromUserName");// 消息来源用户标识String toUserName = map.get("ToUserName");// 消息目的用户标识String msgType = map.get("MsgType");// 消息类型String eventKey = map.get("EventKey");// 消息内容String eventType = map.get("Event");// 文本消息if (msgType.equals(EventConstant.REQ_MESSAGE_TYPE_EVENT)) {// 创建图文消息NewsMessage newsMessage = new NewsMessage();newsMessage.setToUserName(fromUserName);newsMessage.setFromUserName(toUserName);newsMessage.setCreateTime(new Date().getTime());newsMessage.setMsgType(EventConstant.RESP_MESSAGE_TYPE_NEWS);newsMessage.setFuncFlag(0);List<Article> articleList = new ArrayList<Article>();if ("testNews".equals(eventKey)) {Article article = new Article();article.setTitle("我是一条单图文消息");article.setDescription("我是描述信息,哈哈哈哈哈哈哈。。。");article.setPicUrl("/upload/logo/user/603624/2dc5ec35-073c-35e7-9b88-274d6b39d560.jpg");article.setUrl("/atmknight");articleList.add(article);//多图文的话,新建多个article 放入articleList 中即可实现// 设置图文消息个数newsMessage.setArticleCount(articleList.size());// 设置图文消息包含的图文集合newsMessage.setArticles(articleList);// 将图文消息对象转换成xml字符串respMessage = MessageUtil.newsMessageToXml(newsMessage);}}}return respMessage;} catch (Exception e) {throw new RuntimeException(e.getMessage());}}}
生成一个按钮 类型为:
{"type": "click", "name": "点击", "key": "testNews"}
以图文为例子 : 后台收到的request 内容为 :
{"CreateTime":"1533277022","EventKey":"testNews","Event":"CLICK","ToUserName":"gh_0ffcdd3acda5","FromUserName":"o2vLnwpLUWvxHdJJDGaHUahUzhBA","MsgType":"event"}
内容具体情况可能和网上有点不同,还是以实际为准,我这个的字段好像和看到的那些博客里面写的有点不一样,所有我对代码做了一点点的改动。
反馈后公众号收到的图文信息
总的来说,图文推送不难,而且网上也有很多的例子,不过做出来之后还是很好玩的,以后也要好好的学习,多做笔记。
---------------------------------------------------------- 手动分割 ----------------------------------------------------------
下面是需要的工具类和实体类:
request 接受消息:
接受消息基础类 ( 用户-> 公众帐号)BaseMessage :
package com.modou.park.entity.wxevent.req;/*** 消息体基础类* @author cdj*/public class BaseMessage {private String ToUserName;private String FromUserName;private long CreateTime;private String MsgType;private long MsgId;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 long getCreateTime() {return CreateTime;}public void setCreateTime(long createTime) {CreateTime = createTime;}public String getMsgType() {return MsgType;}public void setMsgType(String msgType) {MsgType = msgType;}public long getMsgId() {return MsgId;}public void setMsgId(long msgId) {MsgId = msgId;}}
TextMessage :
package com.modou.park.entity.wxevent.req;/*** 文本消息类* @author cdj*/public class TextMessage extends BaseMessage{private String Content;public String getContent() {return Content;}public void setContent(String content) {Content = content;}}
ImageMessage:
package com.modou.park.entity.wxevent.req;/*** 图片消息事件接受* @author cdj* @date 8月3日 上午8:28:01*/public class ImageMessage extends BaseMessage {private String picUrl;public String getPicUrl() {return picUrl;}public void setPicUrl(String picUrl) {this.picUrl = picUrl;}}
LinkMessage:
package com.modou.park.entity.wxevent.req;/*** 链接消息接受* @author cdj* @date 8月3日 上午8:29:06*/public class LinkMessage extends BaseMessage {/*** 消息标题*/private String Title;/*** 消息描述*/private String Description;/*** 消息链接*/private String Url;public String getTitle() {return Title;}public void setTitle(String title) {Title = title;}public String getDescription() {return Description;}public void setDescription(String description) {Description = description;}public String getUrl() {return Url;}public void setUrl(String url) {Url = url;}}
LocationMessage:
package com.modou.park.entity.wxevent.req;/*** 位置消息* @author cdj* @date 8月3日 上午8:30:35*/public class LocationMessage extends BaseMessage {/*** 地理位置维度*/private String Location_X;/*** 地理位置经度*/private String Location_Y;/*** 地图缩放大小*/private String Scale;/*** 地理位置信息*/private String Label;public String getLocation_X() {return Location_X;}public void setLocation_X(String location_X) {Location_X = location_X;}public String getLocation_Y() {return Location_Y;}public void setLocation_Y(String location_Y) {Location_Y = location_Y;}public String getScale() {return Scale;}public void setScale(String scale) {Scale = scale;}public String getLabel() {return Label;}public void setLabel(String label) {Label = label;}}
VoiceMessage:
package com.modou.park.entity.wxevent.req;/*** 语音消息* * @author cdj* @date 8月3日 上午8:29:49*/public class VoiceMessage extends BaseMessage {/*** 媒体ID*/private String MediaId;/*** 语音格式*/private String Format;public String getMediaId() {return MediaId;}public void setMediaId(String mediaId) {MediaId = mediaId;}public String getFormat() {return Format;}public void setFormat(String format) {Format = format;}}
================================================================================================
response 实体类
反馈消息基础类(公众帐号 -> 用户)BaseMessage:
package com.modou.park.entity.wxevent.resp;/*** 消息基类(公众帐号 -> 用户)* @author cdj* @date 8月3日 上午8:43:54*/public class BaseMessage {/*** 接收方帐号(收到的OpenID)*/private String ToUserName;/*** 开发者微信号*/private String FromUserName;/*** 消息创建时间 (整型)*/private long CreateTime;/*** 消息类型*/private String MsgType;/*** 位0x0001被标志时,星标刚收到的消息*/private int FuncFlag;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 long getCreateTime() {return CreateTime;}public void setCreateTime(long createTime) {CreateTime = createTime;}public String getMsgType() {return MsgType;}public void setMsgType(String msgType) {MsgType = msgType;}public int getFuncFlag() {return FuncFlag;}public void setFuncFlag(int funcFlag) {FuncFlag = funcFlag;}}
TextMessage:
package com.modou.park.entity.wxevent.resp;/*** 文本消息* @author 咳嗽停不了* @date 8月3日 上午8:44:51*/public class TextMessage extends BaseMessage {/*** 回复的消息内容*/private String Content;public String getContent() {return Content;}public void setContent(String content) {Content = content;}}
NewsMessage:
package com.modou.park.entity.wxevent.resp;import java.util.List;/*** 多图文消息,* 单图文的时候 Articles 只放一个就行了* @author cdj* @date 8月3日 上午8:45:22*/public class NewsMessage extends BaseMessage {/*** 图文消息个数,限制为10条以内*/private int ArticleCount;/*** 多条图文消息信息,默认第一个item为大图*/private List<Article> Articles;public int getArticleCount() {return ArticleCount;}public void setArticleCount(int articleCount) {ArticleCount = articleCount;}public List<Article> getArticles() {return Articles;}public void setArticles(List<Article> articles) {Articles = articles;}}
Article:
package com.modou.park.entity.wxevent.resp;/*** 图文消息* @author cdj* @date 8月3日 上午8:45:56*/public class Article {/*** 图文消息名称*/private String Title;/*** 图文消息描述*/private String Description;/*** 图片链接,支持JPG、PNG格式,<br>* 较好的效果为大图640*320,小图80*80*/private String PicUrl;/*** 点击图文消息跳转链接*/private String Url;public String getTitle() {return Title;}public void setTitle(String title) {Title = title;}public String getDescription() {return null == Description ? "" : Description;}public void setDescription(String description) {Description = description;}public String getPicUrl() {return null == PicUrl ? "" : PicUrl;}public void setPicUrl(String picUrl) {PicUrl = picUrl;}public String getUrl() {return null == Url ? "" : Url;}public void setUrl(String url) {Url = url;}}
MusicMessage:
package com.modou.park.entity.wxevent.resp;/*** 音乐消息* @author cdj* @date 8月3日 上午8:46:35*/public class MusicMessage extends BaseMessage {/*** 音乐*/private Music Music;public Music getMusic() {return Music;}public void setMusic(Music music) {Music = music;}}
Music:
package com.modou.park.entity.wxevent.resp;/*** 音乐消息* @author cdj* @date 8月3日 上午8:46:59*/public class Music {/*** 音乐名称*/private String Title;/*** 音乐描述*/private String Description;/*** 音乐链接*/private String MusicUrl;/*** 高质量音乐链接,WIFI环境优先使用该链接播放音乐*/private String HQMusicUrl;public String getTitle() {return Title;}public void setTitle(String title) {Title = title;}public String getDescription() {return Description;}public void setDescription(String description) {Description = description;}public String getMusicUrl() {return MusicUrl;}public void setMusicUrl(String musicUrl) {MusicUrl = musicUrl;}public String getHQMusicUrl() {return HQMusicUrl;}public void setHQMusicUrl(String musicUrl) {HQMusicUrl = musicUrl;}}
新建完后有这些
---------------------------------------------------------- 手动分割 ----------------------------------------------------------
设置微信事件常量:
EventConstant:
package com.modou.park.util.wxevent;/*** 微信事件常量* * @author cdj* @date 8月3日 下午1:17:08*/public class EventConstant {/*** 返回消息类型:文本*/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";}
MessageUtil:
package com.modou.park.util.wxevent;import java.io.InputStream;import java.io.Writer;import java.util.HashMap;import java.util.List;import java.util.Map;import javax.servlet.http.HttpServletRequest;import org.dom4j.Document;import org.dom4j.Element;import org.dom4j.io.SAXReader;import com.modou.park.entity.wxevent.resp.Article;import com.modou.park.entity.wxevent.resp.MusicMessage;import com.modou.park.entity.wxevent.resp.NewsMessage;import com.modou.park.entity.wxevent.resp.TextMessage;import com.thoughtworks.xstream.XStream;import com.thoughtworks.xstream.core.util.QuickWriter;import com.thoughtworks.xstream.io.HierarchicalStreamWriter;import com.thoughtworks.xstream.io.xml.PrettyPrintWriter;import com.thoughtworks.xstream.io.xml.XppDriver;/*** 微信事件消息处理* @author cdj* @date 8月3日 上午9:26:27*/public class MessageUtil {/*** 解析微信发来的请求(XML)* * @param request* @return* @throws Exception*/public static Map<String, String> parseXml(HttpServletRequest request) throws Exception {// 将解析结果存储在HashMap中Map<String, String> map = new HashMap<String, String>();// 读取输入流SAXReader reader = new SAXReader();// 从request中取得输入流InputStream inputStream = request.getInputStream();Document document = reader.read(inputStream);// 得到xml根元素Element root = document.getRootElement();// 得到根元素的所有子节点@SuppressWarnings("unchecked")List<Element> elementList = root.elements();// 遍历所有子节点for (Element e : elementList)map.put(e.getName(), e.getText());// 释放资源inputStream.close();inputStream = null;return map;}/*** 文本消息对象转换成xml* * @param textMessage* 文本消息对象* @return xml*/public static String textMessageToXml(TextMessage textMessage) {xstream.alias("xml", textMessage.getClass());return xstream.toXML(textMessage);}/*** 音乐消息对象转换成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块* */private static XStream xstream = new XStream(new XppDriver() {public HierarchicalStreamWriter createWriter(Writer out) {return new PrettyPrintWriter(out) {// 对所有xml节点的转换都增加CDATA标记boolean cdata = true;protected void writeText(QuickWriter writer, String text) {if (cdata) {writer.write("<![CDATA[");writer.write(text);writer.write("]]>");} else {writer.write(text);}}};}});}
SignUtil:
package com.modou.park.util.wxevent;import java.security.MessageDigest;import java.security.NoSuchAlgorithmException;/*** 请求校验工具类*/public class SignUtil {// 与公众接口配置信息中的Token要一致private static String token = "xxxxxx";public static boolean checkSignature(String signature, String timestamp, String nonce) {// 从请求中(也就是微信服务器传过来的)拿到的token, timestamp, nonceString[] arr = new String[] { token, timestamp, nonce };// 将token、timestamp、nonce三个参数进行字典序排序sort(arr);StringBuilder content = new StringBuilder();for (int i = 0; i < arr.length; i++) {content.append(arr[i]);}MessageDigest md = null;String tmpStr = null;try {md = MessageDigest.getInstance("SHA-1");// 将三个参数字符串拼接成一个字符串进行sha1加密byte[] digest = md.digest(content.toString().getBytes());// 将字节数组转成字符串tmpStr = byteToStr(digest);} catch (NoSuchAlgorithmException e) {e.printStackTrace();}content = null;// 将sha1加密后的字符串可与signature对比,标识该请求来源于微信return tmpStr != null ? tmpStr.equals(signature.toUpperCase()) : false;}// 将加密后的字节数组变成字符串private static String byteToStr(byte[] byteArray) {String strDigest = "";for (int i = 0; i < byteArray.length; i++) {strDigest += byteToHexStr(byteArray[i]);}return strDigest;}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;}// 用于字典排序public static void sort(String a[]) {for (int i = 0; i < a.length - 1; i++) {for (int j = i + 1; j < a.length; j++) {if (a[j].compareTo(a[i]) < 0) {String temp = a[i];a[i] = a[j];a[j] = temp;}}}}}
---------------------------------------------------------- 手动分割 ----------------------------------------------------------