先申请公众号的测试账号:微信公众平台
在关注者与公众号产生消息交互后,公众号可获得关注者的OpenID(加密后的微信号,每个用户对每个公众号的OpenID是唯一的。对于不同公众号,同一用户的openid不同)。公众号可通过本接口来根据OpenID获取用户基本信息,包括昵称、头像、性别、所在城市、语言和关注时间。
请注意,如果开发者有在多个公众号,或在公众号、移动应用之间统一用户帐号的需求,需要前往微信开放平台(open.weixin.qq.com)绑定公众号后,才可利用UnionID机制来满足上述需求。
重点:想获取用户的unionID,就需要将这个测试公众号,配置到微信开放平台
url:填写后点击提交微信会请求,所以你项目需要先部署,并且微信服务器可以访问到
token:随便填写,代码中会使用到
@RequestMapping("/weChatCallBack")
public String weChatCallBack(HttpServletRequest request) throws Exception {
String msgSignature = request.getParameter("signature");
String msgTimestamp = request.getParameter("timestamp");
String msgNonce = request.getParameter("nonce");
String echostr = request.getParameter("echostr");
String encrypt = request.getParameter("encrypt");
if (StringUtils.isNotBlank(echostr)) {
//第一次填写回调地址使用,回调地址通过,后面微信就不会请求了
if (WXPublicUtils.verifyUrl(msgSignature, msgTimestamp, msgNonce, encrypt)) {
return echostr;
}
} else {
//这个是处理逻辑
String msg = weChatService.weChatCallBack(request);
}
return "success";
}
验证过程
/**
* 验证Token
*
* @param msgSignature 签名串,对应URL参数的signature
* @param timeStamp 时间戳,对应URL参数的timestamp
* @param nonce 随机数
* @return 是否为安全签名
* @throws AesException 执行失败,请查看该异常的错误码和具体的错误信息
*/
public static boolean verifyUrl(String msgSignature, String timeStamp, String nonce, String encrypt)
throws AesException {
// 这里的 WXPublicConstants.TOKEN 填写你自己设置的Token就可以了
String signature = SHA1Util.getSHA1("此处填写你在页面中的token", timeStamp, nonce, encrypt);
if (!signature.equals(msgSignature)) {
throw new AesException(AesException.ValidateSignatureError);
}
return true;
}
/**
* 用SHA1算法生成安全签名
*
* @param token 票据
* @param timestamp 时间戳
* @param nonce 随机字符串
* @param encrypt 密文
* @return 安全签名
*/
public static String getSHA1(String token, String timestamp, String nonce, String encrypt) throws AesException {
try {
String[] array = new String[]{token, timestamp, nonce};
StringBuffer sb = new StringBuffer();
// 字符串排序
Arrays.sort(array);
for (int i = 0; i < array.length; i++) {
sb.append(array[i]);
}
String str = sb.toString();
// SHA1签名生成
MessageDigest md = MessageDigest.getInstance("SHA-1");
md.update(str.getBytes());
byte[] digest = md.digest();
StringBuffer hexstr = new StringBuffer();
String shaHex = "";
for (int i = 0; i < digest.length; i++) {
shaHex = Integer.toHexString(digest[i] & 0xFF);
if (shaHex.length() < 2) {
hexstr.append(0);
}
hexstr.append(shaHex);
}
return hexstr.toString();
} catch (Exception e) {
e.printStackTrace();
throw new AesException(AesException.ComputeSignatureError);
}
}
public class AesException extends Exception {
public final static int OK = 0;
public final static int ValidateSignatureError = -40001;
public final static int ParseXmlError = -40002;
public final static int ComputeSignatureError = -40003;
public final static int IllegalAesKey = -40004;
public final static int ValidateAppidError = -40005;
public final static int EncryptAESError = -40006;
public final static int DecryptAESError = -40007;
public final static int IllegalBuffer = -40008;
private int code;
private static String getMessage(int code) {
switch (code) {
case ValidateSignatureError:
return "签名验证错误";
case ParseXmlError:
return "xml解析失败";
case ComputeSignatureError:
return "sha加密生成签名失败";
case IllegalAesKey:
return "SymmetricKey非法";
case ValidateAppidError:
return "appid校验失败";
case EncryptAESError:
return "aes加密失败";
case DecryptAESError:
return "aes解密失败";
case IllegalBuffer:
return "解密后得到的buffer非法";
default:
return null; // cannot be
}
}
public int getCode() {
return code;
}
public AesException(int code) {
super(getMessage(code));
this.code = code;
}
}
这个是微信回调
public String weChatCallBack(HttpServletRequest request) {
// 获得微信端返回的xml数据
StringBuilder returnXml = new StringBuilder();
Map<String, String> map = new HashMap<>(8);
try (InputStream is = request.getInputStream(); InputStreamReader isr = new InputStreamReader(is, StandardCharsets.UTF_8); BufferedReader br = new BufferedReader(isr)) {
String str;
while ((str = br.readLine()) != null) {
//返回的是xml数据
returnXml.append(str);
}
log.info("微信传来消息:{}", returnXml.toString());
map = WXPublicUtils.xmlToMap(returnXml.toString());
} catch (Exception e) {
log.error("处理微信公众号请求信息,失败", e);
}
// 区分消息类型
String msgType = map.get("MsgType");
// 普通消息
if ("text".equals(msgType)) {
// 暂不开发 处理文本消息
} else if ("image".equals(msgType)) {
// 暂不开发 处理图片消息
} else if ("voice".equals(msgType)) {
// 暂不开发 处理语音消息
} else if ("video".equals(msgType)) {
// 暂不开发 处理视频消息
} else if ("shortvideo".equals(msgType)) {
// 暂不开发 处理小视频消息
} else if ("location".equals(msgType)) {
// 暂不开发 处理地理位置消息
} else if ("link".equals(msgType)) {
// 暂不开发 处理链接消息
}
// 事件推送
else if ("event".equals(msgType)) {
// 事件消息
String event = map.get("Event");
//带参数的二维码中的参数 scene_6ba76c65-7825-4507-a79c-84260ca1f087
String eventKey = map.get("EventKey");
String openId = map.get("FromUserName");
if (eventKey.startsWith(Consts.QRSCENE)) {
eventKey = eventKey.split("_")[1];
}
//根据openId换subscribe信息
String jdbAppId = "微信appid";
String accessToken = "获取微信accessToken";
WeChatSubscribe subscribe = weChatApi.isSubscribeByOpenId(accessToken, openId);
if ("subscribe".equals(event)) {
log.info("订阅事件或未关注扫描二维码事件");
} else if ("unsubscribe".equals(event)) { // 取消订阅事件
log.info("处理取消订阅事件,暂时不做任何处理");
} else if ("SCAN".equals(event)) {
log.info("已关注扫描二维码事件");
} else if ("LOCATION".equals(event)) {
// 暂不开发 处理上报地理位置事件
} else if ("CLICK".equals(event)) {
// 暂不开发 处理点击菜单拉取消息时的事件推送事件
} else if ("VIEW".equals(event)) {
// 暂不开发 处理点击菜单跳转链接时的事件推送
}
}
return "success";
}
/**
* XML格式字符串转换为Map
*
* @param strXML XML字符串
* @return XML数据转换后的Map
* @throws Exception
*/
public static Map<String, String> xmlToMap(String strXML) throws Exception {
try {
Map<String, String> data = new HashMap<>();
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
InputStream stream = new ByteArrayInputStream(strXML.getBytes("UTF-8"));
Document doc = documentBuilder.parse(stream);
doc.getDocumentElement().normalize();
NodeList nodeList = doc.getDocumentElement().getChildNodes();
for (int idx = 0; idx < nodeList.getLength(); ++idx) {
Node node = nodeList.item(idx);
if (node.getNodeType() == Node.ELEMENT_NODE) {
org.w3c.dom.Element element = (org.w3c.dom.Element) node;
data.put(element.getNodeName(), element.getTextContent());
}
}
try {
stream.close();
} catch (Exception ex) {
// do nothing
}
return data;
} catch (Exception ex) {
log.warn("Invalid XML, can not convert to map. Error message: {}. XML content: {}", ex.getMessage(), strXML);
throw ex;
}
}
/**
* 将Map转换为XML格式的字符串
*
* @param data Map类型数据
* @return XML格式的字符串
* @throws Exception
*/
public static String mapToXml(Map<String, String> data) throws Exception {
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
Document document = documentBuilder.newDocument();
Element root = document.createElement("xml");
document.appendChild(root);
for (String key : data.keySet()) {
String value = data.get(key);
if (value == null) {
value = "";
}
value = value.trim();
Element filed = document.createElement(key);
filed.appendChild(document.createTextNode(value));
root.appendChild(filed);
}
TransformerFactory tf = TransformerFactory.newInstance();
Transformer transformer = tf.newTransformer();
DOMSource source = new DOMSource(document);
transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
StringWriter writer = new StringWriter();
StreamResult result = new StreamResult(writer);
transformer.transform(source, result);
String output = writer.getBuffer().toString(); //.replaceAll("\n|\r", "");
try {
writer.close();
} catch (Exception ex) {
}
return output;
}