第一部分:前导篇
一、注册公众号开发者
1、新手开发阶段建议申请测试账号进行公众号的开发,注册账号可更直观的了解公众号的结构功能等。
2、登录微信公众平台,使用邮箱进行注册,每一个邮箱只能注册一种项目(小程序、订阅号、服务号、企业微信)。
3、服务号相比公众号,拥有更全的功能,功能差异戳这里。
4、公众号许多功能需要用户认证后才能使用,因此不建议注册无法认证的个人账号。
5、 建议成为公众号开发者。公众号:微信公众平台->开发者工具->web开发者工具->绑定开发者微信
二、公众号开发关键文档和开发工具
1、微信公众平台:公众号的注册、开发管理等。
2、微信公众平台技术文档:提供公众号接口等。
3、微信硬件平台:硬件接入开发技术说明等。
4、微信web开发者工具:用于公众号嵌入网页的调试。
5、ngrok,nat123等内网穿透工具:将本地端口映射到公网,使用域名访问本地服务器。
第二部分:开发阶段
一、开发模式
1、编辑模式(默认):在微信公众平台进行自定义菜单、添加素材等。
2、开发者模式:微信公众平台->基本配置->服务器配置->启用,在该模式下,所有菜单、素材、更多插件功能等都需要调用微信接口生成。本文主要介绍开发者模式,并使用公众号测试账号进行开发,开发语言:java,服务器:tomcat,数据库:mySql.
二、服务器配置
1、搭建本地服务器
创建javaWeb项目,新建TestServlet用于服务器配置。配置服务器时参数必须是echostr,并将其输出
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String echostr = req.getParameter("echostr");
PrintWriter writer = resp.getWriter();
if (null != echostr && !"".equals(echostr)) {
writer.write(echostr);
}
writer.flush();
writer.close();
}
2、内网映射
下载并启动ngrok工具,输入命令ngrok http 80(公众号仅支持80和443端口),https协议也是相同命令(tomcat下重定向到443端口)。弹出如下窗口
3、尝试用ngrok提供的域名访问Test项目
4、进入公众号平台测试号管理界面,为测试账号进行服务器配置(接口配置信息),
URL:填写URL为以上测试时所用的url,不要加入额外参数,提交配置时,微信服务器会携带Token作为echostr等参数转发给testServlet,由testServlet返回该值。
Token:随便填写。
经过以上操作,服务器配置就已经完成了,关注公众号后,用户的操作将会被微信服务器转发给testServlet,我们可以通过testServlet响应用户的操作。
三、为用户提供可视化菜单,学习过程请参考微信公众平台技术文档
1、接口在线调试工具,
微信公众平台提供,接口在线调试工具,方便开发者调用微信接口创建菜单、接入硬件等操作。请打开“自定义菜单”进行学习。
2、获取access_token
在接口在线调试工具中选择“基础支持”,输入测试号所提供的appid,和secret,用于生成access_token,该值有效期为2小时。(Get请求)
3、自定义菜单
在接口在线调试工具中选择“自定义菜单,输入access_token。填入body,用于创建菜单,Body参数作为Post请求的参数。示例创建菜单body代码如下:
{
"button": [
{
"type": "view",
"name": "百度一下",
"url": "http://www.baidu.com"
},
{
"name": "我的助手",
"sub_button": [
{
"type": "click",
"name": "赞我吧",
"key": "zanwoba"
},
{
"type": "scancode_push",
"name": "扫一扫",
"key": "rselfmenu_0_1"
}
]
}
]
}
这便完成了创建菜单的操作
4、扫码关注测试公众号,效果如下
四、处理用户事件
当用户发送消息或点击菜单时,微信服务器会首先捕获取这些事件,并将这些事件数据转发到我们在接口配置信息中填入的testServlet中,我们可以获取这些事件,并进行响应的处理。本文模拟用户发送文本消息,并返回一段逆序的文本。实现效果如下:
结接收用户事件并响应大致流程如下:
1、用户发起事件、点击菜单或发送文本
2、微信服务器获取到该事件,检查是否需要将消息转发到第三方服务器(我们的TestServlet)。
3、将消息转发到我们的服务器,通过接口配置url(testServlet)获取微信服务器转发的消息
4、解析数据。
5、根据需要返回的数据格式,生成正确格式的数据。并将其写入输出流。
6、微信服务器获取到我们的服务器的数据,将其返回给用户。
7、用户接收到经处理过的数据。
以下代码是获取接口配置信息、获取用户事件、返回用户事件的代码,之后讲解具体实现逻辑
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) {
PrintWriter writer = null;
resp.setCharacterEncoding("UTF-8");
resp.setContentType("text/html;charset=UTF-8");
resp.setHeader("Content-Type", "text/html;charset=UTF-8");
String echostr = req.getParameter("echostr");
try {
writer = resp.getWriter();
if (null != echostr && !"".equals(echostr)) {
writer.write(echostr);//如果当前是接口配置,则直接输入返回值
writer.flush();
writer.close();
return;
}
//接收微信服务器转发的请求
String data = acceptRequest(req);
System.out.println("公众号用户请求消息为: " + data);
Map map = XmlHelper.parse(data);
//给微信服务器返回消息,并由微信服务器将消息转发给用户。
callback(writer, data);
} catch (Exception e) {
if (null != writer) {
writer.write("success");
}
}finally{
if (null != writer) {
writer.flush();
writer.close();
}
}
}
1、在TestServlet中获取用户发送的数据
private String acceptRequest(HttpServletRequest req) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(req.getInputStream()));
String sb = "";
StringBuffer stringBuffer = new StringBuffer();
while((sb = br.readLine()) != null){
System.out.println(sb);
stringBuffer.append(sb);
}
br.close();
return stringBuffer.toString();
}
在testServlet的doPost()方法中添加此方法,并在控制台输出该数据,数据如下
1504950429
6463712875121877522
2、解析用户数据格式,并保存起来。
由于该数据并未标准和xml格式,本文对该文本格式文本进行了简单的解析,并以key-value形式保存在map中。本段代码并不能很好的解析所有微信事件数据格式。
package com.uyeh.wx.util;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class XmlHelper {
private static String[] attributs_array = new String[] { "ToUserName", "FromUserName", "CreateTime", "MsgType",
"MsgId", "Content", "PicUrl", "LocationX", "LocationY", "Scale", "Label", "Title", "Description", "Url",
"MediaId", "Format", "Recognition", "Event", "EventKey", "Ticket","ScanCodeInfo","ScanResult","ScanType",
"DeviceType", "DeviceID","SessionID","OpenID"};
private static List list = new ArrayList<>();
private static String cdata = "") && text.contains("" + key + ">");
}
private static String get(String text, String key) {
if (containKey(text, key)) {
String value = text.substring(text.indexOf("<" + key + ">") + ("<" + key + ">").length(),
text.lastIndexOf("" + key + ">"));
if (null != value && value.contains(cdata) && !key.equals("ScanCodeInfo")) {
value = value.substring(value.indexOf(cdata) + (cdata).length(), value.lastIndexOf("]]>"));
list.add(key);
}
return value;
}
return null;
}
/**
* 生成微信端需要的数据格式
* @param map
* @return
*/
public static String generate(Map map) {
StringBuffer sb = new StringBuffer();
if (null != map && map.size() > 0) {
sb.append("");
for (String key : map.keySet()) {
if (key.equals("ScanResult") || key.equals("ScanType")) {
continue;
}
sb.append("<" + key + ">");
if (list.contains(key)) {
sb.append(cdata);
}
sb.append(map.get(key));
if (list.contains(key)) {
sb.append("]]>");
}
sb.append("" + key + ">");
}
sb.append(" ");
}
return sb.toString();
}
/**
* 解析公共号发过来的数据
*
* @param text
* @return
*/
public static Map parse(String text) {
Map map = new HashMap<>();
String[] array = attributs_array;
for (String key : array) {
if (containKey(text, key)) {
String value = get(text, key);
map.put(key, value);
}
}
return map;
}
}
3、给用户返回数据
在本例中,只需要给用户的数据进行逆序转换,只需要将FromUserName和ToUserName进行交换,并反转text文本即可。
private void callback(Writer writer, String data) throws Exception {
Map map = XmlHelper.parse(data);
if (map.containsKey("ToUserName") && map.containsKey("FromUserName")) {
String to = map.get("ToUserName");
String from = map.get("FromUserName");
map.put("FromUserName", to);
map.put("ToUserName", from);
}
switch (map.get("MsgType")) {
case "text":
//1、如果是文字,刚逆序返回
responseText(writer, map);
break;
case "image":
//2、如果是图片,则返回图片
responseImage(writer,map);
break;
case "event":
responseEvent(writer, map);
switch (map.get("Event")) {
case "VIEW":
break;
case "CLICK":
break;
case "scancode_push":
break;
case "scancode_waitmsg":
break;
default:
break;
}
break;
default:
break;
}
}
private void responseText(Writer writer, Map map) throws IOException {
String responseText = "success";
if (map.containsKey("MsgType") && map.containsKey("Content") && map.get("MsgType").equals("text")) {
String type = map.get("Content");
map.put("Content", new StringBuffer(type).reverse().toString());
}
responseText = XmlHelper.generate(map);
System.out.println("返回公众号数据为:" + responseText);
writer.append(responseText);
}
通过以上处理,大致完成了公众号最基本功能的设计。