需求
1、登录有效期固定7天
2、活体验证成功有效期通过字典配置,默认为7天
3、活体验证失败次数通过字典配置,默认为3次,超过3次,账户锁定
24小时有效期,用户启用后,删除缓存;
一旦验证成功,删除缓存;
4、用户设备绑定数量通过字典配置,默认2台
实现技术方案
1、利用redis的sortedSet有序集合,绑定多台设备,以及踢出功能
2、利用redis的hash实现登录状态缓存
一、封装sortedSet api
@Override
public Boolean exists(String key, String member) {
Long rank = redisTemplate.opsForZSet().rank(key, member);
return Objects.nonNull(rank);
}
@Override
public Boolean zadd(String key, String value, Double score) {
return redisTemplate.opsForZSet().add(key, value, score);
}
@Override
public Long zsize(String key) {
return redisTemplate.opsForZSet().size(key);
}
@Override
public Double zscore(String key, String member) {
return redisTemplate.opsForZSet().score(key, member);
}
@Override
public Long remove(String key, String... members) {
return redisTemplate.opsForZSet().remove(key, members);
}
@Override
public Long removeRange(String key, long start, long end) {
return redisTemplate.opsForZSet().removeRange(key, start, end);
}
2、踢出逻辑
//超出指定绑定数量且未绑定当前设备
Long bindingSize = cacheService.zsize(deviceBindingKey);
if (bindingSize >= maxDeviceBindingCount) {
//如果设置为2,保留最近的1个设备,然后添加当前设备
Long deviceBindingCount = cacheService.zsize(deviceBindingKey);
long startIndex = 0L;
if (maxDeviceBindingCount > 1) {
startIndex = deviceBindingCount - maxDeviceBindingCount + 2;
}
Set<String> set = redisTemplate.opsForZSet().range(deviceBindingKey, startIndex, -1);
List<String> memberList = set.stream().collect(Collectors.toList());
cacheService.removeRange(deviceBindingKey, startIndex, -1);
tokenService.disableToken(userId, memberList);
cacheService.zadd(deviceBindingKey, deviceId, Double.valueOf(tokenService.getDeviceExpireTime()));
} else if (bindingSize > 0) {
cacheService.zadd(deviceBindingKey, deviceId, Double.valueOf(tokenService.getDeviceExpireTime()));
} else if (bindingSize == 0) {
//不存在,可能是缓存故障导致,从数据库恢复
ResultDO<List<UserBindDevice>> validUserBindDevice = userBindDeviceService.getValidUserBindDevice(userId);
List<UserBindDevice> userBindDeviceList = validUserBindDevice.getModule();
List<UserBindDevice> targetUserBindDeviceList = null;
if (CollUtil.isNotEmpty(userBindDeviceList)) {
if (userBindDeviceList.size() >= maxDeviceBindingCount) {
targetUserBindDeviceList = userBindDeviceList.subList(0, Integer.valueOf(maxDeviceBindingCount.toString()));
} else {
targetUserBindDeviceList = userBindDeviceList;
}
// 加入缓存
targetUserBindDeviceList.stream().forEach(userBindDevice -> cacheService.zadd(deviceBindingKey, userBindDevice.getDeviceId(), Double.valueOf(userBindDevice.getExpireTime().getTime())));
}
// 绑定当前设备,覆盖超时时间
cacheService.zadd(deviceBindingKey, deviceId, Double.valueOf(tokenService.getDeviceExpireTime()));
}
单机测试sortedSet
/**
* Zset有序集合和Set集合都是string类型元素的集合,且不允许重复的元素。
* 不同的是Zset的每个元素都会关联一个double类型的分数,用于从小到大进行排序。
* 集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1)。
* 集合中最大的成员数为 2^32 - 1 (4294967295, 每个集合可存储40多亿个元素)。
*/
public static void operateZset() {
Jedis jedis = new Jedis("ftcs-reg", 6379);
jedis.auth("Za123456");
log.info("jedis.ping (): " + jedis.ping());
jedis.del("salary");
Map<String, Double> members = new HashMap<String, Double>();
members.put("u01", 1000.0);
members.put("u02", 2000.0);
members.put("u03", 3000.0);
members.put("u04", 13000.0);
members.put("u05", 23000.0);
//批量添加元素,类型为java map映射表
jedis.zadd("salary", members);
//type类型Zset
log.info("jedis.type(): " + jedis.type("salary"));
//获取集合元素的个数
log.info("jedis.zcard(): " + jedis.zcard("salary"));
//按照下标[起,止]遍历元素
log.info("jedis.zrange(): " + jedis.zrange("salary", 0, -1));
//按照下标[起,止]倒序遍历元素
log.info("jedis.zrevrange(): " + jedis.zrevrange("salary", 0, -1));
//按照分数(薪资)[起,止]遍历元素
log.info("jedis.zrangeByScore(): " + jedis.zrangeByScore("salary", 1000, 10000));
//按照薪资[起,止]遍历元素,带分数返回
Set<Tuple> res0 = jedis.zrangeByScoreWithScores("salary", 1000, 10000);
for (Tuple temp : res0) {
log.info("Tuple.get(): " + temp.getElement() + " -> " + temp.getScore());
}
//按照分数[起,止]倒序遍历元素
log.info("jedis.zrevrangeByScore(): " + jedis.zrevrangeByScore("salary", 1000, 4000));
//获取元素[起,止]分数区间的元素数量
log.info("jedis.zcount(): " + jedis.zcount("salary", 1000, 4000));
//获取元素score值:薪资
log.info("jedis.zscore(): " + jedis.zscore("salary", "u01"));
//获取元素的下标
log.info("jedis.zrank(u01): " + jedis.zrank("salary", "u01"));
//倒序获取元素的下标
log.info("jedis.zrevrank(u01): " + jedis.zrevrank("salary", "u01"));
//删除元素
log.info("jedis.zrem(): " + jedis.zrem("salary", "u01", "u02"));
//删除元素,通过下标范围
log.info("jedis.zremrangeByRank(): " + jedis.zremrangeByRank("salary", 0, 1));
//删除元素,通过分数范围
log.info("jedis.zremrangeByScore(): "
+ jedis.zremrangeByScore("salary", 20000, 30000));
//按照下标[起,止]遍历元素
log.info("jedis.zrange(): " + jedis.zrange("salary", 0, -1));
Map<String, Double> members2 = new HashMap<String, Double>();
members2.put("u11", 1136.0);
members2.put("u12", 2212.0);
members2.put("u13", 3324.0);
//批量添加元素
jedis.zadd("salary", members2);
//增加指定分数
log.info("jedis.zincrby(10000): " + jedis.zincrby("salary", 10000, "u13"));
//按照下标[起,止]遍历元素
log.info("jedis.zrange(): " + jedis.zrange("salary", 0, -1));
jedis.close();
}