微信订阅通知实战
前言
一、微信訂閱通知是什么?
二、使用步驟
1.開(kāi)通訂閱通知服務(wù)
2.獲取access_token
我們微信業(yè)務(wù)和內(nèi)部業(yè)務(wù)項(xiàng)目是分離的,所以只在微信項(xiàng)目中實(shí)現(xiàn)接口供內(nèi)部項(xiàng)目調(diào)用即可.具體的接口如下
接口完成后,在業(yè)務(wù)項(xiàng)目中調(diào)用接口即可.
總結(jié)
前言
截止至2021-11-2, 微信的訂閱通知還在灰度測(cè)試當(dāng)中,傳說(shuō)微信的模板消息即將下線,所以采用微信的訂閱通知來(lái)發(fā)送通知.??本文為實(shí)戰(zhàn)中的微信訂閱通知功能開(kāi)發(fā)記錄, 目前已經(jīng)在線上測(cè)試使用,僅供參考.
一、微信訂閱通知是什么?
模板消息被濫用, 很多用戶被騷擾, 微信為了管理,引入訂閱通知, 分為一次性訂閱,和長(zhǎng)期訂閱兩種,一次性訂閱: 訂閱一次,能給用戶發(fā)送一條消息.? 長(zhǎng)期訂閱: 訂閱一次, 目前來(lái)看無(wú)限次發(fā)送消息. 本文為長(zhǎng)期訂閱案例, 一次性訂閱對(duì)于我們來(lái)說(shuō)沒(méi)有價(jià)值.
二、使用步驟
1.開(kāi)通訂閱通知服務(wù)
根據(jù)微信文檔開(kāi)通即可, 需要提供公司的 <<資質(zhì)授權(quán)文件>>,審核幾個(gè)小時(shí)一般就會(huì)有結(jié)果,通過(guò)后只能選取微信提供的模板,長(zhǎng)期訂閱的模板很少,就幾個(gè),不一定符合業(yè)務(wù)需求,只能湊合用.
2.獲取access_token
? ? ?必須操作!
? ? ?2.??AccessToken 類接收 "獲取access_token接口"
@Data public class AccessToken {private String accessToken;private Long expiresIn; }? ? ? 3.WeixinToken? 內(nèi)存中保存access_token?
/*** @Title: WeixinToken* @Description: 持有微信Access_token的對(duì)象* @date 2021-10-25 11:09*/ public class WeixinToken {public static String token;}? ? ? ? 4.WeixinAccessTokenUtil 獲取Access_token
@Component public class WeixinAccessTokenUtil {private Logger logger = LoggerFactory.getLogger(WeixinAccessTokenUtil.class);public AccessToken getToken(String appid, String appSecrect) {AccessToken token = new AccessToken();String url = "https://api.weixin.qq.com/cgi-bin/token" + "?grant_type=client_credential&appid=" + appid+ "&secret=" + appSecrect;HttpClient httpClient = HttpClientBuilder.create().build();HttpGet httpGet = new HttpGet(url);ResponseHandler<String> responseHandler = new BasicResponseHandler();try {String response = httpClient.execute(httpGet,responseHandler);JsonObject returnData = new JsonParser().parse(response).getAsJsonObject();if(returnData != null){token.setAccessToken(returnData.get("access_token").getAsString());token.setExpiresIn(returnData.get("expires_in").getAsLong());}} catch (IOException e) {token = null;e.printStackTrace();logger.error("系統(tǒng)獲取access_token出錯(cuò)了!");}return token;}}5.開(kāi)啟spring定時(shí)任務(wù)? 加入@EnableScheduling 注解
@SpringBootApplication @ServletComponentScan @EnableScheduling public class WeixinApplication {public static void main(String[] args) {SpringApplication.run(WeixinApplication.class, args);}}6.定時(shí)任務(wù)獲取access_token (倆小時(shí)失效,必須倆小時(shí)之內(nèi)更新access_token)
package com.qqrw.wx.service.impl;import com.qqrw.wx.entity.WeixinToken; import com.qqrw.wx.util.WeixinAccessTokenUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component;/*** @Title: AccessTokenTask* @Description: 獲取微信access_token 定時(shí)任務(wù)* @date 2021-10-25 11:10*/ @Component public class AccessTokenTask {private Logger logger = LoggerFactory.getLogger(AccessTokenTask.class);@Autowiredprivate WeixinAccessTokenUtil weixinAccessTokenUtil;@Value("${wechat.appId}")private String appId;@Value("${wechat.appSecret}")private String appSecret;@Scheduled(initialDelay = 1000, fixedDelay = 7000 * 1000)public void getWeixinAccessToken() {try {String token = weixinAccessTokenUtil.getToken(appId, appSecret).getAccessToken();WeixinToken.token = token;logger.info("獲取到的微信accessToken為" + token);} catch (Exception e) {logger.error("獲取微信adcessToken出錯(cuò),信息如下");e.printStackTrace();}}@Scheduled(initialDelay = 3000, fixedDelay = 1000 * 1000)public void testWeixinAccessT() {logger.info(WeixinToken.token);} }7.其中http發(fā)送用的是httpclient, 如果沒(méi)遺漏的話,就不需要其他依賴了.
<dependency><groupId>org.apache.httpcomponents</groupId><artifactId>httpclient</artifactId><version>${httpClient.version}</version></dependency>我們微信業(yè)務(wù)和內(nèi)部業(yè)務(wù)項(xiàng)目是分離的,所以只在微信項(xiàng)目中實(shí)現(xiàn)接口供內(nèi)部項(xiàng)目調(diào)用.具體的接口如下
1.?WeixinMsg 接口用來(lái)接收業(yè)務(wù)相關(guān)數(shù)據(jù)
@Data public class WeixinMsg {private String userCode;private String templateId;private Map<String,Object> map;public String getUserCode() {return userCode;}public void setUserCode(String userCode) {this.userCode = userCode;}public String getTemplateId() {return templateId;}public void setTemplateId(String templateId) {this.templateId = templateId;}public Map<String, Object> getMap() {return map;}public void setMap(Map<String, Object> map) {this.map = map;} }2.Controller中 , 其中R是自定義的返回結(jié)果實(shí)體,替換為你自己的返回結(jié)果Result即可!
//發(fā)送微信訂閱消息@CrossOrigin(maxAge = 3600, origins = "*")@PostMapping("/subscribe/sendWeixinMsg")public R sendWeixinMsg(@RequestBody WeixinMsg weixinMsg) {Assert.isTrue(null != weixinMsg.getUserCode() && null != weixinMsg.getTemplateId() && null != weixinMsg.getMap(), "參數(shù)不正確");return weixinSendMsgService.send(weixinMsg);}3.接口
public interface WeixinSendMsgService {R send(WeixinMsg weixinMsg);}4.service代碼,
UserWechatMapper 和 其中String openId = userWechatMapper.queryOpenIdByUserCode(userCode);是我們有專門(mén)頁(yè)面,實(shí)現(xiàn)微信openId和業(yè)務(wù)之間Id的綁定.?可根據(jù)實(shí)際情況, 只要知道微信用戶的openId即可,具體獲取openId的功能,參考官方文檔!
ImSmsWeixinMapper 和下方的private方法是業(yè)務(wù)代碼,記錄發(fā)送的日志,屏蔽或改成你自己的業(yè)務(wù)代碼即可.
@Service("weixinSendMsgService") public class WeixinSendMsgServiceImpl implements WeixinSendMsgService {private Logger logger = LoggerFactory.getLogger(WeixinSendMsgServiceImpl.class);@Resourceprivate UserWechatMapper userWechatMapper;@Resourceprivate ImSmsWeixinMapper imSmsWeixinMapper;@Overridepublic R send(WeixinMsg weixinMsg) {System.out.println(weixinMsg);Date now = new Date();String userCode = weixinMsg.getUserCode();String openId = userWechatMapper.queryOpenIdByUserCode(userCode);String templateId = weixinMsg.getTemplateId();Map<String, Object> map = weixinMsg.getMap();Map<String, Map<String, Object>> data = new HashMap<>();if (StringUtils.isBlank(openId)) {logger.info("對(duì)用戶{}發(fā)送模板{}消息失敗,用戶未綁定無(wú)法獲取openId!", userCode, templateId);imSmsWeixinMapper.insertSelective(getFailLog(now, userCode, openId, templateId, data, "用戶未綁定,無(wú)法發(fā)送模板消息"));return R.fail("微信消息發(fā)送失敗,用戶未綁定");}for (Map.Entry<String, Object> entry : map.entrySet()) {HashMap<String, Object> one = new HashMap<>();one.put("value", entry.getValue());data.put(entry.getKey(), one);}System.out.println(openId);System.out.println(data);String url = "https://api.weixin.qq.com/cgi-bin/message/subscribe/bizsend?access_token=" + WeixinToken.token;try {HttpClient httpClient = HttpClientBuilder.create().build();HttpPost httpPost = new HttpPost(url);httpPost.setHeader("Accept", "application/json");httpPost.setHeader("Content-Type", "application/json");String charSet = "UTF-8";Map<String, Object> params = new HashMap<>();params.put("access_token", WeixinToken.token);params.put("touser", openId);params.put("template_id", templateId);params.put("data", data);StringEntity entity = new StringEntity(JsonUtils.toJson(params), charSet);httpPost.setEntity(entity);ResponseHandler<String> responseHandler = new BasicResponseHandler();String response = httpClient.execute(httpPost, responseHandler);JsonObject returnData = new JsonParser().parse(response).getAsJsonObject();int errcode = returnData.get("errcode").getAsInt();String errmsg = returnData.get("errmsg").getAsString();if ("ok".equals(errmsg)) {imSmsWeixinMapper.insertSelective(getSuccessLog(now, userCode, openId, templateId, data));logger.info("對(duì)用戶{}發(fā)送模板{}消息成功!", userCode, templateId);} else {logger.error("對(duì)用戶{}發(fā)送模板{}消息異常!", userCode, templateId);logger.error("發(fā)送數(shù)據(jù)為:[{}]!", data);logger.error("錯(cuò)誤碼:[{}]!", errcode);logger.error("錯(cuò)誤信息:[{}]!", errmsg);imSmsWeixinMapper.insertSelective(getFailLog(now, userCode, openId, templateId, data, errmsg));return R.fail("微信消息發(fā)送失敗,錯(cuò)誤碼" + errcode);}} catch (IOException e) {e.printStackTrace();logger.error("對(duì)用戶{}發(fā)送模板{}消息異常!", userCode, templateId);logger.error("發(fā)送數(shù)據(jù)為:[{}]!", data);imSmsWeixinMapper.insertSelective(getFailLog(now, userCode, openId, templateId, data, "程序發(fā)送出現(xiàn)了異常,請(qǐng)檢查" + e.getMessage()));}return R.ok();}private ImSmsWeixin getSuccessLog(Date now, String userCode, String openId, String templateId, Map<String, Map<String, Object>> data) {ImSmsWeixin imSmsWeixin = genLog(now, userCode, openId, templateId, data);imSmsWeixin.setState(1);return imSmsWeixin;}private ImSmsWeixin getFailLog(Date now, String userCode, String openId, String templateId, Map<String, Map<String, Object>> data, String errorMsg) {ImSmsWeixin imSmsWeixin = genLog(now, userCode, openId, templateId, data);imSmsWeixin.setState(2);imSmsWeixin.setErrorMsg(errorMsg);return imSmsWeixin;}private ImSmsWeixin genLog(Date now, String userCode, String openId, String templateId, Map<String, Map<String, Object>> data) {ImSmsWeixin imSmsWeixin = new ImSmsWeixin();imSmsWeixin.setUserCode(userCode);imSmsWeixin.setOpenId(openId);imSmsWeixin.setTemplateId(templateId);imSmsWeixin.setData(JsonUtils.toJson(data));imSmsWeixin.setSendTime(now);return imSmsWeixin;}}5. 一個(gè)問(wèn)題:??開(kāi)發(fā)過(guò)程中遇到? ? ?接口提示{"errcode":47001,"errmsg":"data.......}的問(wèn)題, 檢查過(guò)json, 網(wǎng)上說(shuō)單引號(hào)雙引號(hào)的問(wèn)題,? ?我測(cè)試不是引號(hào)的問(wèn)題,只要json格式正確即可.? 主要是發(fā)送的方式問(wèn)題.? 開(kāi)始的時(shí)候用的是UrlEncodeEntity這種方式發(fā)送的, 導(dǎo)致出現(xiàn)了47001問(wèn)題, 上面代碼發(fā)送方式完全沒(méi)問(wèn)題
6. 我在官網(wǎng)上找了,沒(méi)找到可以提供測(cè)試的接口和方法, 只有線上測(cè)試了,? 發(fā)送一個(gè)圖文消息,? 在文章末尾插入訂閱消息插件 如圖
?7.用戶在文章中點(diǎn)擊訂閱通知,就能選擇相關(guān)訂閱, 訂閱后,再點(diǎn)擊訂閱就不再?gòu)棾鲇嗛喛蛄?
接口完成后,在業(yè)務(wù)項(xiàng)目中調(diào)用接口即可.
public class WeixinMsgUtil {private static HttpClient httpClient = new DefaultHttpClient();public static void sendWeixinSms(String userCode, String templateId, Map data) {HashMap<String, Object> params = new HashMap<>();params.put("userCode",userCode);params.put("templateId",templateId);params.put("map",data);String url = "http://123.456.12.12:8080/api/subscribe/sendWeixinMsg";HttpPost httpPost = new HttpPost(url);httpPost.setHeader("Accept", "application/json");httpPost.setHeader("Content-Type", "application/json");String charSet = "UTF-8";StringEntity entity = new StringEntity(JsonUtil.toJson(params), charSet);httpPost.setEntity(entity);ResponseHandler<String> responseHandler = new BasicResponseHandler();try {httpClient.execute(httpPost, responseHandler);} catch (IOException e) {e.printStackTrace();}}}總結(jié)
- 上一篇: am3358——GPMC——参考网上驱动
- 下一篇: Mixly 触摸开关的使用