序言
由于微信小程序长期订阅的消息模板和下发统一消息推送接口全部失效以后,对于小程序的消息推送可以往公众号推可以使用本文章方案。在网上看了挺多方案,有用用户列表做匹配的等,最终觉得通过关注事件触发的方案是最省事。
准备
1、微信公众平台注册服务号(订阅号是不可以推送的)与小程序,两者都需要认证并且认证主体是一致
2、微信开放平台注册账号(该账号也需要认证),绑定小程序与公众号
3、公众号根据想要的模板消息绑定服务类目,去模板消息中先挑选你的模板消息。(如果是刚注册的公众号还需要去新的功能页面添加模板消息功能,需要微信审核,不过很快)
4、微信公众号绑定小程序
5、小程序与公众号配置服务器的ip地址白名单
6、公众号配置服务器地址
整体实现流程
通过在开放平台绑定的公众号与小程序后,我们在调用微信code2Session接口的时候会返回unionid,这个unionid就是推送的关键。
- 用到微信接口:
1、关注事件推送:用关注事件推送接口文档地址
2、查询用户基础信息:用户基础信息接口文档地址
3、获取accessToken:获取accessToken接口文档地址
4、消息模板推送:消息模板推送接口文档地址
实现代码(Java)
这里使用第三方工具包Wx-Java(非常方便),直接实现推送代码。具体源码可以浏览https://gitee.com/binary/weixin-java-tools
- 先在配置文件yml配置公众号信息
# 公众号配置wx: mp: appId: xiaochengxuappid secret: xiaochengxusecrect token: xiaochengxutoken aesKey: xiaochengxuaes # token存储在redis config-storage: type: RedisTemplate
- 封装推送工具类
/** * 微信公众号推送模板消息 * * @param openid 用户openid * @param templateData 模板参数 */ public void sendTemplateMsg(String openid, List<WxMpTemplateData> templateData) { WxMpTemplateMessage templateMessage = WxMpTemplateMessage.builder() .toUser(openid) // 模板id .templateId(templateId) // 跳转小程序appid,跳转路径 .miniProgram(new WxMpTemplateMessage.MiniProgram(weAppAppId, "", false)) // 模板参数 .data(templateData) .build(); try { log.debug("微信公众号推送模板消息入参:{}", templateMessage); // 成功返回msgId,否则都是抛异常 wxMpService.getTemplateMsgService().sendTemplateMsg(templateMessage); } catch (WxErrorException e) { log.error("微信公众号推送模板消息异常", e); } }
- 公众号接入服务器开发的代码
import lombok.AllArgsConstructor;import lombok.extern.slf4j.Slf4j;import me.chanjar.weixin.mp.api.WxMpMessageRouter;import me.chanjar.weixin.mp.api.WxMpService;import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;import org.apache.commons.lang3.StringUtils;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.*;/** * @author quan * @date 2023/11/7 18:19 */@Slf4j@AllArgsConstructor@RestController@RequestMapping("/wx")public class WxPortalController { @Autowired private final WxMpService wxService; @Autowired private final WxMpMessageRouter messageRouter; @GetMapping(produces = "text/plain;charset=utf-8") public String authGet(@PathVariable String appid, @RequestParam(name = "signature", required = false) String signature, @RequestParam(name = "timestamp", required = false) String timestamp, @RequestParam(name = "nonce", required = false) String nonce, @RequestParam(name = "echostr", required = false) String echostr) { log.info("接收到来自微信服务器的认证消息:[{}, {}, {}, {}]", signature, timestamp, nonce, echostr); if (StringUtils.isAnyBlank(signature, timestamp, nonce, echostr)) { throw new IllegalArgumentException("请求参数非法,请核实!"); } if (!this.wxService.switchover(appid)) { throw new IllegalArgumentException(String.format("未找到对应appid=[%s]的配置,请核实!", appid)); } if (wxService.checkSignature(timestamp, nonce, signature)) { return echostr; } return "非法请求"; } @PostMapping(produces = "application/xml; charset=UTF-8") public String post(@PathVariable String appid, @RequestBody String requestBody, @RequestParam("signature") String signature, @RequestParam("timestamp") String timestamp, @RequestParam("nonce") String nonce, @RequestParam("openid") String openid, @RequestParam(name = "encrypt_type", required = false) String encType, @RequestParam(name = "msg_signature", required = false) String msgSignature) { log.info("接收微信请求:[openid=[{}], [signature=[{}], encType=[{}], msgSignature=[{}]," + " timestamp=[{}], nonce=[{}], requestBody=[/n{}/n] ", openid, signature, encType, msgSignature, timestamp, nonce, requestBody); if (!this.wxService.switchover(appid)) { throw new IllegalArgumentException(String.format("未找到对应appid=[%s]的配置,请核实!", appid)); } if (!wxService.checkSignature(timestamp, nonce, signature)) { throw new IllegalArgumentException("非法请求,可能属于伪造的请求!"); } String out = null; if (encType == null) { // 明文传输的消息 WxMpXmlMessage inMessage = WxMpXmlMessage.fromXml(requestBody); WxMpXmlOutMessage outMessage = this.route(inMessage); if (outMessage == null) { return ""; } out = outMessage.toXml(); } else if ("aes".equalsIgnoreCase(encType)) { // aes加密的消息 WxMpXmlMessage inMessage = WxMpXmlMessage.fromEncryptedXml(requestBody, wxService.getWxMpConfigStorage(), timestamp, nonce, msgSignature); log.debug("消息解密后内容为:{} ", inMessage); WxMpXmlOutMessage outMessage = this.route(inMessage); if (outMessage == null) { return ""; } out = outMessage.toEncryptedXml(wxService.getWxMpConfigStorage()); } log.debug("组装回复信息:{}", out); return out; } private WxMpXmlOutMessage route(WxMpXmlMessage message) { try { return this.messageRouter.route(message); } catch (Exception e) { log.error("路由消息时出现异常!", e); } return null; }}
- 微信消息路由配置类
/** * @author quan * @date 2023/11/7 18:23 */@AllArgsConstructor@Configurationpublic class WxMpConfiguration { private final SubscribeHandler subscribeHandler; @Bean public WxMpMessageRouter messageRouter(WxMpService wxMpService) { final WxMpMessageRouter newRouter = new WxMpMessageRouter(wxMpService); // 关注事件 newRouter.rule().async(false).msgType(EVENT).event(SUBSCRIBE).handler(this.subscribeHandler).end(); return newRouter; }}
- 微信关注事件处理类
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;import com.tfcs.web.domain.bus.BusWxMpUser;import com.tfcs.web.service.BusWxMpUserService;import lombok.extern.slf4j.Slf4j;import me.chanjar.weixin.common.error.WxErrorException;import me.chanjar.weixin.common.session.WxSessionManager;import me.chanjar.weixin.mp.api.WxMpMessageHandler;import me.chanjar.weixin.mp.api.WxMpService;import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;import me.chanjar.weixin.mp.bean.result.WxMpUser;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Component;import java.util.Map;/** * 关注处理器 * * @author quan * @date 2023/9/15 14:05 */@Slf4j@Componentpublic class SubscribeHandler implements WxMpMessageHandler { @Autowired private BusWxMpUserService wxMpUserService; @Override public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map<String, Object> context, WxMpService weixinService, WxSessionManager sessionManager) { log.info("新关注用户 OPENID: " + wxMessage.getFromUser()); // 获取微信用户基本信息 try { WxMpUser userWxInfo = weixinService.getUserService() .userInfo(wxMessage.getFromUser(), null); if (userWxInfo != null) { // TODO 先检查是否存在该用户,不存在再存到数据库 } } catch (WxErrorException e) { log.error("微信公众号获取用户信息异常:", e); if (e.getError().getErrorCode() == 48001) { log.info("该公众号没有获取用户信息权限!"); } } return null; }}
- 业务代码调用demo
// 通过unionid查出公众号openidBusWxMpUser wxMpUser = wxMpUserService.getOne(new LambdaQueryWrapper<BusWxMpUser>() .eq(BusWxMpUser::getUnionid, user.getUnionid())); if (null != wxMpUser) { List<WxMpTemplateData> templateData = new ArrayList<>(5); // 对应消息模板的key templateData.add(new WxMpTemplateData("character_string2", event.getFlightNo())); templateData.add(new WxMpTemplateData("thing10", event.getStartPlace())); templateData.add(new WxMpTemplateData("thing11", event.getDestPlace())); templateData.add(new WxMpTemplateData("time3", DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD, event.getFlightStartTime()))); templateData.add(new WxMpTemplateData("thing9", "请及时处理关注事件")); weChatUtil.sendTemplateMsg(wxMpUser.getOpenid(), templateData); }
- 推送效果