Java 微信公众号消息推送(从零开始)

Posted Tatsumi_zyy

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java 微信公众号消息推送(从零开始)相关的知识,希望对你有一定的参考价值。

1.前期准备

1. 首先需要的是一个能正常运营的微信公众号或者订阅号,根据微信公众平台,使用已授权的用户登录,获取对应的appId 和 开发者密码 appSecret

得到appId和AppSecret是为了后续调用微信官方接口必不可少的参数,例如获取微信基础accessToken则需要以上入参;

2. 设置域名以及ip白名单

ip白名单主要是针对获取acces_token,一般设置成自己服务器的ip地址,因为还需要设置自定义域名,可以关联到服务器地址,从而微信接口请求返回可以有对应的请求地址,例如, 我服务器的ip地址是222.13.11.103 ,那么ip白名单就填入这个地址即可;

设置域名主要做回调地址使用,例如:pay.company.cn,需要根据官方指引,将对应的txt文件放到web服务器的根目录下即可

 在微信授权的时候需要用到其域名

3. 选择合适的消息模板,记住其模板id和点击详情后的内容格式,因为发送模板消息,需要模板id以及内容格式

2.用户微信授权,获取其openId(重要)

这一步非常重要,只有是跟微信对接,都需要得到用户的openId,才能把模板消息发送到对应的用户中,且每个微信用户都有一个唯一的openId,当获取到openId后,建议保存入库;

可参考微信官方文档:开发前必读 | 微信开放文档

这里将会指引你如何将用户进行网页授权也是就

1. 引导用户进入授权页面同意授权,获取code

2. 通过 code 换取网页授权access_token

3. 根据通过网页授权access_token和 openid 获取用户基本信息

以下代码,逻辑跟指引一样,先是构建url地址,然后回调自己定义的方法地址,同时微信会自动带出当前用户的code,然后根据code再次调用授权地址,带出对应的openId,而我们的目的就是获取用户的openId,例如官方示例地址;

https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx520c15f417810387&redirect_uri=https%3A%2F%2Fchong.qq.com%2Fphp%2Findex.php%3Fd%3D%26c%3DwxAdapter%26m%3DmobileDeal%26showwxpaytitle%3D1%26vb2ctag%3D4_2030_5_1194_60&response_type=code&scope=snsapi_base&state=123#wechat_redirect

代码如下;(敏感的appid和密码以及域名地址需要自己替换)

@Controller
public class MyController 

    private static final Logger log = LoggerFactory.getLogger(MyController .class);

    // appId
    private static final String appId = "wxc8xxxxcxxxxxx";

    // appIdSecret
    private static final String appIdSecret = "60b429xxxxxxxxxxxxxxx";

    //1.先查询code
    @RequestMapping("/getCode")
    public String getCode() 
        // 官方地址
        String urlFir = "redirect:https://open.weixin.qq.com/connect/oauth2/authorize?appid=";
        // 微信申请的域名(提前准备)
        String domain = "http://pay.xxx.cn/wxpay";
        // 自定义跳转方法
        String redirectMethod = "/weixinoauth";
        // 地址进行encode转译 (未转译的地址是:http://pay.xxx.cn/wxpay/weixinoauth)
        // 转译后的地址是: http%3A%2F%2Fpay.xxx.cn%2Fwxpay%2Fweixinoauth
        String encoderUrl = getURLEncoderString(domain + redirectMethod);
        log.info(urlFir +appId + "&redirect_uri=" + encoderUrl +"&response_type=code&scope=snsapi_base" + "&state=STATE" + "#wechat_redirect");
        return urlFir + appId + "&redirect_uri=" + encoderUrl +"&response_type=code&scope=snsapi_base" + "&state=STATE" + "#wechat_redirect";
    

    /**
     * 编码
     * @param str
     * @return
     */
    public static String getURLEncoderString(String str) 
        String result = "";
        if (null == str) 
            return "";
        
        try 
            result = java.net.URLEncoder.encode(str, "GBK");
         catch (UnsupportedEncodingException e) 
            e.printStackTrace();
        
        return result;
    

    //2.根据code获取openId
    @GetMapping("/wxpay/weixinoauth")
    public void weixinOauth(@RequestParam String code,@RequestParam String state) throws Exception 
        log.info("获取code:",code);
        String url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid="
                + appId + "&secret=" + appIdSecret + "&code=" + code + "&grant_type=authorization_code";
        Map<String, Object> paramMap = null;
        String res = HttpUtil.get(url, paramMap);
        String openid = JSONObject.parseObject(res).getString("openid");
        log.info("根据code查询得到openId:",openid);

    

需要在微信开发工具输入127.0.0.1/getCode,因为授权页面必须要在微信客户端中打开,所以需要下载微信开发工具,微信开发工具地址:开发前必读 | 微信开放文档

 下载安装后,需要使用已配置了开发者权限的微信账号登录

然后系统会打印出跳转地址和openId ,该工具会自动跳转/getCode返回的Url并且回调我们的方法 /wxpay/weixinoauth 进行获取用户code,再构建授权url地址j获取openId

打印情况如下(敏感信息已打码):

 如果正常到了这里,则说明成功了一半了。

3.获取微信基础accessToken

需要获取微信的基础accessToken用于构建模板消息发送的url入参,且该accessToken有效期只有两小时(下面简称token),因此当调用一下方法一次后,就可以将其放入redis中,设置过期时间(低于两小时);

    @GetMapping("/getToken")
    public void getAccessToken() throws Exception
        String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid="+ appId +"&secret=" + appIdSecret;
        String res = HttpUtil.get(url);
        JSONObject jsonObject = JSONObject.parseObject(res);
        String accessToken = jsonObject.getString("access_token");
        log.info("accessToken:", accessToken);
    

代码会报错,但不影响获取token,日志打印如下; 保存token后续调用发送微信模板消息需要用到;

若出现了无效的ip地址错误,则确认ip白名单是否设置再去

4. 调用微信模板方法发送消息

这一步很简单,通过以上获取的 openId, accessToken 和 前期准备的模板id即可,我们只需要封装模板内容,再调用官方url即可

自定义DTO

@Data
@ToString
public class WeChatTemplateMsg 
    /**
     * 消息
     */
    private String value;
    /**
     * 消息颜色
     */
    private String color;


    public WeChatTemplateMsg(String value) 
        this.value = value;
        this.color = "#173177";
    

    public WeChatTemplateMsg(String value, String color) 
        this.value = value;
        this.color = color;
    

    @GetMapping("/sendMessage")
    public  String sendMessage() 
        // 模板参数
        Map<String, WeChatTemplateMsg> sendMag = new HashMap<String, WeChatTemplateMsg>();

        // openId代表一个唯一微信用户,即微信消息的接收人
        String openId = "oNB9p1BpVJEquxxxxxxxxx";
        // 公众号的模板id(也有相应的接口可以查询到)
        String templateId = "B0YStqTYdjHhY9Da9Sy2NM7xxxxxxxxxxx";
        // 微信的基础accessToken
        String accessToken = "57_LubK-8NKQc6C7jsLMxvdHaI0ju4x3-HPWEFhh7GKkw9fKbWhuxxoZyX4GaVIn6y4yO7RKfSlCyHdedKJlHUMZkd8457nKm0TOoaVkbzK1HCZ4g4gZdrmAGBylGBOZu9yxxxxxxxxxxxxxxxx";
        String url = "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=" + accessToken;

        /**
         *  其他模板可以从模板库中自己添加
         * 模板ID
         * B0YStqTYdjHhY9Da9Sy2NM7HXxxxxxxxxxxxxxx
         * 开发者调用模板消息接口时需提供模板ID
         * 标题
         * 产品兑付成功提醒
         * 行业
         * 金融业 - 证券|基金|理财|信托
         * 详细内容
         * first.DATA
         * 产品名称:keyword1.DATA
         * 当期兑付本金:keyword2.DATA
         * 当期兑付利息:keyword3.DATA
         * 已兑付期数:keyword4.DATA
         * 兑付日期:keyword5.DATA
         * remark.DATA
         */
        sendMag.put("first", new WeChatTemplateMsg("f123"));
        sendMag.put("keyword1", new WeChatTemplateMsg("111"));
        sendMag.put("keyword2", new WeChatTemplateMsg("222"));
        sendMag.put("keyword3", new WeChatTemplateMsg("333"));
        sendMag.put("keyword4", new WeChatTemplateMsg("444"));
        sendMag.put("remark", new WeChatTemplateMsg("r555"));
        RestTemplate restTemplate = new RestTemplate();
        //拼接base参数
        Map<String, Object> sendBody = new HashMap<>();
        sendBody.put("touser", openId);               // openId
        sendBody.put("url", "www.baidu.com");         // 点击模板信息跳转地址
        sendBody.put("topcolor", "#FF0000");          // 顶色
        sendBody.put("data", sendMag);                   // 模板参数
        sendBody.put("template_id", templateId);      // 模板Id
        ResponseEntity<String> forEntity = restTemplate.postForEntity(url, sendBody, String.class);
        log.info("结果是: ",forEntity.getBody());
        JSONObject jsonObject = JSONObject.parseObject(forEntity.getBody());
        // 0
        String messageCode = jsonObject.getString("errcode");
        // 2431260672639467520
        String msgId = jsonObject.getString("msgid");
        System.out.println("messageCode : " + messageCode + ", msgId: " +msgId);
        return forEntity.getBody();
    

 结果是:

可能需要的依赖:

<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.4.0</version>
</dependency>

公众号推送早安问候以及天气预报(JAVA)

公众号推送早安问候以及天气预报(JAVA)

① 概述

功能点

  • 每天早上可以给指定的微信用户推送消息,经过公众号

  • 可以使用第三方接口丰富推送的消息内容

    • 百度天气api:添加天气信息推送
    • 天行数据api:添加美句、彩虹屁等语句推送
    • 通过后台计算纪念日推送
  • 效果图

技术栈点

  • spring boot实现后台
  • 微信测试账号的申请
  • 微信模版推送的配置
  • 对接百度天气api
  • 对接彩虹屁api
  • 对接优美句子api

源码开放

Gitee

GitHub

② 注册微信测试账号,编辑推送模板

  • 使用微信扫码登录此网站https://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login就能得到一个微信公众号测试号

  • 推送消息需要用到的信息

  • 用户扫码关注得到用户的id

  • 编辑消息模板

  • 此步骤的模板id、用户微信号id、以及自己的appID、appsecret是后续推送所需要用到的

③ 使用spring boot 做后台开发,并且与第三方对接

使用第三方接口——控制台 | 百度地图开放平台 (baidu.com)

  • 在百度地图开放平台注册账号,并且到控制台中的应用创建一个应用(其中应用AK是推送需要使用到的

  • 设置ip白名单为0.0.0.0/0

使用第三方接口——天行数据TianAPI - 开发者API数据平台

  • 进去注册账号选择需要的句子接口使用就行

  • 每个接口都有实例代码,直接使用就行

  • 此案例使用了彩虹屁以及英语一句话两种

spring boot后台开发

  • 创建spring boot项目,创建教程

  • 导入需要的依赖

    <properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <spring-boot.version>2.3.7.RELEASE</spring-boot.version>
    </properties>
    
    <dependencies>
        <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.20</version>
            <scope>provided</scope>
        </dependency>
    
        <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>2.0.7</version>
        </dependency>
    
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.github.binarywang/weixin-java-mp -->
        <dependency>
            <groupId>com.github.binarywang</groupId>
            <artifactId>weixin-java-mp</artifactId>
            <version>3.3.0</version>
        </dependency>
    </dependencies>
    
  • 编写对接百度天气api 的工具类

    天气的实体类

    /**
     * @author cVzhanshi
     * @create 2022-08-04 2215
     */
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class Weather 
        String wd_night;
        String date;
        String high;
        String week;
        String text_night;
        String wd_day;
        String low;
        String wc_night;
        String text_day;
        String wc_day;
        // 当前天气
        String text_now;
        // 当前温度
        String temp;
        // 风级大小
        String wind_class;
        // 风向
        String wind_dir;
    
    
    
    /**
     * @author cVzhanshi
     * @create 2022-08-04 22:02
     */
    public class WeatherUtils 
        public static void main(String[] args) 
            System.out.println(getWeather());
        
        public static Weather getWeather()
            RestTemplate restTemplate = new RestTemplate();
            Map<String,String> map = new HashMap<String,String>();
            map.put("district_id","320583"); // 地方行政代码
            map.put("data_type","all");//这个是数据类型
            map.put("ak","自己的应用AK");
            String res = restTemplate.getForObject(
                    "https://api.map.baidu.com/weather/v1/?district_id=district_id&data_type=data_type&ak=ak",
                    String.class,
                    map);
            JSONObject json = JSONObject.parseObject(res);
            JSONArray forecasts = json.getJSONObject("result").getJSONArray("forecasts");
            List<Weather> weathers = forecasts.toJavaList(Weather.class);
            JSONObject now = json.getJSONObject("result").getJSONObject("now");
            Weather weather = weathers.get(0);
            weather.setText_now(now.getString("text"));
            weather.setTemp(now.getString("temp"));
            weather.setWind_class(now.getString("wind_class"));
            weather.setWind_dir(now.getString("wind_dir"));
            return weather;
        
    
    
  • 编写对接天行数据(彩虹屁)api的工具类

    /**
     * @author cVzhanshi
     * @create 2022-08-04 22:58
     */
    public class CaiHongPiUtils 
        public static String getCaiHongPi() 
            String httpUrl = "http://api.tianapi.com/caihongpi/index?key=接口的key";
            BufferedReader reader = null;
            String result = null;
            StringBuffer sbf = new StringBuffer();
    
            try 
                URL url = new URL(httpUrl);
                HttpURLConnection connection = (HttpURLConnection) url
                        .openConnection();
                connection.setRequestMethod("GET");
                InputStream is = connection.getInputStream();
                reader = new BufferedReader(new InputStreamReader(is, "UTF-8"));
                String strRead = null;
                while ((strRead = reader.readLine()) != null) 
                    sbf.append(strRead);
                    sbf.append("\\r\\n");
                
                reader.close();
                result = sbf.toString();
             catch (Exception e) 
                e.printStackTrace();
            
            JSONObject jsonObject = JSONObject.parseObject(result);
            JSONArray newslist = jsonObject.getJSONArray("newslist");
            String content = newslist.getJSONObject(0).getString("content");
            return content;
        
    
        public static Map<String,String> getEnsentence() 
            String httpUrl = "http://api.tianapi.com/ensentence/index?key=接口的key";
            BufferedReader reader = null;
            String result = null;
            StringBuffer sbf = new StringBuffer();
            try 
                URL url = new URL(httpUrl);
                HttpURLConnection connection = (HttpURLConnection) url
                        .openConnection();
                connection.setRequestMethod("GET");
                InputStream is = connection.getInputStream();
                reader = new BufferedReader(new InputStreamReader(is, "UTF-8"));
                String strRead = null;
                while ((strRead = reader.readLine()) != null) 
                    sbf.append(strRead);
                    sbf.append("\\r\\n");
                
                reader.close();
                result = sbf.toString();
             catch (Exception e) 
                e.printStackTrace();
            
            JSONObject jsonObject = JSONObject.parseObject(result);
            JSONArray newslist = jsonObject.getJSONArray("newslist");
            String en = newslist.getJSONObject(0).getString("en");
            String zh = newslist.getJSONObject(0).getString("zh");
            Map<String, String> map = new HashMap<>();
            map.put("zh",zh);
            map.put("en",en);
            return map;
        
    
    
  • 编写计算纪念日的工具类

    public class JiNianRiUtils 
    
        public static int getLianAi()
            return calculationLianAi("2022-12-11");
        
        public static int getBirthday_Jo()
            try 
                return calculationBirthday("2009-03-09");
             catch (ParseException e) 
                e.printStackTrace();
            
            return 0;
        
        public static int getBirthday_Hui()
            try 
                return calculationBirthday("2020-01-11");
             catch (ParseException e) 
                e.printStackTrace();
            
            return 0;
        
    
    	// 计算生日天数
        public static int calculationBirthday(String clidate) throws ParseException 
            SimpleDateFormat myFormatter = new SimpleDateFormat("yyyy-MM-dd");
            Calendar cToday = Calendar.getInstance(); // 存今天
            Calendar cBirth = Calendar.getInstance(); // 存生日
            cBirth.setTime(myFormatter.parse(clidate)); // 设置生日
            cBirth.set(Calendar.YEAR, cToday.get(Calendar.YEAR)); // 修改为本年
            int days;
            if (cBirth.get(Calendar.DAY_OF_YEAR) < cToday.get(Calendar.DAY_OF_YEAR)) 
                // 生日已经过了,要算明年的了
                days = cToday.getActualMaximum(Calendar.DAY_OF_YEAR) - cToday.get(Calendar.DAY_OF_YEAR);
                days += cBirth.get(Calendar.DAY_OF_YEAR);
             else 
                // 生日还没过
                days = cBirth.get(Calendar.DAY_OF_YEAR) - cToday.get(Calendar.DAY_OF_YEAR);
            
            // 输出结果
            if (days == 0) 
                return 0;
             else 
                return days;
            
        
    	
        // 计算天数
        public static int calculationLianAi(String date) 
            DateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
            int day = 0;
            try 
                long time = System.currentTimeMillis() - simpleDateFormat.parse(date).getTime();
                day = (int) (time / 86400000L);
             catch (ParseException e) 
                e.printStackTrace();
            
            return day;
        
    
    
  • 编写推送类

    /**
     * @author cVzhanshi
     * @create 2022-08-04 21:09
     */
    public class Pusher 
    
        public static void main(String[] args) 
            push();
        
        private static String appId = "xx";
        private static String secret = "xx";
    
    
    
        public static void push()
            //1,配置
            WxMpInMemoryConfigStorage wxStorage = new WxMpInMemoryConfigStorage();
            wxStorage.setAppId(appId);
            wxStorage.setSecret(secret);
            WxMpService wxMpService = new WxMpServiceImpl();
            wxMpService.setWxMpConfigStorage(wxStorage);
            //2,推送消息
            WxMpTemplateMessage templateMessage = WxMpTemplateMessage.builder()
                    .toUser("用户微信id") 
                    .templateId("消息模板id")
                    .build();
            //3,如果是正式版发送模版消息,这里需要配置你的信息
            Weather weather = WeatherUtils.getWeather();
            Map<String, String> map = CaiHongPiUtils.getEnsentence();
            templateMessage.addData(new WxMpTemplateData("riqi",weather.getDate() + "  "+ weather.getWeek(),"#00BFFF"));
            templateMessage.addData(new WxMpTemplateData("tianqi",weather.getText_now(),"#00FFFF"));
            templateMessage.addData(new WxMpTemplateData("low",weather.getLow() + "","#173177"));
            templateMessage.addData(new WxMpTemplateData("temp",weather.getTemp() + "","#EE212D"));
            templateMessage.addData(new WxMpTemplateData("high",weather.getHigh()+ "","#FF6347" ));
            templateMessage.addData(new WxMpTemplateData("windclass",weather.getWind_class()+ "",以上是关于Java 微信公众号消息推送(从零开始)的主要内容,如果未能解决你的问题,请参考以下文章

    简单使用Java实现微信公众号推送模板消息

    Java对接微信公众号模板消息推送

    php 推送微信公众号模板消息

    微信公众号模板消息推送

    Java对接微信公众号模板消息推送

    Java对接微信公众号模板消息推送