原理
原理一定要先搞清楚。用户与公众号之间的信息交互是:用户发送的信息是先传送到微信服务器,微信服务器在以xml的格式发送给进行公众号。如图所示:
申请测试公众号
地址:传送
代码
pom.xml
<!-- dom4j-识别解析XML-用于微信发送消息 -->
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
<!-- xstream-Java对象序列化到XML-用于微信发送消息 -->
<dependency>
<groupId>com.thoughtworks.xstream</groupId>
<artifactId>xstream</artifactId>
<version>1.4.10</version>
</dependency>
工具类
import java.io.InputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import com.xffjs.project.wx.domain.ArticleItem;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
/**
* @className: WeChatUtil
* @description: 微信工具类
* @author: xiaofei
* @create: 2020年08月09日
*/
public class WeChatUtil {
/**
* 验证自动回复消息签名
*
* @param signature 微信加密签名
* @param timestamp 时间戳
* @param nonce 随机数
* @return
*/
public static boolean checkSignature(String signature, String timestamp, String nonce) {
String[] arr = new String[]{WeChatContant.TOKEN, timestamp, nonce};
// 将token、timestamp、nonce三个参数进行字典序排序
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;
}
/**
* 排序
* @param a
*/
private 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;
}
}
}
}
/**
* 解析微信发来的请求(xml)
*
* @param request
* @return
* @throws Exception
*/
@SuppressWarnings({"unchecked"})
public static Map<String, String> parseXml(HttpServletRequest request) throws Exception {
// 将解析结果存储在HashMap中
Map<String, String> map = new HashMap<String, String>(10);
// 从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;
}
/**
* map转string
*/
public static String mapToXML(Map map) {
StringBuffer sb = new StringBuffer();
sb.append("<xml>");
mapToXML2(map, sb);
sb.append("</xml>");
try {
return sb.toString();
} catch (Exception e) {
}
return null;
}
private static void mapToXML2(Map map, StringBuffer sb) {
Set set = map.keySet();
for (Iterator it = set.iterator(); it.hasNext(); ) {
String key = (String) it.next();
Object value = map.get(key);
if (null == value) {
value = "";
}
if (value.getClass().getName().equals("java.util.ArrayList")) {
ArrayList list = (ArrayList) map.get(key);
sb.append("<" + key + ">");
for (int i = 0; i < list.size(); i++) {
HashMap hm = (HashMap) list.get(i);
mapToXML2(hm, sb);
}
sb.append("</" + key + ">");
} else {
if (value instanceof HashMap) {
sb.append("<" + key + ">");
mapToXML2((HashMap) value, sb);
sb.append("</" + key + ">");
} else {
sb.append("<" + key + "><![CDATA[" + value + "]]></" + key + ">");
}
}
}
}
/**
* 回复文本消息
*
* @param requestMap
* @param content
* @return
*/
public static String sendTextMsg(Map<String, String> requestMap, String content) {
Map<String, Object> map = new HashMap<String, Object>();
map.put("ToUserName", requestMap.get(WeChatContant.FromUserName));
map.put("FromUserName", requestMap.get(WeChatContant.ToUserName));
map.put("MsgType", WeChatContant.RESP_MESSAGE_TYPE_TEXT);
map.put("CreateTime", System.currentTimeMillis());
map.put("Content", content);
return mapToXML(map);
}
/**
* 回复图文消息
*
* @param requestMap
* @param items
* @return
*/
public static String sendArticleMsg(Map<String, String> requestMap, List<ArticleItem> items) {
if (items == null || items.size() < 1) {
return "";
}
Map<String, Object> map = new HashMap<String, Object>();
map.put("ToUserName", requestMap.get(WeChatContant.FromUserName));
map.put("FromUserName", requestMap.get(WeChatContant.ToUserName));
map.put("MsgType", "news");
map.put("CreateTime", System.currentTimeMillis());
List<Map<String, Object>> Articles = new ArrayList<Map<String, Object>>();
for (ArticleItem itembean : items) {
Map<String, Object> item = new HashMap<String, Object>();
Map<String, Object> itemContent = new HashMap<String, Object>();
itemContent.put("Title", itembean.getTitle());
itemContent.put("Description", itembean.getDescription());
itemContent.put("PicUrl", itembean.getPicUrl());
itemContent.put("Url", itembean.getUrl());
item.put("item", itemContent);
Articles.add(item);
}
map.put("Articles", Articles);
map.put("ArticleCount", Articles.size());
return mapToXML(map);
}
}
微信常量
/**
* @className: WeChatContant
* @description: 微信常量
* @author: xiaofei
* @create: 2020年08月09日
*/
public class WeChatContant {
/**
* 获取token
* "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={appid}&secret={secret}"
*
* 获取用户信息--这个功能需要微信认证
* "https://api.weixin.qq.com/cgi-bin/user/info?access_token={token}&openid={openid}&lang=zh_CN"
* */
/**
* 微信消息加密密钥
*/
public static final String TOKEN = "自己定义密钥";
/**
* 文本消息
*/
public static final String RESP_MESSAGE_TYPE_TEXT = "text";
public static final String REQ_MESSAGE_TYPE_TEXT = "text";
/**
* 图片消息
*/
public static final String REQ_MESSAGE_TYPE_IMAGE = "image";
/**
* 语音消息
*/
public static final String REQ_MESSAGE_TYPE_VOICE = "voice";
/**
* 视频消息
*/
public static final String REQ_MESSAGE_TYPE_VIDEO = "video";
/**
* 地址位置
*/
public static final String REQ_MESSAGE_TYPE_LOCATION = "location";
/**
* 链接消息
*/
public static final String REQ_MESSAGE_TYPE_LINK = "link";
/**
* 事件推送
*/
public static final String REQ_MESSAGE_TYPE_EVENT = "event";
/**
* 用户关注公众号事件
*/
public static final String EVENT_TYPE_SUBSCRIBE = "subscribe";
/**
* 用户取消关注公众号事件
*/
public static final String EVENT_TYPE_UNSUBSCRIBE = "unsubscribe";
/**
* 用户扫描带参数二维码事件
*/
public static final String EVENT_TYPE_SCAN = "scan";
/**
* 上报地理位置事件
*/
public static final String EVENT_TYPE_LOCATION = "location";
/**
* 菜单点击事件
*/
public static final String EVENT_TYPE_CLICK = "click";
/**
* 发送方帐号(一个OpenID)
*/
public static final String FromUserName = "FromUserName";
/**
* 开发者微信号
*/
public static final String ToUserName = "ToUserName";
/**
* 消息类型,event
*/
public static final String MsgType = "MsgType";
/**
* 内容
*/
public static final String Content = "Content";
/**
* 事件类型,CLICK
*/
public static final String Event = "Event";
}
model
/**
* @className: WeChatArticleItem
* @description: 微信消息回复
* @author: xiaofei
* @create: 2020年08月09日
*/
public class WeChatArticleItem {
private String title;
private String description;
private String picUrl;
private String url;
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getPicUrl() {
return picUrl;
}
public void setPicUrl(String picUrl) {
this.picUrl = picUrl;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
}
WeChatService
import javax.servlet.http.HttpServletRequest;
/**
* @className: WeChatService
* @description: 微信消息service
* @author: xiaofei
* @create: 2020年08月09日
*/
public interface WeChatService {
/**
* 核心处理方法
*
* @param request
* @return
*/
String processRequest(HttpServletRequest request);
}
WeChatServiceImpl
import com.xffjs.common.utils.wx.WeChatContant;
import com.xffjs.common.utils.wx.WeChatUtil;
import com.xffjs.project.wx.service.WeChatService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletRequest;
import java.util.Map;
/**
* @className: WeChatServiceImpl
* @description: 微信消息service核心服务类
* @author: xiaofei
* @create: 2020年08月09日
*/
@Service
public class WeChatServiceImpl implements WeChatService {
private final static Logger logger = LoggerFactory.getLogger(WeChatServiceImpl.class);
@Override
public String processRequest(HttpServletRequest request) {
// xml格式的消息数据
String respXml = null;
// 默认返回的文本消息内容
String respContent;
try {
// 调用parseXml方法解析请求消息
Map<String, String> requestMap = WeChatUtil.parseXml(request);
logger.debug("*****************微信消息解析*****************");
logger.debug("openid:" + requestMap.get(WeChatContant.ToUserName));
logger.debug("内容:" + requestMap.get(WeChatContant.Content));
logger.debug("Map:" + requestMap.toString());
logger.debug("*****************微信消息解析*****************");
// 消息类型
String msgType = (String) requestMap.get(WeChatContant.MsgType);
String mes = null;
String openid = requestMap.get(WeChatContant.ToUserName);
switch (msgType) {
// 文本消息识别
case WeChatContant.REQ_MESSAGE_TYPE_TEXT:
logger.debug("*****************文本消息识别*****************");
respContent = "您发送的是文本消息!" ;
respXml = WeChatUtil.sendTextMsg(requestMap, respContent);
break;
// 事件推送
case WeChatContant.REQ_MESSAGE_TYPE_EVENT:
// 获取事件类型
String eventType = (String) requestMap.get(WeChatContant.Event);
if (eventType.equalsIgnoreCase(WeChatContant.EVENT_TYPE_SUBSCRIBE)) {
// 进行关注
respContent = "感谢您关注公众号!";
respXml = WeChatUtil.sendTextMsg(requestMap, respContent);
}
break;
default:
mes = mes == null ? "" : "未能正确识别!";
break;
}
if (respXml == null) {
respXml = WeChatUtil.sendTextMsg(requestMap, mes);
}
return respXml;
} catch (Exception e) {
e.printStackTrace();
}
return "";
}
}
这里面还有很多不同类型的消息,这里我这列举一个消息类型和一个事件。WeChatContant(所有类型都在这里面,需要什么可以自己添加)
Controller
import com.xffjs.common.utils.StringUtils;
import com.xffjs.common.utils.wx.WeChatUtil;
import com.xffjs.framework.aspectj.lang.annotation.VLog;
import com.xffjs.project.wx.service.WeChatService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
/**
* @className: WeChatController
* @description: 微信Controller
* @author: xiaofei
* @create: 2020年08月09日
*/
@Controller
public class WeChatController {
private final static Logger logger = LoggerFactory.getLogger(WeChatController.class);
@Autowired
private WeChatService weChatService;
/**
* 微信验证自动回复消息签名
*/
@GetMapping(value = "/wx/weixin")
@VLog(title = "校验微信消息签名")
public void checkSignature(HttpServletRequest request, HttpServletResponse response) {
PrintWriter out = null;
try {
request.setCharacterEncoding("UTF-8");
String signature = request.getParameter("signature");
String timestamp = request.getParameter("timestamp");
String nonce = request.getParameter("nonce");
String echostr = request.getParameter("echostr");
if (StringUtils.isEmptys(signature, timestamp, nonce, echostr)) {
out = response.getWriter();
out.write("error");
}else{
out = response.getWriter();
if (WeChatUtil.checkSignature(signature, timestamp, nonce)) {
out.write(echostr);
}
}
} catch (Exception e) {
e.printStackTrace();
logger.error("");
} finally {
out.close();
}
}
/**
* 处理用户发送的消息
*/
@PostMapping(value = "/wx/weixin")
@ResponseBody
public String responseEvent(HttpServletRequest request) {
return weChatService.processRequest(request);
}
}
这里有两个方法,第一个是get请求方式,用于一会的接口校验,第二个用于处理用户发送的消息。
测试
1、这个映射工具之前文章里面有:传送
2、url必须以http://或https://开头,分别支持80端口和443端口。
3、Token必须为英文或数字,长度为3-32字符。
以上配置完成以后,往下拉会有一个测试号二维码,扫一下
这个关注公众号默认的回复就是我们刚刚配置的。
打赏
当前共有 0 条评论