微信公眾平臺(tái)的ACCESS_TOKEN和模板消息

最近項(xiàng)目用到了微信公眾平臺(tái)的模板消息痕届,發(fā)現(xiàn)實(shí)現(xiàn)過程并不是一帆風(fēng)順的级乍,所以這里做一下筆記。

閱讀微信公眾平臺(tái)技術(shù)文檔 相關(guān)章節(jié)之后竣贪,了解到要實(shí)現(xiàn)模板消息军洼,與實(shí)際開發(fā)相關(guān)的有以下幾點(diǎn): (具體還是查看平臺(tái)技術(shù)文檔)

1.獲取ACCESS_TOKEN
關(guān)于access_token巩螃,有幾點(diǎn)需要說明:
1.access_token是公眾號(hào)的全局唯一接口調(diào)用憑據(jù),公眾號(hào)調(diào)用各接口時(shí)都需使用access_token.
2.目前微信公眾平臺(tái)提供了獲取access_token的接口,接口同時(shí)會(huì)返回access_token的有效期,目前為7200s;
3.重復(fù)調(diào)用會(huì)導(dǎo)致上次獲取的access_token失效,但是為了保證客戶端的平滑過渡,微信公眾平臺(tái)會(huì)保證老的access_token會(huì)有5分鐘的存活期匕争。
詳見 獲取access_token

2.獲取模板列表
得到access_token之后獲取模板列表就相當(dāng)簡(jiǎn)單了避乏,直接rest接口調(diào)用即可得到模板列表。
注意,模板消息接口中有提示模板參數(shù)的格式:{{xxx.DATA}}汗捡,千萬(wàn)注意這里括號(hào)之間是不能有空格的淑际!文檔中的Demo中帶了空格,誤導(dǎo)人了....

http請(qǐng)求方式:GET
https://api.weixin.qq.com/cgi-bin/template/get_all_private_template?access_token=ACCESS_TOKEN

3.發(fā)送模板消息
發(fā)送模板消息也比較簡(jiǎn)單,選擇一個(gè)模板發(fā)送給指定的用戶(open_id)

http請(qǐng)求方式: POST
https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=ACCESS_TOKEN
請(qǐng)求信息都在body中

4. 測(cè)試
平臺(tái)提供了測(cè)試號(hào)扇住,接口測(cè)試號(hào)申請(qǐng)
開發(fā)需要申請(qǐng)測(cè)試號(hào),獲得appID和appsecret,然后配置測(cè)試模板等.

代碼實(shí)現(xiàn)

代碼實(shí)現(xiàn)部分其實(shí)主要關(guān)注的是access_token的獲取邏輯.
考慮到access_token的特性春缕,以及我們獲取access_token的邏輯所在中心是分布式部署的,所以我這邊獲取access_token的邏輯如下所述:
1.優(yōu)先從redis中獲取艘蹋;
2.redis中不存在,則控制一個(gè)線程X去調(diào)微信接口查詢access_token并存入redis中锄贼;
3.其他線程,如果老的access_token可用則直接使用老的access_token;如果的老的access_token不可用,則等待線程X獲取access_token之后的通知即可女阀。

具體可以看下面代碼宅荤,注釋寫的很詳細(xì)了...
代碼放到 github 上了...不對(duì)的地方還請(qǐng)指正。

/**
 *  獲取微信的AccessToken
 *
 *  微信提供了一個(gè)rest接口浸策,根據(jù)appid和secret 更新并返回 AccessToken冯键;
 *  微信的這個(gè)AccessToken有幾點(diǎn)需要注意:
 *  1.每次調(diào)用該接口,會(huì)返回新的AccessToken庸汗,老的AccessToken會(huì)有5分鐘的存活期
 *  2.微信端該接口返回的AccessToken有效期目前為7200s
 * Created by xh on 2019/4/25.
 */
@Slf4j
public class WeChatAccessTokenUtil {

    private static RestTemplate restTemplate;
    private static WeChatProperties weChatProperties;
    private static RedissonClient redissonClient;

    private volatile static String accessToken;
    private volatile static boolean callFlag = true;
    private static CountDownLatch latch = new CountDownLatch(1);

    private static boolean initFlag = false;

    private static final String LOCK_KEY = "lock-AccessToken";
    private static final String ACCESSTOKEN = "ACCESSTOKEN";
    private static final String ACCESSTOKEN_LASTUPDATE = "ACCESSTOKEN_LASTUPDATE";

    static {
        restTemplate = SpringContext.getBean(RestTemplate.class);
        weChatProperties = SpringContext.getBean(WeChatProperties.class);
        redissonClient = SpringContext.getBean(RedissonClient.class);
    }

    /**
     *  獲取AccessToken
     * @return String
     */
    public static String getAccessToken() throws Exception {
        log.info("WeChatAccessTokenUtil.getAccessToken start");
        //優(yōu)先從redis中獲取
        RBucket<String> accessTokenCache = redissonClient.getBucket(ACCESSTOKEN);
        //redis中存在惫确,返回redis中的ACCESS_TOKEN;
        // 同時(shí)如果accessToken未初始化蚯舱,則將redis中的ACCESS_TOKEN值寫入共享變量accessToken  這個(gè)不需要考慮并發(fā)問題改化,重復(fù)設(shè)置也沒事
        if (accessTokenCache != null && !StringUtils.isEmpty(accessTokenCache.get())) {
            if (!initFlag) {
                accessToken = accessTokenCache.get();
                initFlag = true;
            }
            return accessTokenCache.get();
        }
        //redis中不存在,那么就需要讓一個(gè)線程A去調(diào)用微信接口查詢accessToken并刷入redis;
        //其他線程使用老的accessToken(即共享變量accessToken),如果存在的話;  如果老的accessToken不存在則等待線程A的通知枉昏;
        //老的accessToken有5分鐘的存活期陈肛,所以這里使用一個(gè)緩存key并設(shè)置失效時(shí)間來(lái)控制老的accessToken是否可用,具體方式是:
        //在將accessToken刷入redis時(shí),同時(shí)刷入另一個(gè)key:ACCESSTOKEN_LASTUPDATE,并控制失效時(shí)間比accessToken多五分鐘兄裂,當(dāng)緩存失效時(shí)句旱,我們判斷緩存ACCESSTOKEN_LASTUPDATE是否存在,如果不存在則表示老的accessToken失效不可用了懦窘,這時(shí)候清空共享變量accessToken.
        else {
            Lock lock = redissonClient.getLock(LOCK_KEY);

            //所有線程循環(huán)嘗試獲取分布式鎖,只有一個(gè)線程X 會(huì)獲得鎖前翎,獲得鎖的線程X 首先設(shè)置計(jì)數(shù)器latch為1,然后判斷是否存在緩存ACCESSTOKEN_LASTUPDATE畅涂,不存在表示老的accessToken已經(jīng)過了5分鐘的存活期港华,那么就清空共享變量accessToken;
            //然后線程X 設(shè)置共享變量callFlag = false午衰,那么其他線程會(huì)退出while循環(huán)立宜;
            //對(duì)于線程X冒萄,因?yàn)橐紤]分布式的場(chǎng)景,所以首選再次去redis中查詢accessToken橙数,查詢到則更新共享變量accessToken尊流;查詢不到則調(diào)rest接口獲取accessToken;
            //對(duì)于其他退出循環(huán)的線程,如果共享變量accessToken有值灯帮,表示還在存活期內(nèi)崖技,則使用老的accessToken返回給業(yè)務(wù)使用;如果accessToken為空钟哥,則需要等待線程X 的通知迎献;
            boolean innerFlag = true;  //線程私有的變量, 獲得鎖的線程通過修改這個(gè)標(biāo)志退出循環(huán)
            //callFlag 線程共享的變量,用于當(dāng)一個(gè)線程獲取鎖時(shí)腻贰,通知其他線程跳出循環(huán)
            while (innerFlag && callFlag) {
                if (lock.tryLock()) {  //默認(rèn)30000ms
                    try {
                        latch = new CountDownLatch(1);

                        //判斷老的accessToken是否可用
                        if (redissonClient.getBucket(ACCESSTOKEN_LASTUPDATE).get() == null) {
                            accessToken = null;
                        }
                        callFlag = false;

                        //獲取鎖之后吁恍,首先查詢r(jià)edis ,如果redis中存在則不再需要調(diào)用微信接口了  這里是考慮分布式的場(chǎng)景
                        accessTokenCache = redissonClient.getBucket(ACCESSTOKEN);
                        if (accessTokenCache != null && !StringUtils.isEmpty(accessTokenCache.get())) {
                            accessToken = accessTokenCache.get();
                        }
                        else {
                            //調(diào)用微信的接口查詢ACCESS_TOKEN
                            WeChatAccessTokenResp accessTokenResp =  getAccessTokenFromWechat();
                            accessToken = accessTokenResp.getAccessToken();
                            Long expire = accessTokenResp.getExpiresIn();
                            if (expire > 200) {
                                expire -= 200;
                            }

                            //批量更新緩存
                            RBatch batch = redissonClient.createBatch();
                            batch.getBucket(ACCESSTOKEN).setAsync(accessToken, expire, TimeUnit.SECONDS);
                            batch.getBucket(ACCESSTOKEN_LASTUPDATE).setAsync(System.currentTimeMillis(), expire + 300, TimeUnit.SECONDS);
                            batch.execute();
                        }
                    }
                    finally {
                        //防止因?yàn)榫W(wǎng)絡(luò)等問題導(dǎo)致失敗播演,無(wú)法通知其他線程 所以這里放在finally塊里
                        //共享變量accessToken已經(jīng)設(shè)置新值為可用的accessToken冀瓦,通知其他線程
                        latch.countDown();
                        innerFlag = false;
                        //還原
                        callFlag = true;
                        lock.unlock();
                    }
                }
            }

            if (StringUtils.isEmpty(accessToken)) {
                try {
                    latch.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        log.info("WeChatAccessTokenUtil.getAccessToken end");
        return accessToken;
    }


    public static WeChatAccessTokenResp getAccessTokenFromWechat() throws Exception {
    //調(diào)rest接口查詢AccessToken,這里就不展示了
    }
}

測(cè)試

關(guān)于代碼中提供的2個(gè)rest接口,這里也做了測(cè)試:

測(cè)試1:獲取微信模板消息接口

可以看到返回了我在測(cè)試賬號(hào)中配置的模板消息

user@CentOS7.3[/xxx/xxx]$curl http://10.45.18.85:8080/luoluocaihong/wechat/template -X GET -H 'Content-Type:application/json'
[{"templateId":"NTGqIwifErpioNS1m5bX6M1DtdQAusj0q4bZMFBmRw8","title":"物流模板","primaryIndustry":"","deputyIndustry":"","content":"物流狀態(tài):{{state.DATA}}\\n\\n發(fā)貨時(shí)間: {{deliverTime.DATA}}","example":""},{"templateId":"0RywEuCbkh9tMlaZyaCxyYE2uIrjxMlZYAaF4cODLEs","title":"Test","primaryIndustry":"","deputyIndustry":"","content":"{{result.DATA}}\\n\\n領(lǐng)獎(jiǎng)金額:{{withdrawMoney.DATA}}\\n領(lǐng)獎(jiǎng) 時(shí)間: {{withdrawTime.DATA}}\\n銀行信息:{{cardInfo.DATA}}\\n到賬時(shí)間: {{arrivedTime.DATA}}\\n{{remark.DATA}}","example":""},{"templateId":"NfcHMyxMr3hPTRmDFa8cCRtkKYkPoAOFGd5SmO3d-RA","title":"Hello","primaryIndustry":"","deputyIndustry":"","content":"您好写烤,{{name.DATA}}","example":""}]user@CentOS7.3[/xxx/xxx]$
測(cè)試賬號(hào)配置的模板消息.png
測(cè)試2:發(fā)送具體的模板消息

Demo中我是直接寫死了發(fā)送的消息格式的,實(shí)際項(xiàng)目中是解析存入表中的
然后可以看到微信測(cè)試公眾號(hào)也將消息推送給我了

user@CentOS7.3[/xxx/xxx]$curl http://10.45.18.85:8080/luoluocaihong/wechat/send -X POST -H 'Content-Type:application/json'
781640493582254081user@CentOS7.3[/xxx/xxx]$
微信測(cè)試公眾號(hào)給我推送的消息.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末翼闽,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子洲炊,更是在濱河造成了極大的恐慌肄程,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,542評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件选浑,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡玄叠,警方通過查閱死者的電腦和手機(jī)古徒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,596評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)读恃,“玉大人隧膘,你說我怎么就攤上這事∷卤梗” “怎么了疹吃?”我有些...
    開封第一講書人閱讀 158,021評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)西雀。 經(jīng)常有香客問我萨驶,道長(zhǎng),這世上最難降的妖魔是什么艇肴? 我笑而不...
    開封第一講書人閱讀 56,682評(píng)論 1 284
  • 正文 為了忘掉前任腔呜,我火速辦了婚禮叁温,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好吞瞪,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,792評(píng)論 6 386
  • 文/花漫 我一把揭開白布彰阴。 她就那樣靜靜地躺著,像睡著了一般勇皇。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,985評(píng)論 1 291
  • 那天冀宴,我揣著相機(jī)與錄音,去河邊找鬼嚎杨。 笑死花鹅,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的枫浙。 我是一名探鬼主播刨肃,決...
    沈念sama閱讀 39,107評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼箩帚!你這毒婦竟也來(lái)了真友?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,845評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤紧帕,失蹤者是張志新(化名)和其女友劉穎盔然,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體是嗜,經(jīng)...
    沈念sama閱讀 44,299評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡愈案,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,612評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了鹅搪。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片站绪。...
    茶點(diǎn)故事閱讀 38,747評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖丽柿,靈堂內(nèi)的尸體忽然破棺而出恢准,到底是詐尸還是另有隱情,我是刑警寧澤甫题,帶...
    沈念sama閱讀 34,441評(píng)論 4 333
  • 正文 年R本政府宣布馁筐,位于F島的核電站,受9級(jí)特大地震影響坠非,放射性物質(zhì)發(fā)生泄漏敏沉。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,072評(píng)論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望赦抖。 院中可真熱鬧舱卡,春花似錦、人聲如沸队萤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,828評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)要尔。三九已至舍杜,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間赵辕,已是汗流浹背既绩。 一陣腳步聲響...
    開封第一講書人閱讀 32,069評(píng)論 1 267
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留还惠,地道東北人饲握。 一個(gè)月前我還...
    沈念sama閱讀 46,545評(píng)論 2 362
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像蚕键,于是被迫代替她去往敵國(guó)和親救欧。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,658評(píng)論 2 350

推薦閱讀更多精彩內(nèi)容