微信公众号开发流程(一)


前言

微信公众平台是运营者通过公众号为微信用户提供资讯和服务的平台,而公众平台开发接口则是提供服务的基础,开发者在公众平台网站中创建公众号、获取接口权限后,可以通过阅读本文来帮助开发。
为了识别用户,每个用户针对每个公众号会产生一个安全的OpenID,如果需要在多公众号、移动应用之间做用户共通,则需前往微信开放平台,将这些公众号和应用绑定到一个开放平台账号下,绑定后,一个用户虽然对多个公众号和应用有多个不同的OpenID,但他对所有这些同一开放平台账号下的公众号和应用,只有一个UnionID
名词解释:

OpenID: 是一个以用户为中心的数字身份识别框架,它具有开放、分散性。OpenID 的创建基于这样一个概念:我们可以通过 URI (又叫 URL 或网站地址)来认证一个网站的唯一身份,同理,我们也可以通过这种方式来作为用户的身份认证。
UnionID:如果开发者拥有多个移动应用、网站应用、和公众帐号(包括小程序),可通过 UnionID 来区分用户的唯一性,因为只要是同一个微信开放平台帐号下的移动应用、网站应用和公众帐号(包括小程序),用户的 UnionID 是唯一的。换句话说,同一用户,对同一个微信开放平台下的不同应用,UnionID是相同的。

一、 微信公众号基本配置

1、注册并登陆微信公众号

https://mp.weixin.qq.com/

2、服务器配置

设置于开发——>基本配置——>服务器配置
在这里插入图片描述
填写配置URL 和 Token:自主设置用于验证开发者服务器。

3、 创建微信连接检验工具包:CheckUtil

提交信息后,微信服务器将发送GET请求到填写的服务器地址URL上,GET请求携带参数如下表所示:

signature	微信加密签名,signature结合了开发者填写的token参数和请求中的timestamp参数、nonce参数。
timestamp	时间戳
nonce	随机数
echostr	随机字符串

开发者通过检验signature对请求进行校验(下面有校验方式)。若确认此次GET请求来自微信服务器,请原样返回echostr参数内容,则接入生效,成为开发者成功,否则接入失败。加密/校验流程如下:

 1、将token、timestamp、nonce三个参数进行字典序排序 
 2、将三个参数字符串拼接成一个字符串进行sha1加密 
 3、开发者获得加密后的字符串可与signature对比,标识该请求来源于微信
package com.vx.Util;

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

public class CheckUtil {

        public static final String tooken = "WeChat"; //开发者自行定义Tooken

        public static boolean checkSignature(String signature,String timestamp,String nonce){
//1.定义数组存放tooken,timestamp,nonce

            String[] arr = {tooken,timestamp,nonce};

//2.对数组进行排序

            Arrays.sort(arr);

//3.生成字符串

            StringBuffer sb = new StringBuffer();

            for(String s : arr){
                sb.append(s);

            }

//4.sha1加密,网上均有现成代码

            String temp = getSha1(sb.toString());

//5.将加密后的字符串,与微信传来的加密签名比较,返回结果

            return temp.equals(signature);

        }

        public static String getSha1(String str){
            if(str==null||str.length()==0){
                return null;

            }

            char hexDigits[] = {'0','1','2','3','4','5','6','7','8','9',

                    'a','b','c','d','e','f'};

            try {
                MessageDigest mdTemp = MessageDigest.getInstance("SHA1");

                mdTemp.update(str.getBytes("UTF-8"));

                byte[] md = mdTemp.digest();

                int j = md.length;

                char buf[] = new char[j*2];

                int k = 0;

                for (int i = 0; i < j; i++) {
                    byte byte0 = md[i];

                    buf[k++] = hexDigits[byte0 >>> 4 & 0xf];

                    buf[k++] = hexDigits[byte0 & 0xf];

                }

                return new String(buf);

            } catch (Exception e) {
// TODO: handle exception

                return null;

            }

        }

    }

4、创建controller层用来接收和返回参数

package com.vx.controller;

import com.vx.Util.CheckUtil;
import org.apache.log4j.Logger;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;


@Controller
@RequestMapping("/wechat")
public class WeChatController {
    private  static  Logger logger = Logger.getLogger(WeChatController. class );
    /**
     * 用于接受get请求参数,返回验证参数
     */
    @RequestMapping (value =  "security" , method = RequestMethod.GET)
    public void doGet(HttpServletRequest request, HttpServletResponse response,
                      @RequestParam(value = "signature", required=true)String signature,
                      @RequestParam (value =  "timestamp" , required =  true ) String timestamp,
                      @RequestParam (value =  "nonce" , required =  true ) String nonce,
                      @RequestParam (value =  "echostr" , required =  true ) String echostr) {
        if(CheckUtil.checkSignature(signature, timestamp, nonce)){
            try {
                PrintWriter  out = response.getWriter();
                out.print(echostr);
                out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }

        }
        
    }

}

二、消息的接收与处理

1、接收普通消息

当普通微信用户向公众账号发消息时,微信服务器将POST消息的XML数据包到开发者填写的URL上。

文本消息

<xml>
  <ToUserName><![CDATA[toUser]]></ToUserName>
  <FromUserName><![CDATA[fromUser]]></FromUserName>
  <CreateTime>1348831860</CreateTime>
  <MsgType><![CDATA[text]]></MsgType>
  <Content><![CDATA[this is a test]]></Content>
  <MsgId>1234567890123456</MsgId>
</xml>

参数说明:

ToUserName	开发者微信号
FromUserName	发送方帐号(一个OpenID)
CreateTime	消息创建时间 (整型)	
MsgType	消息类型,文本为text
Content	文本消息内容
MsgId	消息id,64位整型

图片消息

<xml>
  <ToUserName><![CDATA[toUser]]></ToUserName>
  <FromUserName><![CDATA[fromUser]]></FromUserName>
  <CreateTime>1348831860</CreateTime>
  <MsgType><![CDATA[image]]></MsgType>
  <PicUrl><![CDATA[this is a url]]></PicUrl>
  <MediaId><![CDATA[media_id]]></MediaId>
  <MsgId>1234567890123456</MsgId>
</xml>
ToUserName	开发者微信号
FromUserName	发送方帐号(一个OpenID)
CreateTime	消息创建时间 (整型)
MsgType	消息类型,图片为image
PicUrl	图片链接(由系统生成)
MediaId	图片消息媒体id,可以调用获取临时素材接口拉取数据。
MsgId	消息id,64位整型

语音消息

<xml>
  <ToUserName><![CDATA[toUser]]></ToUserName>
  <FromUserName><![CDATA[fromUser]]></FromUserName>
  <CreateTime>1357290913</CreateTime>
  <MsgType><![CDATA[voice]]></MsgType>
  <MediaId><![CDATA[media_id]]></MediaId>
  <Format><![CDATA[Format]]></Format>
  <Recognition>< ![CDATA[腾讯微信团队] ]></Recognition>
  <MsgId>1234567890123456</MsgId>
</xml>
ToUserName	开发者微信号
FromUserName	发送方帐号(一个OpenID)
CreateTime	消息创建时间 (整型)
MsgType	语音为voice
MediaId	语音消息媒体id,可以调用获取临时素材接口拉取数据。
Format	语音格式,如amr,speex等
Recognition	语音识别结果,UTF8编码
MsgId	消息id,64位整型

视频消息

<xml>
  <ToUserName><![CDATA[toUser]]></ToUserName>
  <FromUserName><![CDATA[fromUser]]></FromUserName>
  <CreateTime>1357290913</CreateTime>
  <MsgType><![CDATA[video]]></MsgType>
  <MediaId><![CDATA[media_id]]></MediaId>
  <ThumbMediaId><![CDATA[thumb_media_id]]></ThumbMediaId>
  <MsgId>1234567890123456</MsgId>
</xml>
ToUserName	开发者微信号
FromUserName	发送方帐号(一个OpenID)
CreateTime	消息创建时间 (整型)
MsgType	视频为video
MediaId	视频消息媒体id,可以调用获取临时素材接口拉取数据。
ThumbMediaId	视频消息缩略图的媒体id,可以调用多媒体文件下载接口拉取数据。
MsgId	消息id,64位整型

小视频消息

<xml>
  <ToUserName><![CDATA[toUser]]></ToUserName>
  <FromUserName><![CDATA[fromUser]]></FromUserName>
  <CreateTime>1357290913</CreateTime>
  <MsgType><![CDATA[shortvideo]]></MsgType>
  <MediaId><![CDATA[media_id]]></MediaId>
  <ThumbMediaId><![CDATA[thumb_media_id]]></ThumbMediaId>
  <MsgId>1234567890123456</MsgId>
</xml>
ToUserName	开发者微信号
FromUserName	发送方帐号(一个OpenID)
CreateTime	消息创建时间 (整型)
MsgType	小视频为shortvideo
MediaId	视频消息媒体id,可以调用获取临时素材接口拉取数据。
ThumbMediaId	视频消息缩略图的媒体id,可以调用获取临时素材接口拉取数据。
MsgId	消息id,64位整型

地理位置消息

<xml>
  <ToUserName><![CDATA[toUser]]></ToUserName>
  <FromUserName><![CDATA[fromUser]]></FromUserName>
  <CreateTime>1351776360</CreateTime>
  <MsgType><![CDATA[location]]></MsgType>
  <Location_X>23.134521</Location_X>
  <Location_Y>113.358803</Location_Y>
  <Scale>20</Scale>
  <Label><![CDATA[位置信息]]></Label>
  <MsgId>1234567890123456</MsgId>
</xml>
ToUserName	开发者微信号
FromUserName	发送方帐号(一个OpenID)
CreateTime	消息创建时间 (整型)
MsgType	消息类型,地理位置为location
Location_X	地理位置纬度
Location_Y	地理位置经度
Scale	地图缩放大小
Label	地理位置信息
MsgId	消息id,64位整型

链接信息

<xml>
  <ToUserName><![CDATA[toUser]]></ToUserName>
  <FromUserName><![CDATA[fromUser]]></FromUserName>
  <CreateTime>1351776360</CreateTime>
  <MsgType><![CDATA[link]]></MsgType>
  <Title><![CDATA[公众平台官网链接]]></Title>
  <Description><![CDATA[公众平台官网链接]]></Description>
  <Url><![CDATA[url]]></Url>
  <MsgId>1234567890123456</MsgId>
</xml>
ToUserName	接收方微信号
FromUserName	发送方微信号,若为普通用户,则是一个	OpenID
CreateTime	消息创建时间
MsgType	消息类型,链接为link
Title	消息标题
Description	消息描述
Url	消息链接
MsgId	消息id,64位整型

接受事件推送

关注与取消关注
<xml>
  <ToUserName><![CDATA[toUser]]></ToUserName>
  <FromUserName><![CDATA[FromUser]]></FromUserName>
  <CreateTime>123456789</CreateTime>
  <MsgType><![CDATA[event]]></MsgType>
  <Event><![CDATA[subscribe]]></Event>
</xml>
ToUserName	开发者微信号
FromUserName	发送方帐号(一个OpenID)
CreateTime	消息创建时间 (整型)
MsgType	消息类型,event
Event	事件类型,subscribe(订阅)、unsubscribe(取消订阅)
自定义菜单栏事件

用户点击自定义菜单后,微信会把点击事件推送给开发者,请注意,点击菜单弹出子菜单,不会产生上报。

点击菜单拉取消息时的事件推送

在这里插入代码片<xml>
  <ToUserName><![CDATA[toUser]]></ToUserName>
  <FromUserName><![CDATA[FromUser]]></FromUserName>
  <CreateTime>123456789</CreateTime>
  <MsgType><![CDATA[event]]></MsgType>
  <Event><![CDATA[CLICK]]></Event>
  <EventKey><![CDATA[EVENTKEY]]></EventKey>
</xml>
ToUserName	开发者微信号
FromUserName	发送方帐号(一个OpenID)
CreateTime	消息创建时间 (整型)
MsgType	消息类型,event
Event	事件类型,CLICK
EventKey	事件KEY值,与自定义菜单接口中KEY值对应

点击菜单跳转链接时的事件推送

<xml>
  <ToUserName><![CDATA[toUser]]></ToUserName>
  <FromUserName><![CDATA[FromUser]]></FromUserName>
  <CreateTime>123456789</CreateTime>
  <MsgType><![CDATA[event]]></MsgType>
  <Event><![CDATA[VIEW]]></Event>
  <EventKey><![CDATA[www.qq.com]]></EventKey>
</xml>
ToUserName	开发者微信号
FromUserName	发送方帐号(一个OpenID)
CreateTime	消息创建时间 (整型)
MsgType	消息类型,event
Event	事件类型,VIEW
EventKey	事件KEY值,设置的跳转URL

2、消息接受处理

XML格式处理

微信的消息体是采用xml格式,那么我在这里写了一个MessageUtil去做消息格式的处理,大致代码如下:

package com.vx.Util;

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;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import javax.servlet.http.HttpServletRequest;
import java.io.InputStream;
import java.io.Writer;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

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" ;

    /**
     * 事件类型: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" ;

    /**
     * 解析微信发来的请求(XML)
     */
    @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;
    }

    @SuppressWarnings ( "unused" )
    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);
                    }
                }
            };
        }
    });

}

消息接收

WechatController下添加post方法用于做消息的接收和处理

@RequestMapping (value =  "security" , method = RequestMethod.POST)
    // post方法用于接收微信服务端消息
    public  void  DoPost(HttpServletRequest request,HttpServletResponse response) {
        try {
            Map<String, String> map = MessageUtil.parseXml(request);
        } catch (Exception e) {
            logger.error(e,e);
        }
    }

消息处理

建立消息实体

建立消息实体以方便我们后面的使用

消息实体基础类
@Data
public class BaseMessage {
    // 开发者微信号
    private  String ToUserName;
    // 发送方帐号(一个OpenID)
    private  String FromUserName;
    // 消息创建时间 (整型)
    private  long  CreateTime;
    // 消息类型(text/image/location/link/video/shortvideo)
    private  String MsgType;
    // 消息id,64位整型
    private  long  MsgId;
}
文本消息
public class TextMessage extends BaseMessage {
    //文本内容
    private String Content;
}
链接消息
@Data
public class LinkMessage extends BaseMessage{
    // 消息标题
    private  String Title;
    // 消息描述
    private  String Description;
    // 消息链接
    private  String Url;
}
视频消息
@Data
public class VideoMessage {
    private  String MediaId;  // 视频消息媒体id,可以调用多媒体文件下载接口拉取数据
    private  String ThumbMediaId;  // 视频消息缩略图的媒体id,可以调用多媒体文件下载接口拉取数据
}

其他消息

其他消息类似根据前面不同消息不同内容进行添加属性处理

消息分类处理

按照上面收到想消息类别分别做不同的分发处理,这里我们建立了自己的业务分发器(EventDispatcher、MsgDispatcher),分别做普通消息处理和事件消息处理
EventDispatcher

public class EventDispatcher {
    public  static  String processEvent(Map<String, String> map) {
        if  (map.get( "Event" ).equals(MessageUtil.EVENT_TYPE_SUBSCRIBE)) {  //关注事件
            System.out.println( "==============这是关注事件!" );
        }

        if  (map.get( "Event" ).equals(MessageUtil.EVENT_TYPE_UNSUBSCRIBE)) {  //取消关注事件
            System.out.println( "==============这是取消关注事件!" );
        }

        if  (map.get( "Event" ).equals(MessageUtil.EVENT_TYPE_CLICK)) {  //自定义菜单点击事件
            System.out.println( "==============这是自定义菜单点击事件!" );
        }

        return  "事件消息" ;
    }
}

MsgDispatcher

public class MsgDispatcher {
    public  static String processMessage(Map<String,String> map){
        if (map.get("MsgType").equals(MessageUtil.REQ_MESSAGE_TYPE_TEXT)){
            System.out.println("==============这是文本消息");
        }
        if  (map.get( "MsgType" ).equals(MessageUtil.REQ_MESSAGE_TYPE_IMAGE)) {  // 图片消息
            System.out.println( "==============这是图片消息!" );
        }


        if  (map.get( "MsgType" ).equals(MessageUtil.REQ_MESSAGE_TYPE_LINK)) {  // 链接消息
            System.out.println( "==============这是链接消息!" );
        }


        if  (map.get( "MsgType" ).equals(MessageUtil.REQ_MESSAGE_TYPE_LOCATION)) {  // 位置消息
            System.out.println( "==============这是位置消息!" );
        }


        if  (map.get( "MsgType" ).equals(MessageUtil.REQ_MESSAGE_TYPE_VIDEO)) {  // 视频消息
            System.out.println( "==============这是视频消息!" );
        }


        if  (map.get( "MsgType" ).equals(MessageUtil.REQ_MESSAGE_TYPE_VOICE)) {  // 语音消息
            System.out.println( "==============这是语音消息!" );
        }

        return "普通事件";
    }
}

这个时候我们需要把我们的消息入口【WechatController.java】中的post方法做些修改

public  void  DoPost(HttpServletRequest request,HttpServletResponse response) {
        try {
            Map<String, String> map = MessageUtil.parseXml(request);
            String msgType = map.get("MsgType");
            if (MessageUtil.REQ_MESSAGE_TYPE_EVENT.equals(msgType)){
                EventDispatcher.processEvent(map);  //进入事件处理
            } else {
                MsgDispatcher.processMessage(map);  //进入消息处理
            }
        } catch (Exception e) {
            logger.error(e,e);
        }
    }
消息回复

这里我们要讲述的是我们在给用户回复的消息类型,在这里也可以大致分为两类:文本消息和图文消息

回复消息基类
@Data
public class BaseMessage {
    // 接收方帐号(收到的OpenID)
    private  String ToUserName;
    // 开发者微信号
    private  String FromUserName;
    // 消息创建时间 (整型)
    private  long  CreateTime;
    // 消息类型(text/music/news)
    private  String MsgType;
}
文本消息类
@Data
public class TextMessage extends BaseMessage{
    // 回复的消息内容
    private  String Content;
}

图片消息类
@Data
public class ImageMessage extends BaseMessage {

    // 通过素材管理中的接口上传多媒体文件,得到的id。
    private  String MediaId;
}
消息回复格式转换

MessageUtil 类中添加消息转换方法

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

    /**
     * @Description: 图文消息对象转换成xml
     */
    public  static  String newsMessageToXml(ImageMessage imageMessage) {
        xstream.alias( "xml" , imageMessage.getClass());
        return  xstream.toXML(imageMessage);
    }
消息内容填充

dispatcher消息中根据自己需求进行内容填充

   String openid=map.get( "FromUserName" );  //用户openid
            String mpid=map.get( "ToUserName" );    //公众号原始ID

           if(map.get("Content").equals("文本")){
           TextMessage textMessage = new TextMessage();//文本回复类
           textMessage.setFromUserName(openid);
           textMessage.setToUserName(mpid);
           textMessage.setCreateTime(new Date().getTime());
           textMessage.setMsgType(MessageUtil.RESP_MESSAGE_TYPE_TEXT);
            textMessage.setContent("这是一个文本消息回复");
            return MessageUtil.textMessageToXml(textMessage);
        }
        if(map.get("Content").equals("图片")){
           ImageMessage imageMessage  =  new ImageMessage();//图片回复类
                imageMessage.setFromUserName(openid);
                imageMessage.setToUserName(mpid);
                imageMessage.setCreateTime(new Date().getTime());
                imageMessage.setMsgType(MessageUtil.REQ_MESSAGE_TYPE_IMAGE);
               //通过素材管理中的接口上传多媒体文件,得到的id。
                imageMessage.setMediaId("http://assets.processon.com/chart_image/616f8c10e401fd16dd9090ea.png");
           return MessageUtil.newsMessageToXml(imageMessage);
           }
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值