微信公众号之后台开发
一、公众号后台配置
开发第一步,微信服务器要验证服务器是否有效。登录微信公众平台,在开发-基本设置页面,服务器配置项填写: 服务器地址(URL) 、令牌(Token) 、消息加解密密钥(EncodingAESKey)
针对URL开发两个接口:
GET请求接口:用来做服务器有效性验证
POST请求接口:用来接收微信服务器发送来的消息。
Token可以任意填写,用作生成签名
EncodingAESKey手动填写或随机生成,用作消息体加解密密钥。
现在提交肯定是验证token失败,因为还需要完成代码逻辑。
二、使用内网穿透
这里使用NATAPP内网穿透,文档齐全,很简单。
三、引入依赖
<!--xml解析-->
<dependency>
<groupId>org.dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>2.1.3</version>
</dependency>
<!--对象转xml-->
<dependency>
<groupId>com.thoughtworks.xstream</groupId>
<artifactId>xstream</artifactId>
<version>1.4.10</version>
</dependency>
四、服务器校验
校验逻辑流程
校验逻辑实现
@RestController
@RequestMapping("/WeChat")
public class WxController {
//与微信公众号 开发 基本配置中配置token保持一致
private static final String TOKEN = "token";
private static final char[] HEX_DIGITS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
/**
* 服务器校验
* 安全校验,标识请求是否来源于微信
*
* @param request
* @param response
* @throws UnsupportedEncodingException
*/
@GetMapping("/wx")
public void verifyToken(HttpServletRequest request, HttpServletResponse response) throws UnsupportedEncodingException {
request.setCharacterEncoding("UTF-8");
//微信加密签名
String signature = request.getParameter("signature");
//时间戳
String timestamp = request.getParameter("timestamp");
//随机数
String nonce = request.getParameter("nonce");
//随机字符串
String echostr = request.getParameter("echostr");
PrintWriter out = null;
try {
out = response.getWriter();
if (checkSignature(signature, timestamp, nonce)) {
out.write(echostr);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
out.close();
}
}
/**
* 校验微信签名
*
* @param signature
* @param timestamp
* @param nonce
* @return
*/
private static boolean checkSignature(String signature, String timestamp, String nonce) {
String[] str = new String[]{TOKEN, timestamp, nonce};
//对token、timestamp、nonce三个参数进行字典序排序
Arrays.sort(str);
//将三个参数字符串拼接成一个字符串进行sha1加密
StringBuffer buffer = new StringBuffer();
for (int i = 0; i < str.length; i++) {
buffer.append(str[i]);
}
String temp = encode(buffer.toString());
//获得加密后的字符串与signature对比
return signature.equals(temp);
}
/**
* 对字符串进行加密
*
* @param str
* @return
*/
private static String encode(String str) {
if (str == null) {
return null;
}
MessageDigest messageDigest = null;
try {
messageDigest = MessageDigest.getInstance("SHA1");
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
messageDigest.update(str.getBytes());
byte[] digest = messageDigest.digest();
int len = digest.length;
StringBuilder buf = new StringBuilder(len * 2);
for (int j = 0; j < len; j++) {
buf.append(HEX_DIGITS[(digest[j] >> 4) & 0x0f]);
buf.append(HEX_DIGITS[digest[j] & 0x0f]);
}
return buf.toString();
}
}
五、消息接收
消息接收接口和服务器校验接口地址是一样的,只不过消息接收接口是一个 POST 请求。由于公众号后台配置消息加解密方式:明文模式,在后台收到的消息可以直接处理。
接收逻辑流程
1.收到微信服务器发来的消息之后,进行XML解析,提取出需要的信息。
2.根据发送的消息类型或业务做相关操作,再将操作的结果返回给微信服务器。
接收与回复文本消息格式
1.接受到的文本消息格式
<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>
</xml>
参数 | 是否必须 | 描述 |
---|---|---|
ToUserName | 是 | 开发者微信号 |
FromUserName | 是 | 发送方帐号(一个OpenID) |
CreateTime | 是 | 消息创建时间 (整型) |
MsgType | 是 | 消息类型,文本为text |
Content | 是 | 文本消息内容 |
MsgId | 是 | 消息id,64位整型 |
2.回复文本消息格式
收到粉丝消息后不想或者不能5秒内回复时,需回复“success”字符串
<xml>
<ToUserName><![CDATA[粉丝号]]></ToUserName>
<FromUserName><![CDATA[公众号]]></FromUserName>
<CreateTime>1460541339</CreateTime>
<MsgType><![CDATA[text]]></MsgType>
<Content><![CDATA[test]]></Content>
</xml>
参数 | 是否必须 | 描述 |
---|---|---|
ToUserName | 是 | 接收方帐号(收到的OpenID) |
FromUserName | 是 | 开发者微信号 |
CreateTime | 是 | 消息创建时间 (整型) |
MsgType | 是 | 消息类型,文本为text |
Content | 是 | 回复的消息内容(换行:在content中能够换行,微信客户端就支持换行显示) |
接收与回复逻辑实现
/**
* 接受微信发送的消息
*
* @param request
* @param response
* @throws Exception
*/
@PostMapping("/wx")
public void processTheMessage(HttpServletRequest request, HttpServletResponse response) throws Exception {
Map<String, String> parseXml = parseXml(request);
/**
* 接受到的文本消息格式
* <xml>
* <ToUserName><![CDATA[公众号]]></ToUserName>
* <FromUserName><![CDATA[粉丝号]]></FromUserName>
* <CreateTime>1460537339</CreateTime>
* <MsgType><![CDATA[text]]></MsgType>
* <Content><![CDATA[欢迎开启公众号开发者模式]]></Content>
* <MsgId>6272960105994287618</MsgId>
* </xml>
*/
//开发者微信号
String tousername = parseXml.get("ToUserName");
//发送方帐号(一个OpenID)
String fromusername = parseXml.get("FromUserName");
//消息创建时间 (整型)
String createTime = parseXml.get("CreateTime");
//消息类型,文本为text
String msgType = parseXml.get("MsgType");
//文本消息内容
String content = parseXml.get("Content");
//消息id,64位整型
String msgId = parseXml.get("MsgId");
System.out.println("开发者微信号: " + tousername);
System.out.println("发送方帐号: " + fromusername);
System.out.println("消息创建时间: " + createTime);
System.out.println("消息类型: " + msgType);
System.out.println("文本消息内容: " + content);
System.out.println("消息id: " + msgId);
/**
* 被动回复文本消息的格式
* <xml>
* <ToUserName><![CDATA[粉丝号]]></ToUserName>
* <FromUserName><![CDATA[公众号]]></FromUserName>
* <CreateTime>1460541339</CreateTime>
* <MsgType><![CDATA[text]]></MsgType>
* <Content><![CDATA[test]]></Content>
* </xml>
*
* 需注意ToUserName与FromUserName的设值
*/
Message message = new Message();
message.setToUserName(fromusername);
message.setFromUserName(tousername);
message.setCreateTime(createTime);
message.setMsgType(msgType);
message.setContent("Hello 管理员。");
WxController.xstream.alias("xml", Message.class);
String xml = WxController.xstream.toXML(message);
System.out.println(xml);
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
PrintWriter out = response.getWriter();
out.println(xml);
out.flush();
}
/**
* 解析微信发来的请求(XML)
*
* @param request
* @return
* @throws Exception
*/
private static Map<String, String> parseXml(HttpServletRequest request) throws Exception {
// 将解析结果存储在HashMap中
Map<String, String> map = new HashMap<String, String>();
// 从request中取得输入流
InputStream inputStream = request.getInputStream();
// 读取输入流
SAXReader reader = new SAXReader();
Document document = reader.read(inputStream);
//得到xml根元素
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;
}
/**
* 扩展xstream使其支持CDATA
*/
private static XStream xstream = new XStream(new XppDriver() {
@Override
public HierarchicalStreamWriter createWriter(Writer out) {
return new PrettyPrintWriter(out) {
// 对所有xml节点的转换都增加CDATA标记
boolean cdata = true;
@Override
public void startNode(String name, Class clazz) {
super.startNode(name, clazz);
}
@Override
protected void writeText(QuickWriter writer, String text) {
if (cdata) {
writer.write("<![CDATA[");
writer.write(text);
writer.write("]]>");
} else {
writer.write(text);
}
}
};
}
});
六、 执行测试
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/137093.html