通过微信公众平台发送模板消息,Java语言写后台

前言

近期花了很长时间去写公众号平台发送模板消息,这是仅仅支持服务号发送模板消息的,不牵扯到小程序部分,当然也无须注册开放平台的账号,服务号则必须是需要认证的,只有认证后的服务号有主动发送模板消息的权限。代码都是粘贴即可使用的,废话不多说,正题开始。

正文

获取openid

获取openid,需要网页用户授权(等下我会上代码),因此需要设置网页授权地址,设置路径:公众平台>设置>功能设置>网页授权域名,此处域名填写按照官方文档来。

设置完授权域名之后,设置服务器配置,这里有很多坑,应认真仔细阅读。

  1. 这个验证需要在服务器写相应的代码回应微信端,具体的代码我会写在后面。
  2. 服务器配置填写的必须是域名而不是ip地址。
  3. 服务器配置的地址要写到具体控制器。如下:

网页授权开发文档地址: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] 

发布了47 篇原创文章 · 获赞 14 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/qq_35097794/article/details/89762593