最近,女朋友给我推了一个抖音视频,讲述的是一个女孩子收到了她对象的早安问候,里面文字花花绿绿,问候词都快羞红了我的脸,不过,看的出来她很喜欢。好吧,我懂了。于是,不太会公众号开发的我去B站学习了一下,然后就敲了出来。本来想给她一个惊喜的,让她知道别人的宝有的,我的宝也会有。结果被她早早的发现了,哈哈哈。这里记录与分享一下开发过程。
说明: 我将代码放在了码云仓库里,本文的部分代码已经修改过,仅做参考,感兴趣的可以去仓库把项目拉下来玩玩。
仓库地址:https://gitee.com/zhangdl945/goodmorning.git
我们需要准备如下几个东西。
天行数据的使用本文就不介绍了,很简单,登录官网进去看一下就知道了。里面有很多接口,我们也可以把推送消息做的很有趣。
天行数据接口的数据有时候会改动,如果发现有问题,记得及时修改哦,我今天就遇见了。
百度搜索“公众号”,进入官网首页,扫码登录。选择开发者工具 - 公众平台测试账号。
展示的界面可能不太一样,总之,找到进入公众平台测试账号即可,该步骤会再次扫码登陆。界面如下。
这是测试帐户公众号,我们可以通过这个公众号做很多事情,在该界面的底部有详细的功能说明。而我们自己的订阅号会有诸多限制,不太好使用。也没有我们需要的
很多人会问为什么要使用测试账号呢?
我们自己的订阅号会有诸多限制,很多功能都不能使用或者有调用限制,比如我们这次要使用的微信模板就无法使用。
我们每个人都有一个测试账号,我们可以通过该账号做很多订阅号或服务号无法使用的功能,详细功能说明在当前页的最下方,官方有列表清单。
在测试号二维码处,我们需要扫码关注,关注后,后再右侧列表里显示,注意,此处的微信号并不是我们手机里那个微信号哦~
(提示:我们自己先关注,等开发测试完成后再让你的那个她关注,否则消息轰炸并且暴露的你的目的就很尴尬了)
我们需要的就是微信的模板功能,我们需要在此处先创建咱们的模板。
简单说明一下。
模板标题和模板内容都很随意,可以根据自己的需要自行创建。比如有些人的模板内容就只有{{}}及其内容,没有其他文字,也是可以的,我们可以在代码里编写,说白了就是换了个地方。我的仅作参考。
需要注意两点:
1、{{}}里的内容必须按照要求,参数需以{{开头,以.DATA}}结尾。
2、创建模板内容一定要加换行,你需要展示什么样的格式你就写成什么样
<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>
可以使用工具创建的application.properties文件,也可以创建application.yml文件。
我是用的是properties文件。修改了项目端口号为8081.
server.port=8081
我将很多数据都常量化,放在了常量类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";
}
主要写了两个公用方法。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;
}
}
实体类是跟业务相关,需要的东西也都不一样,此处就不详细记录,上面的结构图有我的实体类。这里以AccessToken为例。
我们使用Lombok,实体类就会很干净。天行数据的接口都很类似,记得好好设计一下,我的也是后面有过改动版本,就不再此处展示了。
@Component
@NoArgsConstructor
@AllArgsConstructor
@Data
@ToString
public class AccessToken {
private String access_token;// 获取到的凭证
private String expires_in;// 凭证有效时间,单位:秒
}
该类主要是通过工具类调用各个接口,获取结果。功能类似,自行编写吧。
@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;
}
}
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,看到页面返回“发送成功”,则表示部署成功啦。
我们也可以通过其他方式,就不用这样还需要去调一次了。
至此,我们就完成了这个早安功能,可以看出步骤还是很多,虽然代码不复杂,还是需要我们细心的去做才能做好,样式大家可以自行调整,比如颜色等。
最最最重要的,大家先将时间缩小一点调试。部署成功后记得要让她关注测试账号哦,就是上面的测试二维码!!!
祝大家都能完成,你的那个她也能喜欢。愿你们长久幸福。