1.微信公众平台->开发->基本配置->服务器配置
服务端使用java开发的,第一次服务器的验证代码如下:
@GetMapping("/checkWechat")
public void wechatCallbackApi(@RequestParam("signature") String
signature,
@RequestParam("timestamp") String timestamp,
@RequestParam("nonce") String nonce,
@RequestParam("echostr") String echostr,HttpServletResponse response)
throws ServletException, IOException {
log.info("[服务器配置],进入是数据signature{},timestamp{},nonce{},echostr{}",signature,timestamp,nonce,echostr);
PrintWriter print;
// 通过检验signature对请求进行校验,若校验成功则原样返回echostr,表示接入成功,否则接入失败
if (signature != null && CheckoutUtil.checkSignature(signature,
timestamp, nonce)) {
log.info("[服务器配置],验证通过","success");
try {
print = response.getWriter();
print.write(echostr);
print.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}
注意CheckOutUtil验证工具类中的token要和上面微信公众号中填写的token一致。
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class CheckoutUtil {
// 与接口配置信息中的Token要一致
private static String token = "XXXXXX";
/**
* 验证签名
*
* @param signature
* @param timestamp
* @param nonce
* @return
*/
public static boolean checkSignature(String signature, String timestamp, String nonce) {
String[] arr = new String[] { token, timestamp, nonce };
// 将token、timestamp、nonce三个参数进行字典序排序
// Arrays.sort(arr);
sort(arr);
StringBuilder content = new StringBuilder();
for (int i = 0; i < arr.length; i++) {
content.append(arr[i]);
}
MessageDigest md = null;
String tmpStr = null;
try {
md = MessageDigest.getInstance("SHA-1");
// 将三个参数字符串拼接成一个字符串进行sha1加密
byte[] digest = md.digest(content.toString().getBytes());
tmpStr = byteToStr(digest);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
content = null;
// 将sha1加密后的字符串可与signature对比,标识该请求来源于微信
return tmpStr != null ? tmpStr.equals(signature.toUpperCase()) : false;
}
/**
* 将字节数组转换为十六进制字符串
*
* @param byteArray
* @return
*/
private static String byteToStr(byte[] byteArray) {
String strDigest = "";
for (int i = 0; i < byteArray.length; i++) {
strDigest += byteToHexStr(byteArray[i]);
}
return strDigest;
}
/**
* 将字节转换为十六进制字符串
*
* @param mByte
* @return
*/
private static String byteToHexStr(byte mByte) {
char[] Digit = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
char[] tempArr = new char[2];
tempArr[0] = Digit[(mByte >>> 4) & 0X0F];
tempArr[1] = Digit[mByte & 0X0F];
String s = new String(tempArr);
return s;
}
public static void sort(String a[]) {
for (int i = 0; i < a.length - 1; i++) {
for (int j = i + 1; j < a.length; j++) {
if (a[j].compareTo(a[i]) < 0) {
String temp = a[i];
a[i] = a[j];
a[j] = temp;
}
}
}
}
}
编写好响应服务器配置的接口后,要确保可以在外网能够访问到。就可以在微信中点击确定,成功的话,微信会提示配置成功。
服务器配置成功后我们就可以将上面的服务器响应接口改成一个接收消息处理的接口,如下,我们可以对不同的事件通知进行处理。
@PostMapping("/checkWechat")
public void wechatCallbackApi(@RequestBody String notifyData) throws ServletException, IOException {
log.info("[服务器配置],进入审核事件通知{}", notifyData);
PushMessage message = (PushMessage) XmlUtil.fromXML(notifyData, PushMessage.class);
// String msgid = message.getFromUserName() + message.getCreateTime() + message.getEvent();
Objects.requireNonNull(message, "[微信接收会员信息事件通知],返回为空!");
log.info("[服务器配置],进入审核事件通知转对象信息结果{}", message);
if ("submit_membercard_user_info".equals(message.getEvent())) {// 激活卡的通知
// 可以查询用户信息
cardService.getOpenCardInfo(message);
} else if ("update_member_card".equals(message.getEvent())) {// 修改会员卡信息
log.info("[服务器配置],修改用户会员卡信息推送{}", message);
} else if ("card_not_pass_check".equals(message.getEvent())) {// 会员卡审核不通
MpMemberCard card = mpMemberCardRepository.findByCardId(message.getCardId());
if(card!=null){
card.setState(false);
mpMemberCardRepository.save(card);
}else{
log.info("[服务器配置],会员卡审核不通过,未查询到该卡信息{}", message);
}
} else if ("card_pass_check".equals(message.getEvent())) {// 会员卡审核通过
MpMemberCard card = mpMemberCardRepository.findByCardId(message.getCardId());
if(card!=null){
card.setState(true);
mpMemberCardRepository.save(card);
}else{
log.info("[服务器配置],会员卡审核通过,未查询到该卡信息{}", message);
}
} else if ("card_sku_remind".equals(message.getEvent())) {// 库存报警给管理员发个通知吧
Notice notice = new Notice();
notice.setContent(message.getCardId() + "库存不过百");
notice.setCreatetime(DateUtil.currentDate());
notice.setCreater(1);
noticeRepository.save(notice);
} else {
log.info("[服务器配置],会员卡信息推送{}", message);
}
}
这里推荐可以使用simpleFrameWork解析xml。
1.我们可以将要解析的数据编成一个类如上面的PushMessage.class。
package com.ddzrh.wxcard.reponse;
import org.simpleframework.xml.Element;
import org.simpleframework.xml.Root;
import lombok.Data;
@Data
@Root(name = "xml", strict = false)
public class PushMessage {
@Element(name = "ToUserName")
private String ToUserName;
@Element(name = "FromUserName")
private String FromUserName;
@Element(name = "CreateTime", required = false)
private Long CreateTime;
@Element(name = "Event", required = false)
private String Event;
@Element(name = "MsgType", required = false)
private String MsgType;
@Element(name = "CardId", required = false)
private String CardId;
@Element(name = "UserCardCode", required = false)
private String UserCardCode;
@Element(name = "ModifyBonus", required = false)
private Integer ModifyBonus;
@Override
public String toString() {
return "PushMessage [ToUserName=" + ToUserName + ", FromUserName=" + FromUserName + ", CreateTime=" + CreateTime
+ ", Event=" + Event + ", MsgType=" + MsgType + ", CardId=" + CardId + ", UserCardCode=" + UserCardCode
+ "]";
}
}
2.编写一个XmlUtil工具类,这里的工具类是借用慕课网廖师兄的。
package com.lly835.bestpay.utils;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.core.util.QuickWriter;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
import com.thoughtworks.xstream.io.naming.NoNameCoder;
import com.thoughtworks.xstream.io.xml.PrettyPrintWriter;
import com.thoughtworks.xstream.io.xml.XppDriver;
import org.simpleframework.xml.Serializer;
import org.simpleframework.xml.core.Persister;
import java.io.Writer;
/**
* Created by 廖师兄
* 2017-07-02 15:30
*/
public class XmlUtil {
private static XStream xStream = new XStream(new XppDriver(new NoNameCoder()) {
@Override
public HierarchicalStreamWriter createWriter(Writer out) {
return new PrettyPrintWriter(out) {
// 对所有xml节点的转换都增加CDATA标记
boolean cdata = true;
@Override
@SuppressWarnings("rawtypes")
public void startNode(String name, Class clazz) {
super.startNode(name, clazz);
}
////当对象属性带下划线时,XStream会转换成双下划线,
// 重写这个方法,不再像XppDriver那样调用nameCoder来进行编译,而是直接返回节点名称,避免双下划线出现
@Override
public String encodeNode(String name) {
return name;
}
@Override
protected void writeText(QuickWriter writer, String text) {
if (cdata) {
writer.write("<![CDATA[");
writer.write(text);
writer.write("]]>");
} else {
writer.write(text);
}
}
};
}
});
/**
* 对象转xml
* @param obj
* @return
*/
public static String toXMl(Object obj) {
//使用注解设置别名必须在使用之前加上注解类才有作用
xStream.processAnnotations(obj.getClass());
return xStream.toXML(obj);
}
public static Object fromXML(String xml, Class objClass) {
Serializer serializer = new Persister();
try {
return serializer.read(objClass, xml);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}