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即可。