文章目录
一、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);
}
}