java實(shí)現(xiàn)微信公眾平臺(tái)發(fā)送模板消息的示例代碼
最近開發(fā)公眾號(hào)項(xiàng)目,前端采用vue開發(fā),后臺(tái)使用java開發(fā),由于業(yè)務(wù)需求,需要實(shí)現(xiàn)公眾號(hào)向用戶發(fā)送重要的服務(wù)通知,提醒工作人員進(jìn)行業(yè)務(wù)審核。這時(shí)候就需要用到微信平臺(tái)的模板消息,為了保證用戶不受到騷擾,在開發(fā)者出現(xiàn)需要主動(dòng)提醒、通知用戶時(shí),才允許開發(fā)者在公眾平臺(tái)網(wǎng)站中模板消息庫(kù)中選擇模板,選擇后獲得模板ID,再根據(jù)模板ID向用戶主動(dòng)推送提醒、通知消息。常用的服務(wù)場(chǎng)景,如信用卡刷卡通知,商品下單成功、購(gòu)買成功通知等。
獲取template_id(注意:僅微信開放平臺(tái)同事可獲?。?/p>
通過向微信公眾平臺(tái)申請(qǐng)模板,來獲取模板id,模板消息調(diào)用時(shí)主要需要模板ID和模板中各參數(shù)的賦值內(nèi)容。請(qǐng)注意:
1.模板中參數(shù)內(nèi)容必須以".DATA"結(jié)尾,否則視為保留字;
2.模板保留符號(hào)"{{ }}"
下圖是在微信測(cè)試公眾號(hào)申請(qǐng)模板
請(qǐng)求模板消息接口
1)微信網(wǎng)頁(yè)授權(quán)
//前端發(fā)請(qǐng)請(qǐng)求 this.axios.get('/wx/get_code_num').then((res) => { window.location.href = res.data; }).catch((error) => { console.log(error) }); /** * 1.用戶同意授權(quán),獲取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換取網(wǎng)頁(yè)授權(quán)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,微信平臺(tái)接口返回{}", openId); return openId; }
3)組裝、發(fā)送模板消息
import java.util.TreeMap; public class WechatTemplate { private String touser;//用戶openid private String template_id;//模板ID private String url;//URL置空,則在發(fā)送后,點(diǎn)擊模板消息會(huì)進(jìn)入一個(gè)空白頁(yè)面(ios),或無(wú)法點(diǎn)擊(android) private TreeMap<String, TreeMap<String, String>> data; //data數(shù)據(jù) 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"; //模板消息詳情跳轉(zhuǎn)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,微信平臺(tái)接口返回{}", 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,微信平臺(tái)接口返回{}", accessToken); TreeMap<String, TreeMap<String, String>> params = new TreeMap<String, TreeMap<String, String>>(); //根據(jù)具體模板參數(shù)組裝 params.put("first", WechatTemplate.item("您的戶外旅行活動(dòng)訂單已經(jīng)支付完成,可在我的個(gè)人中心中查看", "#000000")); params.put("keyword1", WechatTemplate.item("發(fā)現(xiàn)尼泊爾—人文與自然的旅行圣地", "#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("請(qǐng)屆時(shí)攜帶好身份證件準(zhǔn)時(shí)到達(dá)集合地點(diǎn),若臨時(shí)退改將產(chǎn)生相應(yīng)損失,敬請(qǐng)諒解,謝謝!", "#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對(duì)象轉(zhuǎn)換為json對(duì)象 String sendData = json.toString();//將json對(duì)象轉(zhuǎn)換為字符串 logger.info("板參數(shù)組裝{}", 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; }
請(qǐng)求的數(shù)據(jù)格式
{ "data": { "first": { "color": "#000000", "value": "您的戶外旅行活動(dòng)訂單已經(jīng)支付完成,可在我的個(gè)人中心中查看" }, "keyword1": { "color": "#000000", "value": "發(fā)現(xiàn)尼泊爾—人文與自然的旅行圣地" }, "keyword2": { "color": "#000000", "value": "5000元" }, "keyword3": { "color": "#000000", "value": "2019.09.04" }, "keyword4": { "color": "#000000", "value": "5" }, "remark": { "color": "#000000", "value": "請(qǐng)屆時(shí)攜帶好身份證件準(zhǔn)時(shí)到達(dá)集合地點(diǎn),若臨時(shí)退改將產(chǎn)生相應(yīng)損失,敬請(qǐng)諒解,謝謝!" } }, "template_id": "ZUMTnYtG0O4vZSv4bPTtWTOFZ2zirOjaM50GYywRRnA", "touser": "olv_asx8nmggCQEmAFNbQstx3xd0", "url": "https://www.baidu.com/" }
微信平臺(tái)返回的結(jié)果:
微信公眾號(hào)通知消息
工具類:
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); } } /** * 默認(rèn)的http請(qǐng)求執(zhí)行方法 * * @param url url 路徑 * @param method 請(qǐng)求的方法 POST/GET * @param map 請(qǐng)求參數(shù)集合 * @param data 輸入的數(shù)據(jù) 允許為空 * @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("出錯(cuò)參數(shù) {}", 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); } /** * 默認(rèn)的https執(zhí)行方法,返回 * * @param url url 路徑 * @param method 請(qǐng)求的方法 POST/GET * @param map 請(qǐng)求參數(shù)集合 * @param data 輸入的數(shù)據(jù) 允許為空 * @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("出錯(cuò)參數(shù) {}", 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 請(qǐng)求路徑 * @param method 方法 * @param data 輸入的數(shù)據(jù) 允許為空 * @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; } /** * 根據(jù)url的協(xié)議選擇對(duì)應(yīng)的請(qǐng)求方式 * * @param url 請(qǐng)求路徑 * @param method 方法 * @return conn * @throws IOException 異常 */ //待改進(jìn) 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");// 設(shè)置不適用緩存 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; } /** * 根據(jù)url * * @param url 請(qǐng)求路徑 * @return isFile * @throws IOException 異常 */ //待改進(jìn) 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"); } //設(shè)置從httpUrlConnection讀入 conn.setDoInput(true); conn.setDoOutput(true); conn.setUseCaches(false); //如果是上傳文件,則設(shè)為POST if (isFile) { conn.setRequestMethod(POST_METHOD); //GET和 POST都可以 文件略大改成POST } // 設(shè)置請(qǐng)求頭信息 conn.setRequestProperty("Connection", "Keep-Alive"); conn.setRequestProperty("Charset", String.valueOf(StandardCharsets.UTF_8)); conn.setConnectTimeout(8000); conn.setReadTimeout(8000); return conn; } /** * 拼接參數(shù) * * @param url 需要拼接參數(shù)的url * @param map 參數(shù) * @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不對(duì),這邊的token不是授權(quán)的token,是公眾號(hào)調(diào)用各接口時(shí)使用的access_token
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- intellij idea設(shè)置統(tǒng)一JavaDoc模板的方法詳解
- Java 實(shí)現(xiàn)word模板轉(zhuǎn)為pdf
- java 后端生成pdf模板合并單元格表格的案例
- java開發(fā)中使用IDEA活動(dòng)模板快速增加注釋的方法
- 詳解JAVA設(shè)計(jì)模式之模板模式
- Java設(shè)計(jì)模式模板方法(Template)原理解析
- Java接口統(tǒng)一樣式返回模板簡(jiǎn)介
- 微信公眾平臺(tái) 發(fā)送模板消息(Java接口開發(fā))
- java實(shí)現(xiàn)在pdf模板的指定位置插入圖片
- java使用Abobe Acrobat DC生成模板
相關(guān)文章
30w+數(shù)據(jù)使用RedisTemplate?pipeline空指針NullPointerException異常分析
這篇文章主要為大家介紹了30w+數(shù)據(jù)使用RedisTemplate?pipeline空指針NullPointerException異常分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-08-08Java GUI圖形界面開發(fā)實(shí)現(xiàn)小型計(jì)算器流程詳解
本文章向大家介紹Java GUI圖形界面開發(fā)實(shí)現(xiàn)小型計(jì)算器,主要包括布局管理器使用實(shí)例、應(yīng)用技巧、基本知識(shí)點(diǎn)總結(jié)和需要注意事項(xiàng),具有一定的參考價(jià)值,需要的朋友可以參考一下2022-08-08java中字符串如何計(jì)算字節(jié)長(zhǎng)度
這篇文章主要介紹了java中字符串如何計(jì)算字節(jié)長(zhǎng)度,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-10-10java.io.EOFException產(chǎn)生原因及解決方法(附代碼)
java.io.EOFException表示在讀取數(shù)據(jù)時(shí)突然遇到了文件或流的末尾,也就是說客戶端或服務(wù)器已經(jīng)關(guān)閉了連接,但是你還在嘗試讀取數(shù)據(jù),這篇文章主要給大家介紹了關(guān)于java.io.EOFException產(chǎn)生原因及解決的相關(guān)資料,需要的朋友可以參考下2023-09-09