SaToken學習筆記-01


SaToken學習筆記-01

SaToken版本為1.18

如果有排版方面的錯誤,請查看:傳送門

springboot集成

根據官網步驟maven導入依賴

<dependency>
	<groupId>cn.dev33</groupId>
	<artifactId>sa-token-spring-boot-starter</artifactId>
	<version>1.18.0</version>
</dependency>

在resources下的application.ym中增加配置 當然你也可以零配置啟動

server:
    # 端口
    port: 8081

spring: 
    # sa-token配置
    sa-token: 
        # token名稱 (同時也是cookie名稱)
        token-name: satoken
        # token有效期,單位s 默認30天, -1代表永不過期 
        timeout: 2592000
        # token臨時有效期 (指定時間內無操作就視為token過期) 單位: 秒
        activity-timeout: -1
        # 是否允許同一賬號並發登錄 (為true時允許一起登錄, 為false時新登錄擠掉舊登錄) 
        allow-concurrent-login: false
        # 在多人登錄同一賬號時,是否共用一個token (為true時所有登錄共用一個token, 為false時每次登錄新建一個token) 
        is-share: false
        # token風格
        token-style: uuid
        # 是否輸出操作日志 
        is-log: false

創建啟動類

在我學的時候,注意這里有個一個小坑:制作人員改動了1.18版本但是卻沒有及時更改官網信息
原版本官網:

@SpringBootApplication
public class SaTokenDemoApplication {
    public static void main(String[] args) throws JsonProcessingException {
        SpringApplication.run(SaTokenDemoApplication.class, args);
        System.out.println("啟動成功:sa-token配置如下:" + SaTokenManager.getConfig());
    }
}

這樣會導致異常:找不到SaTokenManager
通過我與相關人員取得聯系后,發現其實應該將SaTokenManager更改為SaManager

正確版本應該為:

public static void main(String[] args) throws JsonProcessingException {
		SpringApplication.run(WebApplication.class, args);
		System.out.println("啟動成功:sa-token配置如下:" + SaManager.getConfig());
}

創建測試Controller

官方測試用例:

@RestController
@RequestMapping("/user/")
public class UserController {

    // 測試登錄,瀏覽器訪問: http://localhost:8081/user/doLogin?username=zhang&password=123456
    @RequestMapping("doLogin")
    public String doLogin(String username, String password) {
        // 此處僅作模擬示例,真實項目需要從數據庫中查詢數據進行比對 
        if("zhang".equals(username) && "123456".equals(password)) {
            StpUtil.setLoginId(10001);
            return "登錄成功";
        }
        return "登錄失敗";
    }

    // 查詢登錄狀態,瀏覽器訪問: http://localhost:8081/user/isLogin
    @RequestMapping("isLogin")
    public String isLogin(String username, String password) {
        return "當前會話是否登錄:" + StpUtil.isLogin();
    }

}

到這里所有的基本配置已經全部完成,開始測試

訪問: http://localhost:8081/user/doLogin?username=zhang&password=123456
顯示登錄成功
訪問: http://localhost:8082/user/isLogin
顯示當前會話是否登錄:true


清楚所有cookie后重新嘗試
更改對應數值
訪問: http://localhost:8081/user/doLogin?username=zhang&password=123456
顯示登錄失敗
訪問: http://localhost:8082/user/isLogin
顯示當前會話是否登錄:false

測試成功!!!

源碼解析

看到這里不禁感嘆,哇塞,這是多么的方便啊。同時也對他的實現原理嘗試了好奇,他是如何使用這么便捷的代碼做到的?於是我點開了源碼:

1.StpUtil.setLoginId();

在判斷傳入的用戶名和密碼與數據庫一致后,進行了此操作:StpUtil.setLoginId(10001);
我們看看他做了什么:

public static void setLoginId(Object loginId) {
		stpLogic.setLoginId(loginId);
	}

StpUtil類中的setLoginId將傳入的loginId參數傳給了stpLogic.setLoginId();


什么是stpLogic?

可以看到在StpUtil類中聲明了
public static StpLogic stpLogic = new StpLogic("login");
點進StpLogic類,看到注解信息表明:

/**
 * sa-token 權限驗證,邏輯實現類 
 * <p>
 * (stp = sa-token-permission 的縮寫 )
 * @author kong
 */

得知這個類是用於權限驗證,以及邏輯實現的


繼續深入:

 * 在當前會話上登錄id 
	 * @param loginId 登錄id,建議的類型:(long | int | String)
	 */
	public void setLoginId(Object loginId) {
		setLoginId(loginId, new SaLoginModel());
	}

由此可見他創建了一個新的SaLoginModel,並且和loginId一起傳入到另一個setLoginId中:


什么是SaLoginModel?
/**
 * 調用 `StpUtil.setLogin()` 時的 [配置參數 Model ]
 * @author kong
 *
 */

由注解可得是對於StpUtil.setLogin()時的用於配置參數的mdoel
其中包括設置了:
此次登錄的客戶端設備標識,
是否為持久Cookie,
指定此次登錄token的有效期, 單位:秒 (如未指定,自動取全局配置的timeout值),
......

繼續深入:

/**
	 * 在當前會話上登錄id, 並指定所有登錄參數Model 
	 * @param loginId 登錄id,建議的類型:(long | int | String)
	 * @param loginModel 此次登錄的參數Model 
	 */
	public void setLoginId(Object loginId, SaLoginModel loginModel) {
		
		// ------ 0、檢查此賬號是否已被封禁 
		if(isDisable(loginId)) {
			throw new DisableLoginException(loginKey, loginId, getDisableTime(loginId));
		}
		
		// ------ 1、獲取相應對象  
		SaTokenConfig config = getConfig();
		SaTokenDao dao = SaManager.getSaTokenDao();
		loginModel.build(config);
		
		// ------ 2、生成一個token 
		String tokenValue = null;
		// --- 如果允許並發登錄 
		if(config.getAllowConcurrentLogin() == true) {
			// 如果配置為共享token, 則嘗試從Session簽名記錄里取出token 
			if(config.getIsShare() == true) {
				tokenValue = getTokenValueByLoginId(loginId, loginModel.getDevice());
			}
		} else {
			// --- 如果不允許並發登錄 
			// 如果此時[user-session]不為null,說明此賬號在其他地正在登錄,現在需要先把其它地的同設備token標記為被頂下線 
			SaSession session = getSessionByLoginId(loginId, false);
			if(session != null) {
				List<TokenSign> tokenSignList = session.getTokenSignList();
				for (TokenSign tokenSign : tokenSignList) {
					if(tokenSign.getDevice().equals(loginModel.getDevice())) {
						// 1. 將此token 標記為已頂替 
						dao.update(splicingKeyTokenValue(tokenSign.getValue()), NotLoginException.BE_REPLACED);	
						// 2. 清理掉[token-最后操作時間] 
						clearLastActivity(tokenSign.getValue()); 			
						// 3. 清理user-session上的token簽名記錄 
						session.removeTokenSign(tokenSign.getValue()); 		
				 		// $$ 通知監聽器 
				 		SaManager.getSaTokenListener().doReplaced(loginKey, loginId, tokenSign.getValue(), tokenSign.getDevice());
					}
				}
			}
		}
		// 如果至此,仍未成功創建tokenValue, 則開始生成一個 
		if(tokenValue == null) {
			tokenValue = createTokenValue(loginId);
		}
		
		// ------ 3. 獲取[User-Session] (如果還沒有創建session, 則新建, 如果已經創建,則續期) 
		SaSession session = getSessionByLoginId(loginId, false);
		if(session == null) {
			session = getSessionByLoginId(loginId);
		} else {
			session.updateMinTimeout(loginModel.getTimeout());
		}
		// 在session上記錄token簽名 
		session.addTokenSign(new TokenSign(tokenValue, loginModel.getDevice()));
		
		// ------ 4. 持久化其它數據 
		// token -> uid 
		dao.set(splicingKeyTokenValue(tokenValue), String.valueOf(loginId), loginModel.getTimeout());
		
		// 寫入 [最后操作時間]
		setLastActivityToNow(tokenValue); 
		
		// 在當前會話寫入當前tokenValue 
		setTokenValue(tokenValue, loginModel.getCookieTimeout());
		
		// $$ 通知監聽器
		SaManager.getSaTokenListener().doLogin(loginKey, loginId, loginModel);
	}

根據傳入的id,和相關的配置model帶來的配置信息進行操作:

  • 判斷賬號是否被封禁
    將獲取的loginId傳入isDisable中:
/**
	 * 指定賬號是否已被封禁 (true=已被封禁, false=未被封禁) 
	 * @param loginId 賬號id
	 * @return see note
	 */
	public boolean isDisable(Object loginId) {
		return SaManager.getSaTokenDao().get(splicingKeyDisable(loginId)) != null;
	}

從splicingKeyDisable中拿出token名字+loginkey+loginId格式的字符串
(loginkey持久化的key前綴,用於多賬號認證體系時通過這個key來區分,默認為"")

/**  
	 * 拼接key: 賬號封禁
	 * @param loginId 賬號id
	 * @return key 
	 */
	public String splicingKeyDisable(Object loginId) {
		return getConfig().getTokenName() + ":" + loginKey + ":disable:" + loginId;
	}

調用SaTokenDao接口中的get方法傳入獲取的字符串作為 key
該接口被SaTokenDaoDefaultImpl類所實現,重寫方法get為:

@Override
	public String get(String key) {
		clearKeyByTimeout(key);
		return (String)dataMap.get(key);
	}

clearKeyByTimeout():判斷傳入的key是否已經過期,如果key不為空並且沒有設置為永不過期並且已經超出過期的時間時,則確認key已經過期,就將它對應的值remove
最終返回數據集合dataMap中key對應的值(包括賬號信息是否封禁的值)
(public Map<String, Object> dataMap = new ConcurrentHashMap<String, Object>();)

最終根據傳出的值是否帶有封禁信息,和是否有值返回對應的boolean值來達到檢查此賬號是否封禁的目的

  • 獲取相應對象

SaTokenConfig
SaTokenDao
loginModel

  • 生成一個token

根據SaTokenConfig類中的allowConcurrentLogin值判斷是否允許並發登錄
如果允許就再次判斷是否配置為共享token(在多人登錄同一賬號時,是否共用一個token (為true時所有登錄共用一個token, 為false時每次登錄新建一個token),根據SaTokenConfig中的isShare變量進行判斷)如果是共享token,則根據loginId和登錄模型中的設備標識返回一個token並賦給tokenValue

/** 
	 * 獲取指定loginId指定設備端的tokenValue 
	 * <p> 在配置為允許並發登錄時,此方法只會返回隊列的最后一個token,
	 * 如果你需要返回此賬號id的所有token,請調用 getTokenValueListByLoginId 
	 * @param loginId 賬號id
	 * @param device 設備標識 
	 * @return token值 
	 */
	public String getTokenValueByLoginId(Object loginId, String device) {
		List<String> tokenValueList = getTokenValueListByLoginId(loginId, device);
		return tokenValueList.size() == 0 ? null : tokenValueList.get(tokenValueList.size() - 1);
	}

如果不允許並發登錄,就先通過loginId和isCreate(false無需新建,默認true),返回一個查詢到的Session

/** 
	 * 獲取指定loginId的session, 如果session尚未創建,isCreate=是否新建並返回
	 * @param loginId 賬號id
	 * @param isCreate 是否新建
	 * @return SaSession
	 */
	public SaSession getSessionByLoginId(Object loginId, boolean isCreate) {
		return getSessionBySessionId(splicingKeySession(loginId), isCreate);
	}

判斷seesion是否為空,如果不為空則說明此賬號在其他地正在登錄,現在需要先把其它地的同設備token標記為被頂下線。通過該session獲取到token簽名列表的拷貝副本(底層為Vector),對獲取到底列表副本遍歷,將列表中的設備標識和此時的設備標識一一比對,如果有相同的,則將此token 標記為已頂替 ,清理token最后操作時間

/**
 	 * 清除指定token的 [最后操作時間] 
 	 * @param tokenValue 指定token 
 	 */
 	protected void clearLastActivity(String tokenValue) {
 		// 如果token == null 或者 設置了[永不過期], 則立即返回 
 		if(tokenValue == null || getConfig().getActivityTimeout() == SaTokenDao.NEVER_EXPIRE) {
 			return;
 		}
 		// 刪除[最后操作時間]
 		SaManager.getSaTokenDao().delete(splicingKeyLastActivityTime(tokenValue));
 		// 清除標記 
 		SaHolder.getStorage().delete((SaTokenConsts.TOKEN_ACTIVITY_TIMEOUT_CHECKED_KEY));
 	}

清理user-session上的token簽名記錄

/**
	 * 移除一個token簽名
	 *
	 * @param tokenValue token名稱
	 */
	public void removeTokenSign(String tokenValue) {
		TokenSign tokenSign = getTokenSign(tokenValue);
		if (tokenSignList.remove(tokenSign)) {
			update();
		}
	}

最后通知監聽器,調用SaTokenListener接口中的doReplaced方法,該方法被SaTokenListenerDefaultImpl類實現,並重寫為輸出相關被頂下線時的通知

/**
	 * 每次被頂下線時觸發
	 */
	@Override
	public void doReplaced(String loginKey, Object loginId, String tokenValue, String device) {
		println("賬號[" + loginId + "]被頂下線 (終端: " + device + ")");
	}

做完這些后繼續判斷是否session為null
若仍然沒有成功創建session,也就是說該用戶為不允許並發登錄,並且沒有已經登錄的情況。則創建一個新的token
調用SaTokenAction接口中的createToken方法,SaTokenActionDefaultImpl類實現了這個接口,並重寫了createToken方法

@Override
	public String createToken(Object loginId, String loginKey) {
		// 根據配置的tokenStyle生成不同風格的token 
		String tokenStyle = SaManager.getConfig().getTokenStyle();
		// uuid 
		if(SaTokenConsts.TOKEN_STYLE_UUID.equals(tokenStyle)) {
			return UUID.randomUUID().toString();
		}
		// 簡單uuid (不帶下划線)
		if(SaTokenConsts.TOKEN_STYLE_SIMPLE_UUID.equals(tokenStyle)) {
			return UUID.randomUUID().toString().replaceAll("-", "");
		}
		// 32位隨機字符串
		if(SaTokenConsts.TOKEN_STYLE_RANDOM_32.equals(tokenStyle)) {
			return SaFoxUtil.getRandomString(32);
		}
		// 64位隨機字符串
		if(SaTokenConsts.TOKEN_STYLE_RANDOM_64.equals(tokenStyle)) {
			return SaFoxUtil.getRandomString(64);
		}
		// 128位隨機字符串
		if(SaTokenConsts.TOKEN_STYLE_RANDOM_128.equals(tokenStyle)) {
			return SaFoxUtil.getRandomString(128);
		}
		// tik風格 (2_14_16)
		if(SaTokenConsts.TOKEN_STYLE_TIK.equals(tokenStyle)) {
			return SaFoxUtil.getRandomString(2) + "_" + SaFoxUtil.getRandomString(14) + "_" + SaFoxUtil.getRandomString(16) + "__";
		}
		// 默認,還是uuid 
		return UUID.randomUUID().toString();
	}
  • **獲取[User-Session] (如果還沒有創建session, 則新建, 如果已經創建,則續期) **
    通過loginId,isCreate(false無需新建,默認true)獲取session,並判斷是否為空
    若為空就創建一個新的session,若不為空則,重新修改session的存活時間
/**
	 * 修改此Session的最小剩余存活時間 (只有在Session的過期時間低於指定的minTimeout時才會進行修改)
	 * @param minTimeout 過期時間 (單位: 秒) 
	 */
	public void updateMinTimeout(long minTimeout) {
		if(getTimeout() < minTimeout) {
			SaManager.getSaTokenDao().updateSessionTimeout(this.id, minTimeout);
		}
	}

在session上記錄token簽名

  • **持久化其它數據 **
    調用SaTokenDao接口中的set方法
	/**
	 * 寫入指定key-value鍵值對,並設定過期時間 (單位: 秒)
	 * @param key 鍵名稱 
	 * @param value 值 
	 * @param timeout 過期時間 (單位: 秒)
	 */
	public void set(String key, String value, long timeout);

SaTokenDaoDefaultImpl類實現了該接口並且重寫了set方法

@Override
	public void set(String key, String value, long timeout) {
		dataMap.put(key, value);
		expireMap.put(key, (timeout == SaTokenDao.NEVER_EXPIRE) ? (SaTokenDao.NEVER_EXPIRE) : (System.currentTimeMillis() + timeout * 1000));
	}

通過重寫的此方法,對數據集dataMap,expireMap中的數據進行添加

/**
	 * 數據集合 
	 */
	public Map<String, Object> dataMap = new ConcurrentHashMap<String, Object>();

	/**
	 * 過期時間集合 (單位: 毫秒) , 記錄所有key的到期時間 [注意不是剩余存活時間] 
	 */
	public Map<String, Long> expireMap = new ConcurrentHashMap<String, Long>();

寫入最后操作時間,根據傳入的值確定指定token

/**
 	 * 寫入指定token的 [最后操作時間] 為當前時間戳 
 	 * @param tokenValue 指定token 
 	 */
 	protected void setLastActivityToNow(String tokenValue) {
 		// 如果token == null 或者 設置了[永不過期], 則立即返回 
 		if(tokenValue == null || getConfig().getActivityTimeout() == SaTokenDao.NEVER_EXPIRE) {
 			return;
 		}
 		// 將[最后操作時間]標記為當前時間戳 
 		SaManager.getSaTokenDao().set(splicingKeyLastActivityTime(tokenValue), String.valueOf(System.currentTimeMillis()), getConfig().getTimeout());
 	}

如果token已經是永不過期或者是空,就不做任何操作直接返回,否則就把時間戳寫入作為token的最后操作時間

在當前會話寫入當前tokenValue

/**
 	 * 在當前會話寫入當前tokenValue 
 	 * @param tokenValue token值 
 	 * @param cookieTimeout Cookie存活時間(秒)
 	 */
	public void setTokenValue(String tokenValue, int cookieTimeout){
		SaTokenConfig config = getConfig();
		// 將token保存到[存儲器]里  
		SaStorage storage = SaHolder.getStorage();
		// 判斷是否配置了token前綴 
		String tokenPrefix = config.getTokenPrefix();
		if(SaFoxUtil.isEmpty(tokenPrefix)) {
			storage.set(splicingKeyJustCreatedSave(), tokenValue);	
		} else {
			// 如果配置了token前綴,則拼接上前綴一起寫入 
			storage.set(splicingKeyJustCreatedSave(), tokenPrefix + SaTokenConsts.TOKEN_CONNECTOR_CHAT + tokenValue);	
		}
		
		// 注入Cookie 
		if(config.getIsReadCookie() == true){
			SaResponse response = SaHolder.getResponse();
			response.addCookie(getTokenName(), tokenValue, "/", config.getCookieDomain(), cookieTimeout);
		}
	}

將加了前綴后的token寫入到容器中,並且將加了前綴的token和傳入的Cookie存活時間一起出入Cookie

最終調用SaTokenListener接口中的doLogin方法

/**
	 * 每次登錄時觸發 
	 * @param loginKey 賬號類別
	 * @param loginId 賬號id
	 * @param loginModel 登錄參數
	 */
	public void doLogin(String loginKey, Object loginId, SaLoginModel loginModel);

SaTokenListenerDefaultImpl方法實現了該接口,並且重寫了該方法:
/**
* 每次登錄時觸發
*/
@Override
public void doLogin(String loginKey, Object loginId, SaLoginModel loginModel) {
println("賬號[" + loginId + "]登錄成功");
}


2.StpUtil.isLogin();

訪問頁面時,可以調用此方法直接判斷該用戶是否登錄
開始瀏覽源碼:

/** 
 	 * 獲取當前會話是否已經登錄 
 	 * @return 是否已登錄 
 	 */
	public static boolean isLogin() {
		return stpLogic.isLogin();
	}

可以看到,該方法調用了stpLogic.isLogin()方法,如果已登錄返回true,否則為false
(什么是stpLogic?)見此文檔源碼解析1下

繼續深入

/** 
 	 * 獲取當前會話是否已經登錄 
 	 * @return 是否已登錄 
 	 */
 	public boolean isLogin() {
 		// 判斷條件:不為null,並且不在異常項集合里 
 		return getLoginIdDefaultNull() != null;
 	}

getLoginIdDefaultNull()返回對應情況的loginId,如果有值並且不在異常項集合里則isLogin判斷為已登錄,否則就判斷為未登錄
查看getLoginIdDefaultNull()具體判斷

/** 
	 * 獲取當前會話登錄id, 如果未登錄,則返回null 
	 * @return 賬號id 
	 */
	public Object getLoginIdDefaultNull() {
		// 如果正在[臨時身份切換]
		if(isSwitch()) {
			return getSwitchLoginId();
		}
		// 如果連token都是空的,則直接返回 
		String tokenValue = getTokenValue();
 		if(tokenValue == null) {
 			return null;
 		}
 		// loginId為null或者在異常項里面,均視為未登錄, 返回null 
 		Object loginId = getLoginIdNotHandle(tokenValue);
 		if(loginId == null || NotLoginException.ABNORMAL_LIST.contains(loginId)) {
 			return null;
 		}
 		// 如果已經[臨時過期] 
 		if(getTokenActivityTimeoutByToken(tokenValue) == SaTokenDao.NOT_VALUE_EXPIRE) {
 			return null;
 		}
 		// 執行到此,證明loginId已經是個正常的賬號id了 
 		return loginId;
 	}

- 首先判斷是否在身份互換

/**
	 * 當前是否正處於[身份臨時切換]中 
	 * @return 是否正處於[身份臨時切換]中 
	 */
	public boolean isSwitch() {
		return SaHolder.getStorage().get(splicingKeySwitch()) != null;
	}

調用了splicingKeySwitch()返回一個字符串作為SaHolder.getStorage().get()的key


什么是SaHolder.getStorage()?

SaHolder是sa-Token的上下文持有類
其中的getStorage()實現了返回當前請求存儲器的對象(底層容器操作Bean實現)

public static SaStorage getStorage() {
	return SaManager.getSaTokenContext().getStorage();
}

繼續深入splicingKeySwitch()

/**
	 * 在進行身份切換時,使用的存儲key 
	 * @return key
	 */
	public String splicingKeySwitch() {
		return SaTokenConsts.SWITCH_TO_SAVE_KEY + loginKey;
	}

返回一個常量+loginKey格式的字符串

/**
	 * 常量key標記: 在進行臨時身份切換時使用的key 
	 */
	public static final String SWITCH_TO_SAVE_KEY = "SWITCH_TO_SAVE_KEY_"; 

如果可以在容器中取到對應key的值,則表示為正在臨時身份互換,否則沒有進行臨時身份互換
若進行了臨時身份互換則返回臨時互換身份的loginId:

/**
	 * 返回[身份臨時切換]的loginId 
	 * @return 返回[身份臨時切換]的loginId 
	 */
	public Object getSwitchLoginId() {
		return SaHolder.getStorage().get(splicingKeySwitch());
	}

同樣根據拼接的字符串作為SaHolder類中獲取的容器對象取值的key,返回相對應的值也就是互換身份的臨時loginId

- 取到token進行判斷

通過getTokenValue()取到當前的token值

/**
	 * 獲取當前tokenValue
	 * @return 當前tokenValue
	 */
	public String getTokenValue(){
		// 0. 獲取相應對象 
		SaStorage storage = SaHolder.getStorage();
		SaRequest request = SaHolder.getRequest();
		SaTokenConfig config = getConfig();
		String keyTokenName = getTokenName();
		String tokenValue = null;
		
		// 1. 嘗試從Storage里讀取 
		if(storage.get(splicingKeyJustCreatedSave()) != null) {
			tokenValue = String.valueOf(storage.get(splicingKeyJustCreatedSave()));
		}
		// 2. 嘗試從請求體里面讀取 
		if(tokenValue == null && config.getIsReadBody()){
			tokenValue = request.getParameter(keyTokenName);
		}
		// 3. 嘗試從header里讀取 
		if(tokenValue == null && config.getIsReadHead()){
			tokenValue = request.getHeader(keyTokenName);
		}
		// 4. 嘗試從cookie里讀取 
		if(tokenValue == null && config.getIsReadCookie()){
			tokenValue = request.getCookieValue(keyTokenName);
		}
		
		// 5. 如果打開了前綴模式
		String tokenPrefix = getConfig().getTokenPrefix();
		if(SaFoxUtil.isEmpty(tokenPrefix) == false && SaFoxUtil.isEmpty(tokenValue) == false) {
			// 如果token以指定的前綴開頭, 則裁剪掉它, 否則視為未提供token 
			if(tokenValue.startsWith(tokenPrefix + SaTokenConsts.TOKEN_CONNECTOR_CHAT)) {
				tokenValue = tokenValue.substring(tokenPrefix.length() + SaTokenConsts.TOKEN_CONNECTOR_CHAT.length());
			} else {
				tokenValue = null;
			}
		}
		
		// 6. 返回 
		return tokenValue;
	}

取值方法為:
首先獲取相對應的對象SaStorage,SaRequest,SaTokenConfig,keyTokenName,tokenValue
分別從Storage,請求體,header,cookie中獲取token值(tokenValue)
再判斷是否打開了前綴模式:如果打開了並且token值為空就繼續判斷token是否以指定的前綴開頭, 是則裁剪掉它, 否則視為未提供token(token置空)
返回token值

對返回的token值進行判斷,若為空,則返回空,即getLoginIdDefaultNull()判斷此用戶未登錄

-對LoginId判斷

/**
 	  * 獲取指定token對應的登錄id (不做任何特殊處理) 
 	  * @param tokenValue token值 
 	  * @return loginId
 	  */
 	public String getLoginIdNotHandle(String tokenValue) {
 		return SaManager.getSaTokenDao().get(splicingKeyTokenValue(tokenValue));
 	}

通過調用getLoginIdNotHandle(String token),返回傳入token對應的lginId
使用splicingKeyTokenValue(tokenValue),返回一個常量和token拼接的字符串作為SaManager.getSaTokenDao().get()的key,並且返回查詢到的loginId


什么是SaManager?
/**
 * 管理sa-token所有接口對象 
 * @author kong
 *
 */
public class SaManager

是用來管理所有接口的對象


SaTokenDaoDefaultImpl類實現了SaTokenDao接口並重寫了get方法:

@Override
	public String get(String key) {
		clearKeyByTimeout(key);
		return (String)dataMap.get(key);
	}

通過傳入的key,先判斷是否已經過期,之后返回在數據集dataMap中查詢到的相應的loginId
(詳細操作在本文檔中源碼分析1中解析過)

對獲取到的loginId進行判斷是否是空或者被異常項包括
(異常項:public class NotLoginException extends SaTokenException ,一個異常,代表用戶沒有登錄)
如果滿足,則說明該用戶沒有登錄,直接返回null

-對是否臨近過期進行判斷

/**
 	 * 獲取指定token[臨時過期]剩余有效時間 (單位: 秒)
 	 * @param tokenValue 指定token 
 	 * @return token[臨時過期]剩余有效時間
 	 */
 	public long getTokenActivityTimeoutByToken(String tokenValue) {
 		// 如果token為null , 則返回 -2
 		if(tokenValue == null) {
 			return SaTokenDao.NOT_VALUE_EXPIRE;
 		}
 		// 如果設置了永不過期, 則返回 -1 
 		if(getConfig().getActivityTimeout() == SaTokenDao.NEVER_EXPIRE) {
 			return SaTokenDao.NEVER_EXPIRE;
 		}
 		// ------ 開始查詢 
 		// 獲取相關數據 
 		String keyLastActivityTime = splicingKeyLastActivityTime(tokenValue);
 		String lastActivityTimeString = SaManager.getSaTokenDao().get(keyLastActivityTime);
 		// 查不到,返回-2 
 		if(lastActivityTimeString == null) {
 			return SaTokenDao.NOT_VALUE_EXPIRE;
 		}
 		// 計算相差時間
 		long lastActivityTime = Long.valueOf(lastActivityTimeString);
 		long apartSecond = (System.currentTimeMillis() - lastActivityTime) / 1000;
 		long timeout = getConfig().getActivityTimeout() - apartSecond;
 		// 如果 < 0, 代表已經過期 ,返回-2 
 		if(timeout < 0) {
 			return SaTokenDao.NOT_VALUE_EXPIRE;
 		}
 		return timeout;
 	}

該方法對傳入的token的相應情況設定了不同的返回值

getTokenActivityTimeoutByToken()的返回值與常量(-2)
/** 常量(在對不存在的key獲取剩余存活時間時返回此值) */ public static final long NOT_VALUE_EXPIRE = -2;
進行比對,如果相等,即該用戶的token為空(未登錄),或者滿足剩余時間不足的條件,直接返回null

-最后返回loginId
如果可以執行到此,證明loginId已經是個正常的賬號id了 ,直接返回loginId即可。


END


免責聲明!

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



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