微信公告号实现原理简单介绍;

来源:互联网 发布:淘宝详情页模板源代码 编辑:程序博客网 时间:2024/06/07 23:43
前段时间无聊玩了玩微信公告号的实现,现在简单介绍一下微信公告号的实现原理;开发者模式:开发者模式其实就是,使用自己的服务器,你可以选择任何一种后台web开发语言,我以java web的实现;数据传递:手机app微信客服端发送数据,数据先到微信服务器,然后微信服务器直

下面的json解析以JSONObject.fromObject(即JSONObject和JSONArray)的方式解析,最简单的方式,为简单案列使用的,对于复制项目,不推荐使用这种方式,建议使用gson包或者fastJSON包或者alibaba包,原理请自行查看

appID 和 appsecret;是在微信公告号官网可以查看到的,。我们利用他获取凭证,每获取的凭证有效期只能是2小时;
public final static String appID = “××××××××”;
public final static String appsecret = “××××××××”;
下面是获取平常或解析后的结果;

/**     * 获取接口访问的凭证     * @param appid     * @param appsecret     * @return Token的对象数据     */    public static Token getToken(String appid,String appsecret){        Token token = null;        String  requestUrl = token_url.replace("APPID", appid).replace(                "APPSECRET",appsecret);        JSONObject jsonObject = httpRequest(requestUrl, "GET", null);         if(jsonObject != null){             try {                token = new Token();                token.setAccessToken(jsonObject.getString("access_token"));                token.setExpiresIn(jsonObject.getInt("expires_in"));            } catch (Exception e) {                token = null;                logger.error("获取 token 失败,errcode : {} errmsg {}",                        jsonObject.getInt("errcode"),jsonObject.getString("errmsg"));            }         }        return token;    }

对请求的封装:

    /**     * 发送 https 请求     * @param requestUrl  访问的url     * @param requestMethod GET/POST     * @param outputStr 是否有输出的数据流     * @return JSON对象。服务器返回的数据,json对象     */    public static JSONObject httpRequest(String requestUrl,String requestMethod,            String outputStr){        JSONObject jsonObject = null;        try {            //创建SSL对象            TrustManager[] tm = {new MyX509TrustManager()};            SSLContext sslContext = SSLContext.getInstance("SSL","SunJSSE");            sslContext.init(null,  tm, new SecureRandom());            SSLSocketFactory ssf = sslContext.getSocketFactory();            URL url = new URL(requestUrl);            HttpsURLConnection conn =(HttpsURLConnection) url.openConnection();            conn.setSSLSocketFactory(ssf);            conn.setDoOutput(true);            conn.setDoInput(true);            conn.setUseCaches(false);            //设置请求方式(get post)            conn.setRequestMethod(requestMethod);            //当 outputStr 不为null是 。想输出流写数据            if(null != outputStr){                OutputStream outputStream = conn.getOutputStream();                outputStream.write(outputStr.getBytes("UTF-8"));                outputStream.close();            }            //从输入流中读取返回的数据            InputStream inputStream = conn.getInputStream();            InputStreamReader inputStreamReader = new InputStreamReader(inputStream);            BufferedReader bufferedReader = new BufferedReader(inputStreamReader);            String str = "";            StringBuffer buffer = new StringBuffer();            while ((str = bufferedReader.readLine())!= null) {                buffer.append(str);            }            //释放资源            bufferedReader.close();            inputStreamReader.close();            inputStream.close();            inputStream = null;            conn.disconnect();            jsonObject = JSONObject.fromObject(buffer.toString());        } catch(ConnectException ceException){            logger.error("链接超过时间:{}",ceException);        }catch (Exception e) {            logger.error("https 异常 :{}",e);        }        return jsonObject;    }

有了凭证,就可以修改微信公告号的界面效果等很多功能了。比如修改界面;

/**     * 创建 菜单     * @param menu 菜单对象     * @param accessToken 凭证     * @return 返回是否 成功或者失败     */    public static boolean createMenu(Menu menu, String accessToken) {        boolean result = false;        String url = menu_create_url.replace("ACCESS_TOKEN", accessToken);        // 菜单 对象 转化 成 json字符串        String jsonMenu = JSONObject.fromObject(menu).toString();        System.out.println(jsonMenu);        // 发送post请求        JSONObject jsonObject = CommonUtil.httpRequest(url, "POST", jsonMenu);        if (jsonObject != null) {            int errorCode = jsonObject.getInt("errcode");            String errorMsg = jsonObject.getString("errmsg");            if (0 == errorCode) {                result = true;            } else {                result = false;                logger.error("创建菜单失败 errorCode : {} errmsg : {}", errorCode,                        errorMsg);            }        }        return result;    }
/**     * 查询 菜单     * @param accessToken     * @return     */    public static String getMenu(String accessToken){        String result = null;        String requestUrl = menu_get_url.replace("ACCESS_TOKEN", accessToken);        //发起get请求        JSONObject jsonObject = CommonUtil.httpRequest(requestUrl, "GET", null);        if(null != jsonObject){            result = jsonObject.toString();        }        return result;    }
/**     * 删除 菜单 功能     * @param accessToken     * @return     */    public static boolean deleteMenu(String accessToken){        boolean result = false;        String requestUrl = menu_delete_url.replace("ACCESS_TOKEN",accessToken);        //         JSONObject jsonObject = CommonUtil.httpRequest(requestUrl, "GET",null);        if(jsonObject != null){            int errorCode = jsonObject.getInt("errcode");            String errorMsg = jsonObject.getString("errmsg");            if(errorCode == 0){                result = true;            }else{                result = false;            }        }               return result;    }
// 创建菜单(post)    public final static String menu_create_url = ""            + "https://api.weixin.qq.com/cgi-bin/menu/create?access_token"            + "=ACCESS_TOKEN";    // 菜单查询(get)    public final static String menu_get_url = "https://api.weixin.qq.com/cgi-bin/menu/get?access_token="            + "ACCESS_TOKEN";    // 菜单删除(GET)    public final static String menu_delete_url = "https://api.weixin.qq.com/cgi-bin/menu/delete?access_token"            + "=ACCESS_TOKEN";


下面说名一下 微信客服端访问的
创建自己的服务器,要在 微信公告号 处添加
URL(服务器地址);
Token(令牌);和其他的;
url地址写服务器的访问地址;
Token可以任意写,但是要和服务器代码里面的一样,因为微信的访问是安全加密的,通过一些列的加密后配对成功才可以发送接受消息;

 这是在代码里面写的; // 与接口 配置 信息的中 的 Token 要一致private static String token = "AlphabetMan";

微信验证签名主要就是吧token和timetamp和nonce进行字典排序,然后进行sha1加密算法在转发成String;
网上找的实现原理:

/**     * 验证签名      * @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 (Exception e) {            e.printStackTrace();        }        content = null;         return tmpStr != null ? tmpStr.equals(signature.toUpperCase()) : false;    }
/**     * 字节数组 将 字节数组 转化为 16 进制字符串     *      * @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;    }    /**     * 字节 将字节转化为 16 进制字符串     *      * @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;    }

在微信公告号逛网添加url的时候,会进行验证;微信官方规定以get方式进行访问;如下

/**     * 请求校验 (确定请求来自微信服务器)     */    @Override    protected void doGet(HttpServletRequest req, HttpServletResponse resp)            throws ServletException, IOException {         //微信加密签名        String signature = req.getParameter("signature");        String timestamp = req.getParameter("timestamp");        String nonce = req.getParameter("nonce");        String echostr = req.getParameter("echostr");        System.out.println("get");        PrintWriter out = resp.getWriter();        if(SignUtil.checkSignature(signature,timestamp,nonce)){            //调用核心服务类接收处理请求            out.print(echostr);        }        out.close();        out = null;    }

微信服务器和微信手机app之间的通信,以及和自家服务器的通信规定是以xml文件的格式进行的传输;所有,要无时无刻不对xml文件解析;

官方文档:

文本消息
<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>

对xml文件进行解析,方法很多,大约流行的有4中,自行了解,

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 element : elementList) {            map.put(element.getName(), element.getText());        }        inputStream.close();        inputStream = null;        return map;    }

以及要有生成xml的函数,但是xml文件是(cdata)

    /**     * 扩展 xsstream 使其支持 cdata     */    private static XStream xstream = new XStream(new XppDriver(){        @Override        public HierarchicalStreamWriter createWriter(Writer out) {            // TODO Auto-generated method stub            return new PrettyPrintWriter(out){                boolean cdata = true;                @Override                public void startNode(String name, Class clazz) {                    // TODO Auto-generated method stub                    super.startNode(name, clazz);                }                @Override                protected void writeText(QuickWriter writer, String text) {                    if (cdata) {                        writer.write("<![CDATA[");                        writer.write(text);                        writer.write("]]>");//                      System.out.println(" cdate = true");                    } else {                        writer.write(text);//                      System.out.println(" cdate = false");                    }                }            };        }    });/**     * 文本消息对象,转化 XML     */    public static String messageToXml(TextMessage textMessage){        xstream.alias("xml", textMessage.getClass());        return  xstream.toXML(textMessage);    }    /**     * 图像,消息对象 转化成 xml     */    public static String messageToXml(ImageMessage imageMessage){        xstream.alias("xml", imageMessage.getClass());        return xstream.toXML(imageMessage);    }    /**     * 语音消息 --》 xml     */    public static String messageToXml(VoiceMessage voiceMessage){        xstream.alias("xml", voiceMessage.getClass());        return xstream.toXML(voiceMessage);    }    /**     * 视频     */    public static String messageToXml(VideoMessage videoMessage){        xstream.alias("xml", videoMessage.getClass());        return xstream.toXML(videoMessage);    }

当然上面的生成对应的要有对应的deam对象;

下面是最核心的类

public class CoreService {    /**     * 核心服务。处理 数据,并且换回数据。     * @param request     * @return     */    public static String processRequest(HttpServletRequest request) {        // xml格式消息数据        String respXml = null;        // 默认返回文本消息内容        String respContent = "未知数据类型";        try {            // 调用 parseXml 方法解析请求消息            Map<String, String> requestMap = MessageUtil.parseXml(request);            // 发送 账号            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);            /**             * 信息类型             */            if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_TEXT)) {                //TODO 乱码   。。。                respContent = "你发送的是 文本 消息\n";                respContent += "帅的回复是: " ;//              if(requestMap.get("Content").equals("n")){//                  System.out.println("xinagtong");//              }else {//                  System.out.println("不想同");//              }                if(requestMap.get("Content").equals("n")){                    Article article = new Article();                    article.setTitle("开源中国");                    article.setDescription("开源中国社区成立于2008.8.8是目前最大的开源社区,"                            + "\n\n 开源中国的dsfadfadsfadsfasdf多少发多发多少发多发的法规fgsfgaf"                            + "dafsafad啊的发的发的嘎达"                            + "dfadadsfadfa。\n\n"                            + "dsjfkajdkfjad;f空间的发来快点就是拉福建阿斯顿;了大家撒裂缝空间"                            + "打开来房间里的。");                    article.setPicUrl("");                    article.setUrl("http://m.oschina.net");                    List<Article> articleList = new ArrayList<Article>();                    articleList.add(article);                    //创建图文消息                    NewsMessage newsMessage = new NewsMessage();                    newsMessage.setToUserName(fromUserName);                    newsMessage.setFromUserName(toUserName);                    newsMessage.setCreateTime(new Date().getTime());;                    newsMessage.setMsgType(MessageUtil.RESP_MESSAGE_TYPE_NEWS);                    newsMessage.setArticleCount(articleList.size());                    newsMessage.setArticles(articleList);                    respXml = MessageUtil.messageToXml(newsMessage);                    return respXml;                }else{                    //TODO 存在的问题是,有空个的时候,会出现无法返回数据,                    //提示,该公共号暂时无法提供服务,请稍后再试。                    String title = URLDecoder.decode(requestMap.get("Content"), "utf-8");                    title = title.replaceAll(" ", "");                    respContent  += new TulingController().getTulingRe(title);                }//              respContent = URLEncoder.encode(respContent, "UTF-8");//              respContent = "luan ma ??";            } else if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_IMAGE)) {                respContent = "你发送的是 图片 消息\n";                respContent += "n : 消息推送\n"                                        + "k : 没啥\n";            } else if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_VOICE)) {                respContent = "你发送的是 语音 消息";            } else if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_VIDEO)) {                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_EVENT)) {                      //事件推送                //事件类型                String eventType = requestMap.get("Event");                //关注                if (eventType.equals(MessageUtil.EVENT_MESSAGE_TYPE_SUBSCRIBE)) {                    respContent = "hello ,欢迎关注公告号,我们致力打招最好的东西给你,"                            + "从现在开始,以修复部公共 20160331 !!!" ;                    textMessage.setContent(respContent);                    respXml = MessageUtil.messageToXml(textMessage);                    return respXml;                    //将消息对象转化成xml                    //respXml = MessageUtil.messageToXml(textMessage);                }else if (eventType.equals(MessageUtil.EVENT_MESSAGE_TYPE_UNSUBSCRIBE)) {                     //TODO 取消订阅后,用户不会在收到公共账号发送的消息,因此不需要回复                }else if (eventType.equals(MessageUtil.EVENT_MESSAGE_TYPE_SCAN) ){                     //TODO 处理二维码扫描事件;                    respContent = "二维码扫";                }else if (eventType.equals(MessageUtil.EVENT_MESSAGE_TYPE_LOCATION)) {                     //TODO 处理上报的地理位置事件                    respContent = "地理位置事件";                }else if (eventType.equals(MessageUtil.EVENT_MESSAGE_TYPE_CLICK)) {                     //TODO 处理菜单单击事件                    //事件key值,与创建菜单的key值对应                    String eventKey = requestMap.get("EventKey");                    if(eventKey.equals("oschina")){                        Article article = new Article();                        article.setTitle("开源中国");                        article.setDescription("开源中国社区成立于2008.8.8是目前最大的开源社区,"                                + "\n\n 开源中国的dsfadfadsfadsfasdf多少发多发多少发多发的法规fgsfgaf"                                + "dafsafad啊的发的发的嘎达"                                + "dfadadsfadfa。\n\n"                                + "dsjfkajdkfjad;f空间的发来快点就是拉福建阿斯顿;了大家撒裂缝空间"                                + "打开来房间里的。");                        article.setPicUrl("");                        article.setUrl("http://m.oschina.net");                        Article article2 = new Article();                        article2.setTitle("开源中国");                        article2.setDescription("dkfajldjfjgj;ajdfljd 安静就放假啊的积分卡倒计时疯狂"                                + "的奶粉克拉克;了"                                + "的刷卡的激发4"                                + "报告发掘地根据"                                + "建安费; "                                + "的深刻了激发的经费拉附近路东方了看见");                        article2.setPicUrl("");                        article2.setUrl("");                        List<Article> articleList = new ArrayList<Article>();                        articleList.add(article);                        articleList.add(article2);                        //创建图文消息                        NewsMessage newsMessage = new NewsMessage();                        newsMessage.setToUserName(fromUserName);                        newsMessage.setFromUserName(toUserName);                        newsMessage.setCreateTime(new Date().getTime());;                        newsMessage.setMsgType(MessageUtil.RESP_MESSAGE_TYPE_NEWS);                        newsMessage.setArticleCount(articleList.size());                        newsMessage.setArticles(articleList);                        respXml = MessageUtil.messageToXml(newsMessage);                        return respXml;                    }else if (eventKey.equals("iteye")) {                        respContent = "iteye  事件";                        textMessage.setContent("Iteye 创办于 2003,9.javaEye,从最初的讨论java技术为主的技术"                                + "论坛,已朱静发展成为涵盖整个软件开发领域的综合性网站、\n\n"                                + "http://www.iteye.com");                        respXml = MessageUtil.messageToXml(textMessage);                        return respXml;                    }//                  else {//                      respContent = "else limian ";//                      //设置文本消息 内容////                        textMessage.setContent("sdsdasda");////                        //将文本消息转化成xml////                        respXml = MessageUtil.messageToXml(textMessage);//                  }//                  respContent = "ccccc";                }            }//          //设置文本消息 内容            textMessage.setContent(respContent);            //将文本消息转化成xml            respXml = MessageUtil.messageToXml(textMessage);        } catch (Exception e) {            e.printStackTrace();        }        return respXml;    }}

如果对数据进行处理和封装的入口就是这个类。
通过if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_TEXT)) 判断发送来的消息是那种格式的,获取后,在放回给用户即可,

比如,以 加载一个机器人案列,我们使用图灵机器人的接口;

在判断完是字符串的信息后,访问图灵的接口;

String title = URLDecoder.decode(requestMap.get("Content"), "utf-8");                    title = title.replaceAll(" ", "");                    respContent  += new TulingController().getTulingRe(title);                    //respContent是最后封装返回的数据;
public String getTulingRe(String info){        //调用图灵机器人接口api,获取结果                //http://www.tuling123.com/openapi/api   key:42bca29888818ceea7a214eaadbeb9e7//              String url = "http://www.tuling123.com/openapi/api?key=需要去图灵官网注册获取&info="+info;         String url = "http://www.tuling123.com/openapi/api?key=42bca29××××××××××××××××××××××××&info="+info;        String tlResult = HttpGetRequest.get(url);        //瑙f瀽鍥剧伒缁撴灉鏁版嵁锛屾彁鍙栨墍闇?唴瀹?        JSONObject json = JSONObject.fromObject(tlResult);        tlResult = json.getString("text");        return tlResult;    }public static String get(String url){        try{            HttpGet request = new HttpGet(url);            //执行http get请求            HttpResponse response = HttpClients.createDefault().execute(request);            //根据返回码判断返回是否成功            String result = "";            if(response.getStatusLine().getStatusCode() == 200){                result = EntityUtils.toString(response.getEntity());            }            return result;        }catch(Exception e){            e.printStackTrace();            return "";        }    }

ok

1 0