java 实现微信公众号消息推送
短信预约 -IT技能 免费直播动态提醒
注册微信公众号
设置消息模板
- 可参考微信公众号文档:微信公众号文档
模板根据需求设置(注意:参数长度不能超出微信规定,否则将发送失败)
参数要求规则
java代码
依赖
<dependency> <groupId>com.alibabagroupId> <artifactId>fastjsonartifactId> <version>1.2.54version>dependency><dependency> <groupId>cn.hutoolgroupId> <artifactId>hutool-allartifactId> <version>4.5.6version>dependency><dependency> <groupId>org.apache.httpcomponentsgroupId> <artifactId>httpclientartifactId> <version>4.5version>dependency><dependency> <groupId>org.apache.httpcomponentsgroupId> <artifactId>httpcoreartifactId> <version>4.4.1version>dependency><dependency> <groupId>commons-httpclientgroupId> <artifactId>commons-httpclientartifactId> <version>3.1version>dependency>
请求地址、appid、密钥等信息;信息配置到application配置中,方便统一管理;
package com.andon.wxPush.conf;import lombok.Data;import org.springframework.stereotype.Service;import org.springframework.beans.factory.annotation.Value;@Service("wxConfiguration")@Datapublic class WxConfiguration { //获取微信access_token请求地址 @Value("${wxConfig.url.WX_ACCESS_TOKEN_URL}") private String wxAccessTokenUrl; //发送微信公众号模板消息请求地址 @Value("${wxConfig.url.SEND_MESSAGE_TEMPLATE_URL}") private String sendMessageTemplateUrl; //小程序唯一凭证,即 AppID @Value("${wxConfig.appid}") private String appId; //小程序唯一凭证密钥,即 AppSecret @Value("${wxConfig.secret}") private String secret; //模板id @Value("${wxConfig.templateId}") private String templateId;}
发送请求工具类
package com.andon.wxPush.utils;import com.alibaba.fastjson.JSON;import com.alibaba.fastjson.JSONObject;import com.andon.wxPush.template.HttpResult;import com.google.common.base.Splitter;import lombok.extern.slf4j.Slf4j;import org.apache.commons.lang.StringEscapeUtils;import org.apache.commons.lang3.StringUtils;import org.apache.http.HttpEntity;import org.apache.http.HttpResponse;import org.apache.http.NameValuePair;import org.apache.http.client.config.RequestConfig;import org.apache.http.client.entity.UrlEncodedFormEntity;import org.apache.http.client.methods.CloseableHttpResponse;import org.apache.http.client.methods.HttpGet;import org.apache.http.client.methods.HttpPost;import org.apache.http.entity.ContentType;import org.apache.http.entity.StringEntity;import org.apache.http.impl.client.CloseableHttpClient;import org.apache.http.impl.client.HttpClientBuilder;import org.apache.http.impl.client.HttpClients;import org.apache.http.impl.client.LaxRedirectStrategy;import org.apache.http.message.BasicNameValuePair;import org.apache.http.util.EntityUtils;import org.springframework.stereotype.Component;import java.io.IOException;import java.util.Map;import static org.apache.tomcat.util.file.ConfigFileLoader.getURI;@Component@Slf4jpublic class HttpUtils { public HttpResult stringPostJson(String path, String content) throws Exception{ return stringPost(path, null, content, "utf-8", "utf-8", "application/json"); } public HttpResult stringPost(String path, Map<String,String> headerMap, String content, String contentencode, String encode, String contentType) throws Exception{ StringEntity entity = new StringEntity(content, contentencode); entity.setContentType(contentType); return post(path, headerMap, entity, encode); } private HttpResult post(String path, Map<String,String> headerMap, HttpEntity entity, String encode){ HttpResult httpResult = new HttpResult(); CloseableHttpClient httpClient = null; CloseableHttpResponse response = null; try{ HttpPost httpPost = new HttpPost(getURI(path)); LaxRedirectStrategy redirectStrategy = new LaxRedirectStrategy(); httpClient = HttpClientBuilder.create().setRedirectStrategy(redirectStrategy).build(); RequestConfig requestConfig = RequestConfig.custom() .setSocketTimeout(120000) .setConnectTimeout(120000) .setConnectionRequestTimeout(120000) .setCircularRedirectsAllowed(true) .setRedirectsEnabled(true) .setMaxRedirects(5) .build(); httpPost.setConfig(requestConfig);// httpPost.setHeader("User-Agent", header); if(headerMap != null && headerMap.size() > 0){ for(String name:headerMap.keySet()) { httpPost.addHeader(name, headerMap.get(name)); } } httpPost.setEntity(entity); response = httpClient.execute(httpPost); httpResult.setStatus(response.getStatusLine().getStatusCode()); if(httpResult.getStatus() == 200){ HttpEntity resEntity = response.getEntity(); httpResult.setBody(EntityUtils.toString(resEntity, encode)); } }catch(Exception ex){ log.error("post请求出错", ex); }finally{ try{ if(response != null){ response.close(); } if(httpClient != null){ httpClient.close(); } }catch(Exception ex) { log.error("post请求关闭资源出错", ex); } } return httpResult; } public JSONObject stringGetJson(String url) { JSONObject jsonObject = null; //通过HttpClients.createDefault()获取到CloseableHttpClient对象 CloseableHttpClient httpclient = HttpClients.createDefault(); //创建HttpGet 对象,参数为请求的地址 HttpGet method = new HttpGet(url); try { //执行方法请求接口得到响应 HttpResponse response = httpclient.execute(method); //解析响应数据 String content = EntityUtils.toString(response.getEntity(), "UTF-8"); //转为json格式 jsonObject = JSON.parseObject(content); EntityUtils.consume(response.getEntity());//完全消耗 } catch (Exception e) { log.error("get请求出错", e); e.printStackTrace(); } finally { try { //释放连接 method.releaseConnection(); httpclient.close(); } catch (IOException e) { e.printStackTrace(); } } return jsonObject; }}
json处理工具类
package com.andon.wxPush.utils;import com.fasterxml.jackson.databind.DeserializationFeature;import com.fasterxml.jackson.databind.ObjectMapper;import lombok.extern.slf4j.Slf4j;import org.apache.commons.lang3.StringUtils;import java.text.SimpleDateFormat;@Slf4jpublic class JsonUtils { private static ObjectMapper json; static { json = new ObjectMapper(); json.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")); json.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); } public static String ObjectToString(Object object) { try { return (json.writeValueAsString(object)); } catch (Exception e) { log.error("序列化为JSON字符串出错",e); } return null; } public static <T> T getObject(String jsonString, Class<T> clazz) { if (StringUtils.isEmpty(jsonString)) return null; try { return json.readValue(jsonString, clazz); } catch (Exception e) { log.error("将JSON字符串转化为Map出错",e); return null; } }}
消息模板
package com.andon.wxPush.template;import lombok.Data;import java.util.Map;import java.util.TreeMap;@Datapublic class WxSendMessageTemplate { private String touser; private String template_id; private String url; private Map<String,String> miniprogram; private TreeMap<String, TreeMap<String, String>> 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; }}
结果模板
package com.andon.wxPush.template;import lombok.Data;@Datapublic class HttpResult { private Integer status = 601; private String body;}
消息推送
package com.andon.wxPush.service.impl;import com.alibaba.fastjson.JSON;import com.alibaba.fastjson.JSONObject;import com.andon.util.DateUtil;import com.andon.wxPush.conf.WxConfiguration;import com.andon.wxPush.service.WxSendMsgService;import com.andon.wxPush.template.HttpResult;import com.andon.wxPush.template.WxSendMessageTemplate;import com.andon.wxPush.utils.HttpUtils;import com.andon.wxPush.utils.JsonUtils;import com.google.common.collect.Maps;import lombok.extern.slf4j.Slf4j;import org.apache.commons.lang.StringUtils;import org.springframework.beans.factory.annotation.Value;import org.springframework.http.HttpStatus;import org.springframework.http.ResponseEntity;import org.springframework.stereotype.Service;import javax.annotation.Resource;import java.io.UnsupportedEncodingException;import java.net.URLEncoder;import java.text.SimpleDateFormat;import java.time.LocalDate;import java.util.Date;import java.util.Map;import java.util.TreeMap;@Service@Slf4jpublic class WxSendMsgServiceImpl implements WxSendMsgService { //凭证有效时间 private static final String KEY_EXPIRES_IN = "expiresIn"; // private static final String KEY_TOKEN = "accessToken"; //拼接缓存前缀 private static final String ACCESS_TOKEN_KEY_PREFIX = "AccessToken_"; @Resource private WxConfiguration wxConfiguration; @Resource private HttpUtils httpUtils; private static Map<String, Map<String, Object>> accessTokenMap = Maps.newConcurrentMap(); @Override public JSONObject noticeTemplate(JSONObject templateMsg) { // 模版ID String templateId = wxConfiguration.getTemplateId(); TreeMap<String, TreeMap<String, String>> params = new TreeMap<>(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); //微信公众号根据具体模板参数组装 params.put("keyword4", WxSendMessageTemplate.item(sdf.format(new Date()), "#000000")); //告警时间 params.put("keyword3", WxSendMessageTemplate.item(templateMsg.getString("andonName"), "#000000")); //报警类型 params.put("keyword1", WxSendMessageTemplate.item(templateMsg.getString("equipmentName"), "#000000")); //设备名称 params.put("keyword2", WxSendMessageTemplate.item(templateMsg.getString("installPlace"), "#000000")); //设备位置 params.put("first", WxSendMessageTemplate.item(templateMsg.getString("andonContent"), "#000000")); //告警内容 WxSendMessageTemplate wxTemplateMsg = new WxSendMessageTemplate(); // 模版ID wxTemplateMsg.setTemplate_id(templateId); // openId wxTemplateMsg.setTouser(templateMsg.getString("openId")); // 关键字赋值 wxTemplateMsg.setData(params); String data = JsonUtils.ObjectToString(wxTemplateMsg); System.out.println(data); return this.handleSendMsgLog(data); } //捕获异常 private JSONObject handleSendMsgLog(String data) { JSONObject resultDto = new JSONObject(); try { resultDto = this.sendTemplateMsg(data); System.out.println("handleSendMsgLog:" + resultDto.toJSONString()); } catch (Exception exception) { log.error("发送微信模版失败", exception); } // TODO 可以记录一下发送记录的日志 return resultDto; } //发送模板 public JSONObject sendTemplateMsg(String data) throws Exception { // 获取token JSONObject accessTokenRes = this.getAccessToken(); String accessToken = accessTokenRes.getString("access_token"); // 发送消息 HttpResult httpResult = null; try { httpResult = httpUtils.stringPostJson(wxConfiguration.getSendMessageTemplateUrl() + accessToken, data); } catch (Exception e) { e.printStackTrace(); } return JSON.parseObject(httpResult.getBody()); } public JSONObject getAccessToken() { //拼接获取缓存key String key = ACCESS_TOKEN_KEY_PREFIX.concat(wxConfiguration.getAppId()).concat(wxConfiguration.getSecret()); Map<String, Object> accessTokenJson = accessTokenMap.get(key); if (accessTokenJson != null) { long expiresIn = (Long) accessTokenJson.get(KEY_EXPIRES_IN); if (System.currentTimeMillis() < expiresIn) { return (JSONObject) accessTokenJson.get(KEY_TOKEN); } accessTokenMap.remove(key); } synchronized (this) { accessTokenJson = accessTokenMap.get(key); // 多线程环境下,其他线程可能已经获得最新corpAccessToken,直接返回 if (accessTokenJson != null) { return (JSONObject) accessTokenJson.get(KEY_TOKEN); } String url = String.format(wxConfiguration.getWxAccessTokenUrl(), wxConfiguration.getAppId(), wxConfiguration.getSecret()); JSONObject resJson = httpUtils.stringGetJson(url); if (resJson != null && StringUtils.isNotEmpty(resJson.getString("access_token"))) { accessTokenJson = this.seAccessToken(resJson); } if (accessTokenJson == null) { try { throw new Exception(); } catch (Exception e) { log.error("设置access_token缓存失败"); e.printStackTrace(); } } //放入缓存中 accessTokenMap.put(key, accessTokenJson); JSONObject accessTokenJsonRes = (JSONObject) accessTokenJson.get(KEY_TOKEN); if (accessTokenJsonRes == null) { try { throw new Exception(); } catch (Exception e) { log.error("获取access_token错误"); e.printStackTrace(); } } return accessTokenJsonRes; } } //设置access_token private Map<String, Object> seAccessToken(JSONObject jsonObject) { Map<String, Object> token = Maps.newHashMap(); // 减去3分钟,以免过时 token.put(KEY_EXPIRES_IN, (jsonObject.getLongValue("expires_in") - 3 * 60) * 1000 + System.currentTimeMillis()); token.put(KEY_TOKEN, jsonObject); return token; }}
效果
发送测试
微信公众号接收效果
来源地址:https://blog.csdn.net/weixin_45956838/article/details/129925772
免责声明:
① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。
② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341