1.业务分析
该功能点类似于小助手,在每位同学上课的前一天发送需要上课的信息。
设置为每天18点向各位同学发送。
💡参考:Java 微信公众号消息推送(从零开始)_java微信公众号消息推送-CSDN博客
2.具体实现
首先,每位同学在使用考勤小程序时,登录会有一个请求wx授权,通过这个wx授权,可以拿到微信用户的唯一id(oppenid)。该项目,授权并将该oppenid写入对应的学生数据库已经实现,此处不做赘述。如需要获取参考上述链接。
每位同学授权后,那么就实现了学号与微信id的绑定。在后续中我们可以直接拿取每位学生的oppenid就发送给了对应的学生。
模版消息
在微信公众平台可以选好合适的消息模版:
其中详细内容中的{{time1.DATA}}就是后端需要填入的数据。
准备工作
整个过程操作需要几个必须参数appid(公众号id),secret(密钥)这两个在微信公众平台获取。这是发送消息所需的两个必要的外部条件,再加一个access_token 。
access_token是微信api最重要的一个部分,因为调用其他api很多都需要用到access_token。比如自定义菜单接口、客服接口、获取用户信息接口、用户分组接口、群发接口等在请求的时候都需要用到access_token。而这个access_token则需要我们自己发送请求获取。更多详情参考:java实现微信公众号的模板消息推送_java多线程发送微信模版消息-CSDN博客
获取access_Token代码:
/**
* 获取accessToken 用于构建模板消息发送的url入参 此token有效期只有2小时 要存redis?
* @throws Exception
*
*/
public String getAccessToken(String appId,String appSecret) throws Exception{
String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid="+ appId +"&secret=" + appSecret;
String res = HttpUtil.get(url);
JSONObject jsonObject = JSONObject.parseObject(res);
String accessToken = jsonObject.getString("access_token"); // key : expires_in 为过期时间 为7200s 后续如果需要缓存 可能要用到
System.out.println("accessToken:"+accessToken);
return accessToken;
}
该token有2小时有效期,每天上限2000个。当一个公众号提供多个服务时,各个服务调用都需要使用该token,所以必须将这个token进一步缓存等处理。此处暂时尚未开发其余api功能,暂时没有缓存。
控制层代码:
@Scheduled(cron = "0 0 18 * * ?")
@GetMapping("/sendMessage")
public void sendMessage() throws Exception {
// 公众号的模板id(也有相应的接口可以查询到)
String templateId = "dbxxxxx7W_ZgyxxxxxxxZtE-SoxxxxxxxxxxxSInrs";
//先获取accessToken 此token有效期 2小时 但好像消息推送都只在6点推送其余时间貌似不需要推送 如果没有其他需要accessToken的功能 应该不需要缓存(上限2000个)
//access_token是公众号的全局唯一接口调用凭据,公众号调用各接口时都需使用access_token TODO:如果后续还有别的功能也要 那么就需要缓存
//即每天6点获取一次即可
String accessToken = messageService.getAccessToken(appId, appSecret);
Map<String,Object> paramap=new HashMap<>();
List<SubscriptionMessage> messageList = messageService.sendMessage();
//如果查出来是空 代表明天所有人都没课 直接结束方法 不用发送
if (messageList.isEmpty()){
return ;
}
String url = "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=" + accessToken;
// 上课时间
// {{time1.DATA}}
// 课程名称
// {{thing2.DATA}}
// 班级名称
// {{thing4.DATA}}
// 上课老师
// {{thing5.DATA}}
// 上课地点
// {{thing6.DATA}}
//模版消息封装
RestTemplate restTemplate = new RestTemplate();
//拼接base参数
Map<String, Object> sendBody = new HashMap<>();
for (SubscriptionMessage subscriptionMessage : messageList) {
//此处key value需要和消息模版对应
paramap.put("time1",new WeChatTemplateMsg(subscriptionMessage.getDate())); //上课时间
paramap.put("thing2",new WeChatTemplateMsg(subscriptionMessage.getCoursename())); //课程名称
paramap.put("thing4",new WeChatTemplateMsg(subscriptionMessage.getCname())); //班级名称
paramap.put("thing5",new WeChatTemplateMsg(subscriptionMessage.getNickname())); //上课老师
paramap.put("thing6",new WeChatTemplateMsg(subscriptionMessage.getClassroom())); //上课地点
sendBody.put("touser", subscriptionMessage.getAppid()); // openId 发送给哪个用户 示例 oNTuVxxxxxxxxxxxnQ2v8EY
// sendBody.put("url", "www.baidu.com"); // 点击模板信息跳转地 TODO 换成小程序课表地址
sendBody.put("data", paramap); // 模板参数
sendBody.put("template_id", templateId); // 模板Id
//发送POST请求
ResponseEntity<String> forEntity = restTemplate.postForEntity(url, sendBody, String.class);
JSONObject jsonObject = JSONObject.parseObject(forEntity.getBody());
String messageCode = jsonObject.getString("errcode");
String msgId = jsonObject.getString("msgid");
System.out.println("messageCode : " + messageCode + ", msgId: " +msgId);
System.out.println(forEntity.getBody());
}
3.踩坑
在测试时候发现内容没有发送过去,后找到问题:
这是对应模版每一个消息的封装,此处是以为结构是 key:time value: String 即可。后发现内容为空,
没修改前
paramap.put("time1",subscriptionMessage.getDate());
paramap.put("thing2",subscriptionMessage.getCoursename());
paramap.put("thing4",subscriptionMessage.getCname());
paramap.put("thing5",subscriptionMessage.getNickname());
paramap.put("thing6",subscriptionMessage.getClassroom());
后搜索查到发送的数据格式是这样的。
{"touser":"ogxxxxxxLxxxxxG46xxs","template_id":"1xxxxxs86_x5xxxxxxZSATUPSxxxxxxekk","data":{"first":{"value":"sadasd","color":"#333"},"keyword1":{"value":"keyworasd1","color":"#333"},"keyword2":{"value":"asd","color":"#333"},"keyword3":{"value":"keywoasdrd3","color":"#333"},"keyword4":{"value":"keywoasdrd4","color":"#333"},"keyword5":{"value":"ad","color":"#333"},"remark":{"value":"keyworasdd6","color":"#333"}}}
这个地方的这个值要么是一个map要么就是一个设置好value字段的对象,而不是直接把一个结果字符串传过去。
那么就定义一个字段类,其中这个value值就是我们所需要的内容字段。
模版消息字段DTO:
@Data
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;
}
}
修正过后:可以正常显示:
paramap.put("time1",new WeChatTemplateMsg(subscriptionMessage.getDate()));
paramap.put("thing2",new WeChatTemplateMsg(subscriptionMessage.getCoursename()));
paramap.put("thing4",new WeChatTemplateMsg(subscriptionMessage.getCname()));
paramap.put("thing5",new WeChatTemplateMsg(subscriptionMessage.getNickname()));
paramap.put("thing6",new WeChatTemplateMsg(subscriptionMessage.getClassroom()));