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

SaToken学习笔记-01的更多相关文章

  1. 软件测试之loadrunner学习笔记-01事务

    loadrunner学习笔记-01事务<转载至网络> 事务又称为Transaction,事务是一个点为了衡量某个action的性能,需要在开始和结束位置插入一个范围,定义这样一个事务. 作 ...

  2. C++ GUI Qt4学习笔记01

    C++ GUI Qt4学习笔记01   qtc++signalmakefile文档平台 这一章介绍了如何把基本的C++只是与Qt所提供的功能组合起来创建一些简单的图形用户界面应用程序. 引入两个重要概 ...

  3. SaToken学习笔记-04

    SaToken学习笔记-04 如果有问题,请点击:传送门 角色认证 在sa-token中,角色和权限可以独立验证 // 当前账号是否含有指定角色标识, 返回true或false StpUtil.has ...

  4. SaToken学习笔记-03

    SaToken学习笔记-03 如果排版有问题,请点击:传送门 核心思想 所谓权限验证,验证的核心就是一个账号是否拥有一个权限码 有,就让你通过.没有?那么禁止访问! 再往底了说,就是每个账号都会拥有一 ...

  5. SaToken学习笔记-02

    SaToken学习笔记-02 如果排版有问题,请点击:传送门 常用的登录有关的方法 - StpUtil.logout() 作用为:当前会话注销登录 调用此方法,其实做了哪些操作呢,我们来一起看一下源码 ...

  6. Redis:学习笔记-01

    Redis:学习笔记-01 该部分内容,参考了 bilibili 上讲解 Redis 中,观看数最多的课程 Redis最新超详细版教程通俗易懂,来自 UP主 遇见狂神说 1. Redis入门 2.1 ...

  7. PHP 学习笔记 01

    例子: 为什么要学PHP 主观原因: 前段时间在学校处理了毕业的一些事情,回到上海后开始了找工作的旅程.意向工作是WPF开发或者ASP.NET 作为后端的WEB开发. 陆陆续续一直在面试,其中有一家公 ...

  8. vue.js 2.0 官方文档学习笔记 —— 01. vue 介绍

    这是我的vue.js 2.0的学习笔记,采取了将官方文档中的代码集中到一个文件的形式.目的是保存下来,方便自己查阅. !官方文档:https://cn.vuejs.org/v2/guide/ 01. ...

  9. xml基础学习笔记01

    注意:刚刚看了网上对于XML中的标签,节点和元素?到底应该怎么表述?起初我也有这个疑惑,现在我的想法是:下面出现node的应称作节点,节点对象.element应称作元素,毕竟这更符合英文的本意.至于标 ...

随机推荐

  1. 8、inotify和resync的优缺点

    只有对外提供访问的服务需要有端口号,本地服务无端口号: 8.1.inotify的优缺点: 1.优点: 监控文件系统事件变化,通过同步工具实现实时的数据同步 2.缺点: 并发如果大于200个文件(10- ...

  2. 不带Anchors和NMS的目标检测

    ​前言: 目标检测是计算机视觉中的一项传统任务.自2015年以来,人们倾向于使用现代深度学习技术来提高目标检测的性能.虽然模型的准确性越来越高,但模型的复杂性也增加了,主要是由于在训练和NMS后处理过 ...

  3. 案例分享:Qt西门子机床人机界面以及数据看板定制(西门子通讯,mysql数据库,生产信息,参数信息,信息化看板,权限控制,播放器,二维图表,参数调试界面)

    若该文为原创文章,转载请注明原文出处本文章博客地址:https://blog.csdn.net/qq21497936/article/details/118685521 长期持续带来更多项目与技术分享 ...

  4. Python管道进行数据的吞吐处理

    import multiprocessing import random import time import datetime import struct import os import getF ...

  5. java swagger ui 添加header请求头参数

    我用到的swagger 主要有三款产品,swagger editor,swagger ui 和swagger codegen. swagger editor:主要是一个本地客户端,用来自己添加api, ...

  6. python使用笔记18--写日志

    1 import nnlog 2 import traceback 3 #level:输出日志级别,debug:把所有的日志都打印出来,info:打印info以上的日志, 4 # warning:打印 ...

  7. ti

    一.选择题DCBCDCDACAACBBABACBDCBBDA二.简答题(每小题5分,共20分)1. 1)简洁紧凑,灵活方便2)运算符丰富3)数据类型丰富4)C语言是结构化语言5)语法限制较少,程序设计 ...

  8. 手把手教你在Modelarts平台上进行视频推理

    摘要:为了方便小伙伴们进行视频场景的AI应用开发,Modelarts推理平台将视频推理场景中一些通用的流程抽取出来预置在基础镜像中,小伙伴们只需要简单地编写预处理及后处理脚本,便可以像开发图片类型的A ...

  9. SpringBoot 整合 MybatisPlus 3.0

      CRUD是指在做计算处理时的增加(Create).读取查询(Retrieve).更新(Update)和删除(Delete)几个单词的首字母简写.主要被用在描述软件系统中DataBase或者持久层的 ...

  10. nginx+waf防火墙

    1.官网下载nginx源码包(nginx-1.20.0.tar.gz) 新建nginx安装目录​mkdir -p /opt/nginx​新增nginx运行用户​useradd -s /sbin/nol ...