什么是Redis
Redis是一个使用ANSI C编写的开源、包含多种数据结构、支持网络、基于内存、可选持久性的键值对存储数据库。
特性
Redis基于内存运行,性能高效
支持分布式理论上可以无限拓展
key与value的存储形式
Mysql与Redis的区别
Mysql
属于关系型数据库
优点:易于维护,使用方便,支持sql。
缺点:读写性能比较差,固定的表结构
Redis
属于Nosql(非关系型数据库)
优点:格式灵活,速度快,高可拓展性,成本低
缺点:不提供sql支持,学习和使用成本高,无事务支持,数据结构相对复杂
接下来我们各取所优来完成一个实战案例吧
实战案例:
我们使用Redis+Mybatis-Plus
实现前台登录注册的功能
步骤如下:
- 将手机号和验证码保存到Redis中
- 注册时验证注册验证码
- 用户注册
- 用户名和密码登录
- 登录时将手机号+验证码保存到Redis中
- 手机号+验证码登录
- 获取登录用户名
- 获取登录用户信息
技术栈:
- zookeeper注册中心
- dubbo分布式调用
- Mybatis-Plus操作数据库
- Redis非关系型数据库
- SpringBoot
- JWT分布式鉴权
- 阿里云短信测试
案例说明:
redis可以保存时效性数据,即可以给数据设置过期时间,所以我们会将验证码保存到redis中而不是mysql中,当用户发送验证码后,在redis中查找该验证码,如果用户输入的验证码和redis中保存的验证码一致则进行下一步,输入用户名和密码注意:手机号和用户名都不能重复,客户端传来的明文密码我们通过MD5的方式对它进行加密
,注册成功后将用户信息保存到数据库中。
代码参考:
在Linux中安装zookeeper、dubbo、redis服务
生成验证码工具类:
import java.util.Random;
public class RandomUtil {
public static String buildCheckCode(int digit){
String str = "0123456789";
Random random = new Random();
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0;i < digit;i++){
char ch = str.charAt(random.nextInt(str.length())); // 返回一个[0,str.length)之间的值包括0但不包括str.length
stringBuilder.append(ch);
}
return stringBuilder.toString();
}
}
接口:
// 前台用户服务
public interface ShoppingUserService {
// 用户注册时,将手机号和验证码保存到redis中
void saveRegisterCheckCode(String phone,String checkCode);
// 注册时验证注册验证码
void registerCheckCode(String phone,String checkCode);
// 注册
void register(ShoppingUser shoppingUser);
// 用户名密码登录
String loginPassword(String username,String password);
// 登录时将手机号和验证码保存到redis中
void saveLoginCheckCode(String phone,String checkCode);
// 手机号验证码登录
String loginCheckCode(String phone,String checkCode);
// 获取登录用户名
String getName(String token);
// 获取登录用户
ShoppingUser getLoginUser(String token);
}
接口实现类:
获取登录用户信息这里使用的时JWT
来完成的,通常在用户登录成功后网站上方会显示欢迎回来:XXX
这时候大家可能会有些疑问,为什么不用session来保存登录用户信息呢?
注意:
session保存在服务器中,我们这个项目采用的时dubbo+zookeeper
这样的一个分布式调用,该项目具有很多子系统,session在多台服务器间是无法访问的。
JWT这个技术,是一种令牌生成算法,我们将生成的令牌返回给前端,前端配置在访问其他模块时请求头带上令牌,我们可以解析令牌拿到用户名以及用户信息,JWT中的token还具备时效性,可以设置保存的时间
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.itbaizhan.shopping_common.exception.BusException;
import com.itbaizhan.shopping_common.pojo.ShoppingUser;
import com.itbaizhan.shopping_common.result.CodeEnum;
import com.itbaizhan.shopping_common.service.ShoppingUserService;
import com.itbaizhan.shopping_common.util.JWTUtil;
import com.itbaizhan.shopping_common.util.Md5Util;
import com.itbaizhan.shopping_user_service.mapper.ShoppingUserMapper;
import org.apache.dubbo.config.annotation.DubboService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import java.util.List;
import java.util.concurrent.TimeUnit;
@DubboService
public class ShoppingUserServiceImpl implements ShoppingUserService {
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private ShoppingUserMapper shoppingUserMapper;
/**
* 注册时将手机号和验证码保存到redis中
* @param phone 手机号
* @param checkCode 验证码
*/
@Override
public void saveRegisterCheckCode(String phone, String checkCode) {
ValueOperations valueOperations = redisTemplate.opsForValue();
valueOperations.set("registerCode:"+phone,checkCode,300, TimeUnit.SECONDS);
}
@Override
public void registerCheckCode(String phone, String checkCode) {
ValueOperations valueOperations = redisTemplate.opsForValue();
String code = (String) valueOperations.get("registerCode:"+phone);
if(!checkCode.equals(code)){
throw new BusException(CodeEnum.REGISTER_CODE_ERROR);
}
}
@Override
public void register(ShoppingUser shoppingUser) {
// 验证手机号是否重复
String phone = shoppingUser.getPhone();
QueryWrapper<ShoppingUser> queryWrapper = new QueryWrapper();
queryWrapper.eq("phone",phone);
List<ShoppingUser> shoppingUsers = shoppingUserMapper.selectList(queryWrapper);
if(shoppingUsers!=null && shoppingUsers.size() > 0){
throw new BusException(CodeEnum.REGISTER_REPEAT_PHONE_ERORR);
}
// 验证用户名是否重复
String name = shoppingUser.getName();
QueryWrapper<ShoppingUser> queryWrapper1 = new QueryWrapper();
queryWrapper1.eq("username",name);
List<ShoppingUser> users = shoppingUserMapper.selectList(queryWrapper1);
if(users != null && users.size() > 0){
throw new BusException(CodeEnum.REGISTER_REPEAT_USERNAME_ERORR);
}
// 设置用户状态为Y
shoppingUser.setStatus("Y");
// 对密码加密
shoppingUser.setPassword(Md5Util.encode(shoppingUser.getPassword()));
// 新增用户
shoppingUserMapper.insert(shoppingUser);
}
@Override
public String loginPassword(String username, String password) {
QueryWrapper<ShoppingUser> queryWrapper = new QueryWrapper();
queryWrapper.eq("username",username);
ShoppingUser shoppingUser = shoppingUserMapper.selectOne(queryWrapper);
if(shoppingUser == null){
throw new BusException(CodeEnum.LOGIN_NAME_PASSWORD_ERROR);
}
boolean verify = Md5Util.verify(password, shoppingUser.getPassword());
if(!verify){
throw new BusException(CodeEnum.LOGIN_NAME_PASSWORD_ERROR);
}
return JWTUtil.sign(shoppingUser);
}
// 登录时将手机号和验证码保存到redis中
@Override
public void saveLoginCheckCode(String phone, String checkCode) {
ValueOperations valueOperations = redisTemplate.opsForValue();
valueOperations.set("loginCode:"+phone,checkCode,300, TimeUnit.SECONDS);
}
// 验证手机号和验证码
@Override
public String loginCheckCode(String phone, String checkCode) {
ValueOperations valueOperations = redisTemplate.opsForValue();
String code = (String) valueOperations.get("loginCode:" + phone);
if(!checkCode.equals(code)){
throw new BusException(CodeEnum.LOGIN_CHECK_CODE_ERROR);
}
QueryWrapper<ShoppingUser> queryWrapper = new QueryWrapper();
queryWrapper.eq("phone",phone);
ShoppingUser shoppingUser = shoppingUserMapper.selectOne(queryWrapper);
return JWTUtil.sign(shoppingUser);
}
@Override
public String getName(String token) {
// 解析令牌获取用户名
String username = JWTUtil.verify(token);
return username;
}
@Override
public ShoppingUser getLoginUser(String token) {
String username = JWTUtil.verify(token);
QueryWrapper<ShoppingUser> queryWrapper = new QueryWrapper();
queryWrapper.eq("username",username);
ShoppingUser shoppingUser = shoppingUserMapper.selectOne(queryWrapper);
return shoppingUser;
}
}
发送短信服务:
发送短信服务需要自己去申请了,获取AccessKeyID
和AccessKeySecret
即可。详细请参照:阿里云官网