微信公众号除了后台编辑模式,还提供了高级开发模式,容许开发者接入本身的服务器,代替微信服务器与用户通信。java
那么,开发的第一步,就是配置。git
一.基本设置数组
如图:服务器
URL:服务器接入地址,也就是你的程序入口。微信
token:签名验证用,至关于私有秘钥,要与程序中的token保持一致app
二.程序接入框架
接入部分代码由两部分组成:dom
get方法:是负责验证签名,确保消息是否来源于微信 工具
post方法:请求消息,响应消息post
package com.ever.qthh.servlet; import java.io.IOException; import java.io.PrintWriter; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.ever.qthh.service.GatewayService; import com.ever.qthh.utils.wx.SignUtil; /** * 网关控制层 * @author 51452 * */ @WebServlet("/gateway") public class GatewayServlet extends HttpServlet { private static final long serialVersionUID = 1L; /** * 验证信息是否来自微信 */ protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String signature = request.getParameter("signature");// 微信加密签名 String timestamp = request.getParameter("timestamp");// 时间戳 String nonce = request.getParameter("nonce");// 随机数 String echostr = request.getParameter("echostr");// 随机字符串 PrintWriter out = response.getWriter(); //经过检验signature对请求进行校验,若校验成功则原样返回echostr,表示接入成功,不然接入失败 if (SignUtil.checkSignature(signature, timestamp, nonce)) { out.print(echostr); } out.close(); out = null; } /** * 处理微信服务器发来的消息 */ protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("UTF-8"); response.setCharacterEncoding("UTF-8"); // 调用核心业务类接收消息、处理消息 String respMessage = GatewayService.processRequest(request); // 响应消息 PrintWriter out = response.getWriter(); out.print(respMessage); out.close(); } }
签名验证时咱们须要sign工具,代码以下
package com.ever.qthh.utils.wx; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Arrays; public class SignUtil { private static String token = "qthh"; /** * 验证签名 * * @param signature * @param timestamp * @param nonce * @return */ public static boolean checkSignature(String signature, String timestamp, String nonce) { String[] arr = new String[] { token, timestamp, nonce }; // 将token、timestamp、nonce三个参数进行字典序排序 Arrays.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; } /** * 将字节数组转换为十六进制字符串 * * @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; } }
三.消息工具封装、消息处理
微信收、发消息都用的xml格式进行传输的,为了在程序中方便操做,咱们将须要处理的消息封装成对象,包括请求(微信发个咱们的)和响应(咱们发给微信的)两部分:
1.请求部分:
package com.ever.qthh.model.message.request; /** * 请求消息基类 * @author 51452 * */ public class BaseMessage { private String ToUserName;//开发者微信号 private String FromUserName;//发送方账号(一个OpenID private long CreateTime;//消息建立时间 (整型 private String MsgType; //消息类型(text/image/location/link private long MsgId;// 消息id,64位整型 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; } }
package com.ever.qthh.model.message.request; /** * 文本消息 * @author 51452 * */ public class TextMessage extends BaseMessage { private String Content;//消息内容 public String getContent() { return Content; } public void setContent(String content) { Content = content; } }
package com.ever.qthh.model.message.request; /** * 图片消息 * @author 51452 * */ public class ImageMessage extends BaseMessage { private String PicUrl;//图片连接 public String getPicUrl() { return PicUrl; } public void setPicUrl(String picUrl) { PicUrl = picUrl; } }
package com.ever.qthh.model.message.request; /** * 连接消息 * @author 51452 * */ 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; } }
package com.ever.qthh.model.message.request; /** * 语音消息 * @author 51452 * */ public class VoiceMessage extends BaseMessage { private String MediaId;//媒体ID 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; } }
package com.ever.qthh.model.message.request; /** * 地理位置消息 * @author 51452 * */ 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; } }
2.响应部分
package com.ever.qthh.model.message.response; /** * 响应消息基类 * @author 51452 * */ public class BaseMessage { private String ToUserName;//接收方账号(收到的OpenID) private String FromUserName;//开发者微信号 private long CreateTime;//消息建立时间 (整型) private String MsgType;//消息类型(text/music/news) private int FuncFlag;//位0x0001被标志时,星标刚收到的消息 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; } }
package com.ever.qthh.model.message.response; /** * 响应消息基类 * @author 51452 * */ public class BaseMessage { private String ToUserName;//接收方账号(收到的OpenID) private String FromUserName;//开发者微信号 private long CreateTime;//消息建立时间 (整型) private String MsgType;//消息类型(text/music/news) private int FuncFlag;//位0x0001被标志时,星标刚收到的消息 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; } }
package com.ever.qthh.model.message.response; /** * 响应消息基类 * @author 51452 * */ public class BaseMessage { private String ToUserName;//接收方账号(收到的OpenID) private String FromUserName;//开发者微信号 private long CreateTime;//消息建立时间 (整型) private String MsgType;//消息类型(text/music/news) private int FuncFlag;//位0x0001被标志时,星标刚收到的消息 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; } }
package com.ever.qthh.model.message.response; /** * 图文 * @author 51452 * */ public class Article { private String Title;//图文消息名称 private String Description;//图文消息描述 private String PicUrl;//图片连接,支持JPG、PNG格式,较好的效果为大图640*320,小图80*80,限制图片连接的域名须要与开发者填写的基本资料中的Url一致 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 getPicUrl() { return PicUrl; } public void setPicUrl(String picUrl) { PicUrl = picUrl; } public String getUrl() { return Url; } public void setUrl(String url) { Url = url; } }
package com.ever.qthh.model.message.response; /** * 音乐消息 * @author 51452 * */ public class MusicMessage extends BaseMessage { private Music Music;//音乐 public Music getMusic() { return Music; } public void setMusic(Music music) { Music = music; } }
package com.ever.qthh.model.message.response; /** * 音乐 * @author 51452 * */ public class Music { private String Title;//音乐名称 private String Description;//音乐描述 private String MusicUrl;//音乐连接 private String HQMusicUrl;//高质量音乐连接,WIFI环境优先使用该连接播放音乐 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 hQMusicUrl) { HQMusicUrl = hQMusicUrl; } }
经常使用的消息对象咱们封装好了,接下来咱们须要使用dom4j和xstream这个两个工具包对数据进行经常使用的处理:
解析xml,将xml转成消息对象,将消息对象转xml,代码以下:
package com.ever.qthh.utils.wx; 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.ever.qthh.model.message.response.Article; import com.ever.qthh.model.message.response.MusicMessage; import com.ever.qthh.model.message.response.NewsMessage; import com.ever.qthh.model.message.response.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 51452 * */ public class MessageUtil { public static final String RESP_MESSAGE_TYPE_TEXT = "text";//返回消息类型:文本 public static final String RESP_MESSAGE_TYPE_MUSIC = "music";//返回消息类型:音乐 public static final String RESP_MESSAGE_TYPE_NEWS = "news";//返回消息类型:图文 public static final String REQ_MESSAGE_TYPE_TEXT = "text";//请求消息类型:文本 public static final String REQ_MESSAGE_TYPE_IMAGE = "image";//请求消息类型:图片 public static final String REQ_MESSAGE_TYPE_LINK = "link"; //请求消息类型:连接 public static final String REQ_MESSAGE_TYPE_LOCATION = "location";//请求消息类型:地理位置 public static final String REQ_MESSAGE_TYPE_VOICE = "voice";//请求消息类型:音频 public static final String REQ_MESSAGE_TYPE_EVENT = "event";//请求消息类型:推送 public static final String EVENT_TYPE_SUBSCRIBE = "subscribe";//事件类型:subscribe(订阅) public static final String EVENT_TYPE_UNSUBSCRIBE = "unsubscribe";//事件类型:unsubscribe(取消订阅) public static final String EVENT_TYPE_CLICK = "CLICK";//事件类型:CLICK(自定义菜单点击事件) /** * 解析微信发来的请求(XML) * * @param request * @return * @throws Exception */ @SuppressWarnings("unchecked") 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; } /** * 文本消息对象转换成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块 * * @date 2013-05-19 */ private static XStream xstream = new XStream(new XppDriver() { public HierarchicalStreamWriter createWriter(Writer out) { return new PrettyPrintWriter(out) { // 对全部xml节点的转换都增长CDATA标记 boolean cdata = true; @SuppressWarnings("rawtypes") 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); } } }; } }); }
下面就是GatewayService类的编写
package com.ever.qthh.service; import java.util.Date; import java.util.Map; import javax.servlet.http.HttpServletRequest; import com.ever.qthh.model.message.response.TextMessage; import com.ever.qthh.utils.wx.MessageUtil; /** * 网关业务层 * @author 51452 * */ public class GatewayService { /** * 处理微信发来的请求 * * @param request * @return */ public static String processRequest(HttpServletRequest request) { String respMessage = null; try { // 默认返回的文本消息内容 String respContent = "请求处理异常,请稍候尝试!"; // xml请求解析 Map<String, String> requestMap = MessageUtil.parseXml(request); // 发送方账号(open_id) String fromUserName = requestMap.get("FromUserName"); // 公众账号 String toUserName = requestMap.get("ToUserName"); // 消息类型 String msgType = requestMap.get("MsgType"); // 回复文本消息 TextMessage textMessage = new TextMessage(); textMessage.setToUserName(fromUserName); textMessage.setFromUserName(toUserName); textMessage.setCreateTime(new Date().getTime()); textMessage.setMsgType(MessageUtil.RESP_MESSAGE_TYPE_TEXT); textMessage.setFuncFlag(0); // 文本消息 if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_TEXT)) { respContent = "您发送的是文本消息!"; } // 图片消息 else if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_IMAGE)) { respContent = "您发送的是图片消息!"; } // 地理位置消息 else if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_LOCATION)) { respContent = "您发送的是地理位置消息!"; } // 连接消息 else if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_LINK)) { respContent = "您发送的是连接消息!"; } // 音频消息 else if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_VOICE)) { respContent = "您发送的是音频消息!"; } // 事件推送 else if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_EVENT)) { // 事件类型 String eventType = requestMap.get("Event"); // 订阅 if (eventType.equals(MessageUtil.EVENT_TYPE_SUBSCRIBE)) { respContent = "谢谢您的关注!"; } // 取消订阅 else if (eventType.equals(MessageUtil.EVENT_TYPE_UNSUBSCRIBE)) { // 取消订阅后用户再收不到公众号发送的消息,所以不须要回复消息 } // 自定义菜单点击事件 else if (eventType.equals(MessageUtil.EVENT_TYPE_CLICK)) { //事件KEY值,与建立自定义菜单时指定的KEY值对应 String eventKey = requestMap.get("EventKey"); respContent = "点击菜单:"+eventKey; } } textMessage.setContent(respContent); respMessage = MessageUtil.textMessageToXml(textMessage); } catch (Exception e) { e.printStackTrace(); } return respMessage; } }
以上,微信公众号开发的基本框架就完成了。
四。其余
应用完成后,须要一个服务器去部署。
若是你没用本身的服务器,可使用百度的BAE或者新浪的SAE。