不觉间毕业参加工作将近1年了。本着学点新东西和赚点外快的念头,自学了一下微信公众平台开发。在这篇文章里,主要描述了一下我搭建一个微信公众平台的"helloworld"级程序的历程。其中,柳峰老师的视频和文章给了我很大的帮助。非常感谢。
搭建微信公众平台程序其中的准备如下:
1、微信公众平台上申请微信公众号(需要手持身份证的相片,待会申请SAE也会用到)。此审批流程很快,大概1个工作日就可完成。
2、SAE云服务器(初次注册赠送500云豆,可使用100小时左右,用刚才申请微信公众号的相片可以申请实名验证,通过后会再给2000云豆)
3、开发工具。此处我用的是eclipse4.4 jdk1.6(据说SAE只支持1.6,此处未考证) dom4j1.6.1 xstream1.3
接下来就是环境的搭建。eclipse和jdk均解压缩即可使用,注意的是eclipse和jdk的系统位数要一致,都为32位或64位,视你的系统而定。打开eclipse后新建一个web工程。在工程中新建一个servlet类,其中doGet方法即为用户在访问你的公众号时微信服务器向你发送的验证请求。当验证通过后,微信才会将接下来的消息发送post请求到你的服务上。简单的说,就是微信起到一个中转站的作用,用户的请求经过微信转发到我们提供给微信的服务上(这里是SAE),然后由我们部署到服务上的程序做处理后再把响应消息回复给微信,由微信转发给用户。doGet方法在这里做的就是验证请求是否能够送达我们提供给微信的服务。
微信调用doGet方法时会带有4个参数signature,timestamp,nonce,echostr 这时我们还要用到一个参数就是token
图中是你在微信公众号申请下来后,点击开发者模式弹出来的东西。这里的URL就是你提供给微信的服务地址,微信会将用户的请求转发到这个服务地址上。而画红的Token可以理解为一个个人签名,我在这里用的是LogicCode。等下这个会在程序中用到。接着说doGet的参数,在这里我们要将timestamp.nonce,token这3个参数进行字典排序,并进行sha-1加密后得出来的字符串与signature进行比较,如果相等,则将echostr返回给微信,说明验证已经通过了。代码和包体结构如下:
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(StringUtil.validateSignature(signature, timestamp, nonce)){
out.print(echostr);
}
out.close();
out = null;
}
public class StringUtil {
private static String token = "LogicCode";
public static boolean validateSignature(String signature,String timestamp,String nonce){
boolean result = false;
String[] strArray = new String[]{token,timestamp,nonce};
//采用源码的归并排序,如果数组元素个数小于7采用冒泡排序
Arrays.sort(strArray);
StringBuffer strBuffer = new StringBuffer();
for(int i = 0; i<strArray.length;i++){
strBuffer.append(strArray[i]);
}
MessageDigest md = null;
String mdStr = null;
try {
//采用SHA-1加密
md = MessageDigest.getInstance("sha-1");
md.update(strBuffer.toString().getBytes());
byte[] mdResult = md.digest();
mdStr = convertHexString(mdResult);
} catch (NoSuchAlgorithmException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if(null!=mdStr&&mdStr.equals(signature)){
result = true;
}
return result;
}
//将字节转换成十六进制字符串
public static String convertHexString(byte[] mdResult){
StringBuffer strBuffer = new StringBuffer();
for(int i = 0;i<mdResult.length;i++){
strBuffer.append(Integer.toHexString(0xff&mdResult[i]));
}
return strBuffer.toString();
}
}
<span style="font-family: Arial, Helvetica, sans-serif;">这里可以加强一下stringbuilder和stringbuffer区别的记忆,stringbuffer是线程安全的,因为是线程安全的所以在性能上要比stringbuilder差一些。</span></span>
其中采用SHA-1加密也可以写成
md = MessageDigest.getInstance("sha-1");
byte[] mdResult = md.digest(strBuffer.toString().getBytes());
还值得一提的就是Arrays.sort这个方法会根据元素的类型和个数来采用不同的排序方法。当类型为基本类型,且个数小于7的时候采用的是冒泡排序,大于等于7的时候用的是快速排序。当类型为object时,采用归并排序,当归并的数组长度小于7的时候采用冒泡排序。
而toHexString方法中的参数说明,在网上查阅如下:
如果是一个byte(8位)类型的数字,他的高24位里面都是随机数字,低8位才是实际的数据。java.lang.Integer.toHexString() 方法的参数是int(32位)类型,如果输入一个byte(8位)类型的数字,这个方法会把这个数字的高24为也看作有效位,这就必然导致错误,使用& 0XFF操作,可以把高24位置0以避免这样错误的发生。
至此,我们的doGet方法就写完了,将工程右键export出一个war包就可以部署到SAE上了。这时SAE会给我们一个链接,这个链接就是填在图1中的url上的。我们可以访问一下该地址。如果出现下图,就证明你部署成功,且微信能够访问到你的服务了。
500是你的请求没有传参数,不用担心。
接下来就是写doPost方法用以响应用户的请求。用户的请求会由微信通过post请求到我们,这里只写了一个简单的文本信息回应和关注事件的回应。
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// TODO Auto-generated method stub
req.setCharacterEncoding("UTF-8");
resp.setCharacterEncoding("UTF-8");
String responseMessage = "";
try {
responseMessage = MainService.doRequest(req);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
PrintWriter out = resp.getWriter();
out.print(responseMessage);
out.close();
out = null;
}
MainService
public static String doRequest(HttpServletRequest request) throws Exception {
String result = "请求未得到响应,请稍后再试";//默认返回的信息
Map<String,String> resultMap = MessageUtil.parseXML(request);//读取用户请求
String fromUserName = resultMap.get("FromUserName");
String toUserName = resultMap.get("ToUserName");
String msgType = resultMap.get("MsgType");
String content = resultMap.get("Content");
ResponseTextMessage responseTextMessage = new ResponseTextMessage();//组装返回信息
responseTextMessage.setCreateTime(new Date().getTime());
responseTextMessage.setFromUserName(toUserName);
responseTextMessage.setToUserName(fromUserName);
responseTextMessage.setMsgType(MessageUtil.RESPONSE_MESSAGE_TYPE_TEXT);
if(msgType.equals(MessageUtil.REQUEST_MESSAGE_TYPE_EVENT)){
String eventType = resultMap.get("Event");
if(eventType.equals("subscribe")){
responseTextMessage.setContent("感谢您的关注");
}else if(eventType.equals("unsubscribe")){
}
} else if(msgType.equals(MessageUtil.REQUEST_MESSAGE_TYPE_TEXT)){
if(content.equals("我是谁")){
responseTextMessage.setContent(fromUserName);
}
}
result = MessageUtil.textMessageToXML(responseTextMessage);//将返回信息转换成XML
return result;
}
>public class MessageUtil {
public static final String REQUEST_MESSAGE_TYPE_TEXT = "text";//请求消息为文本类型
public static final String RESPONSE_MESSAGE_TYPE_TEXT = "text";//响应消息为文本类型
public static final String REQUEST_MESSAGE_TYPE_EVENT = "event";//请求类型为事件
public static final String EVENT_TYPE_SUBSCRIBE = "subscribe";//订阅
public static final String EVENT_TYPE_UNSUBSCRIBE = "unsubscribe";//取消订阅
/**解析用户向公众号发送的请求消息
* @param request
* @return
* @throws Exception
*/
public static Map<String,String> parseXML(HttpServletRequest request) throws Exception{
Map<String,String> map = new HashMap<String,String>();
InputStream inputStream = request.getInputStream();//从request中取得输入流
SAXReader saxReader = new SAXReader();
Document document = saxReader.read(inputStream);//读取输入流
Element root = document.getRootElement();//获取根元素
List<Element> elementList = root.elements();//得到根元素的子节点
for(Element e:elementList){//遍历节点
map.put(e.getName(), e.getText());
}
inputStream.close();
inputStream = null;
return map;
}
/**将响应的文本消息转换成XML
* @param responseTextMessage
* @return
*/
public static String textMessageToXML(ResponseTextMessage responseTextMessage){
xstream.alias("xml", responseTextMessage.getClass());
return xstream.toXML(responseTextMessage);
}
/**
* 扩展xstream,使其支持CDATA块
*
* @date 2013-05-19
*/
private static XStream xstream = new XStream(new XppDriver() {
public HierarchicalStreamWriter createWriter(Writer out) {
return new PrettyPrintWriter(out) {
// 对所有xml节点的转换都增加CDATA标记
boolean cdata = true;
@SuppressWarnings("unchecked")
public void startNode(String name, Class clazz) {
super.startNode(name, clazz);
}
protected void writeText(QuickWriter writer, String text) {
if (cdata) {
writer.write("<![CDATA[");
writer.write(text);
writer.write("]]>");
} else {
writer.write(text);
}
}
};
}
});
这里主要用了dom4j将微信发给我们的XML解析成节点字符串,然后再用xstream将我们返回给微信的字符串组装成XML,其中扩展了xstream让其增加CDATA标记。
再将这个工程导出成war包部署到SAE上,我们在用自己的微信搜索我们的公众号关注时便会有事件响应,发送我是谁时,也会有事件响应了。
如果没有响应,请检查一下打出的war包里是否包含有dom4j和xstream的jar包,如果没有的话,右键web工程选择build path中的deployment assembly将两个jar包添加进去即可。
第一次写这种类型的博客,如有错误还请大家多多见谅。