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

前言

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

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

一、效果展示

用公众号给女朋友推送早安问候(恋爱值♥♥♥♥♥)_第1张图片

二、准备

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

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

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

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

3.1 打开公众平台测试账号

百度搜索“公众号”,进入官网首页,扫码登录。选择开发者工具 - 公众平台测试账号。
用公众号给女朋友推送早安问候(恋爱值♥♥♥♥♥)_第2张图片

展示的界面可能不太一样,总之,找到进入公众平台测试账号即可,该步骤会再次扫码登陆。界面如下。
用公众号给女朋友推送早安问候(恋爱值♥♥♥♥♥)_第3张图片
这是测试帐户公众号,我们可以通过这个公众号做很多事情,在该界面的底部有详细的功能说明。而我们自己的订阅号会有诸多限制,不太好使用。也没有我们需要的

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

3.2 关注测试号

在测试号二维码处,我们需要扫码关注,关注后,后再右侧列表里显示,注意,此处的微信号并不是我们手机里那个微信号哦~
提示:我们自己先关注,等开发测试完成后再让你的那个她关注,否则消息轰炸并且暴露的你的目的就很尴尬了
用公众号给女朋友推送早安问候(恋爱值♥♥♥♥♥)_第4张图片

3.3 创建模板

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

用公众号给女朋友推送早安问候(恋爱值♥♥♥♥♥)_第5张图片

四、编写代码

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

4.2 项目结构

用公众号给女朋友推送早安问候(恋爱值♥♥♥♥♥)_第6张图片 ## 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,看到页面返回“发送成功”,则表示部署成功啦。

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

结束语

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

你可能感兴趣的:(项目,/,功能,java,spring,boot,intellij-idea)