百度搜索有很多教程,线面附地址。
教程:消息模板配置教程
值得注意的是,公众号配置菜单之后再配置消息模板会有冲突,导致菜单无法显示。
我使用的是阿里的maven仓库。
<mirror>
<id>alimaven</id>
<name>aliyun maven</name>
<mirrorOf>central</mirrorOf>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
</mirror>
jar包依赖
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
<dependency>
<groupId>com.github.liyiorg</groupId>
<artifactId>weixin-popular</artifactId>
<version>2.8.24</version>
</dependency>
controller
package com.newtouch.springcloud.controller;
import com.newtouch.springcloud.service.WeChatCallBackService;
import com.wechat.wechatUtil.SignUtil;
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.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.UUID;
@Controller
@RequestMapping("/weChatCallBack")
public class WeChatCallBackController {
Logger log = LoggerFactory.getLogger(this.getClass());
@Autowired
private WeChatCallBackService weChatCallBackService;
private static final String token = "d29499fb870e481a85b8818edab15852";
/**
* 微信消息推送的验证。
*
* @param request
* @param response
*/
@RequestMapping(value = "/sendMsg", method = RequestMethod.GET, produces = "text/html;charset=UTF-8")
public void sendMsgget(HttpServletRequest request, HttpServletResponse response) {
// 微信加密签名
String signature = request.getParameter("signature");
log.info("微信接口回调参数:signature========="+ signature);
// 时间戳
String timestamp = request.getParameter("timestamp");
log.info("微信接口回调参数:timestamp========="+ timestamp);
// 随机数
String nonce = request.getParameter("nonce");
log.info("微信接口回调参数:nonce========="+ nonce);
// 随机字符串
String echostr = request.getParameter("echostr");
log.info("微信接口回调参数:echostr========="+ echostr);
PrintWriter out = null;
try {
out = response.getWriter();
} catch (IOException e) {
e.printStackTrace();
}
// 通过检验signature对请求进行校验,若校验成功则原样返回echostr,表示接入成功,否则接入失败
if (SignUtil.checkSignature(token, signature, timestamp, nonce)) {
out.print(echostr);
}
out.close();
out = null;
}
/**
* 处理业务逻辑
* @param request
*/
@RequestMapping(value = "/sendMsg", method = RequestMethod.POST, produces = "text/html;charset=UTF-8")
public void sendMsgPost(HttpServletRequest request, HttpServletResponse response) {
// 调用核心业务类接收消息、处理消息
try {
String returnStr = weChatCallBackService.processRequest(request, response);
} catch (Exception e) {
e.printStackTrace();
}
}
}
Service层
package com.newtouch.springcloud.service.impl;
import com.alibaba.fastjson.JSONObject;
import com.newtouch.springcloud.constant.ProjectConst;
import com.newtouch.springcloud.model.vo.BaseMessage;
import com.newtouch.springcloud.model.vo.TextMessage;
import com.newtouch.springcloud.service.RedisService;
import com.newtouch.springcloud.service.WeChatCallBackService;
import com.newtouch.springcloud.service.WeChatUserAuthService;
import com.newtouch.springcloud.service.WechatSendModelMessageService;
import com.qq.weixin.mp.aes.WXBizMsgCrypt;
import com.wechat.wechatUtil.MessageUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.StringReader;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
@Service
public class WeChatCallBackServiceImpl implements WeChatCallBackService {
Logger log = LoggerFactory.getLogger(this.getClass());
public static String ENCODINGAES_KEY = "wx:encodingAesKey:encodingAesKey";//解密字符串
public static String SENDINTERFACE_TOKEN = "wx:sendInterfaceToken:sendInterfaceToken";//微信推送消息token
private static String timestamp = "1585578656";
private static String nonce = "734446698";
private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
@Autowired
private RedisService redisService ;
@Autowired
private WeChatUserAuthService WeChatUserAuthService;
@Autowired
private WechatSendModelMessageService wechatSendModelMessageService;
@Override
public String processRequest(HttpServletRequest request, HttpServletResponse response) {
try {
String respMessage = processRequest(request);
log.info("返给微信的参数:======" + respMessage);
return respMessage;
} catch (Exception e) {
e.printStackTrace();
}
return "";
}
/**
* 处理数据
* @param request
* @return
* @throws Exception
*/
public String processRequest(HttpServletRequest request) throws Exception {
String signatureTemp = request.getParameter("signature");
log.info("微信接口回调参数:signature========="+ signatureTemp);
// 时间戳
timestamp = request.getParameter("timestamp");
log.info("微信接口回调参数:timestamp========="+ timestamp);
// 随机数
nonce = request.getParameter("nonce");
log.info("微信接口回调参数:nonce========="+ nonce);
// 随机字符串
String echostrTemp = request.getParameter("echostr");
log.info("微信接口回调参数:echostr========="+ echostrTemp);
String msgSignatureStr = request.getParameter("msg_signature");
log.info("微信接口回调参数:msgSignature========="+ msgSignatureStr);
String respMessage = null;
WXBizMsgCrypt pc = new WXBizMsgCrypt(redisService.get(ProjectConst.SENDINTERFACE_TOKEN).toString(), redisService.get(ProjectConst.ENCODINGAES_KEY).toString(), redisService.get(ProjectConst.APPID_KEY).toString());
try {
// xml请求解析
Map<String, String> requestMap = MessageUtil.parseXml(request);
log.info("微信接口回调参数:"+ JSONObject.toJSONString(requestMap));
String mapToStr = "+requestMap.get("Encrypt")+"]]> +requestMap.get("ToUserName")+"]]> +msgSignatureStr+"]]> ";
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
StringReader sr = new StringReader(mapToStr);
InputSource is = new InputSource(sr);
Document document = db.parse(is);
Element root = document.getDocumentElement();
NodeList nodelist1 = root.getElementsByTagName("Encrypt");
NodeList nodelist2 = root.getElementsByTagName("MsgSignature");
String encrypt = nodelist1.item(0).getTextContent();
String msgSignature = nodelist2.item(0).getTextContent();
String format = " ";
String fromXML = String.format(format, encrypt);
// 公众平台发送消息给第三方,第三方处理
// 第三方收到公众号平台发送的消息
String result2 = pc.decryptMsg(msgSignature, timestamp, nonce, fromXML);
log.info("解密后明文: " + result2);
//转换成map放入对象中
Map<String, String> xmlStrToMap = MessageUtil.convertXmlStrToObject(result2);
log.info("转换成msp后的参数: " + JSONObject.toJSONString(xmlStrToMap));
BaseMessage baseMessage = JSONObject.parseObject(JSONObject.toJSONString(xmlStrToMap), BaseMessage.class);
log.info("map转成BaseMessage后的参数: " + JSONObject.toJSONString(baseMessage));
if(MessageUtil.REQ_MESSAGE_TYPE_SCAN.equals(baseMessage.getEvent())){
//已关注
/**已关注之后
* 先查询数据库中的数据并组装返回信息给微信
*/
//chechMessage();
log.info("已关注,无需更新数据库==================== ");
}else if (MessageUtil.EVENT_TYPE_SUBSCRIBE.equals(baseMessage.getEvent())){
//关注
/**
* 先查询数据库中的数据是否存在如果有则更新openid
*/
log.info("未关注,需更新数据库====================== ");
String telePhoneNo = xmlStrToMap.get("EventKey");
if(null != telePhoneNo && !"".equals(telePhoneNo)){
telePhoneNo = telePhoneNo.substring(telePhoneNo.indexOf("_")+1);
}
// 更新数据
log.info("未关注,数据更新完成====================== ");
String content = "恭喜您,您已完成注册!";
log.info("未关注,开始推送模板消息==================");
wechatSendModelMessageService.sendWarningByWechat(baseMessage.getFromUserName(),content,telePhoneNo,sdf.format(new Date()));
log.info("未关注,推送模板消息成功===================");
}
/**
* 处理完成后需组装信息末班,加密后返回给微信
*/
//respMessage = pc.encryptMsg(resultXml, timestamp, nonce);
//respMessage = resultXml;
//log.info("加密后: " + respMessage);
} catch (Exception e) {
e.printStackTrace();
}
return respMessage;
}
}
发送模板消息的Service
package com.newtouch.springcloud.service.impl;
import com.newtouch.springcloud.model.vo.Template;
import com.newtouch.springcloud.model.vo.TemplateParam;
import com.newtouch.springcloud.service.WechatSendModelMessageService;
import com.wechat.wechatUtil.ApacheHttpClientUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import com.newtouch.springcloud.service.RedisService;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
@Service
public class WechatSendModelMessageServiceImpl implements WechatSendModelMessageService {
Logger log = LoggerFactory.getLogger(this.getClass());
@Autowired
private RedisService redisServer;
/**
*
* @param openIds openid组 逗号分割
* @param content 标题
* @param telePhoneNo 手机号
* @param sendDateTime 创建时间
*/
@Override
public void sendWarningByWechat(String openIds, String content, String telePhoneNo, String sendDateTime) {
try {
String templateMsgUrl = redisServer.get("wx:templateMsgUrl:templateMsgUrl").toString();
templateMsgUrl = templateMsgUrl.replace("ACCESS_TOKEN", redisServer.get("wx:accessToken:").toString());
log.info("通过微信推送模板消息地址:" + templateMsgUrl);
//封装请求体
Template template = new Template();
template.setTemplateId(redisServer.get("wx:modelId:modelId").toString());
List<TemplateParam> templateParams = new ArrayList<>();
TemplateParam first = new TemplateParam("first", content + "\\n", "#2021DB");
TemplateParam keyword1 = new TemplateParam("keyword1", telePhoneNo + "", "#2021DB");
TemplateParam keyword2 = new TemplateParam("keyword2", "普通用户", "#2021DB");
TemplateParam keyword3 = new TemplateParam("keyword3", sendDateTime + "", "#2021DB");
TemplateParam remark = new TemplateParam("remark", "若有疑问请联系在线客服或拨打" + redisServer.get("wx:managementTel:managementTe").toString(), "#2021DB");
templateParams.add(first);
templateParams.add(keyword1);
templateParams.add(keyword2);
templateParams.add(keyword3);
templateParams.add(remark);
template.setTemplateParamList(templateParams);
String[] openIdArr = openIds.split(",");
if (openIdArr.length > 0) {
for (String openID : openIdArr) {
template.setToUser(openID);
String paramStr = template.toJSON();
log.info("通过微信推送模板消息入参:" + paramStr);
ApacheHttpClientUtils.doTemplateMsgPost(templateMsgUrl, paramStr);
log.info("通过微信推送模板消息完成===============");
}
}
} catch (Exception e) {
log.error(e.getMessage());
e.printStackTrace();
}
}
}
BaseMessage
package com.newtouch.springcloud.model.vo;
/**
* 消息基类(公众帐号 -> 用户)
*/
public class BaseMessage {
/**
* 接收方帐号(收到的OpenID)
*/
private String ToUserName;
/**
* 开发者微信号
*/
private String FromUserName;
/**
* 消息创建时间 (整型)
*/
private long CreateTime;
/**
* 消息类型
*/
private String MsgType;
/**
* 位0x0001被标志时,星标刚收到的消息
*/
private Integer FuncFlag;
private String EventKey;
private String Event;
private String Ticket;
public String getEvent() {
return Event;
}
public void setEvent(String event) {
Event = event;
}
public String getTicket() {
return Ticket;
}
public void setTicket(String ticket) {
Ticket = ticket;
}
public String getEventKey() {
return EventKey;
}
public void setEventKey(String eventKey) {
EventKey = eventKey;
}
public String getToUserName() {
return ToUserName;
}
public void setToUserName(String toUserName) {
ToUserName = toUserName;
}
public String getFromUserName() {
return FromUserName;
}
public void setFromUserName(String fromUserName) {
FromUserName = fromUserName;
}
public long getCreateTime() {
return CreateTime;
}
public void setCreateTime(long createTime) {
CreateTime = createTime;
}
public String getMsgType() {
return MsgType;
}
public void setMsgType(String msgType) {
MsgType = msgType;
}
public Integer getFuncFlag() {
return FuncFlag;
}
public void setFuncFlag(Integer funcFlag) {
FuncFlag = funcFlag;
}
}
SignUtil工具类
package com.wechat.wechatUtil;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
public class SignUtil {
/**
* 验证签名
*
* @param signature
* @param timestamp
* @param nonce
* @return
*/
public static boolean checkSignature(String token, String signature, String timestamp, String nonce) {
String[] arr = new String[] {
token, timestamp, nonce };
// 将token、timestamp、nonce三个参数进行字典序排序
Arrays.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
*/
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;
}
}
MessageUtil
package com.wechat.wechatUtil;
import com.newtouch.springcloud.model.vo.BaseMessage;
import com.newtouch.springcloud.model.vo.NewsMessage;
import com.newtouch.springcloud.model.vo.TextMessage;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.core.util.QuickWriter;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
import com.thoughtworks.xstream.io.xml.PrettyPrintWriter;
import com.thoughtworks.xstream.io.xml.XppDriver;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import weixin.popular.bean.message.Article;
import weixin.popular.bean.message.message.MusicMessage;
import javax.servlet.http.HttpServletRequest;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Writer;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class MessageUtil {
/**
* 返回消息类型:文本
*/
public static final String RESP_MESSAGE_TYPE_TEXT = "text";
/**
* 返回消息类型:音乐
*/
public static final String RESP_MESSAGE_TYPE_MUSIC = "music";
/**
* 返回消息类型:图文
*/
public static final String RESP_MESSAGE_TYPE_NEWS = "news";
/**
* 请求消息类型:文本
*/
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_LINK = "link";
/**
* 请求消息类型:地理位置
*/
public static final String REQ_MESSAGE_TYPE_LOCATION = "location";
/**
* 请求消息类型:音频
*/
public static final String REQ_MESSAGE_TYPE_VOICE = "voice";
/**
* 请求消息类型:推送
*/
public static final String REQ_MESSAGE_TYPE_EVENT = "event";
/**
* 已关注关注事件
*/
public static final String REQ_MESSAGE_TYPE_SCAN = "SCAN";
/**
* 事件类型:subscribe(订阅) 未关注关注事件
*/
public static final String EVENT_TYPE_SUBSCRIBE = "subscribe";
/**
* 事件类型:unsubscribe(取消订阅)
*/
public static final String EVENT_TYPE_UNSUBSCRIBE = "unsubscribe";
/**
* 事件类型:CLICK(自定义菜单点击事件)
*/
public static final String EVENT_TYPE_CLICK = "CLICK";
/**
* 解析微信发来的请求(XML)
*
* @param request
* @return
* @throws Exception
*/
public 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;
}
/**
* 文本消息对象转换成xml
*
* @param textMessage 文本消息对象
* @return xml
*/
public static String textMessageToXml(TextMessage textMessage) {
xstream.alias("xml", textMessage.getClass());
return xstream.toXML(textMessage);
}
/**
* 文本消息对象转换成xml
*
* @param baseMessage 文本消息对象
* @return xml
*/
public static String baseMessageToXml(BaseMessage baseMessage) {
xstream.alias("xml", baseMessage.getClass());
return xstream.toXML(baseMessage);
}
/**
* 音乐消息对象转换成xml
*
* @param musicMessage 音乐消息对象
* @return xml
*/
public static String musicMessageToXml(MusicMessage musicMessage) {
xstream.alias("xml", musicMessage.getClass());
return xstream.toXML(musicMessage);
}
/**
* 图文消息对象转换成xml
*
* @param newsMessage 图文消息对象
* @return xml
*/
public static String newsMessageToXml(NewsMessage newsMessage) {
xstream.alias("xml", newsMessage.getClass());
xstream.alias("item", new Article().getClass());
return xstream.toXML(newsMessage);
}
/**
* 解密后的报文转成对象
* @param xmlStr
* @return
* @throws DocumentException
* @throws IOException
*/
public static Map<String, String> convertXmlStrToObject(String xmlStr) throws DocumentException, IOException {
// 将解析结果存储在HashMap中
Map<String, String> map = new HashMap<String, String>();
// 从request中取得输入流
InputStream inputStream = new ByteArrayInputStream(xmlStr.getBytes());
// 读取输入流
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块
*
* @date 2013-05-19
*/
private static XStream xstream = new XStream(new XppDriver() {
public HierarchicalStreamWriter createWriter(Writer out) {
return new PrettyPrintWriter(out) {
// 对所有xml节点的转换都增加CDATA标记
boolean cdata = true;
@SuppressWarnings("unchecked")
public void startNode(String name, Class clazz) {
super.startNode(name, clazz);
}
protected void writeText(QuickWriter writer, String text) {
if (cdata) {
writer.write(");
writer.write(text);
writer.write("]]>");
} else {
writer.write(text);
}
}
};
}
});
}
为什么会有两个一样的接口呢?
请注意看代码,一个是get类型一个是post类型。第一次配置服务器试微信平台会发送get请求。
配置好服务器,关注公众号进行消息推送会发送post类型的请求。
url:服务器地址也就是调用你的get接口的接口地址
令牌:自己生成一个uuid就可以
EncodingAESKey:配置服务器手动点击随机生成按钮
注:令牌和EncodingAESKey保存好后面解密是会用到
第一次发博客,找了几位大神的帖子,结合自己的开发经历。有不对的地方请各位大佬指正。