成人无码视频,亚洲精品久久久久av无码,午夜精品久久久久久毛片,亚洲 中文字幕 日韩 无码

資訊專(zhuān)欄INFORMATION COLUMN

一個(gè)AccessToken引發(fā)的思考

rainyang / 3076人閱讀

摘要:最近在做一個(gè)微信預(yù)約洗車(chē)的項(xiàng)目,其中有個(gè)功能是預(yù)約完成后給用戶(hù)發(fā)一個(gè)模板消息,發(fā)送模板消息需要以及格式的消息內(nèi)容,接口如下。關(guān)于微信的介紹是公眾號(hào)的全局唯一票據(jù),公眾號(hào)調(diào)用各接口時(shí)都需使用。

最近在做一個(gè)微信預(yù)約洗車(chē)的項(xiàng)目,其中有個(gè)功能是預(yù)約完成后給用戶(hù)發(fā)一個(gè)模板消息,發(fā)送模板消息需要AccessToken以及json格式的消息內(nèi)容,接口如下。

發(fā)送模板消息

接口調(diào)用請(qǐng)求說(shuō)明

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

POST數(shù)據(jù)說(shuō)明

POST數(shù)據(jù)示例如下:

  {
       "touser":"OPENID",
       "template_id":"ngqIpbwh8bUfcSsECmogfXcV14J0tQlEpBO27izEYtY",
       "url":"http://weixin.qq.com/download",            
       "data":{
               "first": {
                   "value":"恭喜你購(gòu)買(mǎi)成功!",
                   "color":"#173177"
               },
               "keynote1":{
                   "value":"巧克力",
                   "color":"#173177"
               },
               "keynote2": {
                   "value":"39.8元",
                   "color":"#173177"
               },
               "keynote3": {
                   "value":"2014年9月22日",
                   "color":"#173177"
               },
               "remark":{
                   "value":"歡迎再次購(gòu)買(mǎi)!",
                   "color":"#173177"
               }
       }
   }

返回碼說(shuō)明

在調(diào)用模板消息接口后,會(huì)返回JSON數(shù)據(jù)包。正常時(shí)的返回JSON數(shù)據(jù)包示例:

{
       "errcode":0,
       "errmsg":"ok",
       "msgid":200228332
   }

我而同事已經(jīng)寫(xiě)過(guò)這個(gè)功能了,索性就直接拿來(lái)用了。但是在使用的過(guò)程中,發(fā)現(xiàn)第一次可以成功發(fā)送模板消息,第二次就返回 errcode 40001,token驗(yàn)證失敗。

關(guān)于微信AccessToken的介紹:

access_token是公眾號(hào)的全局唯一票據(jù),公眾號(hào)調(diào)用各接口時(shí)都需使用access_token。開(kāi)發(fā)者需要進(jìn)行妥善保存。access_token的存儲(chǔ)至少要保留512個(gè)字符空間。access_token的有效期目前為2個(gè)小時(shí),需定時(shí)刷新,重復(fù)獲取將導(dǎo)致上次獲取的access_token失效。(注:獲取access_token接口的每日調(diào)用限額為2000次)

初步懷疑是不是別的地方更新了AccessToken,于是我打開(kāi)他的代碼,如下(偽代碼):

public String getAccessToken(){
    String token = (String)request.getSession().get(Const.ACCESS_TOKEN);
    if(token 為空){
        toekn = getTokenFormWx();
        request.getSession().add(Const.ACCESS_TOKEN,token).
        return token;
    }
    return token;
}

這樣寫(xiě)看起來(lái)好像沒(méi)什么問(wèn)題,也不是每次都去獲取一個(gè)新的access_token。但他忽略了一點(diǎn),session并不是只有一份的,系統(tǒng)為每個(gè)會(huì)話(huà)都創(chuàng)建一個(gè)多帶帶的session,最后調(diào)用getAccessToken的會(huì)話(huà)讓其他會(huì)話(huà)的session中的access_token都失效了。

我決定動(dòng)手把代碼修改了一下,因?yàn)閍ccess_token的有效時(shí)間是7200秒,當(dāng)時(shí)想著也放在redis里面好了,可以利用redis的自動(dòng)過(guò)期來(lái)保證access_token的有效性,但是項(xiàng)目中沒(méi)有使用redis,加進(jìn)來(lái)也是大材小用了,最后想想還是放在了ServletContext里面。

ServletContext,是一個(gè)全局的儲(chǔ)存信息的空間,服務(wù)器開(kāi)始,其就存在,服務(wù)器關(guān)閉,其才釋放。request,一個(gè)用戶(hù)可有多個(gè);session,一個(gè)用戶(hù)一個(gè);而servletContext,所有用戶(hù)共用一個(gè)。所以,為了節(jié)省空間,提高效率,ServletContext中,要放必須的、重要的、所有用戶(hù)需要共享的線(xiàn)程又是安全的一些信息。

于是就有了下面這段代碼(偽)

public String getAccessToken(){
    Map cacheMap = request.getServletContext().getAttr(Const.WX_TOKEN_MAP);  
    if(cacheMap==null || System.currentTimeMillis()-(Date)cacheMap.get(Const.WX_TOKEN_TIME).getTime()>1000*7000){
        cacheMap = new HashMap<>();
        String token = getTokenFormWx();
        if(token 為空){
            throw new RuntimeException("AccessToken is null");
        }
        cacheMap.put(Const.WX_TOKEN_VAL,token);
        cacheMap.put(Const.WX_TOKEN_TIME,new Date());
    }   
    return (String)cacheMap.get(Const.WX_TOKEN_VAL);
}

這樣看起來(lái)好像是比之前的代碼好了一點(diǎn),不會(huì)為沒(méi)一個(gè)會(huì)話(huà)都創(chuàng)建一個(gè)access_token,而且保證了時(shí)效性。但其實(shí)還是存在一點(diǎn)問(wèn)題的,假如有兩個(gè)線(xiàn)程同時(shí)調(diào)用了這一個(gè)方法,其中第一個(gè)線(xiàn)程進(jìn)了if在調(diào)用getTokenFormWx()的時(shí)候因?yàn)榫W(wǎng)絡(luò)或者其他原因等在這里了,第二個(gè)線(xiàn)程來(lái)了還是進(jìn)了if,并且成功的調(diào)用getTokenFormWx()返回了token給調(diào)用者處理業(yè)務(wù)邏輯,這時(shí)候第一個(gè)線(xiàn)程執(zhí)行完畢,刷新了token,這樣就導(dǎo)致了第二個(gè)線(xiàn)程的token已經(jīng)失效,在處理業(yè)務(wù)邏輯的時(shí)候必然失敗。

我們有沒(méi)有辦法避免這個(gè)問(wèn)題呢?當(dāng)然是有的。

你想我直接使用synchronized好了,加在方法上,這樣就不會(huì)錯(cuò)了。于是方法就變成了這樣

public synchronized String getAccessToken(){
    Map cacheMap = request.getServletContext().getAttr(Const.WX_TOKEN_MAP);  
    if(cacheMap==null || System.currentTimeMillis()-(Date)cacheMap.get(Const.WX_TOKEN_TIME).getTime()>1000*4800){
        cacheMap = new HashMap<>();
        String token = getTokenFormWx();
        if(token 為空){
            throw new RuntimeException("AccessToken is null");
        }
        cacheMap.put(Const.WX_TOKEN_VAL,token);
        cacheMap.put(Const.WX_TOKEN_TIME,new Date());
    }   
    return (String)cacheMap.get(Const.WX_TOKEN_VAL);
}

這樣是能解決問(wèn)題,但是解決問(wèn)題代價(jià)也太大了,每一個(gè)線(xiàn)程想要獲取這個(gè)token就得等其他線(xiàn)程全部獲取完才能拿到,大大降低了效率,不可行的。所以再次改動(dòng)代碼,變成了下面這樣。

public String getAccessToken(){
    Map cacheMap = request.getServletContext().getAttr(Const.WX_TOKEN_MAP);  
    if(cacheMap==null || System.currentTimeMillis()-(Date)cacheMap.get(Const.WX_TOKEN_TIME).getTime()>1000*4800){
        synchronized(this){
            if(cacheMap==null || System.currentTimeMillis()-(Date)cacheMap.get(Const.WX_TOKEN_TIME).getTime()>1000*4800){
                cacheMap = new HashMap<>();
                String token = getTokenFormWx();
                if(token 為空){
                    throw new RuntimeException("AccessToken is null");
                }
                cacheMap.put(Const.WX_TOKEN_VAL,token);
                cacheMap.put(Const.WX_TOKEN_TIME,new Date());
            }
        }
    }   
    return (String)cacheMap.get(Const.WX_TOKEN_VAL);
}

當(dāng)?shù)谝粋€(gè)線(xiàn)程進(jìn)了if之后,執(zhí)行synchronized里面的代碼,等待在了getTokenFormWx(),第二個(gè)線(xiàn)程也進(jìn)了if,但由于加了synchronized,所以會(huì)等待在那里,等第一個(gè)線(xiàn)程處理完它才能執(zhí)行,第一個(gè)線(xiàn)程執(zhí)行完畢之后返回token去執(zhí)行業(yè)務(wù)邏輯,第二個(gè)線(xiàn)程進(jìn)入synchronized代碼塊,執(zhí)行這里面的if判斷,由于第一個(gè)線(xiàn)程已經(jīng)成功獲取token并且刷新了ServletContext中的cacheMap,條件已經(jīng)不滿(mǎn)足,所以第二個(gè)線(xiàn)程是無(wú)法執(zhí)行這個(gè)if里面的代碼了,到此我們就設(shè)計(jì)了一個(gè)線(xiàn)程安全的獲取access_token方案。

看樣子好像一切都o(jì)k了,但是在測(cè)試后還是會(huì)出現(xiàn)一樣的問(wèn)題。

我又仔細(xì)檢查了兩遍代碼,還是沒(méi)有發(fā)現(xiàn)有問(wèn)題的地方。找不到錯(cuò)誤的地方,我決定開(kāi)始試錯(cuò)。

第一次,我把https://api.weixin.qq.com/cgi...改成https://api.weixin.qq.com/cgi...

參數(shù)access_token放入post請(qǐng)求參數(shù)里面,其他參數(shù)放進(jìn)request body里面。

結(jié)果:第一次就返回了40001 access_token無(wú)效。

第二次,我把https://api.weixin.qq.com/cgi...改成https://api.weixin.qq.com/cgi...

參數(shù)access_token放入post請(qǐng)求參數(shù)里面并使用trim()去除空格,其他參數(shù)放進(jìn)request body里面。

結(jié)果:第一次就返回了40001 access_token無(wú)效。

第三次,我把https://api.weixin.qq.com/cgi...

其他參數(shù)放進(jìn)request body里面。

結(jié)果:一切ok。。。。

為什么會(huì)多了空格?我也很想知道,但由于調(diào)試了太久時(shí)間,已經(jīng)很晚了,而第二天就是假期,所以我也就沒(méi)有深究了。

那為什么第二次和第三次都對(duì)ACCESS_TOKEN進(jìn)行了去空格處理,為什么返回的結(jié)果卻不一樣呢?

這就得不得不說(shuō)一下Http協(xié)議了,但這里不需要講太多,所以我們只說(shuō)一下Http協(xié)議之請(qǐng)求消息Request。

客戶(hù)端發(fā)送一個(gè)HTTP請(qǐng)求到服務(wù)器的請(qǐng)求消息包括以下格式:

請(qǐng)求行(request line)、請(qǐng)求頭部(header)、空行和請(qǐng)求數(shù)據(jù)四個(gè)部分組成。

圖片描述

Get請(qǐng)求例子(java按得票排序)

GET https://segmentfault.com/t/java?type=votes HTTP/1.1

Host: segmentfault.com

Connection: keep-alive

Cache-Control: max-age=0

Upgrade-Insecure-Requests: 1

User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.104 Safari/537.36 Core/1.53.2372.400 QQBrowser/9.5.10548.400

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,/;q=0.8

Referer: https://segmentfault.com/t/java

Accept-Encoding: gzip, deflate, sdch, br

Accept-Language: zh-CN,zh;q=0.8

Cookie: 這個(gè)我就不貼出來(lái)了

Post請(qǐng)求例子(添加筆記)

POST https://segmentfault.com/api/notes/add?_=6e0a1202503bc4d86e63672cff567b81 HTTP/1.1

Host: segmentfault.com

Connection: keep-alive

Content-Length: 139

Accept: application/json, text/javascript, /; q=0.01

Origin: https://segmentfault.com

X-Requested-With: XMLHttpRequest

User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.104 Safari/537.36 Core/1.53.2372.400 QQBrowser/9.5.10548.400

Content-Type: application/x-www-form-urlencoded; charset=UTF-8

Referer: https://segmentfault.com/record

Accept-Encoding: gzip, deflate, br

Accept-Language: zh-CN,zh;q=0.8

Cookie: 這個(gè)真的不能貼

title=%E6%B5%8B%E8%AF%95%E7%AC%94%E8%AE%B0&text=%E6%B5%8B%E8%AF%95%E7%AC%94%E8%AE%B0&id=&draftId=1220000008931250&isPrivate=0&language=text

對(duì)比一下你發(fā)現(xiàn)了什么?

get請(qǐng)求參數(shù)在url后面,使用?當(dāng)作標(biāo)志,多個(gè)參數(shù)使用&分割 類(lèi)似?a=1&b=2

post參數(shù)在請(qǐng)求頭部空一行的后面 類(lèi)似 a=1&b=2

那post提交的json串在哪個(gè)位置呢?

其實(shí)你已經(jīng)知道啦,也是在請(qǐng)求頭部空一行的后面 不過(guò)是以json的格式,而服務(wù)器內(nèi)部使用&分割參數(shù),使得開(kāi)發(fā)者可以使用getParameter獲取提交的參數(shù),而其他類(lèi)型的參數(shù)(例如json串和xml)開(kāi)發(fā)者可以使用getInputStream來(lái)讀取到參數(shù)然后自己解析。

那post請(qǐng)求能否把參數(shù)寫(xiě)在url后面呢?就像 post?a=1&b=2

答案是可以的,服務(wù)器可以成功解析到。

那get請(qǐng)求能把參數(shù)寫(xiě)在request body里面嗎?

答案是否定的,服務(wù)器對(duì)get請(qǐng)求只解析url后面的,request body里面的他不關(guān)心。

那你發(fā)送模板消息的參數(shù)為什么寫(xiě)在request body里面就不行呢?

我也不知道微信內(nèi)部是怎么做的,但是我覺(jué)得吧,微信之所以要把a(bǔ)ccess_token寫(xiě)在url后面,因?yàn)檫@個(gè)接口request body里面是模板消息的json串 如果再把a(bǔ)ccess_token加進(jìn)去 數(shù)據(jù)大概會(huì)是這樣

access_toke=xxxxxxxxxxx {"touser":"OPENID","template_id":"ngqIpbwh8bUfcSsECmogfXcV14J0tQlEpBO27izEYtY", "url":"http://weixin.qq.com/download", ... }

微信方面也不好分割這個(gè)串,于是他們覺(jué)得要這個(gè)access_token寫(xiě)在url后面,他們獲取到url后再手動(dòng)分割處理,request body里面就只放純json串,解析起來(lái)也很方便。這就是為什么我第二次操作失敗的原因啦。

第一次寫(xiě)技術(shù)類(lèi)得文章,文筆不好多多見(jiàn)諒。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/66936.html

相關(guān)文章

  • XXL-CONF v1.6.0 發(fā)布,分布式配置管理平臺(tái)。廢棄ZK輕量級(jí)架構(gòu)升級(jí)

    摘要:訪(fǎng)問(wèn)令牌為提升系統(tǒng)安全性,配置中心和客戶(hù)端進(jìn)行安全性校驗(yàn),雙方匹配才允許通訊啟動(dòng)時(shí),優(yōu)先全量加載鏡像數(shù)據(jù)到層,避免逐個(gè)請(qǐng)求耗時(shí)簡(jiǎn)介是一個(gè)輕量級(jí)分布式配置管理平臺(tái),擁有輕量級(jí)秒級(jí)動(dòng)態(tài)推送多環(huán)境多語(yǔ)言配置監(jiān)聽(tīng)權(quán)限控制版本回滾等特性。 Release Notes 1、輕量級(jí)改造:廢棄ZK,改為 DB + 磁盤(pán) + long polling 方案,部署更輕量,學(xué)習(xí)更簡(jiǎn)單;集群部署更方便,與單...

    Pandaaa 評(píng)論0 收藏0
  • Vue.js 2.0 基于OAuth2.0第三方登錄組件

    摘要:第三方登錄是現(xiàn)在常見(jiàn)的登錄方式,免注冊(cè)且安全方便快捷。大部分的第三方登錄都參考了的認(rèn)證方法。這里我主要總結(jié)一下第三方登錄組件的設(shè)計(jì)流程。身份認(rèn)證組件,需解耦,至少要喚起登錄和登出事件。認(rèn)證成功喚起登錄事件并將用戶(hù)信息傳遞出去。 第三方登錄是現(xiàn)在常見(jiàn)的登錄方式,免注冊(cè)且安全方便快捷。 本篇文章將以Github為例,介紹如何在自己的站點(diǎn)添加第三方登錄模塊。 OAuth2.0 OAuth(開(kāi)...

    RancherLabs 評(píng)論0 收藏0
  • 基于vuenuxt框架cnode社區(qū)服務(wù)端渲染

    摘要:基于的框架仿的社區(qū)服務(wù)端渲染,主要是為了優(yōu)化以及首屏加載速度線(xiàn)上地址地址技術(shù)棧目錄結(jié)構(gòu)配置文件封裝工具函數(shù)滾動(dòng)條操作函數(shù)靜態(tài)資源實(shí)例化之前執(zhí)行的插件注冊(cè)全局組件注冊(cè)全局服務(wù)端渲染時(shí)保存供服務(wù)端請(qǐng)求時(shí)的獲取頁(yè)面級(jí)組件首頁(yè)登錄頁(yè)未讀消 nuxt-cnode 基于vue的nuxt框架仿的cnode社區(qū)服務(wù)端渲染,主要是為了seo優(yōu)化以及首屏加載速度 線(xiàn)上地址 http://nuxt-cnod...

    tainzhi 評(píng)論0 收藏0
  • Spring Cloud OAuth 微服務(wù)內(nèi)部Token傳遞源碼實(shí)現(xiàn)解析

    摘要:源碼非常簡(jiǎn)單談?wù)剬?shí)現(xiàn)的問(wèn)題當(dāng)請(qǐng)求上線(xiàn)文沒(méi)有如果調(diào)用會(huì)直接,這個(gè)肯定會(huì)報(bào)錯(cuò),因?yàn)樯舷挛氖∪绻O(shè)置線(xiàn)程隔離,這里也會(huì)報(bào)錯(cuò)。導(dǎo)致安全上下問(wèn)題傳遞不到子線(xiàn)程中。歡迎關(guān)注我們獲得更多的好玩實(shí)踐 背景分析 showImg(https://segmentfault.com/img/remote/1460000018899024?w=494&h=245); 1.客戶(hù)端攜帶認(rèn)證中心發(fā)放的token,...

    Michael_Ding 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

閱讀需要支付1元查看
<