前言
近期花了很长时间去写公众号平台发送模板消息,这是仅仅支持服务号发送模板消息的,不牵扯到小程序部分,当然也无须注册开放平台的账号,服务号则必须是需要认证的,只有认证后的服务号有主动发送模板消息的权限。代码都是粘贴即可使用的,废话不多说,正题开始。
正文
获取openid
获取openid,需要网页用户授权(等下我会上代码),因此需要设置网页授权地址,设置路径:公众平台>设置>功能设置>网页授权域名,此处域名填写按照官方文档来。
设置完授权域名之后,设置服务器配置,这里有很多坑,应认真仔细阅读。
- 这个验证需要在服务器写相应的代码回应微信端,具体的代码我会写在后面。
- 服务器配置填写的必须是域名而不是ip地址。
- 服务器配置的地址要写到具体控制器。如下:
网页授权开发文档地址:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140842
服务器代码
package com.saibei.plat.app.controller;
import java.io.PrintWriter;
import java.util.Date;
import java.util.Enumeration;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.ibatis.session.SqlSession;
import org.apache.log4j.Logger;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import com.mysql.jdbc.CallableStatement;
import com.saibei.plat.bean.ResultData;
import com.saibei.plat.common.DBHelper;
import com.saibei.plat.dao.WxUserMapperDao;
import com.saibei.plat.model.WxUser;
import com.saibei.plat.utils.SqlSessionFactoryUtils;
import com.saibei.plat.wx.utils.GetGZHOpenId;
import com.saibei.plat.wx.utils.SignUtil;
import com.saibei.plat.wx.utils.WX_TokenUtil;
@RequestMapping("/wechat")
@Controller
public class WechatController {
private static Logger logger = Logger.getLogger(WechatController.class);
//这里的token与服务器配置中的token一致
private static String WECHAT_TOKEN = "stupwxid";
@RequestMapping(value = "/wx.do")
public void get(HttpServletRequest request, HttpServletResponse response) throws Exception {
logger.error("WechatController ---- WechatController");
System.out.println("========WechatController========= ");
logger.info("请求进来了...");
Enumeration pNames = request.getParameterNames();
while (pNames.hasMoreElements()) {
String name = (String) pNames.nextElement();
String value = request.getParameter(name);
// out.print(name + "=" + value);
String log = "name =" + name + " value =" + value;
logger.error(log);
}
String signature = request.getParameter("signature");/// 微信加密签名
String timestamp = request.getParameter("timestamp");/// 时间戳
String nonce = request.getParameter("nonce"); /// 随机数
String echostr = request.getParameter("echostr"); // 随机字符串
PrintWriter out = response.getWriter();
if (SignUtil.checkSignature(signature, timestamp, nonce)) {
out.print(echostr);
}
out.close();
out = null;
response.sendRedirect("");
}
}
package com.saibei.plat.wx.utils;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
/**
*
* 请求校验工具类
*
* @author m
*
*
*
*/
public class SignUtil {
// 与接口配置信息中的Token要一致
public static String token = "sytupwxid";
/**
*
* 验证签名
*
*
*
* @param signature
*
* @param timestamp
*
* @param nonce
*
* @return
*
*/
// 检验是否来自微信的签名
public static boolean checkSignature(String signature, String timestamp, String nonce) {
String[] arr = new String[] { token, timestamp, nonce };
// 将token、timestamp、nonce三个参数进行字典序排序
// token令牌,时间戳,随机数
Arrays.sort(arr);
StringBuilder content = new StringBuilder();
for (int i = 0; i < arr.length; i++) {
content.append(arr[i]);
}
MessageDigest md = null;
String tmpStr = null;
try {
md = MessageDigest.getInstance("SHA-1");
// 将三个参数字符串拼接成一个字符串进行sha1加密
byte[] digest = md.digest(content.toString().getBytes());
tmpStr = byteToStr(digest);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
content = null;
// 将sha1加密后的字符串可与signature对比,标识该请求来源于微信
return tmpStr != null ? tmpStr.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;
}
}
目前服务器配置好了,网页授权地址也写好了,最后就是获取openid了。
我是通过点击公众号内的自定义菜单链接进去到授权界面的。类似这种
点击用户绑定后,用户会进入授权界面(若是静默授权,用户则看不到授权界面),我们将回调接口写在自定义菜单栏中,回调接口会接收到来自微信的用户授权access token以及用户openid(静默授权)。此时,我们便可以拿到openid,将其存在数据库中,以后随取随用了。
此处有一个坑,自定义菜单这里的回调接口要写成这样的格式,https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect 若提示“该链接无法访问”,请检查参数是否填写错误,是否拥有scope参数对应的授权作用域权限。
相关代码
控制器
@Controller
@RequestMapping(value = "/wxbind")
public class WxBindController {
private static final Logger logger = LoggerFactory.getLogger(HomeController.class);
@RequestMapping(value = "/getopenid", produces = "application/json;charset=utf-8") // ,method =
// {RequestMethod.POST}method =
// RequestMethod.POST,
@ResponseBody
private String getopenid(HttpServletRequest request, HttpServletResponse response, Locale locale, Model model)
throws ParseException, IOException, org.apache.http.ParseException, URISyntaxException, SQLException {
logger.info("WxBindController", "getopenid");
response.setCharacterEncoding("UTF-8");
request.setCharacterEncoding("UTF-8");
response.setHeader("Access-Control-Allow-Origin", "*");
/* 星号表示所有的域都可以接受, */
response.setHeader("Access-Control-Allow-Methods", "GET,POST");
String code = "";
if (request.getParameter("code") != null && !"".equals(request.getParameter("code"))) {
code = (String) (request.getParameter("code"));
}
// 获取到openId
String openId = GetGZHOpenId.getopendid(code);
// 把openid到session
HttpSession session = request.getSession();
session.setAttribute("openid", openId);
String url = "https://wx.cnsbdz.com";
StringBuffer url_code = new StringBuffer(url);
// 这里请不要使用get请求单纯的将页面跳转到该url即可
response.sendRedirect(url_code.toString());
return null;
}
}
下面是模板消息发送的相关工具类。
package com.saibei.plat.app.controller;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.ibatis.session.SqlSession;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.saibei.plat.bean.ResultData;
import com.saibei.plat.dao.CustomerMapperDao;
import com.saibei.plat.dao.PushTokenMapperDao;
import com.saibei.plat.dao.UserMapperDao;
import com.saibei.plat.dao.WxUserMapperDao;
import com.saibei.plat.model.Devcommu;
import com.saibei.plat.model.EventOrder;
import com.saibei.plat.model.PushToken;
import com.saibei.plat.model.User;
import com.saibei.plat.model.WxUser;
import com.saibei.plat.utils.SqlSessionFactoryUtils;
import com.saibei.plat.wx.utils.TemplateData;
import com.saibei.plat.wx.utils.WX_HttpsUtil;
import com.saibei.plat.wx.utils.WX_TemplateMsgUtil;
import com.saibei.plat.wx.utils.WX_TokenUtil;
import com.saibei.plat.wx.utils.WX_UserUtil;
/**
* 问候语 报警类型 报警设备 报警时间 报警内容
*/
@Controller
@RequestMapping(value = "/wxpush")
public class WxPushController {
@RequestMapping(value = "/dopush", produces = "application/json;charset=utf-8") // ,method = {RequestMethod.POST}method = RequestMethod.POST,
@ResponseBody
private String dopush(HttpServletRequest request, HttpServletResponse response, Locale locale, Model model) {
int deviceid = 0;
//cname 客户名
String cname="",eventtype = "", devicename = "",content="",remark="";
if (request.getParameter("cname") != null && !"".equals(request.getParameter("cname"))) {
cname = request.getParameter("cname");
}
if (request.getParameter("deviceid") != null && !"".equals(request.getParameter("deviceid"))) {
deviceid = Integer.parseInt(request.getParameter("deviceid"));
}
if (request.getParameter("eventtype") != null && !"".equals(request.getParameter("eventtype"))) {
eventtype = request.getParameter("eventtype").toString();
}
if (request.getParameter("devicename") != null && !"".equals(request.getParameter("devicename"))) {
devicename = request.getParameter("devicename").toString();
}
if (request.getParameter("content") != null && !"".equals(request.getParameter("content"))) {
content = request.getParameter("content").toString();
}
if (request.getParameter("remark") != null && !"".equals(request.getParameter("remark"))) {
remark = request.getParameter("remark").toString();
}
Map<String, TemplateData> param = new HashMap();
// 问候语
param.put("first", new TemplateData(cname, "#EE0000"));
// 报警类型
param.put("keyword1", new TemplateData(eventtype, "#EE0000"));
// 报警设备
param.put("keyword2", new TemplateData(devicename, "#EE0000"));
// 报警时间:精确到时分秒
Date date = new Date();
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
param.put("keyword3", new TemplateData((df.format(date)).toString(), "#EE0000"));
// 报警内容
param.put("keyword4", new TemplateData(content, "#EE0000"));
// 备注信息
param.put("remark", new TemplateData(remark, "#EE0000"));
JSON.toJSONString(param);
JSONObject jsonObject = JSONObject.parseObject(JSON.toJSONString(param));
// 调用发送微信消息给用户的接口 ********这里写自己在微信公众平台拿到的模板ID
SqlSession sqlSession = null;
sqlSession = SqlSessionFactoryUtils.openSqlSession();
WxUserMapperDao wxUserMapperDao = sqlSession.getMapper(WxUserMapperDao.class);
List<WxUser> list = wxUserMapperDao.selectOpenidList(deviceid);
for (WxUser wxUser : list) {
System.out.println(wxUser.getOpenidGzh());
WX_TemplateMsgUtil.sendWechatMsgToUser(wxUser.getOpenidGzh(), "此处填写你的模板id",
null,
"#000000", jsonObject);
}
System.out.println("进入了这个方法");
System.out.println(123);
return null;
}
}
package com.saibei.plat.wx.utils;
import java.util.Map;
import javax.servlet.http.HttpSession;
import org.junit.runner.Request;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.fastjson.JSONObject;
public class WX_TemplateMsgUtil {
private static Logger log = LoggerFactory.getLogger(WX_TemplateMsgUtil.class);
/**
* 封装模板详细信息
* @return
*/
public static JSONObject packJsonmsg(Map<String, TemplateData> param) {
JSONObject json = new JSONObject();
for (Map.Entry<String,TemplateData> entry : param.entrySet()) {
JSONObject keyJson = new JSONObject();
TemplateData dta= entry.getValue();
keyJson.put("value",dta.getValue());
keyJson.put("color", dta.getColor());
json.put(entry.getKey(), keyJson);
}
return json;
}
/**
* 根据模板的编号 新增并获取模板ID
* @param templateSerialNumber 模板库中模板的 "编号"
* @return 模板ID
*/
public static String getWXTemplateMsgId(String templateSerialNumber){
String tmpurl = "https://api.weixin.qq.com/cgi-bin/template/api_add_template?access_token="+ WX_TokenUtil.getWXToken().getAccessToken();
JSONObject json = new JSONObject();
//template_id_short是模板编号
json.put("template_id_short", templateSerialNumber);
//路径,请求方式,传的数据
JSONObject result = WX_HttpsUtil.httpsRequest(tmpurl, "POST", json.toString());
JSONObject resultJson = new JSONObject(result);
String errmsg = (String) resultJson.get("errmsg");
log.info("获取模板编号返回信息:" + errmsg);
if(!"ok".equals(errmsg)){
return "error";
}
String templateId = (String) resultJson.get("template_id");
return templateId;
}
/**
* 根据模板ID 删除模板消息
* @param templateId 模板ID
* @return
*/
public static String deleteWXTemplateMsgById(String templateId){
String tmpurl = "https://api.weixin.qq.com/cgi-bin/template/del_private_template?access_token="+ WX_TokenUtil.getWXToken().getAccessToken();
JSONObject json = new JSONObject();
json.put("template_id", templateId);
try{
JSONObject result = WX_HttpsUtil.httpsRequest(tmpurl, "POST", json.toString());
JSONObject resultJson = new JSONObject(result);
log.info("删除"+templateId+"模板消息,返回CODE:"+ resultJson.get("errcode"));
String errmsg = (String) resultJson.get("errmsg");
if(!"ok".equals(errmsg)){
return "error";
}
}catch(Exception e){
e.printStackTrace();
}
return "success";
}
/**
* 发送微信消息(模板消息)
* @param touser 用户 OpenID
* @param templatId 模板消息ID
* @param clickurl URL置空,则在发送后,点击模板消息会进入一个空白页面(ios),或无法点击(android)。
* @param topcolor 标题颜色
* @param data 详细内容
* @return
*/
public static String sendWechatMsgToUser(String touser, String templatId, String clickurl, String topcolor, JSONObject data) {
String tmpurl = "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token="+ WX_TokenUtil.getWXToken().getAccessToken();
JSONObject json = new JSONObject();
json.put("touser", touser);
json.put("template_id", templatId);
json.put("url", clickurl);
json.put("topcolor", topcolor);
json.put("data", data);
try{
JSONObject result = WX_HttpsUtil.httpsRequest(tmpurl, "POST", json.toString());
JSONObject resultJson = new JSONObject(result);
log.info("发送微信消息返回信息:" + resultJson.get("errcode"));
String errmsg = (String) resultJson.get("errmsg");
if(!"ok".equals(errmsg)){ //如果为errmsg为ok,则代表发送成功,公众号推送信息给用户了。
return "error";
}
}catch(Exception e){
e.printStackTrace();
return "error";
}
return "success";
}
}
这里我采取了数据库存储access token,详情见另一篇文章 mysql存储access token
package com.saibei.plat.wx.utils;
import org.apache.ibatis.session.SqlSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.fastjson.JSONException;
import com.alibaba.fastjson.JSONObject;
import com.saibei.plat.dao.AccessTokenMapperDao;
import com.saibei.plat.dao.WxUserMapperDao;
import com.saibei.plat.model.AccessToken;
import com.saibei.plat.utils.SqlSessionFactoryUtils;
public class WX_TokenUtil {
private static Logger log = LoggerFactory.getLogger(WX_TokenUtil.class);
/**
*
* 获得微信 AccessToken
*
* access_token是公众号的全局唯一接口调用凭据,公众号调用各接口时都需使用access_token。
*
* 开发者需要access_token的有效期目前为2个小时,需定时刷新,重复获取将导致上次获取
*
* 的access_token失效。
*
* (此处我是把token存在java缓存里面了)此代码token没有加入缓存,后面附了缓存的工具类,根据需求自己添加
*
*/
public static AccessToken getWXToken() {
String appId = "wxd09cb80b21a89012";
String appSecret = "b0aeca179d5d6c3cbb8c177dd689875f";
String tokenUrl = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + appId
+ "&secret=" + appSecret;
/**
* 判断access token是否过期 若未过期,接着用 若已经过期 重新获取 从数据库中找
*/
JSONObject jsonObject = WX_HttpsUtil.httpsRequest(tokenUrl, "GET", null);
System.out.println("jsonObject:" + jsonObject);
AccessToken access_token = new AccessToken();
if (null != jsonObject) {
try {
access_token.setAccesstoken(jsonObject.getString("access_token"));
access_token.setExpiresin(jsonObject.getInteger("expires_in"));
} catch (JSONException e) {
access_token = null;
// 获取token失败
log.error("获取token失败 errcode:{} errmsg:{}", jsonObject.getInteger("errcode"),
jsonObject.getString("errmsg"));
}
}
return access_token;
}
}
大致过程就是这样,有什么疑惑可以联系我。
邮箱地址:[email protected]