• 微信小程序向公众号推送模板消息(根据用户登录小程序openid实现向同一主体下对应公众号推送模板消息)


    最近文章更新没有那么频繁,可能每隔一个月左右会更一篇文章,主要就是我正在学习的内容demo或者工作中刚好用到的内容,有的篇幅内容相较于之前的文章会略长,可以根据目录检测自己需要查看的段落。

    这里只针对第二种方式写了demo,后期可能对第一种方式进行补充。

    方式一:
    向用户推送消息需要用到用户的openid,当用户关注公众号后就会生产一个唯一不变的openid,小程序是在登录注册时就可以获取到openid,但是小程序的openid和公众号的openid是各自独立的,我们通过返回的unionid来对二者进行关联。
    方式二【本文演示的】
    通过微信提供的接口也可以实现,通过用户登录小程序产生的openid来实现消息的推送(用户如果未登录小程序不能实现消息的推送,也必须对公众号进行关注)
    官方文档地址:https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/mp-message-management/uniform-message/sendUniformMessage.html
    image.png

    注意:当前小程序和公众号必须在同一主体下

    请求的参数主要包含

    • access_token 可以通过get请求来进行获取
    • touser 用户的openid,小程序返回的openid
    • mp_template_msg 推送公众号消息的参数
      • appid 公众号的id
      • template_id 模板的id
      • url 模板所要跳转的内容
      • miniprogram 模板索要跳转的小程序
      • data 小程序要发送的消息内容

    参数示例

    一个消息发送的实例

    {
      "touser":"OPENID",
      "mp_template_msg":{
            "appid":"APPID ",
            "template_id":"TEMPLATE_ID",
            "url":"http://weixin.qq.com/download",
            "miniprogram":{
                "appid":"xiaochengxuappid12345",
                "pagepath":"index?foo=bar"
            },
            "data":{
                "first":{
                    "value":"恭喜你购买成功!",
                    "color":"#173177"
                },
                "keyword1":{
                    "value":"巧克力",
                    "color":"#173177"
                },
                "keyword2":{
                    "value":"39.8元",
                    "color":"#173177"
                },
                "keyword3":{
                    "value":"2014年9月22日",
                    "color":"#173177"
                },
                "remark":{
                    "value":"欢迎再次购买!",
                    "color":"#173177"
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    返回成功数据的实例

    {
      "errCode": 0,
      "errMsg": "openapi.uniformMessage.send:ok"
    } 
    
    • 1
    • 2
    • 3
    • 4

    整合Springboot

    用到的依赖

     <dependencies>
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-webartifactId>
            dependency>
    
            <dependency>
                <groupId>com.alibabagroupId>
                <artifactId>fastjsonartifactId>
                <version>1.2.73version>
            dependency>
    
            
            <dependency>
                <groupId>com.github.ben-manes.caffeinegroupId>
                <artifactId>caffeineartifactId>
            dependency>
            
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-configuration-processorartifactId>
                <optional>trueoptional>
            dependency>
    
            
            <dependency>
                <groupId>org.projectlombokgroupId>
                <artifactId>lombokartifactId>
            dependency>
       
            
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-quartzartifactId>
            dependency>
    
        dependencies>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    1. 首先你要获取到access_token

    官方文档地址:http://caibaojian.com/wxwiki/9a186d136f1e9dce26e9593cadbc7130083b48d0.html
    公众号在进行接口调用时都会需要access_token,这相当于我们和微信服务器进行交互的凭证,access_token每两个小时会刷新一次,新的token生效后前面的token会失效,两个token进行交替时5min内新老token都可以使用。我们每天对接口的调用是有次数限制的,每天最多调用2000次。

    在这里插入图片描述

    1.1 创建一个实体类,用来保存微信相关的配置信息

    @Data
    @Component
    @PropertySource(value = "classpath:/application.yml")
    @ConfigurationProperties(prefix = "wechat")
    public class WeChatProperties {
        /**
         * 微信公众号的appid
         */
        @Value("${appId}")
        private String appId;
        /**
         * 微信小程序的secret
         */
        @Value("${secret}")
        private String secret;
        /**
         * 获取access_token的url
         */
        @Value("${accessUrl}")
        private String accessUrl;
        /**
         * 消息发送的id
         */
        @Value("${uniformSend}")
        private String uniformSend;
        /**
         * 微信小程序的appid
         */
        @Value("${miniAppId}")
        private String miniAppId;
        /**
         * 微信小程序跳转页面路径
         */
        @Value("${pagepath}")
        private String pagepath;
    
        /**
         * 获取token的定时任务corn
         */
        @Value("${corn}")
        private String corn;
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    1.2 这里我直接使用的restTemplate接口接口的调用,也可以使用httpclient,自由选择
    所以这里需要对restTemplate进行配置

    /**
     * @desc:
     * @author: LiuChang
     * @since: 2022/7/22
     */
    @Configuration
    public class RestTemplateConfig {
    
        /**
         * rest 模板
         *
         * @param clientHttpRequestFactory
         * @return
         */
        @Bean
        public RestTemplate restTemplate(ClientHttpRequestFactory clientHttpRequestFactory) {
            return new RestTemplate(clientHttpRequestFactory);
        }
    
        /**
         * 请求连接池的配置信息
         *
         * @return
         */
        @Bean
        public ClientHttpRequestFactory simpleClientHttpRequestFactory() {
            SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
            factory.setConnectTimeout(15000);
            factory.setReadTimeout(5000);
            return factory;
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    1. 在yaml配置文件中指定配置信息
    server:
      port: 7777
    wechat:
      #公众号的appid
      appId: 
      #小程序的appid
      miniAppId: 
      #小程序的secret
      secret: 
      accessUrl: https://api.weixin.qq.com/cgi-bin/token
      uniformSend: https://api.weixin.qq.com/cgi-bin/message/wxopen/template/uniform_send?access_token=
      #小程序跳转的url
      pagepath: pages/index
      #定时任务执行
      corn: 0 0 0/2 * * ?
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    1. 调用接口获取token
    @Resource
    RestTemplate restTemplate;
    @Resource
    WeChatProperties weChatProperties;
    
    public String getAccessToken() {
            String url = weChatProperties.getAccessUrl();
            // 这里的参数要和下面的Map Key值对应
            String path = "?grant_type={grant_type}&appid={appid}&secret={secret}";
            Map<String, String> params = new HashMap<>(3);
            params.put("grant_type", "client_credential");
            params.put("appid", weChatProperties.getMiniAppId());
            params.put("secret", weChatProperties.getSecret());
            ResponseEntity<String> forObject = restTemplate.getForEntity(url + path, String.class, params);
            JSONObject jsonObject = JSONObject.parseObject(forObject.getBody());
            String accessToken = jsonObject.getString("access_token");
            if (null == accessToken) {
                log.error("获取access_token失败");
            } else {
                //将token保存到缓存中
                //caffeineCache.put("access_token", accessToken);
            }
            return accessToken;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    1. 引入缓存(可忽略)

    2.1 缓存的配置文件 我这里缓存用的caffeine,主要是读的性能比较好,由于项目只有token用到了缓存就引入整个,也可以使用redis/memoryCache

    /**
     * @desc: token的缓存配置
     * @author: LiuChang
     * @since: 2022/7/22
     */
    @Configuration
    public class TokenCacheConfig {
    
        @Bean
        public Cache caffeineCache() {
            return Caffeine.newBuilder()
                    // 设置最后一次写入或访问后经过固定时间过期
    //                .expireAfterWrite(90, TimeUnit.MINUTES)
                    // 初始的缓存空间大小
                    .initialCapacity(100)
                    // 缓存的最大条数
                    .maximumSize(1000)
                    .build();
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    2.2 使用cache

    @Resource
    Cache<String, Object> caffeineCache;
    
    
    //获取
     caffeineCache.getIfPresent("access_token")
    //放入数据
    caffeineCache.put("access_token", accessToken);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    1. 引入定时任务(可忽略)

    3.1 需要执行的定时任务

    @Slf4j
    @Component
    public class QuartzJob extends QuartzJobBean {
        @Resource
        WxMessageSendService wxMessageSendService;
    
        @Override
        protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
            try {
                // 定时刷新token
                String token = wxMessageSendService.getAccessToken();
                log.info(token);
            } catch (Exception e) {
                log.error("任务失败 error:{}", e.getMessage());
            }
        }
    }
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    3.2 定时任务的配置文件

    @Configuration
    public class QuartzConfig {
        /**
         * corn表达式
         */
        @Value("${wechat.corn}")
        private String restartCron;
    
        @Bean
        public JobDetail restartJob() {
            return JobBuilder.newJob(QuartzJob.class).withIdentity("QuartzJob").storeDurably().build();
        }
    
        @Bean
        public Trigger restartTrigger() {
            CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(restartCron);
            return TriggerBuilder.newTrigger().forJob(restartJob())
                    .withIdentity("QuartzJob").withSchedule(scheduleBuilder).build();
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    3.3 启动类增加注解

    @SpringBootApplication
    @EnableScheduling
    
    • 1
    • 2
    1. 调用官方接口,推送消息测试

    消息内容的实体类

    /**
     * @desc: 微信推送消息类
     * @author: LiuChang
     * @since: 2022/7/25
     */
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class WxMessageBean {
        private MessageBean first;
    
        private MessageBean keyword1;
    
        private MessageBean keyword2;
    
        private MessageBean keyword3;
    
        private MessageBean keyword4;
    
        private MessageBean remark;
    
        /**
         * 自定义构造,first和remark必传,keyword根据需要自定义个数
         *
         * @param first
         * @param remark
         * @param keyword
         */
        public WxMessageBean(MessageBean first, MessageBean remark, MessageBean... keyword) {
            this.first = first;
            int count = 1;
            for (MessageBean keyword1 : keyword) {
                if (count == 1) {
                    this.keyword1 = keyword1;
                } else if (count == 2) {
                    this.keyword2 = keyword1;
                } else if (count == 3) {
                    this.keyword3 = keyword1;
                } else if (count == 4) {
                    this.keyword4 = keyword1;
                }
                count++;
            }
    
            this.remark = remark;
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class MessageBean {
        /**
         * 消息内容
         */
        private String value;
        /**
         * color
         */
        private String color;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    接口编写

    @Service
    @Slf4j
    public class WxMessageSendService {
        @Resource
        RestTemplate restTemplate;
        @Resource
        WeChatProperties weChatProperties;
        @Resource
        Cache<String, Object> caffeineCache;
    
        /**
         * 推送消息
         *
         * @param openId
         * @param message
         * @return
         */
        public String messageSend(String openId, Object message) {
            String token = (String) caffeineCache.getIfPresent(ConstantEnum.ACCESS_TOKEN.getCode());
            if (token == null) {
                token = this.getAccessToken();
            }
            String url = weChatProperties.getUniformSend() + token;
            // 这里的参数要和下面的Map Key值对应
            JSONObject obj = new JSONObject();
            JSONObject mpTemplateMsg = new JSONObject();
            JSONObject mini = new JSONObject();
            mini.put("appid", weChatProperties.getMiniAppId());
            mini.put("pagepath", weChatProperties.getPagepath());
            mpTemplateMsg.put("data", message);
            mpTemplateMsg.put("miniprogram", mini);
            ///微信公众号appid
            mpTemplateMsg.put("appid", weChatProperties.getAppId());
            ///微信公众号模板id
            mpTemplateMsg.put("template_id", template_id);
            obj.put("touser", openId);
            obj.put("mp_template_msg", mpTemplateMsg);
            ResponseEntity<String> forObject = restTemplate.postForEntity(url, obj, String.class);
            JSONObject object = JSON.parseObject(forObject.getBody());
            Integer errcode = (Integer) object.get("errcode");
            if (errcode == 0) {
                log.info("消息推送成功");
                return null;
            } else if (errcode == 40003) {
                log.error("推送消息的openid错误,openid:{},消息内容:{}", openId, message);
                return "推送消息的openid错误";
            } else if (errcode == 43004) {
                log.error("该用户未关注公众号,openid:{},消息内容:{}", openId, message);
                return "该用户未关注公众号";
            } else {
                return null;
            }
        }
        
        //下面还有个方法是获取access_token的 这里省略没写,在上面获取token已经将方法写了
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56

    调用接口

    @RestController
    @RequestMapping("/message_send")
    public class WxMessageSendController {
        @Resource
        WxMessageSendService wxMessageSendService;
    
        /**
         * 通过openid和消息内容向指定用户推送公众号模板消息
         *
         * @return
         */
        @PostMapping()
        public String messageSend() {
            WxMessageBean messageBean = new WxMessageBean(new MessageBean("你有新的待审批消息,请点击查看", "#173177"),
                                                          new MessageBean("footer", "#173177"),
                                                          new MessageBean("keyword1", "#173177"));
            return wxMessageSendService.messageSend(openid, messageBean);
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    调用接口后的结果

    在这里插入图片描述

    注意事项:

    1. 两个appid和secret要对应填写正确别写反
    2. openid是用户小程序的id
    3. access_token 参数填写正确
  • 相关阅读:
    栈的实现.
    ubuntu/Linux连接redis教程
    双版本数据加载的系统设计
    java swing(GUI) MySQL实现的学生选课成绩管理系统源码+运行教程
    MySQL全解,自顶向下深入解剖,只为诸君掌握MySQL数据库
    python——装饰器深入研究(一)
    『Java 语法基础』面向对象有哪些特性
    普适型GNSS接收机_一体化GNSS测量仪
    2022年物联网统计数据
    xctf攻防世界 MISC之CatFlag
  • 原文地址:https://blog.csdn.net/chang100111/article/details/126347433