微信推送公眾號(hào)模版消息通知 -- machao
業(yè)務(wù)場(chǎng)景:
某一天,小明開車出去接老婆的機(jī),路上不小心碰撞到了其他的車,于是他通過車險(xiǎn)小程序把事故現(xiàn)場(chǎng)上傳給理賠人員處理.
過了一段時(shí)間,經(jīng)過理賠人員的審核后,覺得賠付沒有問題,需要發(fā)送案件進(jìn)度通知給小明.
這時(shí)候,理賠人員問他們的小程序的開發(fā)人員,我們應(yīng)該選擇那種消息通知策略比較合理呢?
消息通知策略:
1. 小程序模版消息
模板推送位置:服務(wù)通知
模板下發(fā)條件:用戶本人在微信體系內(nèi)與頁(yè)面有交互行為后觸發(fā)锈拨,包括: 支付,提交表單
模板跳轉(zhuǎn)能力:點(diǎn)擊查看詳情,僅能跳轉(zhuǎn) 下發(fā)模板的小程序的各個(gè)頁(yè)面
2. 公眾號(hào)模版消息
模板推送位置:公眾號(hào)
模版下發(fā)能力: 服務(wù)端可主動(dòng)下發(fā)消息
模板跳轉(zhuǎn)能力:點(diǎn)擊查看詳情,能跳轉(zhuǎn) 下發(fā)模板消息的公眾號(hào)是綁定關(guān)聯(lián)關(guān)系的小程序的各個(gè)頁(yè)面
3. 小程序模版消息效果與公眾號(hào)模版消息效果對(duì)比:
分析:
通過小程序模版消息推送, 消息會(huì)被推送到“服務(wù)通知”欄目中,“服務(wù)通知”欄目中會(huì)存在很多其他小程序的推送,這樣看起來會(huì)很雜亂.
另外,小程序模版消息推送前提, 必須要在用戶本人在微信體系內(nèi)與頁(yè)面有交互行為后觸發(fā), 即不能延遲推送. 當(dāng)然也可以通過保存交互過程中的fromid達(dá)到延遲推送的效果, 但是這個(gè)消息只能推送給觸發(fā)這個(gè)交互行為的用戶, 這樣會(huì)導(dǎo)致另外一個(gè)問題: 假如理賠人員除了希望推送案件進(jìn)度通知給小明外,還希望把消息推送給上級(jí)領(lǐng)導(dǎo),那這個(gè)就做不到了.
因此,推送公眾號(hào)模版消息才是最便捷的策略.
如何實(shí)現(xiàn)微信小程序推送公眾號(hào)模版消息?
首先我們需要清楚以下幾點(diǎn):
1. 消息發(fā)給誰? -- who?
2. 消息怎么發(fā)? -- how?
3. 消息內(nèi)容發(fā)什么? -- what?
對(duì)于第一點(diǎn)(who?)
由于我們的主體是小程序, 因此我們沒有辦法直接通過用戶的小程序openid直接進(jìn)行公眾號(hào)模版消息的發(fā)送, 這時(shí)候上面的unionId機(jī)制就起了至關(guān)重要的作用了.
我們可以通過將小程序和公眾號(hào)掛載在同一個(gè)微信開放平臺(tái)帳號(hào)下, 通過unionId進(jìn)行邏輯關(guān)聯(lián), 這時(shí)候我們就可以通過用戶的小程序openid找到用戶的公眾號(hào)openid,進(jìn)而進(jìn)行公眾號(hào)的模版消息推送.
由于開發(fā)者經(jīng)常有需在多個(gè)平臺(tái)(移動(dòng)應(yīng)用曼月、網(wǎng)站、公眾帳號(hào))之間共通用戶帳號(hào)厦幅,統(tǒng)一帳號(hào)體系的需求野芒,微信開放平臺(tái)提供了UnionID機(jī)制。
換句話說,同一用戶档玻,對(duì)同一個(gè)微信開放平臺(tái)帳號(hào)下的不同應(yīng)用软棺,UnionID是相同的县貌。
微信開放平臺(tái)帳號(hào)下的不同應(yīng)用包括: 小程序, 公眾號(hào), 移動(dòng)應(yīng)用等.
對(duì)于第二點(diǎn)(how?)
小程序的模版消息有自己的推送模版消息的api接口, 公眾號(hào)的模版消息也有自己的推送模版消息的api接口. 為了便捷管理優(yōu)化, 于是微信api推出一個(gè)“統(tǒng)一服務(wù)消息”接口, 我們接下來也將使用這個(gè)接口進(jìn)行消息的下發(fā).
對(duì)于第三點(diǎn)(what?)
這里當(dāng)然就是發(fā)送模版消息啦, 需要到 公眾號(hào)-模版消息 新增自己的模版.
思路:
- 將小程序和公眾號(hào)掛載在同一個(gè)開發(fā)平臺(tái)賬號(hào)下,這樣就能多個(gè)不同的主體共用一個(gè)相同的UnionID啦.
- 在用戶登錄小程序時(shí), 通過小程序的獲取用戶信息接口, 得到并保存用戶的小程序openid + unionId.
- 同時(shí)引導(dǎo)用戶在小程序內(nèi)通過公眾號(hào)網(wǎng)頁(yè)授權(quán), 從而得到并保存用戶的公眾號(hào)openid + unionId.
- 小程序用戶通過unionId關(guān)聯(lián)找到公眾號(hào)openid,進(jìn)而下發(fā)模版消息.
準(zhǔn)備:
- 公眾號(hào)的appid+ secret
小程序的appid+ secret- 前往微信開放平臺(tái)-管理中心,將公眾號(hào),小程序都綁定在同一個(gè)開放平臺(tái)中
- 認(rèn)證服務(wù)器為公眾號(hào)開發(fā)者[只需認(rèn)證一次]
a. 進(jìn)入微信公眾平臺(tái),登錄公眾號(hào)賬號(hào)
b. 開發(fā)-基本配置, 填寫服務(wù)器配置
c. 驗(yàn)證服務(wù)器地址的有效性
公眾號(hào)開發(fā)者認(rèn)證服務(wù)器詳細(xì)步驟
認(rèn)證服務(wù)器為公眾號(hào)開發(fā)者詳細(xì)步驟:
- 添加開發(fā)者服務(wù)器認(rèn)證接口
@Api("微信公眾號(hào)開發(fā)者API接口") @Controller @RequestMapping("/wxPublic/serverApi") public class WxServerApi { private Logger LOGGER = LoggerFactory.getLogger(this.getClass()); //在 微信公眾號(hào)-服務(wù)器配置 中配置, 對(duì)應(yīng)“令牌(Token)” @Value("${wx.public.wxServerAuthenToken}") String wxServerAuthenToken; /** * 開發(fā)者認(rèn)證接口 * 微信api用于認(rèn)證 服務(wù)器 是否為可用服務(wù)器 * @param signature * @param timestamp * @param nonce * @param echostr * @return */ @ApiOperation(value = "開發(fā)者認(rèn)證接口", notes = "開發(fā)者認(rèn)證接口") @GetMapping("/wxAuthenConfig") @ResponseBody public String wxAuthenConfig(String signature, String timestamp, String nonce, String echostr) { LOGGER.info("開發(fā)者認(rèn)證接口 - 開始簽名驗(yàn)證:" + " PARAM VAL: >>>" + signature + "\t" + timestamp + "\t" + nonce + "\t" + echostr); if (StringUtils.isNotEmpty(signature) && StringUtils.isNotEmpty(timestamp) && StringUtils.isNotEmpty(nonce) && StringUtils.isNotEmpty(echostr)) { String sTempStr = ""; try { sTempStr = SHA1.getSHA1(timestamp, nonce, wxServerAuthenToken, ""); } catch (Exception e) { e.printStackTrace(); } if (StringUtils.isNotEmpty(sTempStr) && StringUtils.equals(signature, sTempStr)) { LOGGER.info("開發(fā)者認(rèn)證接口 - 開始簽名驗(yàn)證 - 驗(yàn)證成功:-----------:" + sTempStr); return echostr; } else { LOGGER.info("開發(fā)者認(rèn)證接口 - 開始簽名驗(yàn)證 - 驗(yàn)證失斄兆础:-----------:00000"); return "-1"; } } else { LOGGER.info("開發(fā)者認(rèn)證接口 - 開始簽名驗(yàn)證 - 驗(yàn)證失斆圊巍:-----------:11111"); return "-1"; } } }
/** * SHA1 class * * 計(jì)算公眾平臺(tái)的消息簽名接口. */ public class SHA1 { /** * 用SHA1算法生成安全簽名 * @param token 票據(jù) * @param timestamp 時(shí)間戳 * @param nonce 隨機(jī)字符串 * @param encrypt 密文 * @return 安全簽名 * @throws AesException */ public static String getSHA1(String token, String timestamp, String nonce, String encrypt) throws AesException { try { String[] array = new String[] { token, timestamp, nonce, encrypt }; StringBuffer sb = new StringBuffer(); // 字符串排序 Arrays.sort(array); for (int i = 0; i < 4; i++) { sb.append(array[i]); } String str = sb.toString(); // SHA1簽名生成 MessageDigest md = MessageDigest.getInstance("SHA-1"); md.update(str.getBytes()); byte[] digest = md.digest(); StringBuffer hexstr = new StringBuffer(); String shaHex = ""; for (int i = 0; i < digest.length; i++) { shaHex = Integer.toHexString(digest[i] & 0xFF); if (shaHex.length() < 2) { hexstr.append(0); } hexstr.append(shaHex); } return hexstr.toString(); } catch (Exception e) { e.printStackTrace(); throw new AesException(AesException.ComputeSignatureError); } } }
- 在開發(fā)-基本配置, 填寫服務(wù)器配置
“服務(wù)器地址(URL)” 填寫 認(rèn)證接口的地址
“令牌(Token)” 填寫 代碼中的wxServerAuthenToken的值
"消息加解密方式" 選擇 明文- 提交修改后, 微信會(huì)請(qǐng)求訪問接口, 這樣認(rèn)證就完成了.
在用戶登錄小程序時(shí), 通過小程序的獲取用戶信息接口,獲得用戶的小程序openid + unionId
步驟
- 小程序通過wx.login()獲得 code
wx.login({ success(res){ let code=res.code }, fail(res){ that.$message("微信登錄失敗锐极,請(qǐng)退出重試笙僚!") } })
- 服務(wù)后端通過code + 小程序appid + 小程序secret, 請(qǐng)求 auth.code2Session API接口得到用戶的小程序openid + unionId
/** * 微信小程序openid工作類 * @author mac * */ @SuppressWarnings("deprecation") @Component public class WxOpenidUtil { private Logger LOGGER = >LoggerFactory.getLogger(this.getClass()); final String openidUrl = "https://api.weixin.qq.com/sns/jscode2session"; @Value("${wx.applet.appid}") String wxAppid; @Value("${wx.applet.secret}") String wxSecret; @Value("${wx.applet.grantType}") String grantType; @SuppressWarnings("resource") public WxOpenIdPo GetWxOpenId(String wxcode) { WxOpenIdPo info = null; // 微信API接口 String url = openidUrl + "?appid=" + wxAppid + "&secret=" + wxSecret + "&js_code=" + wxcode + "&grant_type=" + grantType + ""; HttpGet request = new HttpGet(url); HttpResponse response = null; try { HttpClient client = new DefaultHttpClient(); response = client.execute(request); if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { String strResult = EntityUtils.toString(response.getEntity()); if (!StringUtils.isEmpty(strResult)) { info = JacksonUtil.defaultInstance().json2pojo(strResult, WxOpenIdPo.class); } } } catch (IOException e) { e.printStackTrace(); LOGGER.error("根據(jù)wxcode獲取微信小程序openid失敗: wxcode(" + wxcode + ")"); } return info; } }
- 將獲取到的 用戶的小程序openid + unionId保存
引導(dǎo)用戶在小程序內(nèi)通過公眾號(hào)網(wǎng)頁(yè)授權(quán), 從而得到并保存用戶的公眾號(hào)openid + unionId.
步驟:
- 服務(wù)后端添加“公眾號(hào)授權(quán)網(wǎng)頁(yè)url”接口, 返回“公眾號(hào)授權(quán)網(wǎng)頁(yè)url“
- 服務(wù)后端添加回調(diào)接口, 用于微信api回調(diào), 接口會(huì)攜帶用戶的公眾號(hào)code(劃重點(diǎn))
- 小程序內(nèi)通過web-view標(biāo)簽訪問“公眾號(hào)授權(quán)網(wǎng)頁(yè)url”接口返回的url, 引導(dǎo)用戶公眾號(hào)授權(quán), 授權(quán)通過后, 微信api會(huì)回調(diào)“公眾號(hào)授權(quán)網(wǎng)頁(yè)url”提供的回調(diào)接口
- 在回調(diào)接口接口中, 通過微信api攜帶的用戶公眾號(hào)code, 獲取網(wǎng)頁(yè)授權(quán)access_token+用戶的公眾號(hào)openid
(我們這里
把 [網(wǎng)頁(yè)授權(quán)access_token] 叫為 [Oauth2AccessToken],
把 [基礎(chǔ)支持中的access_token] 叫為 [PublicAccessToken] ,
這樣大家容易理解
)- 這時(shí)候,我們已經(jīng)得到用戶的公眾號(hào)openid了,但是沒有用戶的unionId,沒有辦法與用戶的小程序openid進(jìn)行邏輯關(guān)聯(lián). 于是我們還要通過 Oauth2AccessToken + openid 拉取用戶信息, 這個(gè)接口會(huì)返回用戶的unionId,這時(shí)公眾號(hào)與小程序的用戶正式邏輯關(guān)聯(lián)起來了.
貼代碼啦:
- 1.“公眾號(hào)授權(quán)網(wǎng)頁(yè)url”接口
@Value("${wx.public.appid}") String publicAppid; @Value("${wx.public.secret}") String publicSecret; @Value("${yd.baseHost}") String baseHost;//應(yīng)用鏈接 @Value("${wx.public.clientReceiveOpenidUrl}") String clientReceiveOpenidUrl;//接口路徑 @Value("${wx.public.clientState}") String clientState;//state, 用于回調(diào)接口檢驗(yàn) /** * 返回 微信公眾號(hào)獲取openid的url (并添加回調(diào)路徑) * @throws UnsupportedEncodingException */ @ApiOperation(value = "返回 微信公眾號(hào)獲取openid的url (并添加回調(diào)路徑)", notes = "返回 微信公眾號(hào)獲取openid的url (并添加回調(diào)路徑)") @PostMapping("/return2OpenidUrl") @ResponseBody public String return2OpenidUrl() throws UnsupportedEncodingException{ LOGGER.info("微信公眾號(hào)OpenId接口 - 返回 微信公眾號(hào)獲取openid的url - 開始"); StringBuffer encodeUrl = new StringBuffer(300); encodeUrl.append(baseHost + clientReceiveOpenidUrl); String redirectUrl = URLEncoder.encode(encodeUrl.toString(), "utf-8"); StringBuffer sb = new StringBuffer(); sb.append("https://open.weixin.qq.com/connect/oauth2/authorize?appid="); sb.append(publicAppid); sb.append("&redirect_uri="); sb.append(redirectUrl); sb.append("&response_type=code&scope=snsapi_userinfo"); sb.append("&state="); sb.append(clientState); LOGGER.info("微信公眾號(hào)OpenId接口 - 返回 微信公眾號(hào)獲取openid的url - 結(jié)束 - redirectUrl("+sb.toString()+")"); return sb.toString(); }
- 服務(wù)后端添加回調(diào)接口
@Autowired Oauth2AccessTokenBuilder oauth2AccessTokenBuilder; @Autowired SnsapiUserinfoUtil snsapiUserinfoUtil; @Autowired UserMapper userMapper; @Value("${wx.public.appid}") String publicAppid; @Value("${wx.public.secret}") String publicSecret; @Value("${yd.baseHost}") String baseHost; @Value("${wx.public.clientReceiveOpenidUrl}") String clientReceiveOpenidUrl; @Value("${wx.public.clientState}") String clientState; /** * 初始化 公眾號(hào)用戶基本信息(openid+unionid) 接口 * - 微信公眾號(hào)回調(diào)的接口 [攜帶上 code=CODE&state=STATE] * @param request * @param response * @return */ @GetMapping("/initPublicUserInfo") @ResponseBody public void initPublicUserInfo(@RequestParam("code") String code,@RequestParam("state") String state){ LOGGER.info("微信公眾號(hào)OpenId接口 - 初始化 公眾號(hào)用戶基本信息(openid+unionid) 接口 - 開始 - code("+code+") state("+state+")"); //校驗(yàn) 重定向攜帶的state參數(shù) if(!clientState.equals(state)){ LOGGER.info("微信公眾號(hào)OpenId接口 - 初始化 公眾號(hào)用戶基本信息(openid+unionid) 接口 - state不匹配 - 結(jié)束"); return; } //通過code換取網(wǎng)頁(yè)授權(quán)access_token + openid //這里通過code換取的是一個(gè)特殊的網(wǎng)頁(yè)授權(quán)access_token,與基礎(chǔ)支持中的access_token(該access_token用于調(diào)用其他接口)不同 if(StringUtils.isEmpty(code)){ LOGGER.info("微信公眾號(hào)OpenId接口 - 初始化 公眾號(hào)用戶基本信息(openid+unionid) 接口 - code為空 - 結(jié)束"); return; } Oauth2AccessToken oauth2AccessToken = new Oauth2AccessToken(oauth2AccessTokenBuilder, code); String accessToken = oauth2AccessToken.getAccess_token(); String openid = oauth2AccessToken.getOpenid(); LOGGER.info("微信公眾號(hào)OpenId接口 - 初始化 公眾號(hào)用戶基本信息(openid+unionid) 接口 - 通過code換取網(wǎng)頁(yè)授權(quán)access_token + openid - accessToken("+accessToken+") openid("+openid+")"); //根據(jù) access_token + openid 拉取用戶信息(需scope為 snsapi_userinfo) //得到 unionid + 用戶基本信息 SnsapiUserinfo snsapiUserinfo = snsapiUserinfoUtil.getSnsapiUserinfo(accessToken, openid); String unionid = snsapiUserinfo.getUnionid(); //保存 公眾號(hào)openid User usr = new User(); usr.setUnionid(unionid); User one = userMapper.selectOne(usr); if(one != null){ one.setPublicAccountOpenid(openid); userMapper.updateByPrimaryKey(one); } }
網(wǎng)頁(yè)授權(quán)access_token(Oauth2AccessToken)
/** * 網(wǎng)頁(yè)OAuth2授權(quán) 接口返回值 * @author mac * */ public class Oauth2AccessToken { //網(wǎng)頁(yè)授權(quán)接口調(diào)用憑證,注意:此access_token與基礎(chǔ)支持的access_token不同 private String access_token; //用戶唯一標(biāo)識(shí),請(qǐng)注意灵再,在未關(guān)注公眾號(hào)時(shí)肋层,用戶訪問公眾號(hào)的網(wǎng)頁(yè),也會(huì)產(chǎn)生一個(gè)用戶和公眾號(hào)唯一的OpenID private String openid; public String getAccess_token() { return access_token; } public String getOpenid() { return openid; } /** * 使用構(gòu)建類 構(gòu)建 */ public Oauth2AccessToken(Oauth2AccessTokenBuilder builder, String code){ Oauth2AccessTokenBuilder build = builder.build(code); this.access_token = build.accessToken; this.openid = build.openid; } }
網(wǎng)頁(yè)OAuth2授權(quán) 構(gòu)建類(Oauth2AccessTokenBuilder)
/** * 網(wǎng)頁(yè)OAuth2授權(quán) 構(gòu)建類 * * @author mac * */ @SuppressWarnings("deprecation") @Component public class Oauth2AccessTokenBuilder { private Logger LOGGER = LoggerFactory.getLogger(this.getClass()); @Value("${wx.public.appid}") String publicAppid; @Value("${wx.public.secret}") String publicSecret; //網(wǎng)頁(yè)OAuth2授權(quán) url final private String oauth2AccessTokenUrl = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code"; //刷新OAuth2授權(quán) url //final private String oauth2RefreshTokenUrl = "https://api.weixin.qq.com/sns/oauth2/refresh_token?appid=APPID&grant_type=refresh_token&refresh_token=REFRESH_TOKEN"; // 網(wǎng)頁(yè)授權(quán)接口調(diào)用憑證,注意:此access_token與基礎(chǔ)支持的access_token不同 public String accessToken; // 用戶刷新access_token //public String refreshToken; // 用戶唯一標(biāo)識(shí)翎迁,請(qǐng)注意栋猖,在未關(guān)注公眾號(hào)時(shí),用戶訪問公眾號(hào)的網(wǎng)頁(yè)鸳兽,也會(huì)產(chǎn)生一個(gè)用戶和公眾號(hào)唯一的OpenID public String openid; /** * 構(gòu)建 oauth2AccessTokenBuilder * @param code 微信用戶code * @return */ public Oauth2AccessTokenBuilder build(String code) { //不需要重復(fù)刷新網(wǎng)頁(yè)授權(quán)token //直接根據(jù)code請(qǐng)求得到token+openid即可 Oauth2AccessTokenRespPo po = getAccessToken(code); if(po != null && po.errcode != null && !StringUtils.isEmpty(po.errmsg)){ return null; } this.accessToken = po.access_token; this.openid = po.openid; return this; } /** * 獲取網(wǎng)頁(yè)授權(quán)的access_token + openID * * @return */ @SuppressWarnings({ "resource" }) private Oauth2AccessTokenRespPo getAccessToken(String code) { // 獲取小程序全局唯一后臺(tái)接口調(diào)用憑據(jù) 接口 String url = oauth2AccessTokenUrl; url = url.replace("APPID", publicAppid); url = url.replace("SECRET", publicSecret); url = url.replace("CODE", code); LOGGER.info("網(wǎng)頁(yè)OAuth2授權(quán) 構(gòu)建類 - 獲取網(wǎng)頁(yè)授權(quán)的access_token + openID - url: "+ url); HttpGet request = new HttpGet(url); HttpResponse response = null; Oauth2AccessTokenRespPo po = null; try { HttpClient client = new DefaultHttpClient(); response = client.execute(request); if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { String strResult = EntityUtils.toString(response.getEntity()); if (!StringUtils.isEmpty(strResult)) { LOGGER.info("網(wǎng)頁(yè)OAuth2授權(quán) 構(gòu)建類 - 獲取網(wǎng)頁(yè)授權(quán)的access_token + openID - 返回: " + strResult); po = JacksonUtil.defaultInstance().json2pojo(strResult, Oauth2AccessTokenRespPo.class); } } } catch (IOException e) { e.printStackTrace(); LOGGER.error("獲取網(wǎng)頁(yè)授權(quán)的access_token + openID失敗!!!"); } return po; } /** * 接口返回參數(shù)PO * @author mac * */ @JsonIgnoreProperties(ignoreUnknown = true) public static class Oauth2AccessTokenRespPo{ //網(wǎng)頁(yè)授權(quán)接口調(diào)用憑證,注意:此access_token與基礎(chǔ)支持的access_token不同 private String access_token; //access_token接口調(diào)用憑證超時(shí)時(shí)間掂铐,單位(秒) private Long expires_in; //用戶刷新access_token private String refresh_token; //用戶唯一標(biāo)識(shí),請(qǐng)注意揍异,在未關(guān)注公眾號(hào)時(shí)全陨,用戶訪問公眾號(hào)的網(wǎng)頁(yè),也會(huì)產(chǎn)生一個(gè)用戶和公眾號(hào)唯一的OpenID private String openid; //用戶授權(quán)的作用域衷掷,使用逗號(hào)(,)分隔 private String scope; //錯(cuò)誤碼 - 錯(cuò)誤時(shí)返回 private Integer errcode; //錯(cuò)誤信息 - 錯(cuò)誤時(shí)返回 private String errmsg; public Oauth2AccessTokenRespPo(){} public String getAccess_token() { return access_token; } public void setAccess_token(String access_token) { this.access_token = access_token; } public Long getExpires_in() { return expires_in; } public void setExpires_in(Long expires_in) { this.expires_in = expires_in; } public String getRefresh_token() { return refresh_token; } public void setRefresh_token(String refresh_token) { this.refresh_token = refresh_token; } public String getOpenid() { return openid; } public void setOpenid(String openid) { this.openid = openid; } public String getScope() { return scope; } public void setScope(String scope) { this.scope = scope; } public Integer getErrcode() { return errcode; } public void setErrcode(Integer errcode) { this.errcode = errcode; } public String getErrmsg() { return errmsg; } public void setErrmsg(String errmsg) { this.errmsg = errmsg; } } }
微信公眾號(hào)用戶信息Po(SnsapiUserinfo)
/** * 微信公眾號(hào)用戶信息 * @author mac * */ @JsonIgnoreProperties(ignoreUnknown = true) public class SnsapiUserinfo { //用戶的唯一標(biāo)識(shí) private String openid; //用戶昵稱 private String nickname; //用戶的性別辱姨,值為1時(shí)是男性,值為2時(shí)是女性戚嗅,值為0時(shí)是未知 private Integer sex; //用戶個(gè)人資料填寫的省份 private String province; //普通用戶個(gè)人資料填寫的城市 private String city; //國(guó)家雨涛,如中國(guó)為CN private String country; //用戶頭像,最后一個(gè)數(shù)值代表正方形頭像大信嘲(有0替久、46、64躏尉、96蚯根、132數(shù)值可選,0代表640*640正方形頭像)胀糜,用戶沒有頭像時(shí)該項(xiàng)為空颅拦。若用戶更換頭像,原有頭像URL將失效教藻。 private String headimgurl; //用戶特權(quán)信息距帅,json 數(shù)組,如微信沃卡用戶為(chinaunicom) private List<String> privilege; //只有在用戶將公眾號(hào)綁定到微信開放平臺(tái)帳號(hào)后括堤,才會(huì)出現(xiàn)該字段碌秸。 private String unionid; private String language; private String errcode; private String errmsg; public String getOpenid() { return openid; } public void setOpenid(String openid) { this.openid = openid; } public String getNickname() { return nickname; } public void setNickname(String nickname) { this.nickname = nickname; } public Integer getSex() { return sex; } public void setSex(Integer sex) { this.sex = sex; } public String getProvince() { return province; } public void setProvince(String province) { this.province = province; } public String getCity() { return city; } public void setCity(String city) { this.city = city; } public String getCountry() { return country; } public void setCountry(String country) { this.country = country; } public String getHeadimgurl() { return headimgurl; } public void setHeadimgurl(String headimgurl) { this.headimgurl = headimgurl; } public List<String> getPrivilege() { return privilege; } public void setPrivilege(List<String> privilege) { this.privilege = privilege; } public String getUnionid() { return unionid; } public void setUnionid(String unionid) { this.unionid = unionid; } public String getErrcode() { return errcode; } public void setErrcode(String errcode) { this.errcode = errcode; } public String getErrmsg() { return errmsg; } public void setErrmsg(String errmsg) { this.errmsg = errmsg; } public String getLanguage() { return language; } public void setLanguage(String language) { this.language = language; } }
微信公眾號(hào)用戶信息 工具類(SnsapiUserinfoUtil)
/** * 微信公眾號(hào)用戶信息 工具類 * @author mac * */ @SuppressWarnings("deprecation") @Component public class SnsapiUserinfoUtil { private Logger LOGGER = LoggerFactory.getLogger(this.getClass()); final String openidUrl = "https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN"; /** * 獲得 微信公眾號(hào)用戶信息 * @param accessToken * @param openid * @return */ @SuppressWarnings({ "resource" }) public SnsapiUserinfo getSnsapiUserinfo(String accessToken, String openid) { SnsapiUserinfo info = null; if(StringUtils.isEmpty(accessToken) || StringUtils.isEmpty(openid)){ LOGGER.info("微信公眾號(hào)用戶信息 工具類 - 獲得 微信公眾號(hào)用戶信息 - accessToken||openid 為空"); return null; } // 微信API接口 String url = openidUrl; url = url.replace("ACCESS_TOKEN", accessToken); url = url.replace("OPENID", openid); LOGGER.info("微信公眾號(hào)用戶信息 工具類 - 獲得 微信公眾號(hào)用戶信息 - url: " + url); HttpGet request = new HttpGet(url); HttpResponse response = null; try { HttpClient client = new DefaultHttpClient(); response = client.execute(request); if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { String strResult = EntityUtils.toString(response.getEntity()); if (!StringUtils.isEmpty(strResult)) { info = JacksonUtil.defaultInstance().json2pojo(strResult, SnsapiUserinfo.class); } } } catch (IOException e) { e.printStackTrace(); LOGGER.info("微信公眾號(hào)用戶信息 工具類 - 獲得 微信公眾號(hào)用戶信息 失敗"); } return info; } }
- 小程序內(nèi)通過web-view標(biāo)簽訪問“公眾號(hào)授權(quán)網(wǎng)頁(yè)url”接口返回的url, 引導(dǎo)用戶公眾號(hào)授權(quán)
貼代碼啦:
html: <div v-if="needPublicAccountAuth"> <web-view :src="publicAccountAuthUrl" bindmessage="getMessage"></web-view> </div> js: //進(jìn)入公眾號(hào)授權(quán)頁(yè)面 authPublicAccount(){ let that = this let param={} wx.request({ url: api.wxPublic.return2OpenidUrl, data: param, method: 'POST', header: { 'content-type': 'application/json' }, success:function (res) { //打開webview that.$data.needPublicAccountAuth=true that.$data.publicAccountAuthUrl = res.data; //console.log(res.data) setTimeout(() => { console.log("公眾號(hào)授權(quán)完成") that.$data.needPublicAccountAuth=false that.$data.publicAccountAuthUrl = '' }, 5000); }, fail:function (res) { wx.showToast({ title:"獲得公眾號(hào)授權(quán)URL錯(cuò)誤", icon:"none", duration:2000 }) } }) }
這樣,我們的小程序用戶與公眾號(hào)用戶就邏輯關(guān)聯(lián)起來啦
小程序用戶通過unionId關(guān)聯(lián)找到公眾號(hào)openid,進(jìn)而下發(fā)模版消息.
步驟:
- 通過用戶的unionId關(guān)聯(lián)得到用戶公眾號(hào)openid
通過用戶公眾號(hào)openid,下發(fā)模版消息(這里使用“統(tǒng)一服務(wù)消息接口”)
貼代碼啦:
- 發(fā)送微信統(tǒng)一服務(wù)消息工具類
***代碼中的ReqPo, RespPo, WeappTemplateMsg等類, 是為了方便與接口交互而封裝的(接口使用的是json傳輸) ***
參考: 下發(fā)小程序和公眾號(hào)統(tǒng)一的服務(wù)消息/** * 發(fā)送微信統(tǒng)一服務(wù)消息工具類 * * @author mac * */ @SuppressWarnings("deprecation") @Component public class WechatMessageUtil { private Logger LOGGER = LoggerFactory.getLogger(this.getClass()); // 發(fā)送模版消息接口 final String sendMessageUrl = "https://api.weixin.qq.com/cgi-bin/message/wxopen/template/uniform_send?access_token=ACCESS_TOKEN"; @Value("${wx.applet.appid}") String wxAppid; @Value("${wx.applet.secret}") String wxSecret; @Value("${wx.public.appid}") String publicAppid; @Value("${wx.public.secret}") String publicSecret; @Autowired WechatAccessTokenBuilder builder; /** * 發(fā)送模版信息 * * @param reqPo * @return * @throws JsonProcessingException */ @SuppressWarnings({ "resource" }) public RespPo uniformSend(ReqPo reqPo) throws JsonProcessingException { RespPo respPo = null; LOGGER.info("發(fā)送公眾號(hào)模版信息 start"); String accessToken = reqPo.getAccess_token(); String reqPoJson = JacksonUtil.defaultInstance().pojo2json(reqPo); // 獲取小程序全局唯一后臺(tái)接口調(diào)用憑據(jù) 接口 String url = sendMessageUrl.replace("ACCESS_TOKEN", accessToken); HttpPost request = new HttpPost(url); request.setEntity(new StringEntity(reqPoJson, ContentType.DEFAULT_TEXT.withCharset(Charset.defaultCharset()))); request.setHeader(new BasicHeader("Content-Type", "application/json")); HttpResponse response = null; try { HttpClient client = new DefaultHttpClient(); response = client.execute(request); if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { String strResult = EntityUtils.toString(response.getEntity()); if (!StringUtils.isEmpty(strResult)) { LOGGER.info("發(fā)送公眾號(hào)模版信息 - 返回: " + strResult); respPo = JacksonUtil.defaultInstance().json2pojo(strResult, RespPo.class); } } } catch (IOException e) { e.printStackTrace(); LOGGER.error("發(fā)送公眾號(hào)模版信息失敗!!!"); } return respPo; } /** * 組裝發(fā)送模版信息所需的請(qǐng)求參數(shù) * * @return * @throws Exception */ public ReqPo packageReqPo(TmplBase base, String openId) throws Exception { ReqPo reqPo = new ReqPo(); MpTemplateMsg mpTemplateMsg = new MpTemplateMsg(); String accessToken = getAccessToken(); if (StringUtils.isEmpty(accessToken)) { LOGGER.info("公眾號(hào)主體無法獲得有效的accessToken"); return null; } reqPo.setTouser(openId);//用戶openid绍移,可以是小程序的openid,也可以是mp_template_msg.appid對(duì)應(yīng)的公眾號(hào)的openid reqPo.setAccess_token(accessToken); reqPo.setMp_template_msg(mpTemplateMsg); reqPo.setWeapp_template_msg(null); mpTemplateMsg.setAppid(publicAppid);// 公眾號(hào)appid mpTemplateMsg.setTemplate_id(base.tmplID);// template_id mpTemplateMsg.setData(base.getData());// 公眾號(hào)模板消息的數(shù)據(jù) mpTemplateMsg.setMiniprogram(base.miniprogram);// 公眾號(hào)模板消息所要跳轉(zhuǎn)的小程序 mpTemplateMsg.setUrl(null);// 公眾號(hào)模板消息所要跳轉(zhuǎn)的url return reqPo; } /** * 獲得access_token * * @return * @throws Exception */ private String getAccessToken() { WechatAccessToken token = new WechatAccessToken(builder); return token.getAccessToken(); } }
/** * 下發(fā)小程序和公眾號(hào)統(tǒng)一的服務(wù)消息 請(qǐng)求PO * https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/uniform-message/uniformMessage.send.html * @author mac * */ public class ReqPo { //接口調(diào)用憑證 private String access_token; //用戶openid讥电,可以是小程序的openid登夫,也可以是mp_template_msg.appid對(duì)應(yīng)的公眾號(hào)的openid private String touser; //小程序模板消息相關(guān)的信息,可以參考小程序模板消息接口; 有此節(jié)點(diǎn)則優(yōu)先發(fā)送小程序模板消息 private WeappTemplateMsg weapp_template_msg; //公眾號(hào)模板消息相關(guān)的信息允趟,可以參考公眾號(hào)模板消息接口;有此節(jié)點(diǎn)并且沒有weapp_template_msg節(jié)點(diǎn)時(shí)鸦致,發(fā)送公眾號(hào)模板消息 private MpTemplateMsg mp_template_msg; }
/** * 返回值 PO * @author mac * */ public class RespPo { //錯(cuò)誤碼 0-成功 private int errcode; //錯(cuò)誤信息 private String errmsg; }
/** * 小程序模板消息相關(guān)的信息 * @author mac * */ public class WeappTemplateMsg { //小程序模板ID private String template_id; //小程序頁(yè)面路徑 private String page; //小程序模板消息formid private String form_id; //小程序模板數(shù)據(jù) private String data; //小程序模板放大關(guān)鍵詞 private String emphasis_keyword; }
/** * 公眾號(hào)模板消息相關(guān)的信息 * @author mac * */ public class MpTemplateMsg { //公眾號(hào)appid潮剪,要求與小程序有綁定且同主體 private String appid; //公眾號(hào)模板id private String template_id; //公眾號(hào)模板消息所要跳轉(zhuǎn)的url private String url; //公眾號(hào)模板消息所要跳轉(zhuǎn)的小程序,小程序的必須與公眾號(hào)具有綁定關(guān)系 private Miniprogram miniprogram; //公眾號(hào)模板消息的數(shù)據(jù) private Map<String, DataValue> data; }
/** * 公眾號(hào)模版消息 - 小程序跳轉(zhuǎn)PO * @author mac * */ public class Miniprogram { private String appid="wx2a8dd229406ae6ae"; private String pagepath; }
- 微信小程序WechatAccessToken生成器
由于 發(fā)送微信統(tǒng)一服務(wù)消息 , 需要使用到微信小程序的accessToken, 因此這里加上微信小程序WechatAccessToken生成器/** * 小程序access_token * @author mac * */ public class WechatAccessToken { private String accessToken; private Date builderTime; /** * 獲得 accessToken * @return */ public String getAccessToken() { return accessToken; } /** * 獲得 builderTime * @return */ public Date getBuilderTime() { return builderTime; } /** * 使用 內(nèi)部構(gòu)建類 構(gòu)建 * @param builder * @throws Exception */ public WechatAccessToken(WechatAccessTokenBuilder builder){ WechatAccessTokenBuilder build = builder.build(); if(!StringUtils.isEmpty(build.accessToken)){ this.accessToken = build.accessToken; this.builderTime = build.builderTime; } } }
/** * 微信小程序WechatAccessToken生成器 * @author mac * */ @Component @SuppressWarnings("deprecation") public class WechatAccessTokenBuilder { private Logger LOGGER = LoggerFactory.getLogger(this.getClass()); @Value("${wx.applet.appid}") String appletAppid; @Value("${wx.applet.secret}") String appletSecret; //獲取access_token接口 final String getTokenUrl = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET"; public String accessToken; public Date builderTime; /** * 生成 WechatAccessToken * @return * @throws Exception */ public WechatAccessTokenBuilder build() { if(this.accessToken == null || this.builderTime == null){ this.accessToken = getAccessToken(); this.builderTime = new Date(); return this; } //比較時(shí)間 Calendar cal = Calendar.getInstance(); cal.setTime(this.builderTime); cal.add(Calendar.HOUR, 1);//往后1小時(shí) //cal.add(Calendar.MINUTE, 5);//往后5分鐘 Date outTimeDate = cal.getTime(); Date nowDate = new Date(); if(outTimeDate.after(nowDate)){ return this; }else{ this.accessToken = getAccessToken(); this.builderTime = new Date(); return this; } } /** * 獲取access_token * * @return */ @SuppressWarnings({ "resource"}) private String getAccessToken() { // 獲取小程序全局唯一后臺(tái)接口調(diào)用憑據(jù) 接口 String url = getTokenUrl.replace("APPID", appletAppid); url = url.replace("APPSECRET", appletSecret); HttpGet request = new HttpGet(url); HttpResponse response = null; AccessTokenRespPo po = null; try { HttpClient client = new DefaultHttpClient(); response = client.execute(request); if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { String strResult = EntityUtils.toString(response.getEntity()); if (!StringUtils.isEmpty(strResult)) { LOGGER.info("WechatAccessTokenBuilder.獲取access_token.返回: " + strResult); po = JacksonUtil.defaultInstance().json2pojo(strResult, AccessTokenRespPo.class); } } } catch (IOException e) { e.printStackTrace(); LOGGER.error("獲取微信小程序的access_token失敗!!!"); } if (po != null && !"".equals(po.getAccess_token())) { return po.getAccess_token(); } else { return null; } } /** * 接口返回參數(shù)PO * @author mac * */ @JsonIgnoreProperties(ignoreUnknown = true) public static class AccessTokenRespPo { public String access_token; public int expires_in; public int errcode; public String errmsg; public AccessTokenRespPo(){} public String getAccess_token() { return access_token; } public void setAccess_token(String access_token) { this.access_token = access_token; } public int getExpires_in() { return expires_in; } public void setExpires_in(int expires_in) { this.expires_in = expires_in; } public int getErrcode() { return errcode; } public void setErrcode(int errcode) { this.errcode = errcode; } public String getErrmsg() { return errmsg; } public void setErrmsg(String errmsg) { this.errmsg = errmsg; } } }
- 發(fā)送模版消息Demo
/** * 公眾號(hào)模版類 * @author mac * */ public abstract class TmplBase { /** * 模版id */ public String tmplID; /** * 模版參數(shù) */ public Map<String, DataValue> data; /** * 公總號(hào)跳轉(zhuǎn)小程序 */ public Miniprogram miniprogram; }
demo:
@Autowired WechatMessageUtil wechatMessageUtil; //小程序appid @Value("${wx.applet.appid}") String appletAppid; //公眾號(hào)appid @Value("${wx.public.appid}") String publicAppid; @Test public void test() throws Exception { //點(diǎn)擊模版消息跳轉(zhuǎn)參數(shù) Miniprogram miniprogram = new Miniprogram(); miniprogram.setAppid(appletAppid);//appid需要線上有版本才能匹配對(duì)應(yīng)的路徑 miniprogram.setPagepath("pages/Login");//要跳轉(zhuǎn)的路徑+參數(shù) //模版消息 TestTmpl testTmpl = new TestTmpl(new DataValue("1"), new DataValue("2"), new DataValue("3"), new DataValue("4"), miniprogram); ReqPo reqPo = wechatMessageUtil.packageReqPo4Customer(testTmpl, publicAppid); RespPo uniformSend = wechatMessageUtil.uniformSend(reqPo); System.out.println(uniformSend); }
終于碼完字啦,呼~
看到這里,如果對(duì)你們有用,請(qǐng)鼓勵(lì)一下筆者吧
第一次碼字, 有錯(cuò)誤請(qǐng)見諒~~