有一个这样简单的需求,用户发送消息到公众号,经过Java后台的逻辑处理后,返回处理结果给用户。
1. 编写接入代码
编写代码之前我们需要引入两个依赖。
<!--编解码-->
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.10</version>
</dependency>
<!--解析xml-->
<dependency>
<groupId>org.dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>2.0.0</version>
</dependency>
我们需要向微信后台暴露一个接口,以供微信调用。这里采用get
请求方式,请求路径自己定义。
微信会给我们传signature
、timestamp
、nonce
和echostr
四个参数,我们需要做的是用前三个参数做校验,校验成功之后原封不动的返回echostr
。
这里需要自定义设置一个TOKEN的值,后续配置用得到。
@RestController
public class WxController {
public static final String TOKEN = "123456";
@GetMapping("/wx/echo")
public String echo(HttpServletRequest req, HttpServletResponse resp) {
// 1.获取微信传入的4个参数
String signature = req.getParameter("signature");
String timestamp = req.getParameter("timestamp");
String nonce = req.getParameter("nonce");
String echostr = req.getParameter("echostr");
// 2.用timestamp, nonce, signature进行校验
boolean result = check(timestamp, nonce, signature);
if (result) {
// 3.校验成功返回echostr
return echostr;
}
return "error!";
}
public static boolean check(String timestamp, String nonce, String signature) {
// 1.按字典序对TOKEN, timestamp和nonce排序
String[] arr = new String[]{TOKEN,timestamp,nonce};
Arrays.sort(arr);
// 2.将3个参数拼成一个字符串进行sha1加密
String str = arr[0]+arr[1]+arr[2];
// 3.用commons-codec包中的工具类进行sha1加密
str = DigestUtils.sha1Hex(str);
// 4.将加密后的字符串和signature比较
System.out.println(signature);
return str.equalsIgnoreCase(signature);
}
}
2.公众号配置
登陆公众号后台,选择基本配置。URL填入你的接口地址,Token与步骤1中定义的TOKEN保持一致。简单起见,EncodingAESKey随机生成,消息加密方式选择明文模式。
点击提交,提示提交成功。
由于URL使用的是80端口,而后台服务不一定是80端口,所以这里需要用nginx做一些反向代理。
location ~ / {
proxy_pass http://127.0.0.1:8080;
}
3.接收用户消息
上述操作已经成功连接微信与Java后台,接下来接受用户发送的消息。
当普通微信用户向公众账号发消息时,微信服务器将 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>
<MsgDataId>xxxx</MsgDataId>
<Idx>xxxx</Idx>
</xml>
我们写一个post接口,用来解析消息、逻辑处理以及回复消息。路径与步骤二中的URL一致。
@PostMapping("/wx/echo")
public String echoPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
// 1.解析消息
Map<String, String> param = parseRequest(req.getInputStream());
// 2.逻辑处理(可以根据自身逻辑进行处理,这里略)
// ...........
// 3.回复消息
String textMsg = "<xml>" +
"<ToUserName><![CDATA["+ param.get("FromUserName")+"]]></ToUserName>" +
"<FromUserName><![CDATA["+ param.get("ToUserName")+"]]></FromUserName>" +
"<CreateTime>12345678</CreateTime>" +
"<MsgType><![CDATA[text]]></MsgType>" +
"<Content><![CDATA[你好]]></Content>" +
"</xml>";
return textMsg;
}
// 利用dom4j中的类进行解析
public static Map<String, String> parseRequest(InputStream is) {
Map<String,String> map = new HashMap();
// 1. 通过io流得到文档对象
SAXReader saxReader = new SAXReader();
Document document = null;
try {
document = saxReader.read(is);
} catch (DocumentException e) {
e.printStackTrace();
}
// 2.通过文档对象得到根节点对象
Element root = document.getRootElement();
// 3.通过根节点对象获取所有子节点对象
List<Element> elements = root.elements();
// 4.将所有节点放入map
for (Element element : elements) {
map.put(element.getName(), element.getStringValue());
}
return map;
}
上述代码,无论我们发送什么消息给公众号,公众号都将回复你好
。
由于微信会进行超时重试,因此需要进行排重。建议使用redis进行排重,这里不做演示。