前言:
1、先简单介绍一下微信公众号的开发者模式和编辑模式,玩过微信公众号的同志都知道,在微信公众号后台功能栏里面可以编辑消息自动回复,关键字回复,自定义菜单等功能,这些都是在视图页面下编辑的,叫编辑模式。
在开发栏目里面,有基本配置和开发工具,在基本配置里面点击启用就能进入开发者模式。我想强调的是:编辑模式和开发者模式是互斥的,一旦开启了开发者模式,编辑模式下设置的逻辑全部都会失效,微信服务器会把管理权交给开发者,由代码来完成用户交互逻辑。
2、然后简单介绍一款外网映射工具Sunny-Ngrok。在这里强调,这个映射工具在开发过程起到非常重要的作用,因为微信服务器是在外网的,我们本地开发的代码是部署在本地tomcat服务器的,本地tomcat是不能访问外网的微信服务器的,所以要借助Ngrok这个工具把我们本地ip映射到外网,这样本地代码就可以跟微信服务器交互了,这样调试代码就非常方便了。
Ngrok是外国的一款软件,服务器在美国,正常情况下,国内网络也是不能访问的(被墙了,大家都懂的)。在这里真的非常感谢大神Sunny把服务器部署到了香港,防止被墙让我们能够访问美国Ngrok服务器。更重要的是免费使用啊!免费使用啊!免费使用啊!
准备工作:
1.首先你要有一个微信公众号,申请流程很简单,不在赘述。
2.外网映射工具:Sunny-Ngrok
3.Eclipse,tomcat,jdk(这些都是最基本的开发工具,不在强调了)
一、部署好外网映射工具。
1、下载Sunny-Ngrok客户端:http://www.ngrok.cc/
2、注册Sunny-Ngrok后台管理账号:http://www.ngrok.cc/login/register
3、登录之后开通隧道:隧道名字随便填;前置域名就是你要映射的外网地址;本地端口就是本地ip:127.0.0.1+tomcat端口号:8080(本地的都可以修改,但是微信只能访问80和443端口,所以最好用默认的,不要修改)http验证用户名和密码不要填,因为到时这个地址要部署到微信后台的,设置之后微信服务器就不能访问这个映射地址了,微信服务器不知道你的用户名和密码。
4、本地运行Sunny-Ngrok启动工具.bat,将隧道id复制到启动工具,这样本地ip就映射到外网了。
二、编写接入代码。
1、启动Eclipse,新建web工程Weixin,需要导入3个jar包,目录结构如下:
2、编写WeixinServlet,这个类负责与微信服务器交互数据。参看微信开发文档,可知,开发者接入需要通过GET请求验证。
所以doGet()方法代码如下:
/**
* 开发者提交信息后,微信服务器将发送GET请求到填写的服务器地址URL上
*/
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//GET请求携带参数如下四个
String signature = req.getParameter("signature");
String timestamp = req.getParameter("timestamp");
String nonce = req.getParameter("nonce");
String echostr = req.getParameter("echostr");
//检验微信服务器传来的参数
boolean check = CheckUtil.checkSignature(signature, timestamp, nonce);
PrintWriter out = resp.getWriter();
//若确认此次GET请求来自微信服务器,请原样返回echostr参数内容,则接入生效,成为开发者成功,否则接入失败。
if (check) {
//通过print()方法将参数传送到微信服务器
out.print(echostr);
}
}
上面用到了一个CheckUtil工具类,里面有一个checkSignature()方法,传入三个参数,类中还要用到sha1加密算法,算法是网上找的,自己写还有可能写错,代码如下:
public class CheckUtil {
private static final String token = "jiaxiang";
/**
* 开发者通过检验signature对请求进行校验(下面有校验方式)。
* 若确认此次GET请求来自微信服务器,请原样返回echostr参数内容,则接入生效,成为开发者成功,
* 否则接入失败。加密/校验流程如下:
* 1)将token、timestamp、nonce三个参数进行字典序排序
* 2)将三个参数字符串拼接成一个字符串进行sha1加密
* 3)开发者获得加密后的字符串可与signature对比,标识该请求来源于微信
* @param signature
* @param timestamp
* @param nonce
* @return
*/
public static boolean checkSignature(String signature,String timestamp,String nonce){
String[] arr = new String[]{token,timestamp,nonce};
//排序
Arrays.sort(arr);
//生成字符串
StringBuffer content = new StringBuffer();
for (int i = 0; i < arr.length; i++) {
content.append(arr[i]);
}
//sha1加密
String temp = getSha1(content.toString());
return temp.equals(signature);
}
//sha1加密算法实现
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) {
e.printStackTrace();
return null;
}
}
}
web.xml加上以下代码:
<servlet>
<servlet-name>WeixinServlet</servlet-name>
<servlet-class>com.laijiaxiang.servlet.WeixinServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>WeixinServlet</servlet-name>
<url-pattern>/wx.do</url-pattern>
</servlet-mapping>
到这里开发者接入校验的代码就写完了,把代码部署到本地tomcat服务器,Sunny-Ngrok会自动把我们的项目映射到外网上。
下面到微信公众号后台校验。在基本配置栏修改服务器配置,把我们外网路径和项目WeixinServlet填写到服务器URl栏;Token与代码里的Token常量一致,这个可以随便写没有要求;EncodingAESKey点击随机生成就好。然后点击提交,提示提交成功,开发者接入成功,下面就可以写业务代码了。
三、代码实现被添加自动回复,消息自动回复,关键词自动回复。
1、由微信开发文档可知,当普通微信用户向公众账号发消息时,微信服务器将POST消息的XML数据包到开发者填写的URL上。
文本消息xml和参数如下:
所以在WeixinServlet类上添加doPost()方法,代码如下:
/**
* 当普通微信用户向公众账号发消息时,微信服务器将POST消息的XML数据包到开发者填写的URL上。
*/
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("UTF-8");
resp.setCharacterEncoding("UTF-8");
PrintWriter out = resp.getWriter();
try {
Map<String, String> map = MessageUtil.xmlToMap(req);
String toUserName = map.get("ToUserName");
String fromUserName = map.get("FromUserName");
// String createTime = map.get("CreateTime");
String msgType = map.get("MsgType");
String content = map.get("Content");
// String msgId = map.get("MsgId");
System.out.println("用户输入:"+content);
System.out.println("系统回复:");
String message = null;
//判断如果为文本类型消息
if (MessageUtil.MESSAGE_TEXT.equals(msgType)) {
//判断输入的关键字,回复指定内容
if ("1".equals(content)) {
message = MessageUtil.initText(toUserName, fromUserName, MessageUtil.firstMenu());
System.out.println(message);
}else if ("2".equals(content)) {
message = MessageUtil.initText(toUserName, fromUserName, MessageUtil.secondMenu());
System.out.println(message);
}else if ("?".equals(content) || "?".equals(content)) {
//回复主菜单文本
message = MessageUtil.initText(toUserName, fromUserName, MessageUtil.menuText());
System.out.println(message);
}else { //非关键字,回复原文
TextMessage text = new TextMessage();
text.setFromUserName(toUserName);
text.setToUserName(fromUserName);
text.setMsgType("text");
text.setCreateTime(new Date().getTime()+"");
text.setContent("您发送的消息是:"+content);
message = MessageUtil.textMessageToXml(text);
System.out.println(message);
}
}
//判断如果为事件消息
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());
System.out.println(message);
}
}
// System.out.println(message);
//将消息返回给微信后台
out.print(message);
} catch (DocumentException e) {
e.printStackTrace();
}finally {
out.close();
}
}
上面方法调用了MessageUtil类,这个类的主要作用是实现xml与集合对象的相互转换。代码如下:
/**
* 实现xml与集合对象的相互转换
* @author Administrator
*
*/
public class MessageUtil {
//文本消息类型
public static final String MESSAGE_TEXT = "text";
//事件消息类型
public static final String MESSAGE_EVENT = "event";
//关注事件类型
public static final String MESSAGE_SUBSCRIBE = "subscribe";
/**
* 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();
//获取输入流
InputStream ins = request.getInputStream();
//将输入流转换为xml对象
Document doc = reader.read(ins);
//获取xml文档的根节点
Element root = doc.getRootElement();
//获取子节点
List<Element> list = root.elements();
//遍历子节点
for (Element e : list) {
map.put(e.getName(), e.getText());
// System.out.println(e.getName()+"=="+e.getText());
}
ins.close();
return map;
}
/**
* 将文本消息对象转换为xml
* @param textMessage
* @return
*/
public static String textMessageToXml(TextMessage textMessage){
XStream xstream = new XStream();
//将根节点改为"xml",默认是用实体类路径com.laijiaxiang.po.TextMessage
xstream.alias("xml", textMessage.getClass());
return xstream.toXML(textMessage);
}
/**
* 初始化主菜单
* @param toUserName
* @param fromUserName
* @param content
* @return
*/
public static String initText(String toUserName,String fromUserName,String content){
TextMessage text = new TextMessage();
text.setFromUserName(toUserName);
text.setToUserName(fromUserName);
text.setMsgType(MESSAGE_TEXT);
text.setCreateTime(new Date().getTime()+"");
text.setContent(content);
return textMessageToXml(text);
}
/**
* 主菜单
* @return
*/
public static String menuText(){
StringBuffer sb = new StringBuffer();
sb.append("欢迎您的关注,请按照菜单提示进行操作:\n");
sb.append("1、《你的名字》高清资源\n");
sb.append("2、《声之形》高清资源\n");
sb.append("回复?调出此菜单");
return sb.toString();
}
/**
* 输入"1",回复的文本内容
* @return
*/
public static String firstMenu(){
StringBuffer sb = new StringBuffer();
sb.append("百度网盘:www.panbaidu.com/你的名字/xxxxx \n");
sb.append("密码:xxxxx");
return sb.toString();
}
/**
* 输入"2",回复的文本内容
* @return
*/
public static String secondMenu(){
StringBuffer sb = new StringBuffer();
sb.append("百度网盘:www.panbaidu.com/声之形/xxxxx \n");
sb.append("密码:xxxxx");
return sb.toString();
}
}
还有就是TextMessage实体类,主要是记录微信服务器传过来的参数,代码如下:
public class TextMessage {
private String ToUserName;
private String FromUserName;
private String CreateTime;
private String MsgType;
private String MsgId;
private String Content;
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 String getCreateTime() {
return CreateTime;
}
public void setCreateTime(String createTime) {
CreateTime = createTime;
}
public String getMsgType() {
return MsgType;
}
public void setMsgType(String msgType) {
MsgType = msgType;
}
public String getMsgId() {
return MsgId;
}
public void setMsgId(String msgId) {
MsgId = msgId;
}
public String getContent() {
return Content;
}
public void setContent(String content) {
Content = content;
}
}
到这里整个文本消息回复的逻辑代码就写完了。下面部署到服务器测试一下。
四、文本消息回复功能测试。
1、作为开发者我建议使用官方提供的测试账号进行测试。因为个人订阅号很多接口不能调用,但是测试号就可以,用来调试代码很方便。
进入公众平台测试账号,填写接口配置信息,点击提交。
2、提交成功之后,用手机微信扫描测试号二维码就可以在手机上测试功能了。
Eclipse后台也能看到交互的数据。
到此,Java微信开发算是入门了。。。。。