微信--高效解決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。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM