流程
用户登录小程序,后台记录用户的小程序openId和用户唯一的UnionId。然后用户触发公众号事件(关注公众号或者发送指定消息),后台获取到用户公众号的openId,再调用接口通过公众号的openId查询用户的UnionId,再和数据库里的UnionId进行匹配,将用户的公众号openId存入数据库。此后即可通过userId找到公众号openId 实现公众号消息推送。
1.开通公众号
这个直接操作就好了
2.配置公众号验签接口
添加验签接口
/**
* 服务器有效性验证
*/
@GetMapping("connect")
public String verifyToken(HttpServletRequest request) {
String signature = request.getParameter("signature");
String timestamp = request.getParameter("timestamp");
String nonce = request.getParameter("nonce");
String echostr = request.getParameter("echostr");
log.info("signature: {} nonce: {} echostr: {} timestamp: {}", signature, nonce, echostr, timestamp);
if (this.checkSignature(signature, timestamp, nonce)) {
log.info("token ok");
return echostr;
}
return echostr;
}
将接口配置到公众号上
3.设置用户关注、取关以及发送消息事件处理
接口名称和验签接口相同 只是验签是GET请求 事件处理是POST请求
/**
* 用户公众号关注/取关事件回调
*/
@PostMapping(value = "/connect")
public void getXmlInfo(HttpServletRequest req, HttpServletResponse resp) throws Exception {
req.setCharacterEncoding("UTF-8"); // 接收请求时的编码。
resp.setCharacterEncoding("UTF-8"); // 响应给浏览器的编码。
Map<String, String> map = XMLUtil.parseXml(req);
log.info(JSONObject.toJSONString(map));
if ( map.get("MsgType").equals("text")){
if (map.get("Content").equals("求购留言")){
String result = wechatOfficialAccountService.replySubscribeInfo(map);
resp.setCharacterEncoding("UTF-8");
resp.getWriter().println(result);
}
}else {
if (map.get("Event").equals("subscribe")){
wechatOfficialAccountService.subscribeInfo(map);
} else if (map.containsKey("Event") && map.get("Event").equals("unsubscribe")) {
wechatOfficialAccountService.unSubscribeInfo(map);
}
}
}
关注时给用户绑定openId
public void subscribeInfo(Map<String, String> map){
WeChatEventInfo eventInfo = new WeChatEventInfo()
.setToUserName(map.get("ToUserName"))
.setFromUserName(map.get("FromUserName"))
.setCreateTime(map.get("CreateTime"))
.setMsgType(map.get("MsgType"))
.setEvent(map.get("Event"));
String accessToken = getToken();
log.info("accessToken:{}",accessToken);
String unionId = getUserInfo(accessToken,eventInfo.getFromUserName());
if(StrUtil.isNotBlank(unionId)){
wxCustomerService.bindOfficialAccountOpenId(unionId,eventInfo.getFromUserName());
}
}
回复指定消息时给用户绑定openId
/**
* 回复求购留言进行关注
*/
public String replySubscribeInfo(Map<String, String> map) throws Exception {
WeChatEventInfo eventInfo = new WeChatEventInfo()
.setToUserName(map.get("ToUserName"))
.setFromUserName(map.get("FromUserName"))
.setCreateTime(map.get("CreateTime"))
.setMsgType(map.get("MsgType"))
.setContent(map.get("content"));
String accessToken = getToken();
log.info("accessToken:{}",accessToken);
String unionId = getUserInfo(accessToken,eventInfo.getFromUserName());
ReplyMessage result = new ReplyMessage()
.setToUserName(eventInfo.getFromUserName())
.setFromUserName(eventInfo.getToUserName())
.setCreateTime(System.currentTimeMillis())
.setMsgType("text");
if(StrUtil.isNotBlank(unionId)){
wxCustomerService.bindOfficialAccountOpenId(unionId,eventInfo.getFromUserName());
result.setContent("已为您开通求购留言通知");
}else {
result.setContent("开通失败,暂未获取到您的用户信息");
}
return XMLUtil.textMessageToXml(result);
}
取关时解绑用户openId
public void unSubscribeInfo(Map<String, String> map){
WeChatEventInfo eventInfo = new WeChatEventInfo()
.setToUserName(map.get("ToUserName"))
.setFromUserName(map.get("FromUserName"))
.setCreateTime(map.get("CreateTime"))
.setMsgType(map.get("MsgType"))
.setEvent(map.get("Event"));
wxCustomerService.unSubscribeInfo(eventInfo.getFromUserName());
}
4.申请消息模板
5.给指定用户发送消息
前三步代码
依赖
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-mp</artifactId>
<version>4.0.0</version>
</dependency>
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.1</version>
</dependency>
<!-- java对象转换为xml字符串 -->
<dependency>
<groupId>com.thoughtworks.xstream</groupId>
<artifactId>xstream</artifactId>
<version>1.4.19</version>
</dependency>
接口层
接口需要放行文章来源:https://www.toymoban.com/news/detail-773775.html
@RestController
@RequestMapping("wechat")
@Slf4j
public class WeChatController {
private final WechatOfficialAccountService wechatOfficialAccountService;
private static final String TOKEN = "platformYz";
public WeChatController(WechatOfficialAccountService wechatOfficialAccountService) {
this.wechatOfficialAccountService = wechatOfficialAccountService;
}
/**
* 服务器有效性验证
*/
@GetMapping("connect")
public String verifyToken(HttpServletRequest request) {
String signature = request.getParameter("signature");
String timestamp = request.getParameter("timestamp");
String nonce = request.getParameter("nonce");
String echostr = request.getParameter("echostr");
log.info("signature: {} nonce: {} echostr: {} timestamp: {}", signature, nonce, echostr, timestamp);
if (this.checkSignature(signature, timestamp, nonce)) {
log.info("token ok");
return echostr;
}
return echostr;
}
/**
* 用户公众号关注/取关事件回调
*/
@PostMapping(value = "/connect")
public void getXmlInfo(HttpServletRequest req, HttpServletResponse resp) throws Exception {
req.setCharacterEncoding("UTF-8"); // 接收请求时的编码。
resp.setCharacterEncoding("UTF-8"); // 响应给浏览器的编码。
Map<String, String> map = XMLUtil.parseXml(req);
log.info(JSONObject.toJSONString(map));
if ( map.get("MsgType").equals("text")){
if (map.get("Content").equals("求购留言")){
String result = wechatOfficialAccountService.replySubscribeInfo(map);
resp.setCharacterEncoding("UTF-8");
resp.getWriter().println(result);
}
}else {
if (map.get("Event").equals("subscribe")){
wechatOfficialAccountService.subscribeInfo(map);
} else if (map.containsKey("Event") && map.get("Event").equals("unsubscribe")) {
wechatOfficialAccountService.unSubscribeInfo(map);
}
}
}
private boolean checkSignature(String signature, String timestamp, String nonce) {
String[] str = new String[]{TOKEN, timestamp, nonce};
//排序
Arrays.sort(str);
//拼接字符串
StringBuffer buffer = new StringBuffer();
for (String s : str) {
buffer.append(s);
}
//进行sha1加密
String temp = SHA1.encode(buffer.toString());
//与微信提供的signature进行匹对
return signature.equals(temp);
}
}
service层
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.Optional;
@Service
@Slf4j
public class WechatOfficialAccountService {
private final WxCustomerService wxCustomerService;
private final RestTemplate restTemplate;
private final String appId;
private final String appSecret;
private static final String USER_INFO_URL = "https://api.weixin.qq.com/cgi-bin/user/info?access_token=%s&openid=%s&lang=zh_CN";
private static final String TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/stable_token";
public WechatOfficialAccountService(WxCustomerService wxCustomerService, RestTemplate restTemplate,
@Value("${wx.gzh.appId}") String appId,
@Value("${wx.gzh.secret}") String appSecret) {
this.wxCustomerService = wxCustomerService;
this.restTemplate = restTemplate;
this.appId = appId;
this.appSecret = appSecret;
}
public void subscribeInfo(Map<String, String> map){
WeChatEventInfo eventInfo = new WeChatEventInfo()
.setToUserName(map.get("ToUserName"))
.setFromUserName(map.get("FromUserName"))
.setCreateTime(map.get("CreateTime"))
.setMsgType(map.get("MsgType"))
.setEvent(map.get("Event"));
String accessToken = getToken();
log.info("accessToken:{}",accessToken);
String unionId = getUserInfo(accessToken,eventInfo.getFromUserName());
if(StrUtil.isNotBlank(unionId)){
wxCustomerService.bindOfficialAccountOpenId(unionId,eventInfo.getFromUserName());
}
}
/**
* 回复求购留言进行关注
*/
public String replySubscribeInfo(Map<String, String> map) throws Exception {
WeChatEventInfo eventInfo = new WeChatEventInfo()
.setToUserName(map.get("ToUserName"))
.setFromUserName(map.get("FromUserName"))
.setCreateTime(map.get("CreateTime"))
.setMsgType(map.get("MsgType"))
.setContent(map.get("content"));
String accessToken = getToken();
log.info("accessToken:{}",accessToken);
String unionId = getUserInfo(accessToken,eventInfo.getFromUserName());
ReplyMessage result = new ReplyMessage()
.setToUserName(eventInfo.getFromUserName())
.setFromUserName(eventInfo.getToUserName())
.setCreateTime(System.currentTimeMillis())
.setMsgType("text");
if(StrUtil.isNotBlank(unionId)){
wxCustomerService.bindOfficialAccountOpenId(unionId,eventInfo.getFromUserName());
result.setContent("已为您开通求购留言通知");
}else {
result.setContent("开通失败,暂未获取到您在玉农智链中的用户信息");
}
return XMLUtil.textMessageToXml(result);
}
private String getUserInfo(String accessToken,String openId) {
String url = String.format(USER_INFO_URL, accessToken, openId);
String json = restTemplate.getForObject(url, String.class);
JSONObject jsonObject = JSONObject.parseObject(json);
log.info("json:{}",json);
return jsonObject.getString("unionid");
}
private String getToken() {
ResponseEntity<JSONObject> responseEntity = restTemplate.postForEntity(TOKEN_URL,new JSONObject().fluentPut("grant_type","client_credential").fluentPut("appid",appId).fluentPut("secret",appSecret).toJSONString(), JSONObject.class);
JSONObject json = Optional.ofNullable(responseEntity.getBody()).orElse(new JSONObject());
restTemplate.getMessageConverters().set(1, new StringHttpMessageConverter(StandardCharsets.UTF_8));
log.info("access_token_json:{}",json.toJSONString());
return json.getString("access_token");
}
public void unSubscribeInfo(Map<String, String> map){
WeChatEventInfo eventInfo = new WeChatEventInfo()
.setToUserName(map.get("ToUserName"))
.setFromUserName(map.get("FromUserName"))
.setCreateTime(map.get("CreateTime"))
.setMsgType(map.get("MsgType"))
.setEvent(map.get("Event"));
wxCustomerService.unSubscribeInfo(eventInfo.getFromUserName());
}
}
工具类
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 lombok.extern.slf4j.Slf4j;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.InputStream;
import java.io.Writer;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Slf4j
public class XMLUtil {
private static final String MESSAGE_TYPE_TEXT = "text";
public static Map<String, String> parseXml(HttpServletRequest request) {
Map<String, String> map = new HashMap<>();
try(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());
}
} catch (IOException |DocumentException e) {
log.info("xml信息解析失败");
}
return map;
}
/**
* 文本消息对象转换成xml
*
* @param textMessage 文本消息对象
* @return xml
*/
public static String textMessageToXml(ReplyMessage textMessage) {
XSTREAM.alias("xml", textMessage.getClass());
return XSTREAM.toXML(textMessage);
}
/**
* 扩展xstream,使其支持CDATA块
*/
private static final XStream XSTREAM = new XStream(new XppDriver() {
@Override
public HierarchicalStreamWriter createWriter(Writer out) {
return new PrettyPrintWriter(out) {
// 对所有xml节点的转换都增加CDATA标记
final boolean cdata = true;
@Override
protected void writeText(QuickWriter writer, String text) {
if (cdata) {
writer.write("<![CDATA[");
writer.write(text);
writer.write("]]>");
} else {
writer.write(text);
}
}
};
}
});
/**
* 获取默认文本消息
*
* @param receiver 接收人
* @param officialWxid 官方微信id
* @return 文本消息
*/
public static ReplyMessage getDefaultReplyMessage(String receiver, String officialWxid) {
ReplyMessage textMessage = new ReplyMessage();
textMessage.setToUserName(receiver);
textMessage.setFromUserName(officialWxid);
textMessage.setCreateTime(System.currentTimeMillis());
textMessage.setMsgType(MESSAGE_TYPE_TEXT);
return textMessage;
}
}
模板消息发送服务类文章来源地址https://www.toymoban.com/news/detail-773775.html
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
import java.util.Optional;
@Component
@Slf4j
public class WechatOfficialAccountMessageServer {
private final String appId;
private final String miniAppId;
private final String appSecret;
private final RestTemplate restTemplate;
private String accessToken;
private static final String SEND_URL = "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=%s";
private static final String TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/stable_token";
private WechatOfficialAccountMessageServer(@Value("${wx.gzh.appId}") String appId,
@Value("${wx.xcx.appId}") String miniAppId,
@Value("${wx.gzh.secret}")String appSecret) {
this.appId = appId;
this.appSecret = appSecret;
this.miniAppId = miniAppId;
this.restTemplate = new RestTemplate();
}
/**
* 消息推送
*
* @param templateId 消息模板id
* @param openId 用户openId
* @param param 推送对象
*/
public void pushMessage(String templateId, String openId, WechatOfficialAccountMessageParam param) {
getInstance();
param.getMessageDataList().forEach(item->{
pushMessage(templateId,openId,item,param.getPage());
});
}
private void getInstance(){
//获取access_token
ResponseEntity<JSONObject> responseEntity = restTemplate.postForEntity(TOKEN_URL,new JSONObject().fluentPut("grant_type","client_credential").fluentPut("appid",appId).fluentPut("secret",appSecret).toJSONString(), JSONObject.class);
JSONObject json = Optional.ofNullable(responseEntity.getBody()).orElse(new JSONObject());
restTemplate.getMessageConverters().set(1, new StringHttpMessageConverter(StandardCharsets.UTF_8));
this.accessToken = json.getString("access_token");
}
/**
* 消息推送
*
* @param templateId 消息模板id
* @param openId 用户openId
* @param messageData 消息体
* @param page
*/
private void pushMessage(String templateId, String openId, JSONObject messageData, String page) {
String url = String.format(SEND_URL, this.accessToken);
//拼接推送的模版
SendTemplateRequest request = new SendTemplateRequest()
.setTouser(openId)
.setTemplateId(templateId)
.setData(messageData);
if (StrUtil.isNotBlank(page)){
JSONObject miniProgram = new JSONObject().fluentPut("appid", miniAppId)
.fluentPut("pagepath", page);
request.setMiniProgram(miniProgram);
}
ResponseEntity<SendTemplateResponse> responseEntity = restTemplate.postForEntity(url, JSONObject.toJSONString(request), SendTemplateResponse.class);
SendTemplateResponse response = Optional.ofNullable(responseEntity.getBody()).orElse(SendTemplateResponse.getInstance());
if (Objects.requireNonNull(response).isSuccess()) {
log.info("公众号推送成功");
return;
}
log.error("公众号推送失败:{} {}", response.getErrcode(), response.getErrmsg());
throw new DefaultException("公众号推送失败:" + response.getErrcode() + response.getErrmsg(), ResponseEnum.OPERATE_FAIL);
}
}
相关实体类
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 微信公众号关注取消事件信息
*/
@Data
@Accessors(chain = true)
public class WeChatEventInfo {
private String toUserName;
private String fromUserName;
private String createTime;
private String msgType;
private String event;
private String content;
private String msgId;
}
@Data
@Accessors(chain = true)
public class ReplyMessage {
private String ToUserName;
private String FromUserName;
private Long CreateTime;
private String MsgType;
private String Content;
}
import com.alibaba.fastjson.annotation.JSONField;
import lombok.Data;
import lombok.experimental.Accessors;
@Data
@Accessors(chain = true)
class SendTemplateRequest {
/**
* 接收者(用户)的 openid
*/
private String touser;
/**
* 所需下发的模板消息的id
*/
@JSONField(name = "template_id")
private String templateId;
/**
* 模板跳转链接(海外账号没有跳转能力)
*/
@JSONField(name = "url")
private String url;
/**
* 跳小程序所需数据,不需跳小程序可不用传该数据
* "miniprogram":{
* "appid":"xiaochengxuappid12345",
* "pagepath":"index?foo=bar"
* }
* appid 必填 所需跳转到的小程序appid(该小程序appid必须与发模板消息的公众号是绑定关联关系,暂不支持小游戏)
* pagepath 非必填 所需跳转到小程序的具体页面路径,支持带参数,(示例index?foo=bar),要求该小程序已发布,暂不支持小游戏
*/
@JSONField(name = "miniprogram")
private Object miniProgram;
/**
* 模板数据
*/
private Object data;
/**
* 防重入id。对于同一个openid + client_msg_id, 只发送一条消息,10分钟有效,超过10分钟不保证效果。若无防重入需求,可不填
*/
@JSONField(name = "client_msg_id")
private String clientMsgId;
}
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 消息推送响应对象
*
* @author fenglifei
*/
@Data
@Accessors(chain = true)
public class SendTemplateResponse {
/**
* 错误码
* 0 ok
* 43116 该模板因滥用被滥用过多,已被限制下发
*/
private long errcode;
/**
* 错误信息
*/
private String errmsg;
/**
* 错误信息
*/
private String msgid;
public static SendTemplateResponse getInstance(){
return new SendTemplateResponse()
.setErrcode(404L)
.setErrmsg("");
}
public boolean isSuccess() {
return this.errcode == 0;
}
}
import com.alibaba.fastjson.JSONObject;
import lombok.Data;
import java.util.List;
@Data
public class WechatOfficialAccountMessageParam {
private String page;
private List<JSONObject> messageDataList;
}
到了这里,关于spring boot +微信小程序项目,通过微信公众号实现指定用户消息长期推送的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!