背景:

起因,娘子在小红书看到技术男友给其写的公众号早安推送,深受感动,被PUA。

还好在下有些JAVA基础,在春节假期几天无事时研究了一下,基于Springboot框架自写后台,接入第三方API,实现微信公众号每日早8点准时精准推送早安问候功能。

效果图:

基于在此过程中也学习到了不少知识点,再此做记录,待温故而知新

干货:

前期准备:

  1. Springboot的构建、配置、启动略

  2. 微信公众号,微信公众号测试账号:需获取appID与appsecret,用户关注测试账号后的微信号OpenID,消息模板中的模板ID(为什么要用测试账号,因为正式账号需要企业级认证才能使用开发API等高级功能,我的公众号主体是个人,只能用测试账号),详情如下:

1.在开发接口管理中,点击开发者工具(标号3)

-- 标号2:如果你不是企业级公众号,暂时用不到此页面(正式公众号)的开发者ID与开发着密码

2.点击上图第三步,进入如下图页面,并再次点击进入公众平台测试账号页面

3.标号5:在测试账号内,需要记下appIDappsecret ,两个配合会获得access_tokenaccess_token 是用来调用微信公众平台的各种 API 接口的重要凭证(正式账号内,access_token是每2小时刷新一次);

4.标号6:用你自己的或者亲朋好友的扫码,绑定到测试号内用于后续给其发送消息

5.标号7:在微信号一栏中会生成OpenID唯一标识,用于与模板绑定发送消息

6.标号8:新增测试模板,模板ID与用户OpenID绑定,用于发送指定模板样式的消息

代码:

主要类:

RestTemplateHttpEntityObjectMapperJsonNodeRequestEntityHttpHeaders ...

主要注解:

@Service@Scheduled(cron = "0 0 8 * * ?") ...

创建业务层,随便命个名,本人学术不精,都写在了一个类里,建议分开处理

@Service
class WechatTokenService{
  private static final String APPID = "你的appId";
    private static final String APPSECRET = "你的app_secret";
    private static final String ACCESS_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + APPID + "&secret=" + APPSECRET;
    private static final String Weather_API_KEY = "我用的和风API,注册找一下给的api_key";
    private static final String LOCATION = "109.53,18.22";  //三亚坐标
    private static final String WEIBO_API_KEY = "我用的第三方API,百度搜一大把";
    private static final String OPENID = "关注用户的open_id";
    private static final String TEMPLATE_ID = "你的模板ID";
    private long daysBetween;   //存储天数

    public String getAccessToken() {
        RestTemplate restTemplate = new RestTemplate();
        String response = restTemplate.getForObject(ACCESS_TOKEN_URL, String.class);
        System.out.println("微信返回:" + response);
        return parseAccessToken(response);
    }

    //结婚日期计时,Scheduled()注解不支持返回值
    @Scheduled(fixedRate = 24 * 60 * 60 * 1000)
    public void updateMarryDate() {
        LocalDate startDate = LocalDate.of(2022, 9, 9);  // 结婚日期
        LocalDate currentDate = LocalDate.now();         // 当前日期
        daysBetween = ChronoUnit.DAYS.between(startDate, currentDate);  // 计算相差天数
    }

    //获取结婚至今日期
    public String getMarryDate() {
        return String.valueOf(daysBetween) + "天";
    }

    //获取每日一言
    public String getLetter() {
        String url = "https://v1.hitokoto.cn/";
        RestTemplate restTemplate = new RestTemplate();
        String response = restTemplate.getForObject(url, String.class);
        try {
            ObjectMapper objectMapper = new ObjectMapper();
            JsonNode node = objectMapper.readTree(response);
            return node.path("hitokoto").asText();
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }
    }

    //获取微博热搜
    public String getWeibo() {
        String url = "https://whyta.cn/api/tx/weibohot?key=" + WEIBO_API_KEY;
        RestTemplate restTemplate = new RestTemplate();
        String response = restTemplate.getForObject(url, String.class);
        try {
            ObjectMapper objectMapper = new ObjectMapper();
            JsonNode node = objectMapper.readTree(response);
            JsonNode listNode = node.path("result").path("list");
            return listNode.get(0).path("hotword").asText();
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }
    }

    //获取天气
    public String getWeather() {
        String url = "https://devapi.qweather.com/v7/weather/now?location=" + LOCATION + "&key=" + Weather_API_KEY;
        RestTemplate restTemplate = new RestTemplate();
        RequestEntity<Void> request = new RequestEntity<>(HttpMethod.GET, URI.create(url));
        ResponseEntity<byte[]> response = restTemplate.exchange(request, byte[].class);

        //解压Gzip数据
        String responseString = decompressGzip(response.getBody());
        //String response = restTemplate.getForObject(url, String.class);
        try {
            ObjectMapper objectMapper = new ObjectMapper();
            JsonNode root = objectMapper.readTree(responseString);
            JsonNode now = root.path("now");
            String windDir = now.path("windDir").asText();
            String windScale = now.path("windScale").asText();
            String weatherText = now.path("text").asText();
            String temperature = now.path("temp").asText();
            String humidity = now.path("humidity").asText();
            return "湿度:" + humidity + "%," + windDir + windScale + "级," + weatherText + temperature;
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 解压Weather的Gzip数据
     */
    private String decompressGzip(byte[] gzipData) {
        try (ByteArrayInputStream bis = new ByteArrayInputStream(gzipData);
             GZIPInputStream gzipInputStream = new GZIPInputStream(bis);
             ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
            byte[] buffer = new byte[1024];
            int len;
            while ((len = gzipInputStream.read(buffer)) > 0) {
                bos.write(buffer, 0, len);
            }
            return bos.toString();
        } catch (IOException e) {
            throw new RuntimeException("解压缩 Gzip 数据失败", e);
        }
    }


    /**
     * 解析JSON
     *
     * @param json 传入getAccessToken
     * @return 获取token的access_token
     */
    public String parseAccessToken(String json) {
        ObjectMapper mapper = new ObjectMapper();
        try {
            JsonNode root = mapper.readTree(json);
            return root.path("access_token").asText();  //获取 access_token
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }
    }

    //发送消息模板
    @Scheduled(cron = "0 0 8 * * ?")  //每天at8:00,会执行这段代码
    public void sendTemplateMessage() {
        RestTemplate restTemplate = new RestTemplate();
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);

        //当前时间
        LocalDateTime now = LocalDateTime.now();
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        String date = now.format(formatter);
        DayOfWeek week = now.getDayOfWeek();
        String[] weekDays = {"星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期日"};
        String weekDayChinese = weekDays[week.getValue() - 1];

        //获取微博热搜
        String weibo = getWeibo();
        //每日一言
        String letter = getLetter();
        //结婚至今日期
        String marry = getMarryDate();

        //获取天气
        String weatherInfo = getWeather();
        if (weatherInfo == null) {
            weatherInfo = "天气信息获取失败";
        }

        String requestBody = """
                {
                      "touser": "%s",
                      "template_id": "%s",
                      "data": {
                        "content": {
                          "value": "❤亲爱的宝,早上好,新的一天开始了❤",
                          "color": "#173177"
                        },
                        "time": {
                          "value": "%s",
                          "color": "#173177"
                        },
                        "week": {
                          "value": "%s",
                          "color": "#173177"
                        },
                        "remark": {
                          "value": "三亚",
                          "color": "#173177"
                        },
                        "weather": {
                          "value": "%s",
                          "color": "#173177"
                        },
                        "weibo": {
                          "value": "%s",
                          "color": "#173177"
                        },
                        "letter": {
                          "value": "%s",
                          "color": "#173177"
                        },
                        "marry": {
                          "value": "%s",
                          "color": "#173177"
                        }
                      }
                    }
                """.formatted(OPENID, TEMPLATE_ID, date, weekDayChinese, weatherInfo, weibo, letter, marry);
        HttpEntity<String> request = new HttpEntity<>(requestBody, headers);
        String url = "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=" + getAccessToken();
        String response = restTemplate.postForObject(url, request, String.class);
        System.out.println("微信API响应:" + response);
    }
}

在没有部署服务器上时,需要手动触发给微信公众号API发送请求,所以这里随便写个控制器层,真正上线这里是不需要的,通过注解@Scheduled(cron = "0 0 8 * * ?") 码每天早上八点重复执行注解下代

@RestController
class WechatMessageController{
   @Autowired
    WeChatTokenService service;

    @GetMapping("send")
    public String sendWeChatMessage(){
        service.sendTemplateMessage();
        return "消息已发送";
    }
}

在浏览器中输入“localhost:8088/send”,如果页面返回了“消息已发送”,就代表成功了,快看看微信有没有收到消息吧。端口号8088在application.properties配置中可自定义