近期微信公众平台十分火爆,许多公司企业都纷纷使用这一平台进行一些宣传营销活动,以下是我最近研究公众平台各接口的一些心得成果,在此给大家分享一下,如有错漏之处还请大家指正。
首先,想要测试必须要申请一个公众号,申请过程我就不赘述了,网上有许多教程,我申请的是个人类型的订阅号(申请周期大概1周左右)。申请成功后,登录进入公众平台页面,在开发者中心,开启开发模式,此时需要填写两项接口配置信息:URL和Token,其中URL是开发者用来接受微信服务器数据的接口URL,Token则由开发者任意填写,用来生成签名之后用作进行认证(让微信公众平台确认该服务器资源属于你)。下面的截图截自微信公众平台的开发文档,是对验证时数据加密的说明。
文档中只提供了php的验证,以下是java的验证方式:
/** * 检查签名 * @param request * @param response * @throws IOException */ private void checkSignature(HttpServletRequest request, HttpServletResponse response) throws IOException { String signature = request.getParameter("signature"); String timestamp = request.getParameter("timestamp"); String nonce = request.getParameter("nonce"); String echostr = request.getParameter("echostr"); String token = "zhanglan123456789"; ArrayList验证通过后,在开发者中心找到接口测试申请系统,点击进入。list = new ArrayList (); list.add(token); list.add(timestamp); list.add(nonce); Collections.sort(list,new Comparator () { public int compare(String o1, String o2) { return o1.compareTo(o2); } }); String tempStr = list.get(0)+list.get(1)+list.get(2); tempStr = SHA1.encode(tempStr); if(signature.equals(tempStr)){ response.getWriter().print(echostr); }else{ response.getWriter().print(tempStr); } }
申请成功后,你会获得两个参数:appId和appSecret,使用这两个参数能够获取access_token,获取接口如下:
http://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=yourappid&secret=yoursecret
access_token获取之后,其他的接口在调用的时候都要带着access_token参数。
接下来,我们来简单介绍下微信公众平台提供的各种服务和接口。
首先是基础接口,包括接收消息,回复消息,接收事件。
用户向公众号发送消息(文本、语音、图片)的时候,公众平台会向你之前填写的URL上发送xml格式数据包,格式如下(以文本消息为例):
1348831860 1234567890123456
其中ToUserName是公众号微信号,FromUserName是用户的open_id(非微信号,是用户微信号与关注的公众号微信号加密后的的一串字符)MsgType是消息类型,包含以下类型:text(文本)、image(图片)、voice(语音)、video(视频)、location(经纬度信息),除了接受消息,公众号还能接收推送事件,如关注/取消关注事件、扫描带参数二维码事件、上报地理位置事件、自定义菜单事件等,在URL接收到用户发来的消息或事件推送时,可以通过返回特定结构的数据包来进行回复。
正式的订阅号服务号在经过微信认证后可以开启自定义菜单,自定义菜单可以通过调用接口来创建:
private static void createMenu(){ PostMethod post = new PostMethod("http://api.weixin.qq.com/cgi-bin/menu/create?access_token="+token); post.getParams().setParameter(HttpMethodParams.HTTP_CONTENT_CHARSET, "UTF-8"); String json = ""; JsonObject obj = new JsonObject(); JsonArray arr1 = new JsonArray(); JsonObject obj1 = new JsonObject(); obj1.addProperty("type", "view"); obj1.addProperty("name", "登录"); obj1.addProperty("url", "http://www.baidu.com/"); arr1.add(obj1); JsonObject obj2 = new JsonObject(); obj2.addProperty("type", "view"); obj2.addProperty("name", "商品查询"); obj2.addProperty("url", "http://www.baidu.com/"); arr1.add(obj2); JsonObject obj3 = new JsonObject(); obj3.addProperty("name", "菜单"); JsonArray arr2 = new JsonArray(); JsonObject obj4 = new JsonObject(); obj4.addProperty("type", "click"); obj4.addProperty("name", "卷烟查询"); obj4.addProperty("key", "INSPUR_SEARCH"); JsonObject obj5 = new JsonObject(); obj5.addProperty("type", "view"); obj5.addProperty("name", "搜索"); obj5.addProperty("url", "http://www.baidu.com/"); JsonObject obj6 = new JsonObject(); obj6.addProperty("type", "view"); obj6.addProperty("name", "视频"); obj6.addProperty("url", "http://www.youku.com/"); arr2.add(obj4); arr2.add(obj5); arr2.add(obj6); obj3.add("sub_button", arr2); arr1.add(obj3); obj.add("button", arr1); json = obj.toString(); System.out.println("json-->"+json); post.setRequestBody(json); try { client.executeMethod(post); String str = post.getResponseBodyAsString(); System.out.println(str); } catch (HttpException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } }上面的json参数是用gson这个jar包来组织的,组织后的json如下
服务号在经过微信认证后可以获取全部高级接口权限:包括语音识别、客服接口、OAuth2.0网页授权、生成带参数二维码、获取用户地理位置、获取用户基本信息、获取关注者列表、用户分组等。
语音识别开启后,用户发送语音消息时,公众平台会在后台将识别后的文本传给URL(之后的代码会演示)。
使用客服接口,可以在用户发送消息后48小时内对用户进行回复,次数不限,客服接口的调用方式如下。
private static void sendTextMsg(){
PostMethod post = new PostMethod("http://api.weixin.qq.com/cgi-bin/message/custom/send?access_token="+token);
post.getParams().setParameter(HttpMethodParams.HTTP_CONTENT_CHARSET, "UTF-8");
String json = "{"touser":"orHeruJIJWD_juqOpcZKzyTmdkiQ", "msgtype": "text", "text": {"content": ""
+"你好 这是一条客服消息"
+""}}";
post.setRequestBody(json);
try {
client.executeMethod(post);
} catch (HttpException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
公众号开发者可以通过网页授权接口在用户访问第三方页面时获取当前用户的基本信息。
生成带参数二维码:
private static String getCode(int id){
String res = "";
PostMethod post = new PostMethod("http://api.weixin.qq.com/cgi-bin/qrcode/create?access_token="+token);
String json = "{"action_name": "QR_LIMIT_SCENE", "action_info": {"scene": {"scene_id": "+id+"}}}";
post.setRequestBody(json);
try {
client.executeMethod(post);
res = post.getResponseBodyAsString();
} catch (HttpException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
System.out.println(res);
return res;
}
上面的参数是场景id,返回的是一个ticket,通过这个ticket我们可以获取一个二维码,该二维码在被用户扫面时,公众平台会将该事件推送给URL并且带着场景id参数。
获取用户地理位置开启后,用户再关注该公众号时回被提示是否允许公众号获取其位置信息,如果允许,用在每次进入和公众号的会话页面时或者每隔一段时间向URL发送位置信息(后面的代码会演示)。
获取用户基本信息接口:
private static String getUserInfo(String openId){
String res = "";
GetMethod get = new GetMethod("http://api.weixin.qq.com/cgi-bin/user/info?lang=zh_CN&access_token="+token+"&openid="+openId);
get.getParams().setParameter(HttpMethodParams.HTTP_CONTENT_CHARSET, "UTF-8");
try {
client.executeMethod(get);
res = get.getResponseBodyAsString();
} catch (HttpException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
System.out.println(res);
return res;
}
返回如下格式:
获取关注者列表接口:
private static String getUserList(){
String res = "";
GetMethod get = new GetMethod("http://api.weixin.qq.com/cgi-bin/user/get?access_token="+token);
get.getParams().setParameter(HttpMethodParams.HTTP_CONTENT_CHARSET, "UTF-8");
try {
client.executeMethod(get);
res = get.getResponseBodyAsString();
} catch (HttpException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
System.out.println(res);
return res;
}
返回如下格式
用户分组接口包括创建分组、获取所有分组、获取用户所在分组、修改分组名字、移动用户分组等:
/**
* 创建分组
* @param groupName
* @return
*/
private static String createGroup(String groupName){
String res = "";
PostMethod post = new PostMethod("http://api.weixin.qq.com/cgi-bin/groups/create?access_token="+token);
post.getParams().setParameter(HttpMethodParams.HTTP_CONTENT_CHARSET, "UTF-8");
String json = "{"group":{"name":""+groupName+""}}";
post.setRequestBody(json);
try {
client.executeMethod(post);
res = post.getResponseBodyAsString();
} catch (HttpException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
System.out.println(res);
return res;
}
/**
* 获取所有分组
* @return
*/
private static String getGroupList(){
String res = "";
GetMethod get = new GetMethod("http://api.weixin.qq.com/cgi-bin/groups/get?access_token="+token);
get.getParams().setParameter(HttpMethodParams.HTTP_CONTENT_CHARSET, "UTF-8");
try {
client.executeMethod(get);
res = get.getResponseBodyAsString();
} catch (HttpException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
System.out.println(res);
return res;
}
/**
* 去用户所在分组
* @param openId
* @return
*/
private static String getGroupByOpenId(String openId){
String res = "";
PostMethod post = new PostMethod("http://api.weixin.qq.com/cgi-bin/groups/getid?access_token="+token);
post.getParams().setParameter(HttpMethodParams.HTTP_CONTENT_CHARSET, "UTF-8");
String json = "{"openid":""+openId+""}";
post.setRequestBody(json);
try {
client.executeMethod(post);
res = post.getResponseBodyAsString();
} catch (HttpException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
System.out.println(res);
return res;
}
/**
* 修改分组名称
* @param id
* @param name
* @return
*/
private static String changeGroupName(int id,String name){
String res = "";
PostMethod post = new PostMethod("http://api.weixin.qq.com/cgi-bin/groups/update?access_token="+token);
post.getParams().setParameter(HttpMethodParams.HTTP_CONTENT_CHARSET, "UTF-8");
String json = "{"group":{"id":"+id+","name":""+name+""}}";
post.setRequestBody(json);
try {
client.executeMethod(post);
res = post.getResponseBodyAsString();
} catch (HttpException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
System.out.println(res);
return res;
}
/**
* 移动用户分组
* @param id
* @param openId
* @return
*/
private static String moveUserToGroup(int id,String openId){
String res = "";
PostMethod post = new PostMethod("http://api.weixin.qq.com/cgi-bin/groups/members/update?access_token="+token);
post.getParams().setParameter(HttpMethodParams.HTTP_CONTENT_CHARSET, "UTF-8");
String json = "{"openid":""+openId+"","to_groupid":"+id+"}";
post.setRequestBody(json);
try {
client.executeMethod(post);
res = post.getResponseBodyAsString();
} catch (HttpException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
System.out.println(res);
return res;
}
以下代码是我的后台自动回复的逻辑,注释很清楚我就不赘述了。
/** * 接收用户发的消息 * @param request * @param response * @throws IOException */ private void receiveMsg(HttpServletRequest request, HttpServletResponse response) throws IOException { ServletInputStream in = request.getInputStream(); String xmlMsg = Tools.inputStream2String(in);//获取接收的数据包,解析成xml字符串 Map下面是我申请的测试账号的二维码,大家可以扫码关注后进行测试,该二维码带有参数:123(场景id):inMap = parseXml(xmlMsg);//将xml转换为map String msgType = inMap.get("MsgType");//接收的消息类型 Map reply = new HashMap ();//reply用来组织返回的数据,以达到自动回复的目的 //event表示接收的是推送事件 if(msgType.equals("event")){ if(inMap.get("Event").equals("subscribe")){ //该推送事件为关注事件 reply.put("Content", "欢迎订阅未老先肥的公众号,回复1、2、3、4、5、6、7、8、9或图片可以查看特定信息"); }else if(inMap.get("Event").equalsIgnoreCase("click") && inMap.get("EventKey").equals("INSPUR_INFO")){ //该推送事件为自定义菜单点击事件,并且eventkey为INSPUR_INFO reply.put("Content", "我们的官网是:http://www.inspur.com/"); }else if(inMap.get("Event").equalsIgnoreCase("click") && inMap.get("EventKey").equals("INSPUR_SEARCH")){ //该推送事件为自定义菜单点击事件,并且eventkey为INSPUR_SEARCH reply.put("Content", "红塔山(软经典1956) 70 一条n红塔山(硬经典1956) 70 一条n红塔山(HTS100) 100一条n红塔山(软经典100) 100一条n红塔山(硬经典100) 100一条"); }else if(inMap.get("Event").equals("SCAN")){ //该推送事件为扫码事件 reply.put("Content", "扫码场景id为:"+inMap.get("EventKey")); }else if(inMap.get("Event").equals("LOCATION")){ //该推送事件为用户上报经纬度事件 reply.put("Content", "您的位置:n经度:"+inMap.get("Longitude")+"n纬度:"+inMap.get("Latitude")); }else{//其他推送事件直接返回发来的数据包 reply.put("Content", xmlMsg); } }else if(msgType.equals("text")){//text表示接收的是文本消息 int tempInt = 0; try { tempInt = Integer.parseInt(inMap.get("Content")); } catch (Exception e) { e.printStackTrace(); } //根据发来的文本信息来确定返回 switch (tempInt) { case 1: reply.put("Content", "你回复了数字1,你的openId为"+inMap.get("FromUserName")); break; case 2: SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String date = df.format(new Date()); reply.put("Content", "你回复了数字2,当前时间为:"+date); break; case 3: reply.put("Content", "你回复了数字3,马年大吉"); break; case 4: reply.put("Content", "你回复了数字4,恭喜发财"); break; case 5: reply.put("Content", "你回复了数字5,大吉大利"); break; case 6: reply.put("Content", "你回复了数字6,恭贺新禧"); break; case 7: reply.put("Content", "你回复了数字7,龙马精神"); break; case 8: reply.put("Content", "你回复了数字8,马到功成"); break; case 9: reply.put("Content", "你回复了数字9,马上发财"); break; default: break; } }else if(msgType.equals("voice")){//voice表示是语音消息 reply.put("Content", "您刚才说的是:"+inMap.get("Recognition"));//Recognition是解析后的语音文本 }else { reply.put("Content", xmlMsg);//其他格式的直接返回穿过来的数据包 } // reply.put("Content", "你的openId为"+inMap.get("FromUserName")); reply.put("ToUserName", inMap.get("FromUserName")); reply.put("FromUserName", inMap.get("ToUserName")); reply.put("CreateTime", new Date().getTime()+""); reply.put("MsgType", "text"); // reply.put("FuncFlag", "0"); String outMsg = generatorXml(reply); response.getWriter().print(outMsg); } private Map parseXml(String xmlData){ Document doc = null; try { doc = DocumentHelper.parseText(xmlData); } catch (DocumentException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } Element rootEle = doc.getRootElement(); List list = rootEle.elements(); HashMap map = new HashMap (); for(Element e:list){ map.put(e.getName(), e.getText()); } return map; } private String generatorXml(Map reply){ Document document = DocumentHelper.createDocument(); Element root = document.addElement("xml"); for(Entry entry:reply.entrySet()){ Element e = root.addElement(entry.getKey()); e.setText(entry.getValue()); } return document.getRootElement().asXML(); }
最后,欢迎大家扫码关注我的公众账号:未老先肥