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
SaToken学习笔记-01的更多相关文章
- 软件测试之loadrunner学习笔记-01事务
		
loadrunner学习笔记-01事务<转载至网络> 事务又称为Transaction,事务是一个点为了衡量某个action的性能,需要在开始和结束位置插入一个范围,定义这样一个事务. 作 ...
 - C++ GUI Qt4学习笔记01
		
C++ GUI Qt4学习笔记01 qtc++signalmakefile文档平台 这一章介绍了如何把基本的C++只是与Qt所提供的功能组合起来创建一些简单的图形用户界面应用程序. 引入两个重要概 ...
 - SaToken学习笔记-04
		
SaToken学习笔记-04 如果有问题,请点击:传送门 角色认证 在sa-token中,角色和权限可以独立验证 // 当前账号是否含有指定角色标识, 返回true或false StpUtil.has ...
 - SaToken学习笔记-03
		
SaToken学习笔记-03 如果排版有问题,请点击:传送门 核心思想 所谓权限验证,验证的核心就是一个账号是否拥有一个权限码 有,就让你通过.没有?那么禁止访问! 再往底了说,就是每个账号都会拥有一 ...
 - SaToken学习笔记-02
		
SaToken学习笔记-02 如果排版有问题,请点击:传送门 常用的登录有关的方法 - StpUtil.logout() 作用为:当前会话注销登录 调用此方法,其实做了哪些操作呢,我们来一起看一下源码 ...
 - Redis:学习笔记-01
		
Redis:学习笔记-01 该部分内容,参考了 bilibili 上讲解 Redis 中,观看数最多的课程 Redis最新超详细版教程通俗易懂,来自 UP主 遇见狂神说 1. Redis入门 2.1 ...
 - PHP 学习笔记 01
		
例子: 为什么要学PHP 主观原因: 前段时间在学校处理了毕业的一些事情,回到上海后开始了找工作的旅程.意向工作是WPF开发或者ASP.NET 作为后端的WEB开发. 陆陆续续一直在面试,其中有一家公 ...
 - vue.js 2.0 官方文档学习笔记 —— 01. vue 介绍
		
这是我的vue.js 2.0的学习笔记,采取了将官方文档中的代码集中到一个文件的形式.目的是保存下来,方便自己查阅. !官方文档:https://cn.vuejs.org/v2/guide/ 01. ...
 - xml基础学习笔记01
		
注意:刚刚看了网上对于XML中的标签,节点和元素?到底应该怎么表述?起初我也有这个疑惑,现在我的想法是:下面出现node的应称作节点,节点对象.element应称作元素,毕竟这更符合英文的本意.至于标 ...
 
随机推荐
- BGV方案
			
BGV方案 SIMD技术 中国剩余定理 在<孙子算经>中有这样一个问题:"今有物不知其数,三三数之剩二(除以3余2),五五数之剩三(除以5余3),七七数之剩二(除以7余2),问物 ...
 - SpringBoot Cache 深入
			
这上一篇文章中我们熟悉了SpringBoot Cache的基本使用,接下来我们看下它的执行流程 CacheAutoConfiguration 自动装配类 根据图中标注,看到它引用了CachingCon ...
 - Binding(五):多路绑定
			
Binding不止能绑定一个源,它还能绑定多个源,这就是我们这节要讲的多路绑定:MultiBinding. 使用多路绑定跟一般的绑定还是有区别的,首先它并不能很好的在标记扩展中使用,另外,使用多路绑定 ...
 - Python迭代器和生成器你学会了吗?
			
在了解什么是迭代器和生成器之前,我们先来了解一下容器的概念.对于一切皆对象来说,容器就是对象的集合.例如列表.元祖.字典等等都是容器.对于容器,你可以很直观地想象成多个元素在一起的单元:而不同容器的区 ...
 - Python之面向对象编程【小明跑步】、【置办家具】
			
#!usr/bin/python 2 #encoding=utf-8 3 #-----------------小明跑步------------- 4 #1.小明体重75.0公斤 5 #2.小明每次跑步 ...
 - [小技巧] gcc 编译选项-###
			
原文译至:http://elinux.org/GCC_Tips 的一小部分. -###编译选项用于查看编译的过程 gcc -### <你的命令行的其他部分放在这里> 你运行的GCC其是一系 ...
 - git使用---安装,提交,回退,修改,分支,标签等
			
下面是对git的各种使用及命令的基础使用,来自廖雪峰老师的git教程,这个收录下,作为git的使用总结. github上面地址为:https://github.com/Zhangguoliu/lear ...
 - 史上最全的Nginx配置文档
			
Nginx是一个异步框架的Web服务器,也可以用作反向代理,负载平衡器 和 HTTP缓存.该软件由Igor Sysoev 创建,并于2004年首次公开发布.同名公司成立于2011年,以提供支持.Ngi ...
 - 永恒之蓝ms17_010漏洞复现
			
1.什么是永恒之蓝 永恒之蓝(Eternal Blue)爆发于2017年4月14日晚,是一种利用Windows系统的SMB协议漏洞来获取系统的最高权限,以此来控制被入侵的计算机. 2.SMB协议 SM ...
 - Django基础06篇 分页
			
1.导入Django自带的分页类 from django.core.paginator import Paginator 2.分页类的使用 def index(request): # return H ...