• 用公众号给女朋友推送早安问候(恋爱值+++++)


    前言

    最近,女朋友给我推了一个抖音视频,讲述的是一个女孩子收到了她对象的早安问候,里面文字花花绿绿,问候词都快羞红了我的脸,不过,看的出来她很喜欢。好吧,我懂了。于是,不太会公众号开发的我去B站学习了一下,然后就敲了出来。本来想给她一个惊喜的,让她知道别人的宝有的,我的宝也会有。结果被她早早的发现了,哈哈哈。这里记录与分享一下开发过程。

    说明: 我将代码放在了码云仓库里,本文的部分代码已经修改过,仅做参考,感兴趣的可以去仓库把项目拉下来玩玩。
    仓库地址:https://gitee.com/zhangdl945/goodmorning.git

    一、效果展示

    二、准备

    我们需要准备如下几个东西。

    • 注册一个公众号(我们一般都是订阅号)
    • 注册一个天行数据账号

    天行数据的使用本文就不介绍了,很简单,登录官网进去看一下就知道了。里面有很多接口,我们也可以把推送消息做的很有趣。
    天行数据接口的数据有时候会改动,如果发现有问题,记得及时修改哦,我今天就遇见了。

    三、公众平台测试账号操作

    3.1 打开公众平台测试账号

    百度搜索“公众号”,进入官网首页,扫码登录。选择开发者工具 - 公众平台测试账号。
    在这里插入图片描述

    展示的界面可能不太一样,总之,找到进入公众平台测试账号即可,该步骤会再次扫码登陆。界面如下。
    在这里插入图片描述
    这是测试帐户公众号,我们可以通过这个公众号做很多事情,在该界面的底部有详细的功能说明。而我们自己的订阅号会有诸多限制,不太好使用。也没有我们需要的

    很多人会问为什么要使用测试账号呢?
    我们自己的订阅号会有诸多限制,很多功能都不能使用或者有调用限制,比如我们这次要使用的微信模板就无法使用。
    我们每个人都有一个测试账号,我们可以通过该账号做很多订阅号或服务号无法使用的功能,详细功能说明在当前页的最下方,官方有列表清单。

    3.2 关注测试号

    在测试号二维码处,我们需要扫码关注,关注后,后再右侧列表里显示,注意,此处的微信号并不是我们手机里那个微信号哦~
    提示:我们自己先关注,等开发测试完成后再让你的那个她关注,否则消息轰炸并且暴露的你的目的就很尴尬了
    在这里插入图片描述

    3.3 创建模板

    我们需要的就是微信的模板功能,我们需要在此处先创建咱们的模板。
    简单说明一下。
    模板标题和模板内容都很随意,可以根据自己的需要自行创建。比如有些人的模板内容就只有{{}}及其内容,没有其他文字,也是可以的,我们可以在代码里编写,说白了就是换了个地方。我的仅作参考。
    需要注意两点:
    1、{{}}里的内容必须按照要求,参数需以{{开头,以.DATA}}结尾。
    2、创建模板内容一定要加换行,你需要展示什么样的格式你就写成什么样

    在这里插入图片描述

    四、编写代码

    4.1 创建一个spring boot项目,通过IDEA很容易,此处不做说明了。

    4.2 项目结构

    ## 4.3 改POM文件 此处只记录依赖。
    <dependencies>
            
            <dependency>
                <groupId>org.apache.httpcomponentsgroupId>
                <artifactId>httpclientartifactId>
                <version>4.5.5version>
            dependency>
    
            
            <dependency>
                <groupId>junitgroupId>
                <artifactId>junitartifactId>
                <version>4.12version>
            dependency>
    
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-webartifactId>
            dependency>
    
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-devtoolsartifactId>
                <scope>runtimescope>
                <optional>trueoptional>
            dependency>
            <dependency>
                <groupId>org.projectlombokgroupId>
                <artifactId>lombokartifactId>
                <optional>trueoptional>
            dependency>
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-testartifactId>
                <scope>testscope>
            dependency>
        dependencies>
    

    4.4 建YML文件

    可以使用工具创建的application.properties文件,也可以创建application.yml文件。
    我是用的是properties文件。修改了项目端口号为8081.

    server.port=8081
    

    4.5 创建工具类

    4.5.1 常量类

    我将很多数据都常量化,放在了常量类Constants中,方便修改与调用。

    public class Constants {
    
        // URL
        public static final String BASE_URL = "";
        // 获取Access Token地址 GET 
        public static final String ACCESS_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token";
        // 获取关注用户列表的地址 GET
        public static final String USER_LIST_URL = "https://api.weixin.qq.com/cgi-bin/user/get";
        // 发送模板消息地址 POST 
        public static final String SEND_TEMPLATE_URL = "https://api.weixin.qq.com/cgi-bin/message/template/send";
    
        // Key 微信服务器需要
        public static final String APPID = "wxbe000000000000";// 填写自己的,此处现在是模拟值
        public static final String APPSECRET = "0e1328800000000000000000000";// 填写自己的,此处现在是模拟值
        public static final String GRANT_TYPE = "client_credential";
        // 模板ID
        public static final String TEMPLATE_ID = "lcjC0_k2ISZG89Go0000000000000";// 填写自己的,此处现在是模拟值,该数据可以通过接口获取
    
        // 天行数据
        public static final String TX_KEY = "c091b0b0c1823ab17000000000000";// 填写自己的,此处现在是模拟值
        public static final String TX_CITY = "阆中市";
        // 每日天气地址 GET
        public static final String TX_WEATHER_URL = "http://api.tianapi.com/tianqi/index";
        // 每日一句情话地址(彩虹屁) GET
        public static final String TX_ONE_SENTENCE_URL = "http://api.tianapi.com/caihongpi/index";
        // 查询指定日期的老黄历地址
        public static final String TX_LHL_URL = "http://api.tianapi.com/lunar/index";
        // 每日英语
        public static final String TX_ENGLISH_URL = "http://api.tianapi.com/everyday/index";
    
    
    
        // 本项目重要参数
        public static final String START_DATE = "2021-10-03";// 在一起的日期
        public static final String BIRTHDAY = "05-21";// 生日
        public static final String SEND_TIME_YEAR = "2022";// 发送时间(年)
        public static final String SEND_TIME_MOUTH = "9";// 发送时间(月)
        public static final String SEND_TIME_DAY = "30";// 发送时间(日)
        public static final String SEND_TIME_HOUR = "9";// 发送时间(小时)
        public static final String SEND_TIME_MINUTE = "30";// 发送时间(分钟)
        public static final String SEND_TIME_SECOND = "0";// 发送时间(秒)
        public static final String HERWID = "oqVWd58nmbnpK0000000000000000";// 她的微信ID
        public static Boolean isSendHer = false;// 是否发给她
    
        // 颜色
        public static final String DATA_COLOR = "#F08080";
        public static final String WEEK_COLOR = "#F08080";
        public static final String CITY_COLOR = "#003399";
        public static final String WEATHER_COLOR = "#660066";
        public static final String REAL_COLOR = "#66cc66";
        public static final String LOWEST_COLOR = "#33ccff";
        public static final String HIGHEST_COLOR = "#ff0000";
        public static final String LOVEDAY_COLOR = "#ff33cc";
        public static final String SAYING_COLOR = "#ff66cc";
        public static final String ENGLISH_COLOR = "#5aade0";
    
    }
    

    4.5.2 接口调用工具类

    主要写了两个公用方法。GET/POST方式调用接口。

    public class HttpNet {
    
        public static String sendPost(String url, String json) throws IOException {
            String result = "";
            HttpPost httpPost = new HttpPost(url);
            CloseableHttpClient httpClient = HttpClients.createDefault();
            try {
                BasicResponseHandler handler = new BasicResponseHandler();
                StringEntity entity = new StringEntity(json, "utf-8");//解决中文乱码问题
                entity.setContentEncoding("UTF-8");
                entity.setContentType("application/json");
                httpPost.setEntity(entity);
                result = httpClient.execute(httpPost, handler);
                return result;
            } catch (Exception e) {
                e.printStackTrace();
    
            } finally {
                try {
                    httpClient.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            return result;
        }
    
    
        public static String sendGet(String url){
            CloseableHttpClient httpClient = HttpClients.createDefault();
            HttpGet httpGet = new HttpGet(url);
            CloseableHttpResponse httpResponse = null;
            String content = "";
            try {
                httpResponse = httpClient.execute(httpGet);
                HttpEntity entity =httpResponse.getEntity();
                if(entity!=null) {
                    content = EntityUtils.toString(entity,"utf-8");
                }
    
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if(httpResponse!=null) {
                    try {
                        httpResponse.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                try {
                    httpClient.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return content;
        }
    
    
    }
    

    4.6 写业务代码

    4.6.1 创建实体类

    实体类是跟业务相关,需要的东西也都不一样,此处就不详细记录,上面的结构图有我的实体类。这里以AccessToken为例。
    我们使用Lombok,实体类就会很干净。天行数据的接口都很类似,记得好好设计一下,我的也是后面有过改动版本,就不再此处展示了。

    @Component
    @NoArgsConstructor
    @AllArgsConstructor
    @Data
    @ToString
    public class AccessToken {
        private String access_token;// 获取到的凭证
        private String expires_in;// 凭证有效时间,单位:秒
    }
    

    4.6.2 接口调用类

    该类主要是通过工具类调用各个接口,获取结果。功能类似,自行编写吧。

    @Slf4j
    public class SendReq {
    
        /**
         * 获取AccessToken
         */
        public static AccessToken getAccessToken() throws JsonProcessingException {
            String url = Constants.ACCESS_TOKEN_URL + "?grant_type=" + Constants.GRANT_TYPE + "&appid=" + Constants.APPID + "&secret=" + Constants.APPSECRET;
            String result = HttpNet.sendGet(url);
            ObjectMapper objectMapper = new ObjectMapper();
            // 调用失败
            if(result.indexOf("errcode") >= 0){
                ReturnError returnError = objectMapper.readValue(result, ReturnError.class);
                log.error(returnError.getErrmsg());
                return null;
            }
    
            AccessToken accessToken = objectMapper.readValue(result, AccessToken.class);
            log.info(accessToken.getAccess_token()+"-"+accessToken.getExpires_in()+"s");
            return accessToken;
        }
    }
    

    4.6.3 创建Job类

    Job任务每天早上九点半执行一次,间隔时间24小时。
    Job任务为通过接口获取我们需要展示的数据,然后发送消息。
    最好把起始时间和时间间隔也常数化。

    @Slf4j
    @RestController
    public class SendMorning {
    
        @RequestMapping("/sendGoodMorning")
        public String sendGoodMorning(){
            Calendar calendar = Calendar.getInstance();
            calendar.set(Calendar.HOUR_OF_DAY, Integer.parseInt(Constants.SEND_TIME_HOUR));//24小时制
            calendar.set(Calendar.MINUTE, Integer.parseInt(Constants.SEND_TIME_MINUTE));
            calendar.set(Calendar.SECOND, Integer.parseInt(Constants.SEND_TIME_SECOND));
    
            Date time = calendar.getTime(); //第一次执行定时任务的时间
    
            TimerTask task = new TimerTask() {
                @Override
                public void run() {
                    myTimerTask();
                }
            };
    
            Timer timer = new Timer();
            // 定时任务,每天发送
            timer.schedule(task, time,1000 * 60 * 60 * 24);
    
            return "准备成功,待发送!";
    
        }
    
        /**
         * 我的定时任务
         */
        public void myTimerTask(){
            AccessToken accessToken = null;
            try {
                // 获取Access_Token
                accessToken = SendReq.getAccessToken();
    
                // 获取关注列表
                UserListReturn userListReturn = SendReq.getUserList(accessToken.getAccess_token());
                List<String> userList = userListReturn.getData().getOpenid();
    
                // 获取天气
                TxReturn<TxWeather> weatherInfo = SendReq.getWeatherInfo();
                TxWeather txWeather = weatherInfo.getNewslist().get(0);
    
                // 获取每日一句话
                TxReturn<TxOneSentence> oneSentenceInfo = SendReq.getOneSentenceInfo();
                TxOneSentence txOneSentence = oneSentenceInfo.getNewslist().get(0);
    
                // 获取每日英语
                TxReturn<TxEnglish> englishEveryDay = SendReq.getEnglishEveryDay();
                TxEnglish txEnglish = englishEveryDay.getNewslist().get(0);
    
                // 遍历关注用户,发送问候消息
                for (int i = 0; i < userList.size(); i++) {
                	// 因为部署时当前时间超过了设置时间就会发送一条,此处做个判断,第一次不给她发,第二次开始发
                    if(!Constants.isSendHer){
                        Constants.isSendHer = true;
                        continue;
                    }
                    // 设置发送内容
                    TemplateParams templateParams = new TemplateParams();
                    // 接收人
                    templateParams.setTouser(userList.get(i));
                    // 模板ID
                    templateParams.setTemplate_id(Constants.TEMPLATE_ID);
    
                    // data数据填充
                    TemplateParamsData data = SendMorning.setTemplateContent(txWeather, txOneSentence, txEnglish);
                    templateParams.setData(data);
    
                    // 将对象转为JSON字符串
                    String templateJsonStr = new ObjectMapper().writeValueAsString(templateParams);
    
                    // 发送早安消息
                    ReturnError returnError = SendReq.sendTemplateInfo(accessToken.getAccess_token(), templateJsonStr);
    
                    if("0".equals(returnError.getErrcode())){
                        log.info("QQ发送成功");
                    } else {
                        log.error("QQ发送失败!"+returnError.getErrmsg());
                    }
                }
    
            } catch (Exception e) {
                log.error("程序出错啦~ :" + e.getMessage());
                e.printStackTrace();
            }
        }
    
        /**
         * 给模板数据对象赋值
         */
        public static TemplateParamsData setTemplateContent(TxWeather weather, TxOneSentence oneSentence, TxEnglish english) throws ParseException {
            TemplateParamsData data = new TemplateParamsData();
            TemplateParamsDataValue dataValue = null;
    
            // 日期
            data.setDate(new TemplateParamsDataValue(weather.getDate(), Constants.DATA_COLOR));
    
            // 星期
            data.setWeek(new TemplateParamsDataValue(weather.getWeek(), Constants.WEEK_COLOR));
    
            // 城市
            data.setCity(new TemplateParamsDataValue(weather.getArea(), Constants.CITY_COLOR));
    
            // 天气
            data.setWeather(new TemplateParamsDataValue(weather.getWeather(), Constants.WEATHER_COLOR));
    
            // 实时温度
            data.setReal(new TemplateParamsDataValue(weather.getReal(), Constants.REAL_COLOR));
    
            // 最低温度
            data.setLowest(new TemplateParamsDataValue(weather.getLowest(), Constants.LOWEST_COLOR));
    
            // 最高温度
            data.setHighest(new TemplateParamsDataValue(weather.getHighest(), Constants.HIGHEST_COLOR));
    
            // 恋爱多少天了
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
            Long today = new Date().getTime();
            Long start = sdf.parse(Constants.START_DATE).getTime();
            data.setLoveday(new TemplateParamsDataValue((today-start)/(1000*60*60*24)+"", Constants.LOVEDAY_COLOR));
    
            // 每日一句话
            data.setSaying(new TemplateParamsDataValue(oneSentence.getContent().replaceAll("XXX","倩倩"), Constants.SAYING_COLOR));
    
            // 每日英语
            data.setEnglish(new TemplateParamsDataValue(english.getContent(),Constants.ENGLISH_COLOR));
    
            return data;
        }
    }
    

    五、部署项目

    功能开发已经完成,接下来就是部署项目了。
    我是将自己的项目部署在服务器的。部署Spring Boot项目网上教程很多,这里就不赘述了。
    最后,我的项目还需要一个调用。我们通过浏览器,调用Job方法。
    我的调用地址是 IP:8081/sendGoodMorning,看到页面返回“发送成功”,则表示部署成功啦。

    我们也可以通过其他方式,就不用这样还需要去调一次了。

    结束语

    至此,我们就完成了这个早安功能,可以看出步骤还是很多,虽然代码不复杂,还是需要我们细心的去做才能做好,样式大家可以自行调整,比如颜色等。
    最最最重要的,大家先将时间缩小一点调试。部署成功后记得要让她关注测试账号哦,就是上面的测试二维码!!!
    祝大家都能完成,你的那个她也能喜欢。愿你们长久幸福。

  • 相关阅读:
    一文搞懂MobileNet v1网络
    Word控件Spire.Doc 【段落处理】教程(一):C#/VB.NET:在 Word 中对齐文本
    mysql8离线安装
    【springboot】9、Rest风格请求及视图解析
    Python的命名规范
    wy的leetcode刷题记录_Day51
    博图数值按照特定格式(“T000000”)转换成字符串
    golang升级到1.18.4版本 遇到的问题
    [论文阅读] (23)恶意代码作者溯源(去匿名化)经典论文阅读:二进制和源代码对比
    动态规划习题——
  • 原文地址:https://blog.csdn.net/qq_41404112/article/details/127108992