關於微信開發的話題,例子確實已經有不少,但大部分都是人雲亦雲,很多小細節或者需要注意的地方卻大多沒有講清楚,這令很多剛開始開發的人感覺大很迷茫。而我今天要說的話題,主要着眼於兩個方面。
一:如何存儲獲取用戶信息及調用第三方接口所需要的token.
二 : 第三方頁面授權,如何減少從微信服務器獲取用戶openid的次數以及減少獲取用戶信息的次數,加速第三方頁面的加載速速。
(注:演示所使用的是java語言,其他語言可與此類似)
下面我將開始講述第一個問題。
如何存儲獲取用戶信息及調用第三方接口所需要的token?
從微信的官方文檔上,我們知道,獲取token的次數為1天2000次,每兩小時token失效一次,對於那種一天沒有幾個人訪問的微信公眾號而言,他們可能只是簡單的每次調用高級接口都會去獲取一遍token.眾所周知,這種做法極其的耗費時間。從網上其他網友給出的存儲方案大概有如下幾種:
- 數據庫:通過微信接口獲取到 Token 之后,將 Token存儲到數據庫,每次需要時從數據庫取出。采用定時任務的方法每隔一個固定的時間去獲取一次token.
- NoSQl:這里以 Redis 為例子。通過微信接口獲取到 Token 之后,存入 Redis,可以通過設置redis的過期時間,每次需要token時從redis中取出來,若沒有,則證明Token 已過期可重新獲取(當然也可采用上面的定時任務的方式定期獲取)。
- 文件存儲:這個比較適合單一公眾號的情況。通過微信接口獲取到 Token 之后,存入文件,采用定時任務的方法每隔一個固定的時間去獲取一次token.
第三方頁面授權,如何減少從微信服務器獲取用戶openid的次數以及減少獲取用戶信息的次數,加速第三方頁面的加載速速?
從諸多的博客中,我們了解到,第三方頁面授權獲取用戶信息,我們要調用兩次微信接口。
- 第一次:構造應用授權的url,通過返回的code,換取用戶的openid.
- 第二次:通過用戶的openid與token獲取用戶信息。
對於頁面比較多的應用,每個頁面請求時都需要調用兩個方法,於用戶而言這是極其耗費時間的。
然而諸多的博客只是告訴我們改如何處理用戶信息,諸如將用戶信息先拉取下來存儲到自己的數據庫,然后每次需要時從自己的數據庫中通過openid來獲取。殊不知這種博客只說了一點,他們沒有考慮到的問題是:
- 用戶若修改了自己的信息,該如何同步到自己的表里面.
- 用戶的信息是獲取到了,但是每次用戶訪問網頁,標識用戶身份的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);
}}
<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。