由于柳峰老师的新书还没有出来,网上也没有过多介绍基于Java语言的开发微信公众平台模板消息的例子,因此有了本文。由于个人表达能力和编程能力有限,请多多包涵。本文仅介绍拥有模板消息权限的微信公众账号开发。
本文分为以下两部分:
1.开发模板消息SDK
2.构造模板消息并发送
首先看一下模板消息接口文档:
为了保证用户不受到骚扰,在开发者出现需要主动提醒、通知用户时,才允许开发者在公众平台网站中模板消息库中选择模板,选择后获得模板ID,再根据模板ID向用户主动推送提醒、通知消息。
模板消息调用时主要需要模板ID和模板中各参数的赋值内容。请注意:
1.模板中参数内容必须以".DATA"结尾,否则视为保留字;
2.模板保留符号"{{ }}"
具体调用方法
第一步:获取模板ID
通过在模板消息功能的模板库中使用需要的模板,可以获得模板ID。
第二步:请求接口
请注意,URL置空,则在发送后,点击模板消息会进入一个空白页面(iOS),或无法点击(Android)。
POST请求
https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=ACCESS_TOKEN请求包为一个json:
{ "touser":"OPENID", "template_id":"ngqIpbwh8bUfcSsECmogfXcV14J0tQlEpBO27izEYtY", "url":"http://weixin.qq.com/download", "topcolor":"#FF0000", "data":{ "User": { "value":"黄先生", "color":"#173177" }, "Date":{ "value":"06月07日 19时24分", "color":"#173177" }, "CardNumber":{ "value":"0426", "color":"#173177" }, "Type":{ "value":"消费", "color":"#173177" }, "Money":{ "value":"人民币260.00元", "color":"#173177" }, "DeadTime":{ "value":"06月07日19时24分", "color":"#173177" }, "Left":{ "value":"6504.09", "color":"#173177" } } }
发送效果图:
![](https://i-blog.csdnimg.cn/blog_migrate/cc279afd407ae5713d70452798b6d7da.png)
事件推送
在模版消息发送任务完成后,微信服务器会将是否送达成功作为通知,发送到开发者中心中填写的服务器配置地址中。
1、送达成功时,推送的XML如下:
<xml> <ToUserName><![CDATA[gh_7f083739789a]]></ToUserName> <FromUserName><![CDATA[oia2TjuEGTNoeX76QEjQNrcURxG8]]&g;</FromUserName> <CreateTime>1395658920</CreateTime> <MsgType><![CDATA[event]]></MsgType> <Event><![CDATA[TEMPLATESENDJOBFINISH]]></Event> <MsgID>200163836</MsgID> <Status><![CDATA[success]]></Status> </xml>
2、送达由于用户拒收(用户设置拒绝接收公众号消息)而失败时,推送的XML如下:
<xml> <ToUserName><![CDATA[gh_7f083739789a]]></ToUserName> <FromUserName><![CDATA[oia2TjuEGTNoeX76QEjQNrcURxG8]]></FromUserName> <CreateTime>1395658984</CreateTime> <MsgType><![CDATA[event]]></MsgType> <Event><![CDATA[TEMPLATESENDJOBFINISH]]></Event> <MsgID>200163840</MsgID> <Status><![CDATA[failed:user block]]></Status> </xml>
3、送达由于其他原因失败时,推送的XML如下:
<xml> <ToUserName><![CDATA[gh_7f083739789a]]></ToUserName> <FromUserName><![CDATA[oia2TjuEGTNoeX76QEjQNrcURxG8]]></FromUserName> <CreateTime>1395658984</CreateTime> <MsgType><![CDATA[event]]></MsgType> <Event><![CDATA[TEMPLATESENDJOBFINISH]]></Event> <MsgID>200163840</MsgID> <Status><![CDATA[failed: system failed]]></Status> </xml>
返回码说明
在调用模板消息接口后,会返回JSON数据包。正常时的返回JSON数据包示例:
{ "errcode":0, "errmsg":"ok", "msgid":200228332 }
错误时的返回JSON数据,形式类似,错误码请见本页下方返回码说明。
返回码 | 说明 |
---|---|
-1 | 系统繁忙 |
0 | 请求成功 |
40001 | 验证失败 |
40002 | 不合法的凭证类型 |
40003 | 不合法的OpenID |
40004 | 不合法的媒体文件类型 |
40005 | 不合法的文件类型 |
40006 | 不合法的文件大小 |
40007 | 不合法的媒体文件id |
40008 | 不合法的消息类型 |
40009 | 不合法的图片文件大小 |
40010 | 不合法的语音文件大小 |
40011 | 不合法的视频文件大小 |
40012 | 不合法的缩略图文件大小 |
40013 | 不合法的APPID |
41001 | 缺少access_token参数 |
41002 | 缺少appid参数 |
41003 | 缺少refresh_token参数 |
41004 | 缺少secret参数 |
41005 | 缺少多媒体文件数据 |
41006 | access_token超时 |
42001 | 需要GET请求 |
43002 | 需要POST请求 |
43003 | 需要HTTPS请求 |
44001 | 多媒体文件为空 |
44002 | POST的数据包为空 |
44003 | 图文消息内容为空 |
45001 | 多媒体文件大小超过限制 |
45002 | 消息内容超过限制 |
45003 | 标题字段超过限制 |
45004 | 描述字段超过限制 |
45005 | 链接字段超过限制 |
45006 | 图片链接字段超过限制 |
45007 | 语音播放时间超过限制 |
45008 | 图文消息超过限制 |
45009 | 接口调用超过限制 |
46001 | 不存在媒体数据 |
47001 | 解析JSON/XML内容错误 |
一、开发模板消息SDK
模板消息的定义如下:
模板消息也是使用access_token作为授权来发送。但是请大家注意:这里的access_token与网页授权的access_token完全不是一回事。可不要拿网页授权的access_token当作参数传递。
获取模板消息access_token:
String appId = "xxxxxxxxxxxxx"; //公众账号的唯一标识
String appSecret="xxxxxxxxxxx"; //公众账号的密钥
Token token = CommonUtil.getToken(appId, appSecret);
String access_token = token.getAccessToken();
#CommonUtil工具类具体代码:
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.NET.ConnectException;
import java.net.URL;
import javax.Net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import net.sf.json.JSONException;
import net.sf.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 通用工具类
*
* @author liufeng
*/
public class CommonUtil {
private static Logger log = LoggerFactory.getLogger(CommonUtil.class);
// 凭证获取(GET)
public final static String token_url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET";
/**
* 发送 https 请求
*
* @param requestUrl 请求地址
* @param requestMethod 请求方式(GET、POST)
* @param outputStr 提交的数据
* @return JSONObject(通过 JSONObject.get(key) 的方式获取 JSON 对象的属性值)
*/
public static JSONObject httpsRequest(String requestUrl, String requestMethod, String outputStr) {
JSONObject jsonObject = null;
try {
// 创建 SSLContext 对象,并使用我们指定的信任管理器初始化
TrustManager[] tm = { new MyX509TrustManager() };
SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE");
sslContext.init(null, tm, new java.security.SecureRandom());
// 从上述 SSLContext 对象中得到 SSLSocketFactory 对象
SSLSocketFactory ssf = sslContext.getSocketFactory();
URL url = new URL(requestUrl);
HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
conn.setSSLSocketFactory(ssf);
conn.setDoOutput(true);
conn.setDoInput(true);
conn.setUseCaches(false);
// 设置请求方式(GET/POST)
conn.setRequestMethod(requestMethod);
// 当 outputStr 不为 null 时,向输出流写数据
if (null != outputStr) {
OutputStream outputStream = conn.getOutputStream();
// 注意编码格式
outputStream.write(outputStr.getBytes("UTF-8"));
outputStream.close();
}
// 从输入流读取返回内容
InputStream inputStream = conn.getInputStream();
InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8");
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
String str = null;
StringBuffer buffer = new StringBuffer();
while ((str = bufferedReader.readLine()) != null) {
buffer.append(str);
}
// 释放资源
bufferedReader.close();
inputStreamReader.close();
inputStream.close();
inputStream = null;
conn.disconnect();
jsonObject = JSONObject.fromObject(buffer.toString());
} catch (ConnectException ce) {
log.error(" 连接超时:{}", ce);
} catch (Exception e) {
log.error("https 请求异常:{}", e);
}
return jsonObject;
}
/**
* 获取接口访问凭证
*
* @param appid 凭证
* @param appsecret 密钥
* @return
*/
public static Token getToken(String appid, String appsecret) {
Token token = null;
String requestUrl = token_url.replace("APPID", appid).replace("APPSECRET", appsecret);
// 发起GET请求获取凭证
JSONObject jsonObject = httpsRequest(requestUrl, "GET", null);
if (null != jsonObject) {
try {
token = new Token();
token.setAccessToken(jsonObject.getString("access_token"));
token.setExpiresIn(jsonObject.getInt("expires_in"));
} catch (JSONException e) {
token = null;
// 获取token失败
log.error("获取token失败 errcode:{} errmsg:{}", jsonObject.getInt("errcode"), jsonObject.getString("errmsg"));
}
}
return token;
}
/**
* URL编码(utf-8)
*
* @param source
* @return
*/
public static String urlEncodeUTF8(String source) {
String result = source;
try {
result = java.net.URLEncoder.encode(source, "utf-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return result;
}
/**
* 根据内容类型判断文件扩展名
*
* @param contentType 内容类型
* @return
*/
public static String getFileExt(String contentType) {
String fileExt = "";
if ("image/jpeg".equals(contentType))
fileExt = ".jpg";
else if ("audio/mpeg".equals(contentType))
fileExt = ".mp3";
else if ("audio/amr".equals(contentType))
fileExt = ".amr";
else if ("video/mp4".equals(contentType))
fileExt = ".mp4";
else if ("video/mpeg4".equals(contentType))
fileExt = ".mp4";
return fileExt;
}
}
#Token实体类具体代码:
/**
* 凭证实体类
*
* @author liufeng
*/
public class Token {
// 接口访问凭证
private String accessToken;
// 凭证有效期,单位:秒
private int expiresIn;
public String getAccessToken() {
return accessToken;
}
public void setAccessToken(String accessToken) {
this.accessToken = accessToken;
}
public int getExpiresIn() {
return expiresIn;
}
public void setExpiresIn(int expiresIn) {
this.expiresIn = expiresIn;
}
}
至此,我们获得了模板消息的access_token。将它作为参数传递到模板消息接口文档中POST请求的ACCESS_TOKEN。
//得到构造好的模板消息请求地址
二、构造模板消息并发送
我们以一个餐饮行业的收到新订单通知的模板为例,模板详情如下:
- 模板ID
YtO2XATY0VtRbgE4jWWNl-Zdc992FDdguhMUbomNkA0
开发者调用模板消息接口时需提供模板ID - 标题收到新订单通知
- 行业餐饮 - 餐饮
- 详细内容
{{first.DATA}} 日期:{{Day.DATA}} 订单编号:{{orderId.DATA}} 订单类型:{{orderType.DATA}} 联系人:{{customerName.DATA}} 联系电话:{{customerPhone.DATA}} {{remark.DATA}}
在发送时,需要将内容中的参数({{.DATA}}内为参数)赋值替换为需要的信息 - 内容示例
收到一个新的订单,确认接受请回复0,拒绝请回复1。 日期:2014-10-10 订单编号:1002 订单类型:订位 联系人:陈丑丑 联系电话:13222222222 订单金额:100.00元 付款状态:已微信支付 请及时处理您的订单。
- 按照上述条件,我们的消息体构造如下:
- {"data":{"customerName":{"color":"#173177","value":"陈丑丑"},"customerPhone":{"color":"#173177","value":"13222222222"},"Day":{"color":"#173177","value":"15时06分"},"first":{"color":"#173177","value":"收到一个新的订单"},"orderId":{"color":"#173177","value":"1002"},"orderType":{"color":"#173177","value":"订位"},"remark":{"color":"#173177","value":"请及时处理您的订单"}},"template_id":"YtO2XATY0VtRbgE4jWWNl-Zdc992FDdguhMUbomNkA0","topcolor":"#173177","touser":"orsrOt9qBgO6dC-F3IL_MF52eplI","url":"http://weixin.qq.com/download"}
- 该消息体为一个json。具体封装如下:
- /**
* 餐饮行业收到新订单通知模板消息实体类
* @author xjw
*
*/
public class NewOrdersTemplate {
private String touser; //用户OpenID
private String template_id; //模板消息ID
private String url; //URL置空,则在发送后,点击模板消息会进入一个空白页面(ios),或无法点击(android)。
private String topcolor; //标题颜色
private Data data; //详细内容
public String getTouser() {
return touser;
}
public void setTouser(String touser) {
this.touser = touser;
}
public String getTemplate_id() {
return template_id;
}
public void setTemplate_id(String templateId) {
template_id = templateId;
}
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 Data getData() {
return data;
}
public void setData(Data data) {
this.data = data;
}
}
- /**
* "餐饮行业收到新订单通知"模板消息详细内容实体类
* @author xjw
*
*/
public class Data {
private Data_first first;
private Data_Day Day; //日期
private Data_orderId orderId; //订单编号
private Data_orderType orderType; //订单类型
private Data_customerName customerName; //联系人
private Data_customerPhone customerPhone; //联系电话
private Data_remark remark;
public Data_first getFirst() {
return first;
}
public void setFirst(Data_first first) {
this.first = first;
}
public Data_Day getDay() {
return Day;
}
public void setDay(Data_Day day) {
Day = day;
}
public Data_orderId getOrderId() {
return orderId;
}
public void setOrderId(Data_orderId orderId) {
this.orderId = orderId;
}
public Data_orderType getOrderType() {
return orderType;
}
public void setOrderType(Data_orderType orderType) {
this.orderType = orderType;
}
public Data_customerName getCustomerName() {
return customerName;
}
public void setCustomerName(Data_customerName customerName) {
this.customerName = customerName;
}
public Data_customerPhone getCustomerPhone() {
return customerPhone;
}
public void setCustomerPhone(Data_customerPhone customerPhone) {
this.customerPhone = customerPhone;
}
public Data_remark getRemark() {
return remark;
}
public void setRemark(Data_remark remark) {
this.remark = remark;
}
}
/**
* first
* @author xjw
*
*/
public class Data_first {
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;
}
}
/**
* 日期
* @author xjw
*
*/
public class Data_Day {
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;
}
}
/**
* 订单标号
* @author xjw
*
*/
public class Data_orderId {
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;
}
}
/**
* 订单类型
* @author xjw
*
*/
public class Data_orderType {
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;
}
}
/**
* 联系人
* @author xjw
*
*/
public class Data_customerName {
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;
}
}
/**
* 联系电话
* @author xjw
*
*/
public class Data_customerPhone {
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;
}
}
/**
* remark
* @author xjw
*
*/
public class Data_remark {
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;
}
}
/**
* 发送模板消息
* appId 公众账号的唯一标识
* appSecret 公众账号的密钥
* openId 用户标识
*/
public void send_template_message(String appId, String appSecret, String openId) {
Token token = CommonUtil.getToken(appId, appSecret);
String access_token = token.getAccessToken();
String url = "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token="+access_token;
NewOrdersTemplate temp = new NewOrdersTemplate();
Data data = new Data();
Data_first first = new Data_first();
Data_Day Day = new Data_Day();
Data_orderId orderId = new Data_orderId();
Data_orderType orderType = new Data_orderType();
Data_customerName customerName = new Data_customerName();
Data_customerPhone customerPhone = new Data_customerPhone();
Data_remark remark = new Data_remark();
first.setValue("收到一个新的订单");
first.setColor("#173177");
Day.setValue("14时56分");
Day.setColor("#173177");
orderId.setValue("1002");
orderId.setColor("#173177");
orderType.setValue("订位");
orderType.setColor("#173177");
customerName.setValue("陈丑丑");
customerName.setColor("#173177");
customerPhone.setValue("13222222222");
customerPhone.setColor("#173177");
remark.setValue("请及时处理您的订单");
remark.setColor("#173177");
data.setFirst(first);
data.setDay(Day);
data.setOrderId(orderId);
data.setOrderType(orderType);
data.setCustomerName(customerName);
data.setCustomerPhone(customerPhone);
data.setRemark(remark);
temp.setTouser(openId);
temp.setTemplate_id("YtO2XATY0VtRbgE4jWWNl-Zdc992FDdguhMUbomNkA0");
temp.setUrl("http://weixin.qq.com/download");
temp.setTopcolor("#173177");
temp.setData(data);
String jsonString = JSONObject.fromObject(temp).toString().replace("day", "Day");
JSONObject jsonObject = WeixinUtil.httpRequest(url, "POST", jsonString);
System.out.println(jsonObject);
int result = 0;
if (null != jsonObject) {
if (0 != jsonObject.getInt("errcode")) {
result = jsonObject.getInt("errcode");
log.error("错误 errcode:{} errmsg:{}", jsonObject.getInt("errcode"), jsonObject.getString("errmsg"));
}
}
log.info("模板消息发送结果:"+result);
}
WeixinUtil工具类在柳峰老师的博客里有源码。。。。。