GitHub Repository:School-Assistant
一、准备工作
1.微信公众号的介绍
详情请看微信公众平台开发概述。
2.开发环境的搭建
利用ngrok进行内网穿透,测试是否联通:在本地内部如果想访问自己的程序是很容易的,在同一个IP地址下就可以访问同一个内网(局域网),但是如果不在该内网下的其他的用户是无法访问的。内网穿透顾名思义,就是打破这层“隔膜”,让外面的用户可以访问。当然在这个地方的话,这个主要是还是用来测试我们的微信公众号是不是打通了,传到服务器上就不必考虑这些事情了。
内网穿透的原理:传统的开发的软件的通信是网址---服务器---应用程序,ngrok的作用就是替代这个应用程序,通信地址换成一个分配好的二级地址,可以直接进行访问。这样的话,我们的开发的程序和软件就可以直接进行通信。
ngrok如何使用以及下载的地址:Sunny_ngrok,此处不再赘述。这个地方需要注意的是,如果ngrok和tomcat两个都没有开的,其他人是无法访问这个页面的,所以无需担心被入侵的问题。打开之后,输入sunny clientid xxxxx,这里的xxxxx就是你的隧道id,当发现状态为Tunnel Status变成online的时候,就可以了。在MyEclipse中有自带的tomcat可以使用,把接口调成80即可。
这个地方记录一下这个ngrok踩过的坑:因为这个地方的tomcat的端口号是8080,但是ngrok却是一个80端口,所以需要修改一下tomcat的端口号为80端口。MyEclipse中其实自带着一个tomcat,启动的时候要注意不要启动错了,否则启动了这个tomcat的话怎么改都改都改不对。链接:如何去修改tomcat的端口号?
3.开发接入
因为安全问题,微信的诸多功能是无法针对个人用户开发的,微信针对开发者开放了开发者账号用来测试接口。地址如下:接口测试号申请。以下是Java代码的实现各个功能。
微信打通实现:WxServlet.java
开发者通过检验signature对请求进行校验。若确认此次GET请求来自微信服务器,请原样返回echostr参数内容,则接入生效,成为开发者成功,否则接入失败。
package servlet;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import service.WxService;
public class WxServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String signature = request.getParameter("signature");
String timestamp = request.getParameter("timestamp");
String nonce = request.getParameter("nonce");
String echostr = request.getParameter("echostr");
//登陆验证:如果接入成功原样返回一个数值//
if(WxService.check(signature , timestamp , nonce)){
//System.out.println("接入成功");
PrintWriter out = response.getWriter();
out.print(echostr);
out.flush();
out.close();
}
else{
System.out.println("接入失败");
}
}
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("post");
}
}
sha1检验算法类:WxService.java
第一步:将token、timestamp、nonce三个参数进行字典序排序。 第二步:将三个参数字符串拼接成一个字符串进行sha1加密 。第三步:开发者获得加密后的字符串可与signature对比,标识该请求来源于微信。(暂时开坑sha1算法,这个地方值得学一下。在Java中已经有了MessageDigest这个类了,所以可以直接进行使用。sha1是一个加密算法,把数值转换成Byte型,然后进行16位的转换。)
package service;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
public class WxService {
//微信服务的验证签名:
private static final String TOKEN = "bonstop";//这里的Token的数值必须和微信开发的配置方式一致//
private static String sha1(String s){ // 加密方法//
try{
//获取一个加密的对象//
MessageDigest md = MessageDigest.getInstance("sha1");
//加密//
byte[] digest = md.digest(s.getBytes());
char[] chars = {'0' , '1' , '2' , '3' , '4' , '5' , '6' , '7' , '8' , '9' , 'a' , 'b' , 'c' , 'd' , 'e' , 'f'};
StringBuilder sb = new StringBuilder();
//处理加密的结果//
for(byte b : digest)
{
sb.append(chars[(b >> 4) & 15]);//取其高四位//
sb.append(chars[b & 15]);//取其低四位//
}
return sb.toString();
}
catch(NoSuchAlgorithmException e){
e.printStackTrace();
}
return null;
}
public static boolean check(String signature , String timestamp , String nonce){
//1)将token、timestamp、nonce三个参数进行字典序排序
String[] str = new String[] {TOKEN , timestamp , nonce};
Arrays.sort(str);
//2)将三个参数字符串拼接成一个字符串进行sha1加密
String s = str[0] + str[1] + str[2];
String mysig = sha1(s);
System.out.println(mysig);
System.out.println(signature);
//3)开发者获得加密后的字符串可与signature对比,标识该请求来源于微信
return mysig.equalsIgnoreCase(signature);
}
}
微信用户发消息样例:test.xml
这个是我从手机上发送一个“”得到的结果。拉取来自一个用户的消息。
<xml>
<!--
ToUserName : 开发者微信号
FromUserName : 发送方帐号(一个OpenID : 只是随机放的一个数据)
CreateTime : 消息创建时间 (整型)单位时间为秒
MsgType : 消息类型,文本为text
Content : 文本消息内容
MsgId : 消息id,64位整型
-->
<ToUserName><![CDATA[gh_c67e59e8f8c6]]></ToUserName>
<FromUserName><![CDATA[oBzBC1Qwf-7-nxXs6ghXCrrxK13A]]></FromUserName>
<CreateTime>1554984695</CreateTime>
<MsgType><![CDATA[text]]></MsgType>
<Content><![CDATA[浣犲ソ]]></Content><!-- 我发了一个“你好”,这个需要后序做一个解析才能使用 -->
<MsgId>22262029333601665</MsgId>
</xml>
Day2:接入用户消息
接入用户消息:WxServlet.Java
Post可以接入从微信上发过来的消息。
package servlet;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import service.WxService;
public class WxServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String signature = request.getParameter("signature");
String timestamp = request.getParameter("timestamp");
String nonce = request.getParameter("nonce");
String echostr = request.getParameter("echostr");
//登陆验证:如果接入成功原样返回一个数值//
if(WxService.check(signature , timestamp , nonce)){
//System.out.println("接入成功");
PrintWriter out = response.getWriter();
out.print(echostr);
out.flush();
out.close();
}
else{
System.out.println("接入失败");
}
}
/*
* doPost用来接收消息和事件的推送
* */
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//处理消息和事件推送
Map<String , String> requestMap = WxService.parseRequest(request.getInputStream());
System.out.println(requestMap);
}
}
添加parseRequest 方法:WxService.Java
parseRequest这个功能就是让XML信息可以被传回来。这里用了dom4j.jar包进行进行解析XML。dom4j.jar放在WebROOT/Web-INF/lib下的文件内。注意不要下载错了jar包(dom4j-1.6.1),否则会报错。下载地址:<dom4j>。
public class WxService {
//微信服务的验证签名:
private static final String TOKEN = "bonstop";//这里的Token的数值必须和微信开发的配置方式一致//
private static String sha1(String s){ // 加密方法//
try{
//获取一个加密的对象//
MessageDigest md = MessageDigest.getInstance("sha1");
//加密//
byte[] digest = md.digest(s.getBytes());
char[] chars = {'0' , '1' , '2' , '3' , '4' , '5' , '6' , '7' , '8' , '9' , 'a' , 'b' , 'c' , 'd' , 'e' , 'f'};
StringBuilder sb = new StringBuilder();
//处理加密的结果//
for(byte b : digest)
{
sb.append(chars[(b >> 4) & 15]);//取其高四位//
sb.append(chars[b & 15]);//取其第四位//
}
return sb.toString();
}
catch(NoSuchAlgorithmException e){
e.printStackTrace();
}
return null;
}
public static boolean check(String signature , String timestamp , String nonce){
//1)将token、timestamp、nonce三个参数进行字典序排序
String[] str = new String[] {TOKEN , timestamp , nonce};
Arrays.sort(str);
//2)将三个参数字符串拼接成一个字符串进行sha1加密
String s = str[0] + str[1] + str[2];
String mysig = sha1(s);
System.out.println(mysig);
System.out.println(signature);
//3)开发者获得加密后的字符串可与signature对比,标识该请求来源于微信
return mysig.equalsIgnoreCase(signature);
}
public static Map<String, String> parseRequest(InputStream is) {
Map<String , String> map = new HashMap();
SAXReader reader = new SAXReader();
try{
//读取输入流,获取文档对象//
Document document = reader.read(is);
//根据文档对象获取根节点//
Element root = document.getRootElement();
//获取根节点下的所有子节点//
List<Element> elements = root.elements();
for(Element e : elements){
map.put(e.getName(), e.getStringValue());
}
}
catch(DocumentException e){
e.printStackTrace();
}
return map;
}
}
Day3:封装用户消息
第一步:将传送回来的数据进行分类处理。把map的数据拿出来,放入switch分类判断。
public static String getResponse(Map<String, String> requestMap) {
BaseMessage msg = null;
String msgType = requestMap.get("MsgType");
switch(msgType) {
case "text":
msg = dealTextMessage(requestMap);
break;
case "image":
break;
case "voice":
break;
case "video":
break;
case "shortvideo":
break;
case "link":
break;
default :
break;
}
//把消息对象处理为XML数据包
if(msg != null){
return beanToXMl(msg);
}
return null;
}
第二步:利用dealTextMessage函数将数据传送 “还好” 回来。
private static BaseMessage dealTextMessage(Map<String, String> requestMap) {
TextMessage tm = new TextMessage(requestMap , "pretty good");
return tm;
}
TestMessage.java: 封装这个"还好"这个词汇。当然返还回来的是一个string类型的语句。
public class TextMessage extends BaseMessage{
@XStreamAlias("Content")
private String content;
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public TextMessage(Map<String , String> requestMap , String content){
super(requestMap);
this.setMsgType("text");
this.content = content;
}
@Override
public String toString() {
return "TextMessage [content=" + content + ", getToUserName()="
+ getToUserName() + ", getFromUserName()=" +
getFromUserName()+ ", getCreateTime()=" + getCreateTime() + "]";
}
}
第三步:将string语句转化成XML语言。
这个地方利用了一个XStream包,可以转换成xml语言。XStream的包位置依旧放在文件lib下。
//把对象转换成XML代码
private static String beanToXMl(BaseMessage msg) {
XStream stream = new XStream();
//设置需要处理XStreamAlias("xml")注释的类
stream.processAnnotations(TextMessage.class);
stream.processAnnotations(NewsMessage.class);
stream.processAnnotations(MusicMessage.class);
stream.processAnnotations(ImageMessage.class);
stream.processAnnotations(VideoMessage.class);
stream.processAnnotations(VoiceMessage.class);
String xml = stream.toXML(msg);
//System.out.println(xml);
return xml;
}