开发微信公众号的时候,我们经常会使用微信公众平台的测试号来进行调试,因为测试号的可用接口比较全,不然只有经过认证的服务号才可以调用比较高级的接口。
从开始搞一个微信公众平台测试号开始。
打开微信公众平台,注册登录什么的就不说了,一个微信号只能绑定一个测试账号。
之后进入页面会看到有一个接口配置信息修改,
里面的URL需要填写一个80端口的公网地址,下面的token自己随便填,填完记住,后面要用。
如果手头没有服务器或没有可用的公网地址和端口,可以参考前面写的博文使自己的本地开发环境映射出一个可用的公网地址然后填进去:ngrok的使用
地址填进去之后,后面跟一个springmvc的路径,下面有一个提交的按钮,点击提交按钮的时候会需要调用本地的接口(因为地址已经和本地服务连起来了)
上代码:
@RestController
@RequestMapping("/api/wechat")
public class WechatController {
@PostMapping("/sendMsgTest")
public void sendMsgTest(HttpServletRequest request, HttpServletResponse response) throws IOException {
// 微信加密签名.
String signature = request.getParameter("signature");
// 时间戳.
String timestamp = request.getParameter("timestamp");
// 随机数.
String nonce = request.getParameter("nonce");
// 随机字符串.
String echostr = request.getParameter("echostr");
// 请求校验,若校验成功则原样返回echostr,表示接入成功,否则接入失败.
PrintWriter out = response.getWriter();
// 请求校验,若校验成功则原样返回echostr,表示接入成功,否则接入失败.
if (SignUtil.checkSignature(signature, timestamp, nonce)) {
out.print(echostr);
}
out.close();
out = null;
}
}
校验的代码如下,注意校验类里面的token要和刚才在页面上填写的一致:
public class SignUtil {
// 与开发模式接口配置信息中的Token保持一致.
private static String token = "eladminshuiniwechat";
/**
* 校验签名
* @param signature 微信加密签名.
* @param timestamp 时间戳.
* @param nonce 随机数.
* @return
*/
public static boolean checkSignature(String signature, String timestamp, String nonce) {
// 对token、timestamp、和nonce按字典排序.
String[] paramArr = new String[] {token, timestamp, nonce};
Arrays.sort(paramArr);
// 将排序后的结果拼接成一个字符串.
String content = paramArr[0].concat(paramArr[1]).concat(paramArr[2]);
String ciphertext = null;
try {
MessageDigest md = MessageDigest.getInstance("SHA-1");
// 对拼接后的字符串进行sha1加密.
byte[] digest = md.digest(content.toString().getBytes());
ciphertext = byteToStr(digest);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
// 将sha1加密后的字符串与signature进行对比.
return ciphertext != null ? ciphertext.equals(signature.toUpperCase()) : false;
}
/**
* 将字节数组转换为十六进制字符串.
* @param byteArray
* @return
*/
private static String byteToStr(byte[] byteArray) {
String strDigest = "";
for (int i = 0; i < byteArray.length; i++) {
strDigest += byteToHexStr(byteArray[i]);
}
return strDigest;
}
/**
* 将字节转换为十六进制字符串.
* @param mByte
* @return
*/
private static String byteToHexStr(byte mByte) {
char[] Digit = { '0', '1' , '2', '3', '4' , '5', '6', '7' , '8', '9', 'A' , 'B', 'C', 'D' , 'E', 'F'};
char[] tempArr = new char[2];
tempArr[0] = Digit[(mByte >>> 4) & 0X0F];
tempArr[1] = Digit[mByte & 0X0F];
String s = new String(tempArr);
return s;
}
}
本地的项目启动起来,点击测试号页面下面的提交按钮。会进到刚才写的controller中,验证通过后会在测试号页面上方弹一个配置成功的字样。
------------------------------------------------------------分割线------------------------------------------------------------------
现在就可以开始使用微信公众平台的测试号来调用接口了。
下面调用一个模板消息(业务通知)的接口作为示例。
先来看一下公众平台开发文档找一下发送模板消息的地址:
https://api.weixin.qq.com/cgi-bin/template/api_set_industry?access_token=ACCESS_TOKEN
是POST请求,我们看到需要一个叫做ACCESS_TOKEN的参数。
那么这个ACCESS_TOKEN从哪里来呢?查找一下微信公众平台的开发文档:公众平台开发文档
看到获得ACCESS_TOKEN的地址是这个,这里就可以用到刚才得到的appID和appsecret了。
https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET
调用接口获得ACCESS_TOKEN,返回信息如下:
PS:获得access_token的请求是GET请求,这个Demo就统一用requestPost对象去做请求了,不做get与post的区分,get方法就把参数直接拼在url地址里然后post的参数传空,严格来说的话是应该是要使用httpGet对象的。
{
"access_token": "35_nr5fPCyws1f1WVTiEBKLBjDc-ig6hCEsdAlApZICjM_sEM4thRWNtA2DNessqYv_LdwfCwF46jM7lYwiMFyymc9gb5sBxhkVdv2MGklnaWO9PVomBkH6YNqrDkXO5K5dL7Zl0SIjihn3FzyqMCTdAFAHXO",
"expires_in": 7200
}
ACCESS_TOKEN得到了,但是有效时间是7200秒,两个小时后现在获得的ACCESS_TOKEN就会失效,而且获得ACCESS_TOKEN 的接口一天只能调用200次。
所以我们在使用的时候就需要一个定时任务去刷新ACCESS_TOKEN了,这里定为100分钟获取一次ACCESS_TOKEN,代码如下:
定义一个静态变量存放ACCESS_TOKEN
public static String access_token;
然后使用java发起post请求,发请求的方法如下:
入参是请求地址和参数(json字符串)
public class HttpUtils {
public static String access_token;
public static String requestPost(String url,String jsonParam){
System.out.println(jsonParam);
// 获取连接客户端工具
CloseableHttpClient httpClient = HttpClients.createDefault();
String entityStr = null;
CloseableHttpResponse response = null;
try {
// 创建POST请求对象s
HttpPost httpPost = new HttpPost(url);
if (!"".equals(jsonParam)){
// 创建请求参数
StringEntity s = new StringEntity(jsonParam, "utf-8");
//设置参数到请求对象中
httpPost.setEntity(s);
}
//添加请求头信息
httpPost.addHeader("User-Agent", "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.6)");
httpPost.addHeader("Content-Type", "application/json");
// 执行请求
response = httpClient.execute(httpPost);
// 获得响应
HttpEntity entity = response.getEntity();
// 将响应结果转换成字符串
entityStr = EntityUtils.toString(entity, "UTF-8");
} catch (Exception e) {
e.printStackTrace();
} finally {
// 释放连接
if (null != response) {
try {
response.close();
httpClient.close();
} catch (IOException e) {
log.info("释放连接出错");
e.printStackTrace();
}
}
}
// 打印响应内容
System.out.println(entityStr);
return entityStr;
}
}
调用发送请求的方法如下:
public static void getAccessToken(){
Timer timer=new Timer();
timer.schedule(new java.util.TimerTask() {
@Override
public void run() {
dogetToken();
}
},3000,60*100*1000); // 三秒后执行,一百分钟执行一次
}
//请求access_token的方法
public static String dogetToken(){
String url = WechatConfig.accesstokenURL;
//将配置文件中的appid和appsecret值填到url中
String realURL = url.replaceAll("APPID", WechatConfig.appID).replaceAll("APPSECRET",WechatConfig.appsecret);
System.out.println(realURL);
//调用获得access_token的接口
String result = requestPost(realURL, "");
System.out.println(result);
JSONObject obj = JSONObject.parseObject(result);
Map<String, Object> map =obj;
try {
//从结果中取出access_token
String access_token = map.get("access_token").toString();
HttpUtils.access_token = access_token; //把得到的token存在静态变量中
return access_token;
}catch (Exception e){
//如果返回的结果中有errcode和errmsg,说明接口调用失败
String errcode = map.get("errcode").toString();
String errmsg = map.get("errmsg").toString();
throw new BadRequestException("微信公众平台获得access_token失败,错误码为:"+errcode+"错误信息为:"+errmsg);
}
}
@Component
@ConfigurationProperties(prefix = "wechat")
public class WechatConfig {
//appID
public static String appID;
//appsecret
public static String appsecret;
//获取access_token的url
public static String accesstokenURL;
@Value("${wechat.appID}")
public void setAppID(String appID) {
WechatConfig.appID = appID;
}
@Value("${wechat.appsecret}")
public void setAppsecret(String appsecret) {
WechatConfig.appsecret = appsecret;
}
@Value("${wechat.accesstokenURL}")
public void setAccesstokenURL(String accesstokenURL) {
WechatConfig.accesstokenURL = accesstokenURL;
}
}
这样就可以获得access_token了。
得到access_token了,url也有了,下一步就是看看发送模板消息需要哪些入参了。
从公众平台开发文档中得到入参的json格式如下:
{
"touser":"OPENID",
"template_id":"ngqIpbwh8bUfcSsECmogfXcV14J0tQlEpBO27izEYtY",
"url":"http://weixin.qq.com/download",
"miniprogram":{
"appid":"xiaochengxuappid12345",
"pagepath":"index?foo=bar"
},
"data":{
"first": {
"value":"恭喜你购买成功!",
"color":"#173177"
},
"keyword1":{
"value":"巧克力",
"color":"#173177"
},
"keyword2": {
"value":"39.8元",
"color":"#173177"
},
"remark":{
"value":"欢迎再次购买!",
"color":"#173177"
}
}
}
这里我们不需要跳小程序也不需要模板跳转,所以不需要url和miniprogram。
需要touser,template_id和data,下面一个一个说。
touser:openID 就是关注该公众号的微信用户的一个ID,怎么获得呢?
在微信公众平台测试号页面上:
扫码关注这个测试公众号,就会在下面添加关注者的信息,微信号那一栏里就是所需的openID。
至于template_id,还是在微信公众平台测试号页面上:
可以自己新增,测试号中的模板格式示例如下,你自己可以多加几个信息:
{{first.DATA}}
运输单号为:{{keyword1.DATA}}
工程名称为:{{keyword2.DATA}}
{{remark.DATA}}
自己新增一个模板,模板ID就出来了。模板里的值有value和color两个,指的是keyword.DATA的内容和文字颜色。
这样,我们就具备调用发送模板信息的条件了!
先不着急写java。用postman试着调用一下,(remark.DATA好像前面要加一个\n,不然会和前一条信息连在一起)
返回errcode为0,errmsg是OK就说明调用成功了。
可以打开刚才关注的测试公众号,可以看到:
下面附上使用java调用模板信息的代码供参考,请大家灵活使用:
这是入参和出参的一些类(get/set方法省略):
//入参类
public class WXTransportTemplate {
private String openid;//目标客户
private String transNum; //运输单号
private String engiName; //工程名称
}
//模板所需信息类
public class TranTemplate {
//模板信息
Map<String , TemplateInfo> data;
//模板ID
String template_id;
//接收人ID
String touser;
}
//模板内容类
public class TemplateInfo {
//内容
private String value;
//字体颜色
private String color;
public TemplateInfo(String value, String color) {
this.value = value;
this.color = color;
}
public TemplateInfo(){
}
}
这是调用发送模板方法的controller和service:
PS:发送模板信息是post方法,使用httppost对象
@PostMapping("/sendTemplate")
public String sendTranTemplate(@Validated @RequestBody WXTransportTemplate resource) throws IOException {
//templateid,模板id,就是发送信息的格式模板id
String result = wxSendService.sendTranTemplate(resource);
//判断发送模板消息结果
JSONObject obj = JSONObject.parseObject(result);
Map<String, Object> map =obj;
Integer code = (Integer)map.get("errcode");
String msg =map.get("errmsg").toString();
if (0 != code || !"ok".equals(msg)){
throw new BadRequestException("微信公众号模板消息推送失败,错误代码为:+"+code+"错误消息为:"+msg);
}
return result;
}
//发送运输单模板信息给司机
public String sendTranTemplate(WXTransportTemplate resource){
//拿到accessToken
String accessToken = HttpUtils.access_token;
if (null == accessToken){
accessToken = HttpUtils.dogetToken();
}
System.out.println("现在取出的token是:"+accessToken);
//发送模板消息开始
TranTemplate tranTemplate = new TranTemplate();
//设置接收司机微信ID
tranTemplate.setTouser(resource.getOpenid());
//设置模板ID
tranTemplate.setTemplate_id(TemplateID);
//给模板的内容赋值
Map<String , TemplateInfo> dataMap = new HashMap<>();
TemplateInfo first = new TemplateInfo("您好,您有一条运输单需要运输。","#DC143C");
dataMap.put("first",first);
TemplateInfo keyword1 = new TemplateInfo(resource.getTransNum(),"#173177");
dataMap.put("keyword1",keyword1);
TemplateInfo keyword2 = new TemplateInfo(resource.getEngiName(),"#173177");
dataMap.put("keyword2",keyword2);
TemplateInfo remark = new TemplateInfo("\n请及时运输","#DC143C");
dataMap.put("remark",remark);
tranTemplate.setData(dataMap);
JSONObject jsonObject = JSONUtil.parseObj(tranTemplate);
//将入参转为字符串
String jsonParam = jsonObject.toString();
//发起请求发送模板信息
String result = HttpUtils.requestPost(sendTemplateUrl + accessToken, jsonParam);
return result;
}
以上。