SaToken学习笔记-01

时间:2023-01-04 18:41:34

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