首頁>技術>

SpringBoot應用搭建之使用ThreadLocal儲存當前登入資訊

在我們的SpringBoot應用中,通常程式碼都是分層的,介面層、服務層、持久層,大部分的介面請求中我們都需要使用當前登入的使用者資訊,比如使用者名稱、使用者編碼、當前組織等資訊。通常如果是使用session、header、token來儲存當前登入資訊,不管用哪種方式,都需要每一層程式碼傳遞下去,這樣很麻煩,程式碼看起來也不夠簡潔。

我們知道前前端發起的每個請求都會對應一個執行緒,我們只要在這一個執行緒裡定義一個共享變數來儲存當前登入資訊,這樣就可以方便的再任何地方取得當前登入資訊了。java 為我們提供了一個ThreadLocal類,本地執行緒,其實它的物件是本地執行緒的區域性變數。該變數為其所屬執行緒所有,各個執行緒互不影響,我們可以將需要的資料儲存到該物件中。但是要注意執行緒結束要釋放該物件中的資料,不然會出現記憶體洩露的問題

首先我們定義一個類,該類用於初始化ThreadLocal的物件,同時提供基本的設定資料方法,獲取資料方法,移除資料方法:

package com.xtoad.ecms.common.web;import java.util.HashMap;import java.util.Map;/** * 獲取當前使用者資訊的輔助類 * * @author xtoad * @date 2021/02/19 */public class UserContext {    private static ThreadLocal<Map<String, Object>> threadLocal;    /**     * tokenKey     */    public static final String CONTEXT_KEY_USER_TOKEN = "token";    /**     * 使用者idKey     */    public static final String CONTEXT_KEY_USER_ID = "userId";    /**     * 使用者名稱     */    public static final String CONTEXT_KEY_USER_NAME = "userName";    static {        threadLocal = new ThreadLocal<>();    }    /**     * 設定資料     *     * @param key 鍵     * @param value 值     */    public static void set(String key, Object value) {        Map<String, Object> map = threadLocal.get();        if (map == null) {            map = new HashMap<>(6);            threadLocal.set(map);        }        map.put(key, value);    }    /**     * 獲取資料     *     * @param key 鍵     * @return 值     */    public static Object get(String key) {        Map<String, Object> map = threadLocal.get();        if (map == null) {            map = new HashMap<>(6);            threadLocal.set(map);        }        return map.get(key);    }    /**     * 清除資料     *     */    public static void remove() {        threadLocal.remove();    }    public static void setUserId(String userId) {        set(CONTEXT_KEY_USER_ID, userId);    }    public static Long getUserId() {        Object value = get(CONTEXT_KEY_USER_ID);        return Long.valueOf(String.valueOf(value));    }    public static void setUserName(String userName) {        set(CONTEXT_KEY_USER_NAME, userName);    }    public static String getUserName() {        Object value = get(CONTEXT_KEY_USER_NAME);        return String.valueOf(value);    }    public static void setToken(String token) {        set(CONTEXT_KEY_USER_TOKEN, token);    }    public static String getToken() {        Object value = get(CONTEXT_KEY_USER_TOKEN);        return String.valueOf(value);    }}

ThreadLocal類支援泛型,我們這裡利用一個map 來儲存資料,也可以是User類等。

然後,我們攔截介面請求,在請求裡透過token、session、header等途徑獲取當前登入資訊,存放到UserContext類中,

package com.xtoad.study.common.web.interceptor;import com.auth0.jwt.interfaces.Claim;import com.xtoad.study.common.utils.TokenUtils;import com.xtoad.study.common.web.UserContext;import com.xtoad.study.common.web.annotation.RequiredToken;import com.xtoad.study.common.web.exception.BusinessException;import com.xtoad.study.common.web.exception.ResultCodeEnum;import org.springframework.http.HttpHeaders;import org.springframework.stereotype.Component;import org.springframework.web.method.HandlerMethod;import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.lang.reflect.Method;import java.util.Map;/** * 介面Token驗證的攔截類 * * @author xtoad * @date 2021/02/18 */@Componentpublic class AuthenticationInterceptor extends HandlerInterceptorAdapter {    @Override    public boolean preHandle(HttpServletRequest httpServletRequest            , HttpServletResponse httpServletResponse, Object object) {        // 如果不是對映到方法直接透過        if (!(object instanceof HandlerMethod)) {            return true;        }        HandlerMethod handlerMethod = (HandlerMethod) object;        Method method = handlerMethod.getMethod();        // 檢查方法上是否有RequiredToken註解        if (method.isAnnotationPresent(RequiredToken.class)) {            // 若方法上有,則判斷設定的是否false            RequiredToken requiredToken = method.getAnnotation(RequiredToken.class);            if (!requiredToken.value()) {                // 若設定的required = false 則跳過驗證                return true;            }        } else if (handlerMethod.getBeanType().isAnnotationPresent(RequiredToken.class)) {            // 若方法上沒有註解則判斷該方法的類上是否有RequiredToken註解            // 若類上有,則判斷設定的是否false            RequiredToken requiredToken = handlerMethod.getBeanType().getAnnotation(RequiredToken.class);            if (!requiredToken.value()) {                // 若設定的required = false 則跳過驗證                return true;            }        }        // 從 http 請求頭中取出 token        String token = httpServletRequest.getHeader(HttpHeaders.AUTHORIZATION);        // 驗證 token        boolean verifyToken = TokenUtils.verifyToken(token);        if (!verifyToken) {            httpServletResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);            throw new BusinessException(ResultCodeEnum.EXPIRED_TOKEN);        }        // 儲存當前登入資訊        Map<String, Claim> claims = TokenUtils.getClaims(token);        UserContext.setToken(token);        UserContext.setUserId(claims.get(UserContext.CONTEXT_KEY_USER_ID).asString());        UserContext.setLoginNo(claims.get(UserContext.CONTEXT_KEY_LOGIN_NO).asString());        UserContext.setUserName(claims.get(UserContext.CONTEXT_KEY_USER_NAME).asString());        UserContext.setNickName(claims.get(UserContext.CONTEXT_KEY_USER_NICK_NAME).asString());        return true;    }    @Override    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {        // ThreadLocal值清除,防止記憶體洩漏        UserContext.remove();        super.afterCompletion(request, response, handler, ex);    }}

我們在攔截器中先進行Token 的驗證,如果驗證成功,就取出Token中的登入資訊,這裡你可以根據你的實際情況呼叫快取、資料庫查詢等方式獲取當前登入資訊,然後儲存到UserContext的ThreadLocal物件中。

然後在其他地方,需要用到登入資訊的地方直接 呼叫即可!

String userName = UserContext.get(UserContext.CONTEXT_KEY_USER_NAME);

截圖強調一下重點:

9
最新評論
  • BSA-TRITC(10mg/ml) TRITC-BSA 牛血清白蛋白改性標記羅丹明
  • MATLAB R2020b for Mac(商業數學軟體)