秋招结束 准备学习开源代码提升自己
Sa-token:https://github.com/dromara/Sa-Token?tab=readme-ov-file
模型解析
传统HttpSession:客户端在cookie中保存sessionId,到服务端去查询,因此两个客户端登陆相同账号分配不同的sessionId。
Sa-token:以账号为维度,仅登陆时分配,而并非握手时分配。
地址如下:https://sa-token.cc/doc.html#/fun/session-model
以下是核心源码解析
读取配置
读取配置的代码统一收口
public SaTokenConfig getConfigOrGlobal() {
SaTokenConfig cfg = getConfig();
if(cfg != null) {
return cfg;
}
return SaManager.getConfig();
}
单例实现配置只读取一次,且唯一
public static SaTokenConfig getConfig() {
if (config == null) {
synchronized (SaManager.class) {
if (config == null) {
setConfigMethod(SaTokenConfigFactory.createConfig());
}
}
}
return config;
}
从配置文件的路径读取转化成Map
private static Map<String, String> readPropToMap(String propertiesPath) {
Map<String, String> map = new HashMap<>(16);
try {
InputStream is = SaTokenConfigFactory.class.getClassLoader().getResourceAsStream(propertiesPath);
if (is == null) {
return null;
}
Properties prop = new Properties();
prop.load(is);
for (String key : prop.stringPropertyNames()) {
map.put(key, prop.getProperty(key));
}
} catch (IOException e) {
throw new SaTokenException("配置文件(" + propertiesPath + ")加载失败", e).setCode(SaErrorCode.CODE_10021);
}
return map;
}
反射创建Config对象
private static Object initPropByMap(Map<String, String> map, Object obj) {
if (map == null) {
map = new HashMap<>(16);
}
// 1、取出类型
Class<?> cs;
if (obj instanceof Class) {
// 如果是一个类型,则将obj=null,以便完成静态属性反射赋值
cs = (Class<?>) obj;
obj = null;
} else {
// 如果是一个对象,则取出其类型
cs = obj.getClass();
}
// 2、遍历类型属性,反射赋值
for (Field field : cs.getDeclaredFields()) {
String value = map.get(field.getName());
if (value == null) {
// 如果为空代表没有配置此项
continue;
}
try {
Object valueConvert = SaFoxUtil.getValueByType(value, field.getType());
field.setAccessible(true);
field.set(obj, valueConvert);
} catch (IllegalArgumentException | IllegalAccessException e) {
throw new SaTokenException("属性赋值出错:" + field.getName(), e).setCode(SaErrorCode.CODE_10022);
}
}
return obj;
}
会话的操作
session的写入与读取在SaTokenDao接口中获取,根据实现策略不同(例如存储在redis中),有15种不同的实现类。
/**
* 获取 SaSession,如无返空
* @param sessionId sessionId
* @return SaSession
*/
default SaSession getSession(String sessionId) {
return (SaSession)getObject(sessionId);
}
/**
* 写入 SaSession,并设定存活时间(单位: 秒)
* @param session 要保存的 SaSession 对象
* @param timeout 过期时间(单位: 秒)
*/
default void setSession(SaSession session, long timeout) {
setObject(session.getId(), session, timeout);
}
生成唯一token的算法如下:
// 4、如果代码走到此处,说明未能成功复用旧 token,需要根据算法新建 token
return SaStrategy.instance.generateUniqueToken.execute(
"token",
getConfigOfMaxTryTimes(),
() -> {
return createTokenValue(id, loginModel.getDeviceOrDefault(), loginModel.getTimeout(), loginModel.getExtraData());
},
tokenValue -> {
return getLoginIdNotHandle(tokenValue) == null;
}
);
/**
* 生成唯一式 token 的算法
*/
public SaGenerateUniqueTokenFunction generateUniqueToken = (elementName, maxTryTimes, createTokenFunction, checkTokenFunction) -> {
// 为方便叙述,以下代码注释均假设在处理生成 token 的场景,但实际上本方法也可能被用于生成 code、ticket 等
// 循环生成
for (int i = 1; ; i++) {
// 生成 token
String token = createTokenFunction.get();
// 如果 maxTryTimes == -1,表示不做唯一性验证,直接返回
if (maxTryTimes == -1) {
return token;
}
// 如果 token 在DB库查询不到数据,说明是个可用的全新 token,直接返回
if (checkTokenFunction.apply(token)) {
return token;
}
// 如果已经循环了 maxTryTimes 次,仍然没有创建出可用的 token,那么抛出异常
if (i >= maxTryTimes) {
throw new SaTokenException(elementName + " 生成失败,已尝试" + i + "次,生成算法过于简单或资源池已耗尽");
}
}
};
监听器
每次用户在执行创建会话时均会调取监听中心的方法,注册事件,是硬编码写在代码中的,并非注解方式,如下:
public SaSession(String id) {
this.id = id;
this.createTime = System.currentTimeMillis();
// $$ 发布事件
SaTokenEventCenter.doCreateSession(id);
}