首先上效果图
在这里插入图片描述
可以看出这是公众号推送的消息,下面还有小程序的链接,可以直接跳转到小程序,这个可以根据发送的消息类型和业务决定是否让跳转到小程序。

  • 我们主要是做房地产营销业务的,业务场景:比如销售对小程序进行的转发,有新的用户进入到小程序中,这个用户就属于是该销售的用户,就会通过公众号给销售发送通知,告诉销售有新用户进行了访问。等等一些需要用到通知的业务。

(都知道小程序无法直接做消息提醒,所以使用公众号来做提醒,还有一种提醒的方式不需要通过公众号就可以完成,就是微信的服务通知,但是这种每次都需要订阅后才可以给你发送通知,只有一些行业的小程序才可以让永久订阅。这种服务通知的教程在: https://blog.csdn.net/weixin_44467567/article/details/111942539
本来我也想用这种方法,但是我们的小程序没有办法申请永久订阅,如果每次都让销售点击允许本次订阅是不现实的,所以就改用公众号的方式了。
在这里插入图片描述

  1. 提前准备 :微信小程序,微信公众号(服务号),微信开放平台
    微信小程序不用说啦,肯定要有的。
    微信公众号(服务号)是需要有企业资质才可以申请的,个人无法申请,认证费300RMB
    微信开放平台申请,这个需要企业资质,认证费用300RMB

  2. 开发前关系绑定
    在公众号后台绑定小程序,原因:公众号绑定了小程序可以再直接跳转到小程序中,位置再公众号后台左侧菜单栏 -> 小程序 -> 小程序管理中。一个公众号可以绑定多个小程序。 在这里插入图片描述
    把小程序和公众号都绑定到微信开放平台,原因:为了获取unionid,unionid是什么?简单来说,就是开放平台用于区分是否是同一用户的标识,你把多个小程序,公众号,第三方平台等都绑定到开放平台上,取到的unionid都是一样的,这样就可以知道多个不同程序之间的用户是否是同一用户。
    我们这里获取unionid就是为了知道使用我们小程序的销售和关注了公众号的销售是不是同一个销售,从而给该销售发送提醒通知,我们在小程序端获取销售的openId和unionid,在公众号端通过关注也获取销售的openId和unionid,其中openId是不同的,unionid是相同的。我们就可以通过小程序的unionid找到公众号的openid。通过openId就知道给谁发送通知。(openId就是小程序或公众号的唯一标识)
    在这里插入图片描述

  3. 公众号白名单配置和服务器配置

    白名单配置: 在公众号后台进行配置,开发 -> 基本配置 -> IP白名单。把部署的服务器IP配置进来就可以
    在公众号后台进行配置

    服务器配置: 在公众号后台进行配置,开发 -> 基本配置 -> 服务器配置
    官方文档: https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Access_Overview.html
    在这里插入图片描述
    URL: 用户微信服务器调用进行验证和发送信息使用。
    Token: 可以随便写,但是要和代码中一致,才可以验证过去
    验证服务器配置接口: 要先把接口写好放到服务器中,服务器配置才可以成功。

    	//这个token要与公众平台服务器配置填写的token一致
        private final static String TOKEN = "xxxxxx";
    	//该接口就是在公众号后台配置的服务器URL
        @GetMapping("/mp/serverCheck")
        public void doGet(HttpServletRequest request, HttpServletResponse response) throws AesException, IOException {
            // 微信加密签名,signature结合了开发者填写的token参数和请求中的timestamp参数、nonce参数。
            String signature = request.getParameter("signature");
            // 时间戳
            String timestamp = request.getParameter("timestamp");
            // 随机数
            String nonce = request.getParameter("nonce");
            // 随机字符串
            String echostr = request.getParameter("echostr");
            log.info("\n接收到来自微信服务器的认证消息:[{}, {}, {}, {}]", signature, timestamp, nonce, echostr);
            if (StringUtils.isAnyBlank(signature, timestamp, nonce, echostr)) {
                throw new IllegalArgumentException("请求参数非法,请核实!");
            // 将token、timestamp、nonce三个参数进行字典序排序 2)将三个参数字符串拼接成一个字符串进行sha1加密 3)开发者获得加密后的字符串可与signature对比,标识该请求来源于微信
            String signatureCheck = getSHA1(TOKEN, timestamp, nonce);
            log.info("\n加密后的signatureCheck = {}", signatureCheck);
            if (signatureCheck.equals(signature)) {
                log.info("\n接入成功");
                PrintWriter out = response.getWriter();
                //原样返回echostr参数
                out.print(echostr);
                out.flush();
                out.close();
            } else {
                throw new AesException(AesException.ValidateSignatureError);
    

    SHA1加密方法

        /**
         * 用SHA1算法验证Token
         * @param token     票据
         * @param timestamp 时间戳
         * @param nonce     随机字符串
         * @return 安全签名
         * @throws AesException
        public static String getSHA1(String token, String timestamp, String nonce) throws AesException {
            try {
                String[] array = new String[]{token, timestamp, nonce};
                StringBuffer sb = new StringBuffer();
                // 字符串排序
                Arrays.sort(array);
                for (int i = 0; i < 3; 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);
    
  4. 获取公众号用户的openId 和 unionId

    我这里就不写获取小程序的openId 和 unionId和用户信息了,获取小程序的代码有点杂,是以前写的,不会的可以上网找一下如何获取,如果不绑定开放平台时获取不到unionId的,可以看一下官方文档 https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/union-id.html

    下面是获取公众号的openId和unionId代码 :根据关注的用户进行获取
    这个接口就是服务器配置的URL,上面验证的时候也使用过一次,不过验证时get请求,这次是post请求。
    Controller层

    	@Autowired
        private WeChatMPService weChatMPService;
        @ApiOperation(value = "处理微信服务器发来的消息", notes = "处理微信服务器发来的消息")
        @PostMapping("/mp/serverCheck")
        public String doPost(HttpServletRequest request, HttpServletResponse response)  {
            // 调用核心服务类接收处理请求
            return weChatMPService.processRequest(request);
    

    Service接口层

    String processRequest(HttpServletRequest request, String projectId);
    

    ServiceImpl实现类

    import com.minapp.management.config.WeChatContant;
    import com.minapp.management.service.TdSysMpStaffLoginService;
    import com.minapp.management.service.WeChatMPService;
    import com.minapp.management.utils.WeChatUtil;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.stereotype.Service;
    import javax.annotation.Resource;
    import javax.servlet.http.HttpServletRequest;
    import java.util.Map;
     * @ClassName: WeChatServiceImpl
     * @Description: 可以结合官网的api看是什么意思 ↓  消息管理 -> 接受事件推送
     * https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Receiving_standard_messages.html
     * @Authror: XQD
     * @Date: 2021/1/4 15:42
    @Slf4j
    @Service
    public class WeChatMPServiceImpl implements WeChatMPService {
        @Resource
        private TdSysMpStaffLoginService mpStaffLoginService;
        @Override
        public String processRequest(HttpServletRequest request) {
            // xml格式的消息数据
            String respXml = null;
            // 默认返回的文本消息内容
            String respContent;
            try {
                // 调用parseXml方法解析请求消息
                Map<String, String> requestMap = WeChatUtil.parseXml(request);
                // 消息类型
                String msgType = requestMap.get(WeChatContant.MsgType);
                log.info("\n消息类型:{}", msgType);
                String mes = null;
                // 文本消息
                if (msgType.equals("text")) {
                    respContent = "您发送的是文本消息!";
                    respXml = WeChatUtil.sendTextMsg(requestMap, respContent);
                // 图片消息
                else if (msgType.equals("image")) {
                    respContent = "您发送的是图片消息!";
                    respXml = WeChatUtil.sendTextMsg(requestMap, respContent);
                // 语音消息
                else if (msgType.equals("voice")) {
                    respContent = "您发送的是语音消息!";
                    respXml = WeChatUtil.sendTextMsg(requestMap, respContent);
                // 视频消息
                else if (msgType.equals("video")) {
                    respContent = "您发送的是视频消息!";
                    respXml = WeChatUtil.sendTextMsg(requestMap, respContent);
                // 地理位置消息
                else if (msgType.equals("location")) {
                    respContent = "您发送的是地理位置消息!";
                    respXml = WeChatUtil.sendTextMsg(requestMap, respContent);
                // 链接消息
                else if (msgType.equals("link")) {
                    respContent = "您发送的是链接消息!";
                    respXml = WeChatUtil.sendTextMsg(requestMap, respContent);
                // 事件推送
                else if (msgType.equals("event")) {
                    // 事件类型
                    String eventType = requestMap.get("Event");
                    log.info("\n事件类型为:{}", eventType);
                    // 关注
                    if (eventType.equals("subscribe")) {
                        mpStaffLoginService.subscribeMPUserInfo(requestMap.get(WeChatContant.FromUserName));
                        respContent = "谢谢您的关注!";
                        respXml = WeChatUtil.sendTextMsg(requestMap, respContent);
                    // 取消关注
                    else if (eventType.equals("unsubscribe")) {
                        mpStaffLoginService.unsubscribeMPUserInfo(requestMap.get(WeChatContant.FromUserName));
                        // TODO 取消订阅后用户不会再收到公众账号发送的消息,因此不需要回复
                    // 扫描带参数二维码
    
    
    
    
        
    
                    else if (eventType.equals("SCAN")) {
                        // TODO 处理扫描带参数二维码事件
                    // 上报地理位置
                    else if (eventType.equals("LOCATION")) {
                        // TODO 处理上报地理位置事件
                    // 自定义菜单
                    else if (eventType.equals("CLICK")) {
                        // TODO 处理菜单点击事件
                mes = mes == null ? "不知道你在干嘛" : mes;
                if (respXml == null) {
                    respXml = WeChatUtil.sendTextMsg(requestMap, mes);
                log.info("\n"+respXml);
                return respXml;
            } catch (Exception e) {
                e.printStackTrace();
            return "";
    

    WeChatUtil工具类

    package com.minapp.management.utils;
    import com.minapp.management.config.WeChatContant;
    import org.dom4j.Document;
    import org.dom4j.Element;
    import org.dom4j.io.SAXReader;
    import javax.servlet.http.HttpServletRequest;
    import java.io.InputStream;
    import java.security.MessageDigest;
    import java.security.NoSuchAlgorithmException;
    import java.util.*;
     * @ClassName: WeChatUtil
     * @Description: 请求校验工具类
     * @Authror: XQD
     * @Date: 2021/1/4 15:35
    public class WeChatUtil {
         * 将字节数组转换为十六进制字符串
         * @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;
        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>();
            // 从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;
        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);
    

    在这里插入图片描述

    在用户关注了,会触发关注事件,微信服务器通过接口给你发送上面的一段xml信息,里面包括了openId和一些其他信息,解析出来后,可以通过openId 和access_token (access_token获取方法在我推送服务通知的博客里面) 获取到用户的昵称,头像,unionId 等等一些信息,把这些信息存入数据库中就可以。代码如下
    service接口

    import com.alibaba.fastjson.JSONObject;
    import com.minapp.management.entity.TdSysMpStaffLogin;
    import com.baomidou.mybatisplus.extension.service.IService;
     *  服务类
     * @author XQD
     * @since 2021-01-04
    public interface TdSysMpStaffLoginService extends IService<TdSysMpStaffLogin> {
    	// 关注公众号事件调用的接口
        public void subscribeMPUserInfo(String openId);
    	// 取消关注公众号事件调用的接口
        public void unsubscribeMPUserInfo(String openId);
    	// 发送模板信息接口
        boolean sendTemplateMsg(String staffIds, String messageType, JSONObject param);
    

    ServiceImpl实现类

    	@Resource
        private ObjectMapper objectMapper;
         * @Description:  订阅的公众号用户信息存入数据库
         * @Param: [projectId, openId]
         * @return: void
         * @Author: XQD
         * @Date:2021/1/4 17:45
        @Override
        public void subscribeMPUserInfo(String openId) {
            String url = "https://api.weixin.qq.com/cgi-bin/user/info?access_token="+getAccessToken()+"&openid="+openId+"&lang=zh_CN";
            String mpUserInfo = HttpClientUtil.get(url);
            Map<String, Object> map = null;
            try {
                map = objectMapper.readValue(mpUserInfo, Map.class);
            } catch (IOException e) {
                log.error("公众号异常通知-获取用户信息转化异常", e);
            TdSysMpStaffLogin mpStaffLogin = new TdSysMpStaffLogin();
            mpStaffLogin.setId(GeneratorIDUtil.generatorId())
                    .setOpenId(openId)
                    .setProjectId(projectId)
                    .setNickName((String) map.get("nickname"))
                    .setHeadImageUrl((String) map.get("headimgurl"))
                    .setSex((Integer) map.get("sex"))
                    .setSubscribe((Integer) map.get("subscribe"))
                    .setUnionId((String) map.get("unionid"))
                    .setCountry((String) map.get("country"))
                    .setProvince((String) map.get("province"))
                    .setCity((String) map.get("city"))
                    .setSubscribeScene((String) map.get("subscribe_scene"));
            UpdateWrapper<TdSysMpStaffLogin> mpStaffLoginUpdateWrapper = new UpdateWrapper<>();
            mpStaffLoginUpdateWrapper.set("subscribe",1)
                    .eq("open_id",openId);
                    //数据库操作
            if (mpStaffLoginService.saveOrUpdate(mpStaffLogin, mpStaffLoginUpdateWrapper)){
                log.info("\n关注的用户信息添加成功 openId = {}", openId);
            }else {
                log.info("\n关注的用户信息添加失败 openId = {}", openId);
         * @Description: 取消订阅用户
         * @Param: [projectId, openId]
         * @return: void
         * @Author: XQD
         * @Date:2021/1/4 17:45
        @Override
        public void unsubscribeMPUserInfo(String openId) {
            // 数据库操作,就是把关注的状态改一下
            UpdateWrapper<TdSysMpStaffLogin> mpStaffLoginUpdateWrapper = new UpdateWrapper<>();
            mpStaffLoginUpdateWrapper.set("subscribe",0)
                    .eq("open_id",openId);
            if (mpStaffLoginService.update(mpStaffLoginUpdateWrapper)){
                log.info("\n取消关注的用户操作成功 openId = {}", openId);
            }else {
                log.info("\n取消关注的用户操作失败 openId = {}", openId);
         

    HttpClientUtil类就是用来调用外部接口的,就不贴出来了,

    到现在为止是把所有数据都获取出来了,接下来要做的就是推送消息。在这里插入图片描述

    首先上效果图可以看出这是公众号推送的消息,下面还有小程序的链接,可以直接跳转到小程序,这个可以根据发送的消息类型和业务决定是否让跳转到小程序。业务需求我们主要是做房地产营销业务的,业务场景:比如销售对小程序进行的转发,有新的用户进入到小程序中,这个用户就属于是该销售的用户,就会通过公众号给销售发送通知,告诉销售有新用户进行了访问。等等一些需要用到通知的业务。(都知道小程序无法直接做消息提醒,所有使用公众号来做提醒,还有一种提醒的方式不需要通过公众号就可以完成,就是微信的服务通知,但是这种每次
    微信服务现在用的比较火,用户可以通过微信订阅信息,有时候会用到模板消息。下面贴上代码,有注释写的很详细。在此@access_token 请调用 https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=@appid&secret=@secret 接口获取。 具体代码: public static void Send() dynamic postData = new ExpandoObject(); postData.touser = OpenId; postD
    最近开发的一个小程序项目要通过服务推送通知。但是在最开始开发小程序的时候并没有考虑到这个功能。 我在做小程序服务用户关联的过程中也是踩了无数坑,所以我会在这篇文章中给出自己摸索出的解决方案。 二、准备条件 预备知识: 小程序openid:小程序用户的唯一id 公众号openid:公众号用户的唯一id unionid:同一用户,对同一个微信开放平台下的不同应用,unionid是相同的 1.将小程序公众号绑定(绑定后才可获取unionid),官方文档:小程序与公众
    基于python以及微信测试实现天气推送,用到了微信测试平台、天行数据api、和风天气api等,zip压缩包内含有相关的应用程序和本项目的word详细步骤文档,word详细步骤文档介绍了整个天气推送代码和详细文档步骤。本项目非常适合小白以及没有很好计算机基础的小伙伴们!不要写相关的代码以及什么的等等!!!在申请了微信测试、天行数据api、和风天气api等后,只要正确地填写压缩包中的txt文档中的相关信息后(文档中有相关的例子,根据例子照着填写),点击压缩包内的exe应用程序文件即可执行,从而在对应的微信接收到实时推送消息。另外,若要实现定时推送步骤文档里面也有相关的详细的步骤介绍,实现在一定的时间段或时间点进行定时推送相关信息。 以上项目实现功能以及相关描述都经测试和执行无误,其中模板的内容还可以定制化,可以进一步地根据自己求对模板进行相关的改进(压缩包内的word文档中有介绍到)。 篇幅较长,感谢您的阅读和支持,若有代码或项目执行时出错有问题等情况,望在评论区指出或直接私信联系博主!您的指出和建议能给作者带来很大的动力!!! 你也可以实现给对象做一个天气推送!!!
    四、发送消息 这里有个要特别注意的点,我们要给用户发送消息,就必须引导用户授权,如下 因为用户不点击允许,你是没有办法给用户推送消息的。每一次授权只允许发送一条消息,所以如果你想尽量多的发送消息,就得尽量多的引导用户授权。wx.requestSubscribeMessage这个方法,来获取用户的授权。 代码: 后台发送接口: 测试发送demo: 补充实体WxMssVo,TemlateData
    第一步:官网下载对应版本的cryptoDemo下载地址:https://wximg.gtimg.com/shake_tv/mpwiki/cryptoDemo.zip第二步:创建检查文件wxcheck.php这个文件名可以随便命名,要保证url中检查的文件名与之相同即可。&lt;?php printLog(json_encode($_GET)); $signature = $_G...
    推动公众号通知提醒! 关于推送获取的数据请移步:https://blog.csdn.net/weixin_44467567/article/details/112304488 第一步: 登录公众号后台创建模板消息 创建完成后点击详情 第二步 :代码实现 接口传参格式 "touser":"OPENID", "template_id":"ngqIpbwh8bUfcSsECmogfXcV14J0tQlEpBO27izEYtY", "url":"http
    微信小程序可以通过订阅消息的方式推送消息到公众号。具体步骤如下: 1. 在小程序端,开发者要先在小程序后台配置订阅消息的模板,并获取模板的模板ID。 2. 用户在小程序中进行订阅操作,授权订阅消息。 3. 小程序调用 `wx.requestSubscribeMessage` 接口,向用户发起订阅请求,用户确认后,小程序可以获取到订阅消息的订阅状态。 4. 小程序通过服务端接口将推送的消息内容发送给指定公众号。 5. 公众号在收到消息后,可以通过模板消息接口将消息推送给用户。 要注意的是,订阅消息的模板要在小程序后台和公众号后台分别配置。同时,开发者在进行消息推送要遵守微信的相关规定和限制。