需求
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();
    }
Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐