最近帮朋友搭建一个微信公众号,通过查看微信公众号的开发者手册把基本功能实现了。把一些基本的可以通用的代码贴出来。

微信公众号提供了两种模式,一种是直接编辑模式,通过操作页面就能搭建基本的功能,比如自定义菜单功能就很好用。一种是开发者模式,需要写代码来搭建,基于HTTP接口(不完全符合REST风格)。比较麻烦的是两种模式是二选一的关系,使用了开发者模式,就得丢弃一些直接编辑的方便。

使用开发者模式的第一步是配置服务器。配置了URL,Token之后,微信服务器会发送一个HTTP GET请求到URL,并附加了signature,timestamp, nonce,echostr这几个参数。只要把echostr原样返回,微信服务器就认为配置成功了。而自己的服务器通过下列规则来判断是否配置成功

1. 把timestamp, nonce, token三个参数排序,然后拼成一个字符串

2. 使用SHA1加密这个字符串

3. 如果加密后的字符串等于signature,那么自己的服务器就可以认为配置成功。

配置的URL对应的控制器相当于一个前端控制器,负责处理微信服务器转发过来的请求。这里注意access_token的作用,如果自己的代码访问微信的HTTP接口,需要在HTTP请求中加入access_token。如果是微信服务器转发过来的请求,那么和access_token没有关系,可以直接处理。微信服务器转发过来的请求就两种,一种是GET,一般只在配置服务器的时候会有。另一种是POST XML的请求,比如用户点击微信公众的菜单,用户在微信公众号中发送的请求等等。

基本的处理微信服务器转发过来的请求代码框架如下

import java.io.InputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.springframework.web.servlet.ModelAndView;
import org.w3c.dom.Document;
public class WeixinAPIController extends AbstractBackController {
private static final String token = "1234567890";
@Override
public ModelAndView handleBackRequest(HttpServletRequest request,
HttpServletResponse response, Map model) throws Exception {
boolean isGet = request.getMethod().toLowerCase().equals("get");
if (isGet) {
String echostr = request.getParameter("echostr");
StringBuilder weixinContent = new StringBuilder();
if (checkSignature(request)) {
weixinContent.append(echostr);
} else {
weixinContent.append("Invalid request");
}
response.getWriter().print(weixinContent.toString());
} else {
ServletInputStream in = request.getInputStream();
acceptXML(in, response);
}
response.getWriter().flush();
response.getWriter().close();
return null;
}
private void acceptXML(InputStream in, HttpServletResponse response) throws Exception{
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
try {
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(in);
String toUserName = doc.getElementsByTagName("ToUserName").item(0).getFirstChild().getNodeValue();
String fromUserName = doc.getElementsByTagName("FromUserName").item(0).getFirstChild().getNodeValue();
String msgType = doc.getElementsByTagName("MsgType").item(0).getFirstChild().getNodeValue();
if("text".equals(msgType)){
String content = doc.getElementsByTagName("Content").item(0).getFirstChild().getNodeValue();
if(content != null && content.length() > 0){
// 接收到微信服务器POST过来的文本信息
// Do something
}
}else if("event".equals(msgType)){
String event = doc.getElementsByTagName("Event").item(0).getFirstChild().getNodeValue();
if("CLICK".equals(event)){
String commandStr = doc.getElementsByTagName("EventKey").item(0).getFirstChild().getNodeValue();
// 接收到微信服务器POST过来的点击菜单信息
// Do something
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
private boolean checkSignature(HttpServletRequest request) {
boolean valid = false;
String signature = request.getParameter("signature");
String timestamp = request.getParameter("timestamp");
String nonce = request.getParameter("nonce");
if (signature == null || timestamp == null || nonce == null) {
return false;
}
List array = new ArrayList();
array.add(token);
array.add(timestamp);
array.add(nonce);
Collections.sort(array);
String tempString = array.get(0) + array.get(1) + array.get(2);
MessageDigest sha1;
try {
sha1 = MessageDigest.getInstance("SHA1");
sha1.update(tempString.getBytes());
byte[] bytes = sha1.digest();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < bytes.length; i++) {
String shaHex = Integer.toHexString(bytes[i] & 0xFF);
if (shaHex.length() < 2) {
sb.append(0);
}
sb.append(shaHex);
}
String encodedToken = sb.toString();
if (encodedToken.equals(signature)) {
valid = true;
}
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return valid;
}
}

当启动服务器配置后,需要等待几分钟才会生效。生效之后就可以接收来自微信服务器转发的消息了。