java实现微信公众平台发送模板消息的示例代码

最近开发公众号项目,前端采用vue开发,后台使用java开发,由于业务需求,需要实现公众号向用户发送重要的服务通知,提醒工作人员进行业务审核。这时候就需要用到微信平台的模板消息,为了保证用户不受到骚扰,在开发者出现需要主动提醒、通知用户时,才允许开发者在公众平台网站中模板消息库中选择模板,选择后获得模板ID,再根据模板ID向用户主动推送提醒、通知消息。常用的服务场景,如信用卡刷卡通知,商品下单成功、购买成功通知等。

获取template_id(注意:仅微信开放平台同事可获取)

通过向微信公众平台申请模板,来获取模板id,模板消息调用时主要需要模板ID和模板中各参数的赋值内容。请注意:
1.模板中参数内容必须以".DATA"结尾,否则视为保留字;
2.模板保留符号"{{ }}"

下图是在微信测试公众号申请模板

请求模板消息接口

1)微信网页授权

 //前端发请请求
 this.axios.get('/wx/get_code_num').then((res) => {
  window.location.href = res.data;
 }).catch((error) => {
  console.log(error)
 });

 /**
  * 1.用户同意授权,获取code
  */
  @RequestMapping(value = "/get_code_num", method = RequestMethod.GET)
  public String getCode() throws UnsupportedEncodingException {
    return "https://open.weixin.qq.com/connect/oauth2/authorize?appid=" + Constants.APPID + "&redirect_uri="
        + URLEncoder.encode("http://192.168.0.152:8085/wx/send_wx_msg", "UTF-8") + "&response_type=code&scope="
        + Constants.GRANTSCOPE + "&state=STATE#wechat_redirect";
  }  

2)获取用户openid

/**
 * 2.通过code换取网页授权access_token及openid
 */
@RequestMapping(value = "/send_wx_msg", method = RequestMethod.GET)
public String sendWxMsg(String code) {
  String access_token_url = "https://api.weixin.qq.com/sns/oauth2/access_token";
  String accessTokenObj = HttpClientUtil.sendGet(access_token_url, "appid=" + Constants.APPID + "&secret="
      + Constants.APPSECRET + "&code=" + code + "&grant_type=authorization_code");
  JSONObject jsonToken = JSONObject.fromObject(accessTokenObj);

  String openId = null;
  if (StringUtils.isNotBlank(String.valueOf(jsonToken))) {
    openId = jsonToken.getString("openid");
  }
  logger.info("获取openid,微信平台接口返回{}", openId);
  return openId;
}

3)组装、发送模板消息

import java.util.TreeMap;

public class WechatTemplate {

  private String touser;//用戶openid

  private String template_id;//模板ID

  private String url;//URL置空,则在发送后,点击模板消息会进入一个空白页面(ios),或无法点击(android)

  private TreeMap<String, TreeMap<String, String>> data; //data数据

  public static TreeMap<String, String> item(String value, String color) {
    TreeMap<String, String> params = new TreeMap<String, String>();
    params.put("value", value);
    params.put("color", color);
    return params;
  }

  public TreeMap<String, TreeMap<String, String>> getData() {
    return data;
  }

  public void setData(TreeMap<String, TreeMap<String, String>> data) {
    this.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 template_id) {
    this.template_id = template_id;
  }

  public String getUrl() {
    return url;
  }

  public void setUrl(String url) {
    this.url = url;
  }

  @Override
  public String toString() {
    return "WechatTemplate{" +
        "touser='" + touser + '\'' +
        ", template_id='" + template_id + '\'' +
        ", url='" + url + '\'' +
        ", data=" + data +
        '}';
  }
}
//微信模板接口
private final String SEND_TEMPLATE_MESSAGE_URL = "https://api.weixin.qq.com/cgi-bin/message/template/send";
//模板消息详情跳转URL
private static String url = "https://www.baidu.com/";

@RequestMapping(value = "/send_wx_msg", method = RequestMethod.GET)
public String sendWxMsg(String code) {
  String access_token_url = "https://api.weixin.qq.com/sns/oauth2/access_token";
  String accessTokenObj = HttpClientUtil.sendGet(access_token_url, "appid=" + Constants.APPID + "&secret="
      + Constants.APPSECRET + "&code=" + code + "&grant_type=authorization_code");
  JSONObject jsonToken = JSONObject.fromObject(accessTokenObj);

  String openId = null;
  if (StringUtils.isNotBlank(String.valueOf(jsonToken))) {
    openId = jsonToken.getString("openid");
  }
  logger.info("获取openid,微信平台接口返回{}", openId);

  String urlToken = "https://api.weixin.qq.com/cgi-bin/token";
  String tokenObj = HttpClientUtil.sendGet(urlToken, "grant_type=client_credential" + "&secret=" + Constants.APPSECRET + "&appid=" + Constants.APPID);
  JSONObject retToken = JSONObject.fromObject(tokenObj);
  String accessToken = String.valueOf(retToken.get("access_token"));
  logger.info("获取access_token,微信平台接口返回{}", accessToken);

  TreeMap<String, TreeMap<String, String>> params = new TreeMap<String, TreeMap<String, String>>();
  //根据具体模板参数组装
  params.put("first", WechatTemplate.item("您的户外旅行活动订单已经支付完成,可在我的个人中心中查看", "#000000"));
  params.put("keyword1", WechatTemplate.item("发现尼泊尔—人文与自然的旅行圣地", "#000000"));
  params.put("keyword2", WechatTemplate.item("5000元", "#000000"));
  params.put("keyword3", WechatTemplate.item("2019.09.04", "#000000"));
  params.put("keyword4", WechatTemplate.item("5", "#000000"));
  params.put("remark", WechatTemplate.item("请届时携带好身份证件准时到达集合地点,若临时退改将产生相应损失,敬请谅解,谢谢!", "#000000"));
  WechatTemplate wechatTemplate = new WechatTemplate();
  wechatTemplate.setTemplate_id(Constants.TEMPLATEID);
  wechatTemplate.setTouser(openId);
  wechatTemplate.setUrl(url);
  wechatTemplate.setData(params);
  JSONObject json = JSONObject.fromObject(wechatTemplate);//将java对象转换为json对象
  String sendData = json.toString();//将json对象转换为字符串
  logger.info("板参数组装{}", sendData);

  TreeMap<String, String> treeMap = new TreeMap<String, String>();
  treeMap.put("access_token", accessToken);
  String retInfo = HttpUtil.doPost(SEND_TEMPLATE_MESSAGE_URL, treeMap, sendData);
  logger.info("消息模板返回{}", retInfo);
  return retInfo;
}

请求的数据格式

{
  "data": {
    "first": {
      "color": "#000000",
      "value": "您的户外旅行活动订单已经支付完成,可在我的个人中心中查看"
    },
    "keyword1": {
      "color": "#000000",
      "value": "发现尼泊尔—人文与自然的旅行圣地"
    },
    "keyword2": {
      "color": "#000000",
      "value": "5000元"
    },
    "keyword3": {
      "color": "#000000",
      "value": "2019.09.04"
    },
    "keyword4": {
      "color": "#000000",
      "value": "5"
    },
    "remark": {
      "color": "#000000",
      "value": "请届时携带好身份证件准时到达集合地点,若临时退改将产生相应损失,敬请谅解,谢谢!"
    }
  },
  "template_id": "ZUMTnYtG0O4vZSv4bPTtWTOFZ2zirOjaM50GYywRRnA",
  "touser": "olv_asx8nmggCQEmAFNbQstx3xd0",
  "url": "https://www.baidu.com/"
}

微信平台返回的结果:

微信公众号通知消息

工具类:


import org.apache.commons.collections.MapUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.net.ssl.*;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Map;

public class HttpUtil {
  private static Logger logger = LoggerFactory.getLogger(HttpUtil.class);
  protected static final String POST_METHOD = "POST";
  private static final String GET_METHOD = "GET";

  static {
    TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() {
      @Override
      public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
        logger.debug("ClientTrusted");
      }

      @Override
      public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
        logger.debug("ServerTrusted");
      }

      @Override
      public X509Certificate[] getAcceptedIssuers() {
        return new X509Certificate[]{};
      }
    }};

    HostnameVerifier doNotVerify = (s, sslSession) -> true;

    try {
      SSLContext sc = SSLContext.getInstance("SSL", "SunJSSE");
      sc.init(null, trustAllCerts, new SecureRandom());
      HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
      HttpsURLConnection.setDefaultHostnameVerifier(doNotVerify);
    } catch (Exception e) {
      logger.error("Initialization https impl occur exception : {}", e);
    }
  }

  /**
   * 默认的http请求执行方法
   *
   * @param url  url 路径
   * @param method 请求的方法 POST/GET
   * @param map  请求参数集合
   * @param data  输入的数据 允许为空
   * @return result
   */
  private static String HttpDefaultExecute(String url, String method, Map<String, String> map, String data) {
    String result = "";
    try {
      url = setParmas(url, map, null);
      result = defaultConnection(url, method, data);
    } catch (Exception e) {
      logger.error("出错参数 {}", map);
    }
    return result;
  }

  public static String httpGet(String url, Map<String, String> map) {
    return HttpDefaultExecute(url, GET_METHOD, map, null);
  }

  public static String httpPost(String url, Map<String, String> map, String data) {
    return HttpDefaultExecute(url, POST_METHOD, map, data);
  }

  /**
   * 默认的https执行方法,返回
   *
   * @param url  url 路径
   * @param method 请求的方法 POST/GET
   * @param map  请求参数集合
   * @param data  输入的数据 允许为空
   * @return result
   */
  private static String HttpsDefaultExecute(String url, String method, Map<String, String> map, String data) {
    try {
      url = setParmas(url, map, null);
      logger.info(data);
      return defaultConnection(url, method, data);
    } catch (Exception e) {
      logger.error("出错参数 {}", map);
    }
    return "";
  }

  public static String doGet(String url, Map<String, String> map) {
    return HttpsDefaultExecute(url, GET_METHOD, map, null);
  }

  public static String doPost(String url, Map<String, String> map, String data) {
    return HttpsDefaultExecute(url, POST_METHOD, map, data);
  }

  /**
   * @param path  请求路径
   * @param method 方法
   * @param data  输入的数据 允许为空
   * @return
   * @throws Exception
   */
  private static String defaultConnection(String path, String method, String data) throws Exception {
    if (StringUtils.isBlank(path)) {
      throw new IOException("url can not be null");
    }
    String result = null;
    URL url = new URL(path);
    HttpURLConnection conn = getConnection(url, method);
    if (StringUtils.isNotEmpty(data)) {
      OutputStream output = conn.getOutputStream();
      output.write(data.getBytes(StandardCharsets.UTF_8));
      output.flush();
      output.close();
    }
    if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {
      InputStream input = conn.getInputStream();
      result = IOUtils.toString(input, StandardCharsets.UTF_8);
      input.close();
      conn.disconnect();
    }
//    log.info(result);
    return result;
  }

  /**
   * 根据url的协议选择对应的请求方式
   *
   * @param url  请求路径
   * @param method 方法
   * @return conn
   * @throws IOException 异常
   */
  //待改进
  protected static HttpURLConnection getConnection(URL url, String method) throws IOException {
    HttpURLConnection conn;
    if (StringUtils.equals("https", url.getProtocol())) {
      conn = (HttpsURLConnection) url.openConnection();
    } else {
      conn = (HttpURLConnection) url.openConnection();
    }
    if (conn == null) {
      throw new IOException("connection can not be null");
    }
    conn.setRequestProperty("Pragma", "no-cache");// 设置不适用缓存
    conn.setRequestProperty("Cache-Control", "no-cache");
    conn.setRequestProperty("Connection", "Close");// 不支持Keep-Alive
    conn.setUseCaches(false);
    conn.setDoOutput(true);
    conn.setDoInput(true);
    conn.setInstanceFollowRedirects(true);
    conn.setRequestMethod(method);
    conn.setConnectTimeout(8000);
    conn.setReadTimeout(8000);

    return conn;
  }

  /**
   * 根据url
   *
   * @param url 请求路径
   * @return isFile
   * @throws IOException 异常
   */
  //待改进
  protected static HttpURLConnection getConnection(URL url, boolean isFile) throws IOException {
    HttpURLConnection conn = (HttpURLConnection) url.openConnection();
    if (conn == null) {
      throw new IOException("connection can not be null");
    }
    //设置从httpUrlConnection读入
    conn.setDoInput(true);
    conn.setDoOutput(true);
    conn.setUseCaches(false);
    //如果是上传文件,则设为POST
    if (isFile) {
      conn.setRequestMethod(POST_METHOD); //GET和 POST都可以 文件略大改成POST
    }
    // 设置请求头信息
    conn.setRequestProperty("Connection", "Keep-Alive");
    conn.setRequestProperty("Charset", String.valueOf(StandardCharsets.UTF_8));
    conn.setConnectTimeout(8000);
    conn.setReadTimeout(8000);
    return conn;
  }

  /**
   * 拼接参数
   *
   * @param url   需要拼接参数的url
   * @param map   参数
   * @param charset 编码格式
   * @return 拼接完成后的url
   */
  public static String setParmas(String url, Map<String, String> map, String charset) throws Exception {
    String result = StringUtils.EMPTY;
    boolean hasParams = false;
    if (StringUtils.isNotEmpty(url) && MapUtils.isNotEmpty(map)) {
      StringBuilder builder = new StringBuilder();
      for (Map.Entry<String, String> entry : map.entrySet()) {
        String key = entry.getKey().trim();
        String value = entry.getValue().trim();
        if (hasParams) {
          builder.append("&");
        } else {
          hasParams = true;
        }
        if (StringUtils.isNotEmpty(charset)) {
          builder.append(key).append("=").append(URLEncoder.encode(value, charset));
        } else {
          builder.append(key).append("=").append(value);
        }
      }
      result = builder.toString();
    }

    URL u = new URL(url);
    if (StringUtils.isEmpty(u.getQuery())) {
      if (url.endsWith("?")) {
        url += result;
      } else {
        url = url + "?" + result;
      }
    } else {
      if (url.endsWith("&")) {
        url += result;
      } else {
        url = url + "&" + result;
      }
    }
    logger.debug("request url is {}", url);
    return url;
  }
}

遇到的问题

1)"errcode":40001,"errmsg":"invalid credential, access_token is invalid or not latest hint: [Ua2IXa0080sz47!]"
获取的access_token不对,这边的token不是授权的token,是公众号调用各接口时使用的access_token

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • 微信公众平台 发送模板消息(Java接口开发)

    前言:最近一直再弄微信扫码推送图文消息和模板消息发送,感觉学习到了不少东西.今天先总结一下微信公众平台模板消息的发送.因为这个自己弄了很久,开始很多地方不明白,所以今天好好总结一下. 微信公众平台技术文档:模板消息接口 一.概述 模板消息仅用于公众号向用户发送重要的服务通知,只能用于符合其要求的服务场景中,如信用卡刷卡通知,商品购买成功通知等.不支持广告等营销类消息以及其它所有可能对用户造成骚扰的消息. 关于使用规则,请注意: 1.所有服务号都可以在功能->添加功能插件处看到申请模板消息功能的入

  • java实现微信公众平台发送模板消息的示例代码

    最近开发公众号项目,前端采用vue开发,后台使用java开发,由于业务需求,需要实现公众号向用户发送重要的服务通知,提醒工作人员进行业务审核.这时候就需要用到微信平台的模板消息,为了保证用户不受到骚扰,在开发者出现需要主动提醒.通知用户时,才允许开发者在公众平台网站中模板消息库中选择模板,选择后获得模板ID,再根据模板ID向用户主动推送提醒.通知消息.常用的服务场景,如信用卡刷卡通知,商品下单成功.购买成功通知等. 获取template_id(注意:仅微信开放平台同事可获取) 通过向微信公众平台

  • Java实现微信公众平台朋友圈分享功能详细代码

    其实分享的方法在微信官网有较为详细的文档说明,现就其中一些比较绕的步骤进行总结,有问题随时交流哈. 首先微信其实已经自带分享到朋友圈,朋友,qq空间等功能,对于开发微信专门提供了一个接口,可以根据需要修改一些配置.例如修改要分享内容的头像,链接,描述等. 开发步骤: 1.在公众平台配置js-sdk接口 "公众号设置"--"功能设置"--"JS接口安全域名" 2.在要分享的页面引入js http://res.wx.qq.com/open/js/jw

  • java实现微信公众号发送模版消息

    前言: 在我们购买商品或其他操作的时候,这时候微信公众号会推送相关模版消息.接下来简单介绍开发流程:(本文以订单推送为例) 首先在测试号新建模版消息 格式如下: {{first.DATA}} 用户名:{{keyword1.DATA}} 订单号:{{keyword2.DATA}} 订单金额:{{keyword3.DATA}} 商品信息:{{keyword4.DATA}} {{remark.DATA}} 这里会生成一个模版ID,后面会用到 然后后台在上传订单接口,上传成功后调用发送模版消息,代码实现

  • java实现微信公众平台自定义菜单的创建示例

    复制代码 代码如下: import java.io.IOException;import java.io.InputStream;import java.io.OutputStream;import java.net.HttpURLConnection;import java.net.MalformedURLException;import java.net.URL; import org.json.JSONObject; public class MenuUtil { /**  * 获得ACC

  • 微信支付java版本之JSAPI支付+发送模板消息

    本文为大家分享了java版本之JSAPI支付+发送模板消息的相关资料,供大家参考,具体内容如下 1.工具类 工具类见:微信支付JAVA版本之Native付款 2.公众账号设置 3.代码实现 openId:openId为用户与该公众账号之间代表用户的唯一标示  以下类中涉及到生成token,关闭订单接口调用,获取配置文件信息,和工具类,在其他文章中有具体代码实现 package com.zhrd.bussinss.platform.controller.rest; import java.io.F

  • java版微信公众平台消息接口应用示例

    本文实例讲述了java版微信公众平台消息接口应用方法.分享给大家供大家参考,具体如下: 微信公众平台现在推出自动回复消息接口,但是由于是接口内容用的是PHP语言写的,很多地方操作起来让本人这个对Java比较熟悉的小伙很别扭,所以仿照PHP的接口代码做了一套jsp语言编写的接口. 首先先把整个接口代码贴出来做下比较,然后我们再分析代码: PHP: <?php /** * wechat php test */ //define your token define("TOKEN", &

  • C#开发之微信小程序发送模板消息功能

    步骤一:获取模板ID 有两个方法可以获取模版ID 通过模版消息管理接口获取模版ID 在微信公众平台手动配置获取模版ID 步骤二:页面的 <form/> 组件,属性report-submit为true时,可以声明为需发模板消息,此时点击按钮提交表单可以获取formId,用于发送模板消息.或者当用户完成支付行为,可以获取prepay_id用于发送模板消息. 步骤三:调用接口下发模板消息 今天重要的说第三步怎么实现,前面的步骤比较简单就略过. ----------------------------

  • python实现企业微信定时发送文本消息的示例代码

    企业微信定时发送文本消息 使用工具:企业微信机器人+python可执行文件+计算机管理中的任务计划程序 第一步:创建群机器人 选择群聊,单击鼠标右键,添加群机器人. 建立群机器人后,右键查看机器人,如下 复制机器人的链接. 第二步:编辑python程序 import requests from datetime import datetime url = 'https://qyapi.we......' #机器人的webhook地址 headers = {'Content-type':'appl

  • Python 微信公众号文章爬取的示例代码

    一.思路 我们通过网页版的微信公众平台的图文消息中的超链接获取到我们需要的接口 从接口中我们可以得到对应的微信公众号和对应的所有微信公众号文章. 二.接口分析 获取微信公众号的接口: https://mp.weixin.qq.com/cgi-bin/searchbiz? 参数: action=search_biz begin=0 count=5 query=公众号名称 token=每个账号对应的token值 lang=zh_CN f=json ajax=1 请求方式: GET 所以这个接口中我们

随机推荐