首先需要确认一点,一旦接入第三方服务器,微信就认为你已经具备了开发能力,像自动回复、关键词回复、自定义菜单这些功能,微信公众平台就不再提供了(需要开发者调用相关接口),停用服务器之后,这些功能也就恢复了,二者是互斥的。
一 填写服务器配置
依照官方文档步骤一配置时会显示URL验证失败,原因是在设置第三方服务器时微信会发送一个GET请求给第三方服务器,只有收到服务器正确回应后才能设置成功。 规则如下:
开发者通过检验signature对请求进行校验(下面有校验方式)。若确认此次GET请求来自微信服务器,请原样返回echostr参数内容,则接入生效,成为开发者成功,否则接入失败。
二 URL接入验证
由官方文档介绍可知,当我们填入url与token的值,并提交后,微信发送的GET请求携带4个参数,而signature参数结合了timestamp参数、nonce参数来做的加密签名,我们在后台需要对该签名进行校验,看是否合法。通过微信传入的timestamp与nonce做相同算法的加密操作,若结果与微信传入的signature相同,即为合法,则原样返回echostr参数,代表接入成功,否则不做处理,则接入失败。
接入验证代码示例
String sha1 = SHA1Util.getSHA1(TOCKEN, request.getParameter("timestamp"), request.getParameter("nonce"));
if (!request.getParameter("signature").equals(sha1)) {
log.warn("未知来源请求");
return;
}
if (request.getParameter("echostr") != null) {
response.getWriter().write(request.getParameter("echostr"));
return;
}
SHA1工具类示例
import lombok.extern.slf4j.Slf4j;
import java.security.MessageDigest;
import java.util.Arrays;
public class SHA1Util {
/**
* 用SHA1算法生成安全签名
*
* @param params 包含用于校验的字符串,包括: token,timestamp,nonce等
* @return 安全签名
*/
public static String getSHA1(String... params) {
try {
String[] array = params.clone();
StringBuilder sb = new StringBuilder();
// 字符串字典排序
Arrays.sort(array);
for (String anArray : array) {
if (anArray != null) {
sb.append(anArray);
}
}
String str = sb.toString();
// SHA1签名生成
MessageDigest md = MessageDigest.getInstance("SHA-1");
md.update(str.getBytes());
byte[] digest = md.digest();
StringBuilder hexstr = new StringBuilder();
String shaHex;
for (byte aDigest : digest) {
shaHex = Integer.toHexString(aDigest & 0xFF);
if (shaHex.length() < 2) {
hexstr.append(0);
}
hexstr.append(shaHex);
}
return hexstr.toString();
} catch (Exception e) {
//...
}
}
}
三 接收微信推送消息
在公众号接入第三方服务器后,微信会将消息以XML形式推送的我们的服务器,流程如下图所示:
如官方文档所示,微信会将数据以XML格式放在POST请求体中,我们可以使用SpringMvc解析XML参数或手动使用XML工具类解析数据。由于微信发送的请求类型不是application/xml,暂未找到合适自动解析方式,所以使用手动解析。
XML工具类示例
import java.io.StringReader;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Unmarshaller;
public static <T> T convertToObject(String xml, Class<T> clazz) {
T _clazz = null;
StringReader reader = null;
try {
JAXBContext context = JAXBContext.newInstance(clazz);
Unmarshaller unmarshaller = context.createUnmarshaller();
reader = new StringReader(xml);
_clazz = (T) unmarshaller.unmarshal(reader);
} catch (Exception e) {
throw new RuntimeException(e);
}finally{
if(reader!=null){
reader.close();
}
}
return _clazz;
}
不同类型的消息返回的数据不一定相同,如需要接收多种消息,建议按消息类型维护不同的实体类接收参数并抽取公共参数(如CreateTime,MsgType等)。
接收实体类示例
import lombok.Data;
import lombok.ToString;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
@Data
@XmlRootElement(name = "xml")
public class WechatMessageVo{
/**
* 开发者微信号
*/
@XmlElement(name="ToUserName")
private String ToUserName;
/**
* 发送方帐号(一个OpenID)
*/
@XmlElement(name="FromUserName")
private String FromUserName;
/**
* 消息创建时间 (整型)
*/
@XmlElement(name="CreateTime")
private Long CreateTime;
/**
* 消息类型
*/
@XmlElement(name="MsgType")
private String MsgType;
/**
* 消息内容
*/
@XmlElement(name="Content")
private String Content;
/**
* 消息id
*/
@XmlElement(name="MsgId")
private String MsgId;
}
拿到消息内容后可按不同的业务逻辑具体实现