Redis基础应用Demo

一、Redis常用命令操作

1、String类型

赋值命令:set key value

取值命令:get key

数字递增:incr key

数字递减:incr key

增加指定的整数:incrby key increment

减少指定的整数:decrby key decrement

E:\Redis>redis-cli.exe
127.0.0.1:6379> set test:name zhangsan
OK
127.0.0.1:6379> get test:name
"zhangsan"
127.0.0.1:6379> incr num  -- 自动初始化num,并返回加1后的结果
(integer) 1
127.0.0.1:6379> decr num  -- 返回减1后的结果
(integer) 0
127.0.0.1:6379> incrby num 5 -- 返回加5后的结果
(integer) 5
127.0.0.1:6379> decrby num 4 -- 返回减5后的结果
(integer) 1
127.0.0.1:6379>

2、Hash类型

赋值命令:hset key field value 返回1表示新的 Field 被设置了新值,0表示Field已经存在,用新值覆盖原有值

取值指令:hget key field

删除指令:hdel key field

127.0.0.1:6379> hset test:teacher username zhangsan -- value是hash类型包括key-value
(integer) 1
127.0.0.1:6379> hget test:teacher username
"zhangsan"
127.0.0.1:6379> hset test:person id 1
(integer) 1
127.0.0.1:6379> hget test:person id
"1"
127.0.0.1:6379> hset test:person id 2
(integer) 0
127.0.0.1:6379> hdel test:person id
(integer) 1

3、列表类型list

list可以做成栈和队列,如果是左进右出就是栈,左进左出就是队列

向列表头部添加元素:lpush key value [value … ] 插入成功后返回列表中的元素个数

向列表尾部添加元素:rpushkey value [value …] 插入成功后返回列表中的元素个数

元素从左边出栈 : lpop key 第一步是将列表左边的元素从列表中移除,第二步是返回被移除的元素值

元素从右边出栈 : rpop key 第一步是将列表右边的元素从列表中移除,第二步是返回被移除的元素值

获取列表中元素的个数:llen key

获取指定索引的元素值:lindex key index 该命令将返回链表中指定位置(index)的元素,index 是0-based

获得列表:lrange key start stop 返回指定范围内元素的列表。

127.0.0.1:6379> lpush list 1 2 3 4 5
(integer) 5
127.0.0.1:6379> lpop list
"5"
127.0.0.1:6379> llen list
(integer) 4
127.0.0.1:6379> lindex list 2
"2"
127.0.0.1:6379> rpop list
"1"

4、无序集合set

增加元素:sadd key memer 返回实际插入到集合中元素的个数

获得集合中元素个数:scard key

从集合中弹出一个元素:spop key 返回移除的成员 (任意的)

判断元素是否在集合中:sismemer key member 返回1表示已经存在,0表示不存在

127.0.0.1:6379> sadd myset 1
(integer) 1
127.0.0.1:6379> sadd myset 2 3 4
(integer) 3
127.0.0.1:6379> scard myset
(integer) 4
127.0.0.1:6379> spop myset
"2"
127.0.0.1:6379> sismember myset 3
(integer) 1
127.0.0.1:6379>

5、有序集合sorted sort

增加元素:zadd key score member [score] [member ] 添加成功返回实际插入的成员数量

获得集合中元素个数:zcard key

获得指定分数范围内的元素个数:zcount key min max 该命令用于获取分数(score)在min和max之间的成员数量

获得元素的分数:zscore key member

增加某个元素的分数:zincrby key increment member

获得元素的排名:zrank key member

127.0.0.1:6379> zadd myzset 1 a 2 b 3 c
(integer) 3
127.0.0.1:6379> zcard myzset
(integer) 3
127.0.0.1:6379> zcount myzset 1 3
(integer) 3
127.0.0.1:6379> zscore myzset a
"1"
127.0.0.1:6379> zscore myzset c
"3"
127.0.0.1:6379> zincrby myzset 2 a
"3"
127.0.0.1:6379> zscore myzset a
"3"
127.0.0.1:6379> zrank myzset a
(integer) 1
127.0.0.1:6379> zrank myzset c
(integer) 2
127.0.0.1:6379>

二、分布式共享session

实现思路:
(1) 登录页面提交用户名密码。
(2) 登录成功后生成token。Token相当于原来的sessionid,字符串,可以使用uuid。
(3) 把用户信息保存到redis。Key就是token,value就是userId。
(4) 设置key的过期时间。模拟Session的过期时间。一般一个小时。
(5) 拦截器拦截请求校验 sessionId

1、LoginReqVO 登录用户用来提交用户名和密码

@Data
public class LoginReqVO {
    @ApiModelProperty(value = "用户名")
    private String username;

    @ApiModelProperty(value = "密码")
    private String password;
}

2、LoginRespVO 将用户信息和token响应给浏览器

@Data
public class LoginRespVO {
    @ApiModelProperty(value = "用户认证凭证")
    private String token;

    @ApiModelProperty(value = "用户id")
    private String userId;
}

3、UserServiceImpl

@Service
public class UserServiceImpl implements UserService {
    @Autowired
    private SysUserMapper sysUserMapper;

    @Autowired
    private RedisTemplate redisTemplate;
    @Override
    public LoginRespVO login(LoginReqVO vo) {
        //根据提交的用户名到数据库汇总查找该用户
        SysUser sysUser = sysUserMapper.selectByUsername(vo.getUsername());

        //如果用户不存在
        if(sysUser==null){
            throw new BusinessException(4001005,"不存在该用户,请先注册");
        }

        //如果用户状态为禁用
        if(sysUser.getStatus()==2){
            throw new BusinessException(4001006,"该账号已被禁用请联系系统管理员");
        }

        //如果用户密码不正确(将明文加密后再与数据库中的密码进行比较)
 if(!PasswordUtils.matches(sysUser.getSalt(),vo.getPassword(),sysUser.getPassword())){
            throw new BusinessException(4001007,"用户名密码不匹配");
        }

        //登录成功,生成token
        String token= UUID.randomUUID().toString();

        //将token和用户信息响应给浏览器
        LoginRespVO respVO=new LoginRespVO();
        respVO.setUserId(sysUser.getId());
        respVO.setToken(token);

        //将用户信息在redis缓存中,key是token,值是用户信息,同时设置token过期时间为60s
        redisTemplate.opsForValue().set(token,sysUser.getId(),60, TimeUnit.MINUTES);
        return respVO;
    }
}

4、UserController

@RestController
@RequestMapping("/api")
@Api(tags = "用户模块")
public class UserController {

    @Autowired
    private UserService userService;

    @PostMapping("/user/login")
    @ApiOperation(value = "用户登录接口")
    public LoginRespVO login(@RequestBody LoginReqVO vo){
        return userService.login(vo);
    }
}

5、配置拦截路径:登录注册请求不能拦截,需要放行

@Configuration
public class WebAppConfig implements WebMvcConfigurer {
    @Bean
    public TokenInterceptor tokenInterceptor(){
        return new TokenInterceptor();
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(tokenInterceptor())
.addPathPatterns("/api/**").excludePathPatterns("/api/user/login","/api/user/register","/api/user/code/*");
    }
}

6、TokenInterceptor配置拦截器:对于除登录注册外的其他请求,都要进行拦截,判断用户是否处于登录状态。

public class TokenInterceptor implements HandlerInterceptor {
    @Autowired
    private RedisService redisService;

    private RedisTemplate redisTemplate;

    //在执行Controller层方法之前拦截登录请求,验证用户是否处于登录状态
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        //从请求头中获取token
        String token=request.getHeader("token");
        //判断token是否为空
        if(StringUtils.isEmpty(token)){
            throw new BusinessException(4001002,"用户凭证不能为空,请重新登录");
        }else {
            //如果token不为空,需要判断token是否有效
            if(!redisTemplate.hasKey(token)){
                throw new BusinessException(4001002,"用户凭证无效,请重新登录");
            }
        }
        return true;
    }
}

三、异地登录提醒下线

最近接到产品提的一个需求说,一个账号同时只能在一个地方登录,如果在其他地方登录则提示已在别处登录,同时,同一浏览器同时只能登录一个用户。

实现思路:

(1) 登录页面提交用户名密码。

(2) 登录成功后生成token。

(3) 把用户信息保存到redis。Key就是token,value就是userId。

(4) 设置key的过期时间。

(5) 把token存入redis,key为 userId,value 就是 token

(6) 拦截器拦截请求校验 token。

(7) 获取 userId 后再去比较 header 携带的token和redis标记的token是否一致,不一致则提示用户已经异地登录。

1、 LoginReqVO

@Data
public class LoginReqVO {
    @ApiModelProperty(value = "用户名")
    private String username;

    @ApiModelProperty(value = "密码")
    private String password;
}

2、LoginRespVO

@Data
public class LoginRespVO {
    @ApiModelProperty(value = "用户认证凭证")
    private String token;

    @ApiModelProperty(value = "用户id")
    private String userId;
}

3、 UserServiceImpl :用户登录成功后,生成token并响应给浏览器

@Service
public class UserServiceImpl implements UserService {
    @Autowired
    private SysUserMapper sysUserMapper;

    @Autowired
    private RedisTemplate redisTemplate;
    
    @Override
    public LoginRespVO login(LoginReqVO vo) {
        //根据提交的用户名到数据库汇总查找该用户
        SysUser sysUser = sysUserMapper.selectByUsername(vo.getUsername());

        //如果用户不存在
        if(sysUser==null){
            throw new BusinessException(4001005,"不存在该用户,请先注册");
        }

        //如果用户状态为禁用
        if(sysUser.getStatus()==2){
            throw new BusinessException(4001006,"该账号已被禁用请联系系统管理员");
        }

        //如果用户密码不正确
        if(!PasswordUtils.matches(sysUser.getSalt(),vo.getPassword(),sysUser.getPassword())){
            throw new BusinessException(4001007,"用户名密码不匹配");
        }

        //登录成功,生成token
        String token= UUID.randomUUID().toString();

        //将token和用户信息响应给浏览器
        LoginRespVO respVO=new LoginRespVO();
        respVO.setUserId(sysUser.getId());
        respVO.setToken(token);

        //将用户信息在redis缓存中,key是token,值是用户信息,过期时间为60分钟
        redisTemplate.opsForValue().set(token,sysUser.getId(),60, TimeUnit.MINUTES);

        //标记token,把token存入redis缓存中,key是userId,值为token,过期时间为60分钟
        redisTemplate.opsForValue().set(sysUser.getId(),token,60,TimeUnit.MINUTES);
        return respVO;
    }
}

4、UserController:

@RestController
@RequestMapping("/api")
@Api(tags = "用户模块")
public class UserController {

    @Autowired
    private UserService userService;

    @PostMapping("/user/login")
    @ApiOperation(value = "用户登录接口")
    public LoginRespVO login(@RequestBody LoginReqVO vo){
        return userService.login(vo);
    }
}

5、配置拦截器拦截路径:放行登录

@Configuration
public class WebAppConfig implements WebMvcConfigurer {
    @Bean
    public TokenInterceptor tokenInterceptor(){
        return new TokenInterceptor();
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(tokenInterceptor())
                .addPathPatterns("/api/**").excludePathPatterns("/api/user/login","/api/user/register","/api/user/code/*");
    }
}

6、配置拦截器:判断前端穿过类的token和redis缓存中标记的token是否一致

public class TokenInterceptor implements HandlerInterceptor {
    @Autowired
    private RedisService redisService;

    private RedisTemplate redisTemplate;

    //在执行Controller层方法之前拦截登录请求,验证用户是否处于登录状态
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        //从请求头中获取token
        String token=request.getHeader("token");
        //判断token是否为空
        if(StringUtils.isEmpty(token)){
            throw new BusinessException(4001002,"用户凭证不能为空,请重新登录");
        }else {
            //判断缓存中token是否过期
            if(!redisTemplate.hasKey(token)){
                throw new BusinessException(4001002,"用户凭证无效,请重新登录");
            }

            //根据键token获取值userId
            String userId= (String) redisTemplate.opsForValue().get(token);

            //如果redis中不存在userId或者浏览器传过来的token和redis缓存中的token不匹配
if(redisTemplate.hasKey(userId)&&!token.equals(redisTemplate.opsForValue().get(userId)){
                throw new BusinessException(4001002,"您的账号已经在异地登录,请重新登录");
            }
        }
        return true;
    }
}

四、注册短信验证码

短信验证码是所有项目必不可少的基础功能模块之一,假如突然有一天你领导给你布置的一个需求。在用户注册的时候要校验手机号。

要求如下:
(1) 注册的时候校验手机号
(2) 每个手机号每天最多发送五条注册短信验证码
(3) 验证码5分钟内有效。

思路:
(1) 发送前验证手机号是否符合要求。
(2) 生成短信验证码。
(3) 发送验证码到手机。
(4) 把验证码存入redis
(5) 标记手机号
(6) 注册的时候校验手机号和验证码是否正确

1、RegisterReqVO

@Data
public class RegisterReqVO {
    @ApiModelProperty(value = "账号")
    private String username;
    @ApiModelProperty(value = "手机号")
    private String phone;
    @ApiModelProperty(value = "密码")
    private String password;
    @ApiModelProperty(value = "验证码")
    private String code;
}

2、 Contant:构建redis的key

 */
public class Contant {

    /**
     * 判断是否达上线的key
     */
    public final static String REGISTER_CODE_COUNT_KEY="register-code-count-key_";

    /**
     * 验证码有效期key
     */
    public final static String REGISTER_CODE_COUNT_VALIDITY_KEY="register-code-count-validity-key_";
}

3、UserServiceImpl

@Service
public class UserServiceImpl implements UserService {
    @Autowired
    private SysUserMapper sysUserMapper;

    @Autowired
    private RedisTemplate redisTemplate;
    
    /**
     * 获取验证码
     */
    @Override
    public String getCode(String phone) {
        //验证手机号是否合法
        Pattern pattern = Pattern.compile("^1(3|4|5|7|8)\\d{9}$");
        Matcher matcher = pattern.matcher(phone);
        if(!matcher.matches()) {
            throw  new BusinessException(4001004,"手机号格式错误");
        }
        //每次获取验证码就让次数加1,判断获取手机号验证码是否超限
        long count = redisTemplate.opsForValue().increment(Contant.REGISTER_CODE_COUNT_KEY+phone,1);
        if(count>5){
            throw new BusinessException(4001004,"当日发送已达上限");
        }

        //生成6位随机数,作为验证码
        String code=generateCode();
      
        //将生成的验证码存入 redis 过期时间为 5 分钟
        redisService.set(Contant.REGISTER_CODE_COUNT_VALIDITY_KEY+phone,code,5,TimeUnit.MINUTES;
        //发送短信这里用输出模拟
        System.out.println(code);
        return code;
    }

    /**
     * 生成六位验证码
     */
    private String generateCode(){
        Random random = new Random();
        int x = random.nextInt(899999);
        String code = String.valueOf(x + 100000);
        return code;
    }

    /**
     * 用户注册
     */
    @Override
    public String register(RegisterReqVO vo) {

        //判断验证码是否有效
        if(!redisTemplate.hasKey(Contant.REGISTER_CODE_COUNT_VALIDITY_KEY+vo.getPhone()){
            throw new BusinessException(4001008,"验证码已失效请重新获取");
        }
        //校验验证码是否正确(前端传过来的验证码和redis缓存中的是否一致)
        if(!vo.getCode().equals(redisTemplate.opsForValue().get(Contant.REGISTER_CODE_COUNT_VALIDITY_KEY+vo.getPhone()))){
         throw new BusinessException(4001009,"请输入正确的验证码");
        }

        SysUser sysUser = sysUserMapper.selectByUsername(vo.getUsername());
        if(sysUser!=null){
            throw new BusinessException(4001010,"该用户名已被注册");
        }

        //构造user用户信息存入数据库中
        SysUser user=new SysUser();
        //属性复制
        BeanUtils.copyProperties(vo,user);
        user.setId(UUID.randomUUID().toString());
        user.setCreateTime(new Date());
        String salt=PasswordUtils.getSalt();
        String ecdPwd=PasswordUtils.encode(vo.getPassword(),salt);
        user.setSalt(salt);
        user.setPassword(ecdPwd);
        int i = sysUserMapper.insertSelective(user);

        if(i!=1){
            throw new BusinessException(4001011,"操作失败");
        }

        //注册成功时将redis缓存清除
        redisTemplate.delete(Contant.REGISTER_CODE_COUNT_VALIDITY_KEY+vo.getPhone());
        redisTemplate.delete(Contant.REGISTER_CODE_COUNT_KEY+vo.getPhone());

        return "注册成功";
    }
}

4、UserController

@RestController
@RequestMapping("/api")
@Api(tags = "用户模块")
public class UserController {

    @Autowired
    private UserService userService;

    @PostMapping("/user/register")
    @ApiOperation(value = "用户注册接口")
    public String register(@RequestBody RegisterReqVO vo){
        return userService.register(vo);
    }
    
    @GetMapping("/user/code/{phone}")
    public String getCode(@PathVariable("phone") String phone){
        return userService.getCode(phone);
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章