Java 微信公众号开发_学习笔记
一、课程介绍
- 本套课程的学习内容,开发语言
- 微信公众号介绍,申请以及后台设置详解
- 编辑模式下的消息回复,菜单建立,素材管理等
- 开发前的环境搭建以及工具准备
- 开发者模式的切换、以及消息的接收与响应
- 百度BAE服务器的搭建,代码上传
二、公众号与微信的区别
- 微信定位于聊天、朋友圈等个人娱乐
- 公众号定位于商业用途,达到个人品牌的推广、企业品牌宣传等
三、公众号类型介绍
账号类型一旦成功建立账号,类型不可更改(企业号、服务号与订阅号)
企业号与服务号、订阅号的区别:
企业号 | 服务号 | 订阅号 | |
面向人群 | 面向企业、政府、事业单位和非政府组织,实现生产管理、协作运营的移动化 | 面向企业、政府或组织,用以对用户进行服务 | 面向媒体和个人提供一种信息传播方式 |
消息显示方式 | 出现在好友会话列表首层 | 出现在好友会话列表首层 | 折叠在订阅号目录中 |
消息次数限制 | 最高每分钟可群发200次 | 每月主动发送消息不超过4条 | 每天群发一条 |
验证关注身份 | 通讯录成员可关注 | 微信用户扫码即可关注 | 微信用户扫码即可关注 |
消息保密 | 消息可转发、分享。支持保密消息,防止成员转发 | 消息可转发、分享 | 消息可转发、分享 |
高级接口权限 | 支持 | 支持 | 不支持(微信支付、推广等) |
定制应用 | 可根据需要定制应用,多个应用聚合成一个企业号 | 不支持,新增服务号需要重新关注 | 不支持,新增服务号需要重新关注 |
四、公众号申请
a.公众号申请需要在微信公众号平台进行操作:https://mp.weixin.qq.com/
b.申请流程
登陆微信公众平台->点击立即注册->选择订阅号->按流程注册->类型选择(订阅号)->主体类型(个人)->继续填写注册信息->进入公众号信息填写
c.公众号信息填写
账号名称:微信公众账号的昵称而并非微信号(微信号是在审核通过以后后台进行设置的)
功能介绍:内容介绍
五、微信公众平台介绍
微信公众平台是腾讯为了让用户申请和管理微信公众账号而推出的一个WEB平台。微信公众账号的操作和管理都要在这个平台进行。
a.功能模块
群发功能:主要是微信公众账号把所需要的信息推送给所有的关注用户,其中的信息有文字,图片,语音,图文五大类型。文字消息
自动回复:被添加自动回复、消息自动回复、关键词自动回复
自定义菜单:总共可添加15个菜单
投票管理:功能
b.管理模块
消息管理:查看用户发送的消息
用户管理:可对用户进行分组
素材管理:--
六、自动回复
编辑模式介绍:在编辑模式下实现消息自动回复、菜单创建;以及在微信公众平台推送消息给关注用户
a.设置被关注回复:
b.关键词自动回复
七、Java公众号开发环境准备
1.一个微信公众号
2.外网映射工具(开发调试)
与微信对接的url要具备以下条件
- 在公网上能够访问
- 端口只支持80端口
结论:需要借助映射工具将本机地址映射到公网上。(花生壳、ngrok等工具)
3.ngrok可以将内网映射到公网上去,这样就可以在公网访问你的本地网络服务
用法:
ngrok 8080
ngrok -config ngrok.cfg -subdomain dzj0712 8080
4.ngrok的使用
youName是你想要设置的域名,如 wswx
Port是你想映射的本地端口,如8080
最后,访问http://wswx.tunnel.echomod.cn/ 即可在外网访问你的本地服务器
a.在dos中进入所在盘符执行ngrok -config ngrok.cfg -subdomain dzj0712 8080得到以下结果
b.存在问题
ngrok是国外的工具,服务器也是在国外,在国内访问较慢,甚至无法访问
映射的公网地址是随机的,会造成不便
c.以上的a步骤直接使用了在国内部署的tunnel网络服务,它支持了ngrok的绝大多数功能,同时解决了ngrok在美国造成的访问缓慢等问题
八、数据交互原理
1.开发模式
开发模式与编辑模式是两者互斥的关系,也就是当打开开发模式之后编辑模式的自动回复与自定义菜单等功能就会跟着失效,同样的开发模式也会在编辑模式中失效
2.数据交互原理
当用户发送请求之后,用户会进行解析公众号的地址,并根据该地址将请求转发至微信公众号服务器。服务器处理完成后会将处理的结果返回给微信后台,之后微信后台再将结果转发给客户端。
开发人员主要进行的工作就是红字显示的服务器开发
九、开发模式接入
1.登陆微信公众平台
打开开发文档填写服务器配置
2.校验signature对请求进行加密
参数 | 描述 |
---|---|
signature | 微信加密签名,signature结合了开发者填写的token参数和请求中的timestamp参数、nonce参数。 |
timestamp | 时间戳 |
nonce | 随机数 |
echostr | 随机字符串 |
开发者通过检验signature对请求进行校验(下面有校验方式)。若确认此次GET请求来自微信服务器,请原样返回echostr参数内容,则接入生效,成为开发者成功,否则接入失败。加密/校验流程如下:
将token、timestamp、nonce三个参数进行字典序排序 2)将三个参数字符串拼接成一个字符串进行sha1加密 3)开发者获得加密后的字符串可与signature对比,标识该请求来源于微信
3.检验signature的Java示例代码:
a.微信服务器入口代码
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 package com.imooc.servlet; 2 3 import java.io.IOException; 4 import java.io.PrintWriter; 5 6 import javax.servlet.ServletException; 7 import javax.servlet.http.HttpServlet; 8 import javax.servlet.http.HttpServletRequest; 9 import javax.servlet.http.HttpServletResponse; 10 11 import com.imooc.util.CheckUtil; 12 13 public class WeixinServlet extends HttpServlet{ 14 15 @Override 16 /** 17 * 使用doget方法进行验证 18 */ 19 protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 20 // 获取微信公众号服务器发送的参数 21 String signature = req.getParameter("signature"); 22 String timestamp = req.getParameter("timestamp"); 23 String nonce = req.getParameter("nonce"); 24 String echostr = req.getParameter("echostr"); 25 26 PrintWriter out = resp.getWriter(); 27 //校验成功将随机字符串返回 28 if(CheckUtil.checkSignature(signature, timestamp, nonce)) { 29 out.print(echostr); 30 } 31 } 32 }
b.校验的工具类
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 package com.imooc.util; 2 3 import java.security.MessageDigest; 4 import java.util.Arrays; 5 6 /** 7 * 校验工具类 8 */ 9 public class CheckUtil { 10 private static final String token = "imooc"; 11 /** 12 * 13 * @param signature 加密签名 14 * @param timestamp 时间戳 15 * @param nonce 随机数字 16 * @return 17 */ 18 public static boolean checkSignature(String signature, String timestamp, String nonce) { 19 20 String[] arr = new String[] { token, timestamp, nonce }; 21 // 排序 22 Arrays.sort(arr); 23 // 生成字符串 24 StringBuffer content = new StringBuffer(); 25 for (int i = 0; i < arr.length; i++) { 26 content.append(arr[i]); 27 } 28 // sha1加密 29 String temp = getSha1(content.toString()); 30 // 与微信传过来的加密签名进行比较 31 return temp.equals(signature); 32 } 33 34 public static String getSha1(String str) { 35 if (str == null || str.length() == 0) { 36 return null; 37 } 38 char hexDigits[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; 39 try { 40 MessageDigest mdTemp = MessageDigest.getInstance("SHA1"); 41 mdTemp.update(str.getBytes("UTF-8")); 42 43 byte[] md = mdTemp.digest(); 44 int j = md.length; 45 char buf[] = new char[j * 2]; 46 int k = 0; 47 for (int i = 0; i < j; i++) { 48 byte byte0 = md[i]; 49 buf[k++] = hexDigits[byte0 >>> 4 & 0xf]; 50 buf[k++] = hexDigits[byte0 & 0xf]; 51 } 52 return new String(buf); 53 } catch (Exception e) { 54 // TODO: handle exception 55 return null; 56 } 57 } 58 }
c.在xml中对servlet进行配置
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5"> <display-name>Weixin</display-name> <servlet> <servlet-name>weixinServlet</servlet-name> <servlet-class>com.imooc.servlet.WeixinServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>weixinServlet</servlet-name> <url-pattern>/wx.do</url-pattern> </servlet-mapping> <welcome-file-list> <welcome-file>index.html</welcome-file> <welcome-file>index.htm</welcome-file> <welcome-file>index.jsp</welcome-file> <welcome-file>default.html</welcome-file> <welcome-file>default.htm</welcome-file> <welcome-file>default.jsp</welcome-file> </welcome-file-list> </web-app>
十、消息的接收与响应
1.将本地地址映射到公网上,在公网上面访问本地的servlet
2.在浏览器中进行访问
3.成功访问后:将该地址配置到微信的后台服务器
点击提交之后,就会执行servlet中的doget方法进行接入的校验
4.提交之后启用开发模式(编辑模式I失效)
5.开发者模式下实现消息的接收与响应
之前的接入是通过get方法,而现在的消息响应微信服务器将post请求,并以xml的形式返回数据,因为微信后台发回的数据是一个xml格式,因此需要将其转换为集合类型,并提供对象类型转换为xml类型的方法
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 package com.imooc.util; 2 3 import java.awt.font.TextMeasurer; 4 import java.io.IOException; 5 import java.io.InputStream; 6 import java.util.HashMap; 7 import java.util.List; 8 import java.util.Map; 9 10 import javax.servlet.http.HttpServletRequest; 11 12 import org.dom4j.Document; 13 import org.dom4j.DocumentException; 14 import org.dom4j.Element; 15 import org.dom4j.io.SAXReader; 16 17 import com.imooc.po.TextMessage; 18 import com.thoughtworks.xstream.XStream; 19 20 /** 21 * 消息的格式转换 22 * 23 * @author Administrator 24 * 25 */ 26 public class MessageUtil { 27 /** 28 * xml转为map集合 29 * 30 * @param request 31 * @return 32 * @throws IOException 33 * @throws DocumentException 34 */ 35 public static Map<String, String> xmlToMap(HttpServletRequest request) throws IOException, DocumentException { 36 Map<String, String> map = new HashMap<String, String>(); 37 SAXReader reader = new SAXReader(); 38 // 从Request中获取输入流 39 InputStream ins = request.getInputStream(); 40 Document doc = reader.read(ins); 41 Element root = doc.getRootElement(); 42 43 List<Element> list = root.elements(); 44 45 for (Element e : list) { 46 map.put(e.getName(), e.getText()); 47 } 48 ins.close(); 49 50 return map; 51 } 52 53 /** 54 * 将文本消息对象类型转为xml类型 55 * 56 * @param textMessage 57 * @return 58 */ 59 public static String textMessageToXml(TextMessage textMessage) { 60 61 XStream xstream = new XStream(); 62 //将xml的根节点替换为xml 63 xstream.alias("xml", textMessage.getClass()); 64 return xstream.toXML(textMessage); 65 } 66 67 }
文本对象类
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 package com.imooc.po; 2 3 public class TextMessage { 4 private String ToUserName; 5 private String FromUserName; 6 private String CreateTime; 7 private String MsgType; 8 private String Content; 9 private String MsgId; 10 public String getToUserName() { 11 return ToUserName; 12 } 13 public void setToUserName(String toUserName) { 14 ToUserName = toUserName; 15 } 16 public String getFromUserName() { 17 return FromUserName; 18 } 19 public void setFromUserName(String fromUserName) { 20 FromUserName = fromUserName; 21 } 22 public String getCreateTime() { 23 return CreateTime; 24 } 25 public void setCreateTime(String createTime) { 26 CreateTime = createTime; 27 } 28 public String getMsgType() { 29 return MsgType; 30 } 31 public void setMsgType(String msgType) { 32 MsgType = msgType; 33 } 34 public String getContent() { 35 return Content; 36 } 37 public void setContent(String content) { 38 Content = content; 39 } 40 public String getMsgId() { 41 return MsgId; 42 } 43 public void setMsgId(String msgId) { 44 MsgId = msgId; 45 } 46 47 48 }
6.写完消息格式的转换之后,我们现在通过post方法实现消息的接收与响应
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 package com.imooc.servlet; 2 3 import java.io.IOException; 4 import java.io.PrintWriter; 5 import java.util.Date; 6 import java.util.Map; 7 8 import javax.servlet.ServletException; 9 import javax.servlet.http.HttpServlet; 10 import javax.servlet.http.HttpServletRequest; 11 import javax.servlet.http.HttpServletResponse; 12 13 import org.dom4j.DocumentException; 14 15 import com.imooc.po.TextMessage; 16 import com.imooc.util.CheckUtil; 17 import com.imooc.util.MessageUtil; 18 19 public class WeixinServlet extends HttpServlet{ 20 21 @Override 22 /** 23 * 使用doget方法进行验证 24 */ 25 protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 26 // 获取微信公众号服务器发送的参数 27 String signature = req.getParameter("signature"); 28 String timestamp = req.getParameter("timestamp"); 29 String nonce = req.getParameter("nonce"); 30 String echostr = req.getParameter("echostr"); 31 32 PrintWriter out = resp.getWriter(); 33 //校验成功将随机字符串返回 34 if(CheckUtil.checkSignature(signature, timestamp, nonce)) { 35 out.print(echostr); 36 } 37 } 38 /** 39 * 接收消息 40 */ 41 protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 42 req.setCharacterEncoding("UTF-8"); 43 resp.setCharacterEncoding("UTF-8"); 44 //通过PrintWriter将消息返回给微信后台 45 PrintWriter out = resp.getWriter(); 46 try { 47 //通过集合去接收 48 Map<String,String> map = MessageUtil.xmlToMap(req); 49 //获取对应的属性 50 String fromUserName = map.get("FromUserName"); 51 String toUserName = map.get("ToUserName"); 52 String msgType = map.get("MsgType"); 53 String content = map.get("Content"); 54 55 String message = null; 56 //判断是否为文本消息 57 if("text".equals(msgType)) { 58 //创建文本消息对象 59 TextMessage text = new TextMessage(); 60 //返回给发送者 61 text.setFromUserName(toUserName); 62 text.setToUserName(fromUserName); 63 text.setMsgType("text"); 64 text.setCreateTime(new Date().getTime()); 65 text.setContent("您发送的消息是:" + content); 66 message = MessageUtil.textMessageToXml(text); 67 68 System.out.println(message); 69 } 70 out.print(message); 71 } catch (DocumentException e) { 72 e.printStackTrace(); 73 } finally { 74 out.close(); 75 } 76 } 77 }
十一、欢迎消息回复和关键字消息回复
添加关注后的消息回复和按照关键字进行消息回复的功能。
1.消息类型有哪些
文本消息-text
图片消息-image
语音消息-voice
视频消息-video
链接消息-link
地理位置消息-location
事件推送-event
--关注-subscribe
--取消关注-unsubscribe
--菜单点击-CLICK、VIEW
2.servlet类的完善
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
package com.imooc.servlet; import java.io.IOException; import java.io.PrintWriter; import java.util.Date; import java.util.Map; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.dom4j.DocumentException; import com.imooc.po.TextMessage; import com.imooc.util.CheckUtil; import com.imooc.util.MessageUtil; public class WeixinServlet extends HttpServlet{ @Override /** * 使用doget方法进行验证 */ 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"); PrintWriter out = resp.getWriter(); //校验成功将随机字符串返回 if(CheckUtil.checkSignature(signature, timestamp, nonce)) { out.print(echostr); } } /** * 接收消息 */ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding("UTF-8"); resp.setCharacterEncoding("UTF-8"); //通过PrintWriter将消息返回给微信后台 PrintWriter out = resp.getWriter(); try { //通过集合去接收 Map<String,String> map = MessageUtil.xmlToMap(req); //获取对应的属性 String fromUserName = map.get("FromUserName"); String toUserName = map.get("ToUserName"); String msgType = map.get("MsgType"); String content = map.get("Content"); String message = null; //判断是否为文本消息 if(MessageUtil.MESSAGE_TEXT.equals(msgType)) { if("1".equals(content)) { message = MessageUtil.initText(toUserName, fromUserName, MessageUtil.firstMenu()); }else if("2".equals(content)) { message = MessageUtil.initText(toUserName, fromUserName, MessageUtil.secondtMenu()); }else if("?".equals(content)||"?".equals(content)) { message = MessageUtil.initText(toUserName, fromUserName, MessageUtil.menuText()); } //事件推送的逻辑 }else if(MessageUtil.MESSAGE_EVENT.equals(msgType)) { String eventType = map.get("Event"); if(MessageUtil.MESSAGE_SUBSCRIBE.equals(eventType)) { message = MessageUtil.initText(toUserName, fromUserName, MessageUtil.menuText()); } } out.print(message); } catch (DocumentException e) { e.printStackTrace(); } finally { out.close(); } } }
3.MessageUtil类的完善
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
package com.imooc.util; import java.awt.font.TextMeasurer; import java.io.IOException; import java.io.InputStream; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.servlet.http.HttpServletRequest; import org.dom4j.Document; import org.dom4j.DocumentException; import org.dom4j.Element; import org.dom4j.io.SAXReader; import com.imooc.po.TextMessage; import com.thoughtworks.xstream.XStream; /** * 消息的格式转换 * * @author Administrator * */ public class MessageUtil { /** * 消息类型常量 */ public static final String MESSAGE_TEXT = "text"; public static final String MESSAGE_IMAGE = "image"; public static final String MESSAGE_VOICE = "voice"; public static final String MESSAGE_VIDEO = "video"; public static final String MESSAGE_LINK = "link"; public static final String MESSAGE_LOCATION = "location"; public static final String MESSAGE_EVENT = "event"; public static final String MESSAGE_SUBSCRIBE = "subscribe"; public static final String MESSAGE_UNSUBSCRIBE = "unsubscribe"; public static final String MESSAGE_CLICK = "CLICK"; public static final String MESSAGE_VIEW = "VIEW"; /** * xml转为map集合 * * @param request * @return * @throws IOException * @throws DocumentException */ public static Map<String, String> xmlToMap(HttpServletRequest request) throws IOException, DocumentException { Map<String, String> map = new HashMap<String, String>(); SAXReader reader = new SAXReader(); // 从Request中获取输入流 InputStream ins = request.getInputStream(); Document doc = reader.read(ins); Element root = doc.getRootElement(); List<Element> list = root.elements(); for (Element e : list) { map.put(e.getName(), e.getText()); } ins.close(); return map; } /** * 将文本消息对象类型转为xml类型 * * @param textMessage * @return */ public static String textMessageToXml(TextMessage textMessage) { XStream xstream = new XStream(); //将xml的根节点替换为xml xstream.alias("xml", textMessage.getClass()); return xstream.toXML(textMessage); } public static String initText(String toUserName,String fromUserName,String content) { TextMessage text = new TextMessage(); text.setFromUserName(toUserName); text.setToUserName(fromUserName); text.setMsgType(MessageUtil.MESSAGE_TEXT); text.setCreateTime(new Date().getTime()); text.setContent("您发送的消息是:" + content); return MessageUtil.textMessageToXml(text); } /** * 主菜单 * @return */ public static String menuText() { StringBuffer sb = new StringBuffer(); sb.append("欢迎您的关注,请按照菜单提示进行操作: \n\n"); sb.append("1、地区介绍"); sb.append("2、城区介绍"); sb.append("回复?调出此菜单"); return sb.toString(); } public static String firstMenu() { StringBuffer sb = new StringBuffer(); sb.append("主要介绍了本区域的气候环境,农产品分布及产量以及水利建设等方面"); return sb.toString(); } public static String secondtMenu() { StringBuffer sb = new StringBuffer(); sb.append("主要介绍了城区的历史及居民生活习惯等方面"); return sb.toString(); } }
http://dzj0712.tunnel.echomod.cn