最近项目需要开发一个微信公众号作为其中一个模块的功能入口,花了两天时间简单了解了一下微信公众号的开发流程,这里简单总结一下。
1. 效果图—发送消息简单的回复“您好,感谢关注本公众号!”
2. 学习用的是测试号,测试号申请流程:
(1)打开微信测试号地址:点此跳转登录后如下图
(2) 上图中需要填写url地址和token
开发者提交信息(包括URL、Token)后,微信服务器将发送Http Get请求到填写的URL上,GET请求携带四个参数:signature、timestamp、nonce和echostr。公众号服务程序应该按如下要求进行接入验证:
- 将token、timestamp、nonce三个参数进行字典序排序
- 将三个参数字符串拼接成一个字符串进行sha1加密
- 将加密后获得的字符串与signature对比,如果一致,则原样返回echostr参数内容
(3)使用Ngrok内网穿透工具将localhos:8080的本地地址映射到外网
详细教程请参考Sunny的博客,下载并开通好隧道服务以后,在ngrok解压后目录执行一下命令:
./sunny clientid 隧道id
此时就可以直接在浏览器输入地址,如http://example.ngrok.cc,来访问本地的服务器了
(4)进入正题,服务器介入验证(这里使用原生的servlet来实现)
在doget方法里取出四个参数
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String timestamp = request.getParameter("timestamp");
String nonce = request.getParameter("nonce");
String echostr = request.getParameter("echostr");
String signature = request.getParameter("signature");
PrintWriter out = response.getWriter();
if(CheckUtil.checkSignature(signature, timestamp, nonce)){//CheckUtil是验证工具类
out.print(echostr);
}
}
编写CheckUtil类进行sha1加密并与signature进行比较
public class CheckUtil {
private static final String token = "example";
public static boolean checkSignature(String signature, String timestamp, String nonce){
ArrayList<String> list = new ArrayList<>();
list.add(token);
list.add(timestamp);
list.add(nonce);
Collections.sort(list);
StringBuffer content = new StringBuffer();
for(String item : list){
content.append(item);//连成字符串进行加密
}
String result = getSha1(content.toString());
return signature.equals(result)?true:false;
}
/**
* 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) {
return null;
}
}
}
如果checkSignature比较后返回true,说明该消息是来自微信服务器,则将echostr原样返回
(5)顺利的话,在测试号申请页面,点击提交按钮以后便会提示成功,并且出现如下界面
扫一下二维码就可以关注自己的公众号啦,虽然只是一个测试号,但是还是很激动滴。。。不过这时候你不论向公众号发送多少消息,返回的都是“该公众号暂时无法提供服务,请稍后再试”。。。。。
(6)接下来实现收发文本消息的功能。首先查看一下微信公众平台的开发者规范消息管理这部分:
消息管理
当普通微信用户向公众账号发消息时,微信服务器将POST消息的XML数据包到开发者填写的URL上。
文本消息
<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>
参数 | 描述 |
---|---|
ToUserName | 开发者微信号 |
ToUserName | 开发者微信号 |
FromUserName | 发送方帐号(一个OpenID) |
CreateTime | 消息创建时间 (整型) |
MsgType | text |
Content | 文本消息内容 |
MsgId | 消息id,64位整型 |
在doPost方法中接受消息:
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
PrintWriter out = response.getWriter();
try {
//将xml请求信息转换为map形式保存
Map<String, String> map = MsgUtils.xmlToMap(request);
TextMessage respMsg = new TextMessage();
//返回"您好,感谢您关注本公众号!"消息
respMsg.setToUserName(map.get("FromUserName"));
respMsg.setFromUserName(map.get("ToUserName"));
respMsg.setCreateTime(new Date().toString());
respMsg.setContent("您好,感谢您关注本公众号!");
respMsg.setMsgType(MsgUtils.MESSAGE_TEXT);
//将发送的信息转换为xml字符串
String respXml = MsgUtils.textToXml(respMsg);
out.println(respXml);
} catch (DocumentException e) {
e.printStackTrace();
}finally{
out.close();
}
}
上例中TextMessage是经过封装的一个消息的javabean类:
private String ToUserName;
private String FromUserName;
private String CreateTime;
private String MsgType;
private String Content;
MsgUtils是用于将消息文本和xml格式字符串相互转换的工具类:
public class MsgUtils {
public static final String MESSAGE_TEXT = "text";
public static Map<String, String> xmlToMap(HttpServletRequest req) throws IOException, DocumentException{
Map<String, String> resultMap = new HashMap<>();
//获取SAXReader
SAXReader reader = new SAXReader();
//获取request输入流
InputStream is = req.getInputStream();
//转换为Document对象
Document document = reader.read(is);
//获取根节点
Element root = document.getRootElement();
//获取元素集合
List<Element> elements = root.elements();
for(Element e : elements){
System.out.println("e.getName()>>>"+e.getName()+" e.getText()>>>"+e.getText());
resultMap.put(e.getName(), e.getText());
}
is.close();
return resultMap;
}
public static String textToXml(TextMessage textMessage){
Document document = DocumentHelper.createDocument();
//创建根节点<xml><xml>
Element rootElement = document.addElement("xml");
//将节点添加到根节点中
Element ToUserName = rootElement.addElement("ToUserName");
ToUserName.setText(textMessage.getToUserName());
Element FromUserName = rootElement.addElement("FromUserName");
FromUserName.setText(textMessage.getFromUserName());
Element CreateTime = rootElement.addElement("CreateTime");
CreateTime.setText(textMessage.getCreateTime());
Element MsgType = rootElement.addElement("MsgType");
MsgType.setText(textMessage.getMsgType());
Element Content = rootElement.addElement("Content");
Content.setText(textMessage.getContent());
//将xml的document对象转化为字符串,打印查看一下
System.out.println(document.asXML());
return document.asXML();
}
}
xml转换使用到了dom4j的jar包,经过上面的6步就实现了接受关注的用户发来的消息并且返回一条消息。再此之上,可以详细的封装一个消息内容详情类,比如收到“1”,返回“您发送了1”,等等文本信息。下一篇再写其他类型的消息,篇幅比较长,本人也是初学者,不对的地方还请指出来,互相学习