微信公众号开发《二》发送模板消息实现消息业务实时通知
原创声明:本文为原创作品,绝非他处摘取,转载请联系博主
前篇文章讲解了如何获取用户微信基本详情,实现微信绑定后自动登录,回看请点击这里:http://www.cnblogs.com/zhaixiajiao/p/6760194.html
本篇文章主要介绍利用上篇文章获取到的微信ID,向已绑定用户发送模板消息,如我们常见的消费通知、订单通知等业务都可以用该功能实现。理论知识就不反复强调了,实践是检验真理的唯一标准,直接看例子,相信大家就能一目了然了。下面我们来看下要准备哪里步骤:
1.配置模板
登录测试公众号/正式公众号(认证后的服务号),测试公众号:模板消息接口->新增测试模板中添加模板,正式公众号:在功能->模板消息中添加模板,模板可以在模板库中选择,如果没有你需要的模板,可以申请添加,一个月可以申请三条。模板添加成功后,有个模板ID(用于接口调用)。
具体如何配置可以参看官方文档:https://mp.weixin.qq.com/wiki 中消息管理->发送消息-模板消息接口
2.代码应用展示
封装发送模板接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | import java.util.Map; /** * 模板基类 * @author lh * */ public class WxTemplate { private String template_id; //模板ID private String touser; //目标客户 private String url; //用户点击模板信息的跳转页面 private String topcolor; //字体颜色 private Map<String,TemplateData> data; //模板里的数据 public String getTemplate_id() { return template_id; } public void setTemplate_id(String template_id) { this .template_id = template_id; } public String getTouser() { return touser; } public void setTouser(String touser) { this .touser = touser; } public String getUrl() { return url; } public void setUrl(String url) { this .url = url; } public String getTopcolor() { return topcolor; } public void setTopcolor(String topcolor) { this .topcolor = topcolor; } public Map<String,TemplateData> getData() { return data; } public void setData(Map<String,TemplateData> data) { this .data = data; } } |
一条模板包含多条数据,模板数据类封装
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | /** * 模板数据 * @author lh * */ public class TemplateData { private String value; //模板显示值 private String color; //模板显示颜色 public String getValue() { return value; } public void setValue(String value) { this .value = value; } public String getColor() { return color; } public void setColor(String color) { this .color = color; } } |
看文档,发送模板信息接口为:https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=ACCESS_TOKEN,前期模板数据都准备好了,现在缺少ACCESS_TOKEN,缺什么就去获取什么,查看文档可知获取ACCESS_TOKEN接口为:https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET,现在就封装个获取ACCESS_TOKEN的请求接口:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | public class WeixinUtil { private static Logger log = LoggerFactory.getLogger(WeixinUtil. class ); // 获取access_token的接口地址(GET) 限200(次/天) public final static String access_token_url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET" ; /** * 获取access_token * @param appid 凭证 * @param appsecret 密钥 * @return */ public static AccessToken getAccessToken(String appid, String appsecret) { AccessToken accessToken = null ; String requestUrl = access_token_url.replace( "APPID" , appid).replace( "APPSECRET" , appsecret); JSONObject jsonObject = httpRequest(requestUrl, "GET" , null ); // 如果请求成功 if ( null != jsonObject) { try { accessToken = new AccessToken(); accessToken.setToken(jsonObject.getString( "access_token" )); accessToken.setExpiresIn(jsonObject.getInt( "expires_in" )); } catch (JSONException e) { accessToken = null ; // 获取token失败 log.error( "获取token失败 errcode:{} errmsg:{}" , jsonObject.getInt( "errcode" ), jsonObject.getString( "errmsg" )); } } return accessToken; } } |
注意:ACCESS_TOKEN有请求次数限制,而且会在获取后7200秒后自动失效,所以要妥善保存好ACCESS_TOKEN,本实例是放在内存里面,每次获取时,判断是否已经存在,不存在才去请求,而如何保证保存的ACCESS_TOKEN为有效的呢?一般解决方法是定时请求获取最新ACCESS_TOKEN,更新内存里的数据,可使用线程定时请求、quartz,或者Spring的任务调度功能,下面展示比较通用的线程方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | /** * 定时获取微信access_token的线程 */ public class TokenThread implements Runnable { private static Logger log = LoggerFactory.getLogger(TokenThread. class ); // 第三方用户唯一凭证 public static String appid = "xxx" ; // 第三方用户唯一凭证密钥 public static String appsecret = "xxxx" ; public static AccessToken accessToken = null ; //保存ACCESS_TOKEN到内存 public void run() { while ( true ) { try { accessToken = WeixinUtil.getAccessToken(appid, appsecret); if ( null != accessToken) { log.info( "获取access_token成功,有效时长{}秒 token:{}" , accessToken.getExpiresIn(), accessToken.getToken()); // 休眠7000秒 Thread.sleep((accessToken.getExpiresIn() - 200 ) * 1000 ); } else { // 如果access_token为null,60秒后再获取 Thread.sleep( 60 * 1000 ); } } catch (InterruptedException e) { try { Thread.sleep( 60 * 1000 ); } catch (InterruptedException e1) { log.error( "{}" , e1); } log.error( "{}" , e); } } } } |
现在ACCESS_TOKEN已经获取,万事俱备,只欠调用了,下面给出发送模板
1 2 3 4 5 | {{first.DATA}} 订单编号:{{keyword1.DATA}} 订单类型:{{keyword2.DATA}} 商品名称:{{keyword3.DATA}} {{remark.DATA}} |
的具体调用实例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | /** * 发送模板消息调用实例 * @param websiteAndProject 请求地址与工程名:http://192.168.2.113/seafood * @param receiverWeixinId 接受者的微信ID,如何获取,可看前一篇文章 * @return */ WxTemplate template = new WxTemplate(); template.setUrl( "" +TimedTask.websiteAndProject+ "/weixinTwo/gotoOrderConfirm?orderId=" +map.get( "orderId" )); template.setTouser( "receiverWeixinId" )); template.setTopcolor( "#000000" ); template.setTemplate_id( "ai3WcdUjq-x4v0Reir442UCIzl3AsyCgpAy0e5q2mkY" ); Map<String,TemplateData> m = new HashMap<String,TemplateData>(); TemplateData first = new TemplateData(); first.setColor( "#000000" ); first.setValue( "您好,您有一条待确认订单。" ); m.put( "first" , first); TemplateData keyword1 = new TemplateData(); keyword1.setColor( "#328392" ); keyword1.setValue( "OD0001" ); m.put( "keyword1" , keyword1); TemplateData keyword2 = new TemplateData(); keyword2.setColor( "#328392" ); keyword2.setValue( "预定订单" ); m.put( "keyword2" , keyword2); TemplateData keyword3 = new TemplateData(); keyword3.setColor( "#328392" ); keyword3.setValue( "大龙虾" ); m.put( "keyword3" , keyword3); TemplateData remark = new TemplateData(); remark.setColor( "#929232" ); remark.setValue( "请及时确认订单!" ); m.put( "remark" , remark); WeixinUtil.sendMessageBefore( "" ,template, m); |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 | public class WeixinUtil { private static Logger log = LoggerFactory.getLogger(WeixinUtil. class ); // 获取access_token的接口地址(GET) 限200(次/天) public final static String access_token_url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET" ; /** * 获取access_token * @param appid 凭证 * @param appsecret 密钥 * @return */ public static AccessToken getAccessToken(String appid, String appsecret) { AccessToken accessToken = null ; String requestUrl = access_token_url.replace( "APPID" , appid).replace( "APPSECRET" , appsecret); JSONObject jsonObject = httpRequest(requestUrl, "GET" , null ); // 如果请求成功 if ( null != jsonObject) { try { accessToken = new AccessToken(); accessToken.setToken(jsonObject.getString( "access_token" )); accessToken.setExpiresIn(jsonObject.getInt( "expires_in" )); } catch (JSONException e) { accessToken = null ; // 获取token失败 log.error( "获取token失败 errcode:{} errmsg:{}" , jsonObject.getInt( "errcode" ), jsonObject.getString( "errmsg" )); } } return accessToken; } /** * 发送模板消息前获取token * @param template_id_short 模板库中模板的编号 * @param t * @param m */ public static void sendMessageBefore(String template_id_short,WxTemplate t,Map<String,TemplateData> m){ AccessToken token = null ; if (TokenThread.accessToken== null || TokenThread.accessToken.getToken()== "" ){ token = WeixinUtil.getAccessToken(TokenThread.appid, TokenThread.appsecret); } else { token = TokenThread.accessToken; } if (template_id_short!= null &&! "" .equals(template_id_short)){ Template template = WeixinUtil.getTemplate(template_id_short,token.getToken()); t.setTemplate_id(template.getTemplate_id()); } t.setData(m); int result = WeixinUtil.sendMessage(t,token.getToken()); } /** * 发送模板消息 * @param t * @param accessToken * @return */ public static int sendMessage(WxTemplate t,String accessToken) { int result = 0 ; // 拼装创建菜单的url String url = sendTemplateMessage_url.replace( "ACCESS_TOKEN" , accessToken); // 将菜单对象转换成json字符串 String jsonMenu = JSONObject.fromObject(t).toString(); // 调用接口创建菜单 JSONObject jsonObject = httpRequest(url, "POST" , jsonMenu); if ( null != jsonObject) { if ( 0 != jsonObject.getInt( "errcode" )) { result = jsonObject.getInt( "errcode" ); log.error( "发送模板消息失败 errcode:{} errmsg:{}" , jsonObject.getInt( "errcode" ), jsonObject.getString( "errmsg" )); } } return result; } } |
本人另一博客【http://blog.csdn.net/liaohaojian/article/details/70225367】
至此,发送消息流程已全部完成,如有不足,不解或者改进之处,欢迎大家在评论区指出。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架