微信--高效解決token及授權用戶openid的持久化處理辦法

摘要

       關於微信開發的話題,例子確實已經有不少,但大部分都是人云亦云,很多小細節或者需要注意的地方卻大多沒有講清楚,這令很多剛開始開發的人感覺大很迷茫。而我今天要說的話題,主要着眼於兩個方面。

一:如何存儲獲取用戶信息及調用第三方接口所需要的token.

二 : 第三方頁面授權,如何減少從微信服務器獲取用戶openid的次數以及減少獲取用戶信息的次數,加速第三方頁面的加載速速。

(注:演示所使用的是java語言,其他語言可與此類似)

下面我將開始講述第一個問題。

如何存儲獲取用戶信息及調用第三方接口所需要的token?

       從微信的官方文檔上,我們知道,獲取token的次數爲1天2000次,每兩小時token失效一次,對於那種一天沒有幾個人訪問的微信公衆號而言,他們可能只是簡單的每次調用高級接口都會去獲取一遍token.衆所周知,這種做法極其的耗費時間。從網上其他網友給出的存儲方案大概有如下幾種:

  1. 數據庫:通過微信接口獲取到 Token 之後,將 Token存儲到數據庫,每次需要時從數據庫取出。採用定時任務的方法每隔一個固定的時間去獲取一次token.
  2. NoSQl:這裏以 Redis 爲例子。通過微信接口獲取到 Token 之後,存入 Redis,可以通過設置redis的過期時間,每次需要token時從redis中取出來,若沒有,則證明Token 已過期可重新獲取(當然也可採用上面的定時任務的方式定期獲取)。
  3. 文件存儲:這個比較適合單一公衆號的情況。通過微信接口獲取到 Token 之後,存入文件,採用定時任務的方法每隔一個固定的時間去獲取一次token.
固定的時間:一般設爲1小時爲宜,如1小時後因網絡原因,請求獲取token失敗,則原有的token還可以在使用1小時,這種方式將錯誤降低了一半)
大致有這三種方案。當然對於那些將token存儲在session或者cookie裏面的,這裏我只能呵呵一笑了。
基於以上三種方式,我個人比較推崇的一個順序是NoSQl>數據庫>文件存儲。
下面我分別來說說他們的優缺點:
採用數據庫和文件存儲,對於單節點並且用戶請求數不是很多的web項目而言,是可以的正常運行的,但是對於分佈式多節點的項目,採用這兩種方式是行不通的。而我今天要說的第三種通過Redis方法存儲,一方面它可以提升獲取token的速度,另一方面當分佈式項目的多個節點要公用同一個token的時候,我們可以方便的取到。

第三方頁面授權,如何減少從微信服務器獲取用戶openid的次數以及減少獲取用戶信息的次數,加速第三方頁面的加載速速?

從諸多的博客中,我們瞭解到,第三方頁面授權獲取用戶信息,我們要調用兩次微信接口。

  • 第一次:構造應用授權的url,通過返回的code,換取用戶的openid.
  • 第二次:通過用戶的openid與token獲取用戶信息。

      對於頁面比較多的應用,每個頁面請求時都需要調用兩個方法,於用戶而言這是極其耗費時間的。

      然而諸多的博客只是告訴我們改如何處理用戶信息,諸如將用戶信息先拉取下來存儲到自己的數據庫,然後每次需要時從自己的數據庫中通過openid來獲取。殊不知這種博客只說了一點,他們沒有考慮到的問題是:

  1. 用戶若修改了自己的信息,該如何同步到自己的表裏面.
  2. 用戶的信息是獲取到了,但是每次用戶訪問網頁,標識用戶身份的openid依舊每次都要去調用接口獲取。(獲取用戶信息的次數微信API規定爲500000次)

那麼接下來我要說的這個就是如何解決上面兩個問題,處理過程大致如下:

a.添加攔截器,攔截需要授權頁面的controller

攔截器:

複製代碼
package com.fdc.home.dec.wx.filter;

import com.alibaba.dubbo.common.utils.StringUtils;
import com.fdc.platform.common.yfutil.PropertyReader;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Arrays;

/**
 * Created by pl on 2017/1/13.
 */
public class OAuth2Interceptor extends  HandlerInterceptorAdapter {


    public static String indexUrl = PropertyReader.getValue("indexUrl");//從配置文件中讀取域名
   // private static String[] arrQueController = {"newquestion", "mycenter","testCookie"};
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        Cookie[] cookies = request.getCookies();
        String openId=null;
        //判斷cookie中是否存在openid 若存在則直接跳過,不存在則獲取一次
        if(cookies!=null){
            for(Cookie cookie : cookies){
                if(cookie.getName().equals("openId")){
                    openId = cookie.getValue();
                }
            }
        }
        if (StringUtils.isEmpty(openId)) {
            if (handler instanceof HandlerMethod) {
                HandlerMethod handlerMethod = (HandlerMethod) handler;
                String methodName = handlerMethod.getMethod().getName();
                String uri = request.getRequestURI();
              //  if (checkList(arrQueController, methodName)) {
                   // System.out.println("執行了");
                    response.sendRedirect(indexUrl + "/oauth2Api?resultUrl=" + indexUrl + uri);
                    return false;
              //  }
            }
            return true;
        } else {
            return true;
        }
    }

    public void postHandle(
            HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
            throws Exception {
    }

    public void afterCompletion(
            HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
            throws Exception {
    }


    public boolean checkList(String[] arr, String targetValue) {
        return Arrays.asList(arr).contains(targetValue);
    }

}
複製代碼
spring-servlet.xml (攔截兩個指定的controller:testCookie,testCookie1)
複製代碼
<mvc:interceptors>
        <mvc:interceptor>
            <mvc:mapping path="/testCookie"/>
            <mvc:mapping path="/testCookie1"/>
            <bean class="com.fdc.home.dec.wx.filter.OAuth2Interceptor"/>
        </mvc:interceptor>
    </mvc:interceptors>
複製代碼

b.用戶首次訪問,調用微信接口獲取openid和用戶信息。將openid寫入服務器端的cookie,將用戶的信息寫入redis緩存中,以openid作爲redis的key。

複製代碼
package com.fdc.home.dec.wx.controller;

import com.fdc.home.dec.service.inter.service.DecWxService;
import com.fdc.home.dec.wx.service.CheckUserInfo;
import com.fdc.home.dec.wx.utils.JSONHelper;
import com.fdc.home.dec.wx.utils.WxUtils;
import com.fdc.home.dec.wx.vo.token.WeixinOauth2Token;
import com.fdc.home.dec.wx.vo.user.WeixinUserInfo;
import com.fdc.platform.common.yfutil.PropertyReader;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;

/**
 * Created by pl on 2017/1/13.
 */
@Controller
public class OAuth2Controller {

    @Autowired
    private DecWxService decWxService;
    //判斷用戶是否登錄的公用方法
    CheckUserInfo checkUserInfo = new CheckUserInfo();
    //從配置文件獲取appid
    public static String appid = PropertyReader.getValue("appid");
    //從配置文件獲取appsecret
    public static String appsecret = PropertyReader.getValue("appsecret");
    //從配置文件獲取主域名
    public static String indexUrl = PropertyReader.getValue("indexUrl");
    public static String wtoken = PropertyReader.getValue("wholetoken");

    /**
     * 組裝授權url
     * @param request
     * @param resultUrl
     * @return
     */
    @RequestMapping(value ="/oauth2Api")
    public String oauth2API(HttpServletRequest request, @RequestParam String resultUrl) {
        String redirectUrl = "";
        if (resultUrl != null) {
            String backUrl =indexUrl+"/oauth2MeUrl?oauth2url="+resultUrl;
            //組裝授權url
            redirectUrl = WxUtils.oAuth2Url(appid, backUrl);
        }
        return "redirect:" + redirectUrl;
    }

    /**
     * 獲取用戶信息
     * @param request
     * @param response
     * @param code
     * @param oauth2url
     * @return
     * @throws IOException
     */
    @RequestMapping(value = "/oauth2MeUrl")
    public String oauth2MeUrl(HttpServletRequest request,HttpServletResponse response, @RequestParam String code, @RequestParam String oauth2url) throws IOException {
        request.setCharacterEncoding("utf-8");
        response.setCharacterEncoding("utf-8");
        HttpSession session = request.getSession();
        session.setAttribute("code",code);
        // 用戶同意授權
        if (!"authdeny".equals(code)) {
            // 獲取網頁授權access_token
            WeixinOauth2Token weixinOauth2Token = WxUtils.getOauth2AccessToken(appid, appsecret, code);
            // 網頁授權接口訪問憑證
            String accessToken = weixinOauth2Token.getAccessToken();
            // 用戶標識
            String openId = weixinOauth2Token.getOpenId();
            String wholetoken = decWxService.getToken(wtoken);
            //獲取微信用戶openid存儲在cookie中的信息
            Cookie userCookie=new Cookie("openId",openId);
            userCookie.setMaxAge(-1);
            userCookie.setPath("/");
            response.addCookie(userCookie);
            WeixinUserInfo weixinUserInfo = WxUtils.getWeixinUserInfo(wholetoken, openId);
            //將用戶信息寫入redis
            decWxService.setToken(openId, JSONHelper.beanToJson(weixinUserInfo));
        }else {
            return "redirect:"+indexUrl+"/error404";
        }
        return "redirect:" + oauth2url;
    }
}
複製代碼

 (注:WxUtils中封裝各種請求微信服務器的接口,具體可自行百度)

以上兩步基本可以解決用戶授權的問題。基於此需要說明的是:

  • 開發中要設置cookie過期時間,設置爲負數,表明當用戶關閉瀏覽器的時候自動清空cookie,但在實際的測試中,微信瀏覽器並不會立刻清理cookie,你可以自行清理cookie.每次用戶訪問時直接從cookie中獲取openid,若沒有,纔會調用微信接口獲取。在獲取openid的同時,更新redis緩存中的用戶信息,這樣達到及時同步用戶信息的效果,也減少了對微信服務器的訪問。
  • 有部分網頁在博客中提到微信瀏覽器沒有cookie和session,基於這類問題,還請自己動手驗證嗎,微信瀏覽器是有cookie和session的(請區分服務端session和客戶端session),這裏之所以沒有將openid存儲在session中,其主要是考慮到分佈式多點項目中session比較難以處理。
  • 比較重要的一點是,當用戶更換微信登錄時,cookie會自動清除,登錄成功後,會重新獲取新登錄的用戶的openid。

結語

如果你還需要了解更多技術文章信息,請繼續關注白衣秀才的博客
個人網站:http://penglei.top/
Github:https://github.com/whitescholars
微博:http://weibo.com/u/3034107691?refer_flag=1001030102_&is_all=1


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章