微信公告号实现原理简单介绍;
来源:互联网 发布:淘宝详情页模板源代码 编辑:程序博客网 时间: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
- 微信公告号实现原理简单介绍;
- CString实现原理简单介绍
- CString实现原理简单介绍
- dubbo实现原理简单介绍
- 微信公众号开发实现原理
- 网站公告==>>微信订阅号正式开通
- 微信红包实现原理
- 微信红包实现原理
- 微信红包实现原理
- 简单介绍一下微信公众号的开发流程
- [转]CString实现原理简单介绍
- 微信自研生产级 Paxos 类库 PhxPaxos 实现原理介绍
- 微信支付简单实现
- 微信WeixinJSBridge API的简单介绍
- 微信公众平台接口简单介绍
- 对微信的简单介绍
- 微信平台——微信公众号简单介绍
- 触摸事件和微信实现原理
- python heapq
- 后台管理系统2.0.0新版本修改说明
- Anroid-强制用户下线功能
- mongo 查询 数组 查 数组 存在 即命中查询
- 幻灯片播放效果
- 微信公告号实现原理简单介绍;
- 面试金典系列2--原串翻转
- 树莓派之 Dancing Leds
- 基于websocket的扫码登录
- Java文档注释【自制API】
- 【南理oj】55 - 懒省事的小明(优先队列,贪心)
- 解惑java下dao,model,service,impl,util包名含义
- EC2上源安装vnstat
- 图解y420