微信公众平台开发

微信公众号除了后台编辑模式,还提供了高级开发模式,允许开发者接入自己的服务器,代替微信服务器与用户通讯。

那么,开发的第一步,就是配置。

 

一.基本设置

    如图:

   

    URL:服务器接入地址,也就是你的程序入口。

    token:签名验证用,相当于私有秘钥,要与程序中的token保持一致

  

二.程序接入

      接入部分代码由两部分组成:

      get方法:是负责验证签名,确保消息是否来源于微信  

      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。

 

posted @ 2017-05-16 17:07  晴天很嗨  阅读(251)  评论(0编辑  收藏  举报