Redis缓存餐厅数据
我们来看主要的流程
很简单,就是在数据库和接口之间加了一层缓冲,在redis之前其实还可以加其他的缓存 例如 nginx的缓存
接下来,就是结合我的业务,来做缓存
我这里的业务逻辑是,按了分类的按钮,分别以不同的 分类为一组缓存数据
所以,这里的缓存粒度是分类
我设计的id如下
public class RedisContants {//用户端--------------------------------//分类下的菜品缓存public static final String DISH_CATEGORY = "dish:category:";}
为了体现粒度,我们操作redis key的时候,加上分类id
我们来看我已经写好的样子
我们需要改造的地方有几处
第一:
通过分类id获得此分类下的菜品的时候,我们要保存redis
第二:
当我们修改菜品,不管是添加 + 删除 + 修改,都要修改这里的代码
我在想其实,这里会有写耦合,redis的操作 + 数据库的操作,如果我们可以分离出来就完美了
代码
redis的工具类在我另外一个博客里边
https://blog.csdn.net/weixin_52232901/article/details/135850329
获得商品的controller修改
@RestController("userDishController")
@RequestMapping("/user/dish")
@Slf4j
@Api(tags = "菜品管理")
public class DishController {@Autowiredprivate DishService dishService;@Autowiredprivate RedisCache redisCache;@ApiOperation("根据分类id查询菜品")@GetMapping("/list")public Result getByCategoryId(Long categoryId) {log.info("根据分类id查询菜品 参数为{}",categoryId);//先去redis中查List<DishVO> dishVOs = redisCache.getCacheObject(RedisContants.DISH_CATEGORY + categoryId);if(dishVOs != null && dishVOs.size() > 0) {return Result.success(dishVOs);}//没查到redis,去mysql查,并且更新redis,设置一天的时间为过期时间dishVOs = dishService.getByCategoryIdUser(categoryId);redisCache.setCacheObject(RedisContants.DISH_CATEGORY + categoryId,dishVOs,RedisContants.DISH_CATEGORY_TTL,RedisContants.DISH_CATEGORY_TIMEUNIT);return Result.success(dishVOs);}}
public static final String DISH_CATEGORY = "dish:category:";public static final Integer DISH_CATEGORY_TTL = 24;public static final TimeUnit DISH_CATEGORY_TIMEUNIT = TimeUnit.HOURS;
修改相关数据的controller修改
这里的修改,你们可以不用看,这里只是我的业务有相关性,所以记录下来
/*** 员工管理*/
@RestController
@RequestMapping("/admin/dish")
@Slf4j
@Api(tags = "菜品管理")
public class DishController {@Autowiredprivate DishService dishService;@Autowiredprivate RedisCache redisCache;@ApiOperation("分页查询")@GetMapping("/page")public Result<PageResult> pageQuery(DishPageQueryDTO dishPageQueryDTO) {log.info("分页查询 参数为{}",dishPageQueryDTO);PageResult page = dishService.pageQuery(dishPageQueryDTO);return Result.success(page);}@ApiOperation("根据id查询菜品")@GetMapping("/{id}")public Result getByid(@PathVariable Long id) {log.info("根据id查询菜品 参数为{}",id);DishVO dish = dishService.getByid(id);return Result.success(dish);}@ApiOperation("根据分类id查询菜品")@GetMapping("/list")public Result getByCategoryId(Long categoryId) {log.info("根据分类id查询菜品 参数为{}",categoryId);List<Dish> dish = dishService.getByCategoryId(categoryId);return Result.success(dish);}@ApiOperation("修改菜品")@PutMappingpublic Result<PageResult> update(@RequestBody DishDTO dishDTO) {log.info("修改菜品 参数为{}",dishDTO);dishService.updateDish(dishDTO);//删除缓存redisCache.deleteObject(RedisContants.DISH_CATEGORY + dishDTO.getCategoryId());return Result.success();}@ApiOperation("新增菜品")@PostMappingpublic Result<PageResult> add(@RequestBody DishDTO dishDTO) {log.info("新增菜品 参数为{}",dishDTO);dishService.addDish(dishDTO);//删除缓存redisCache.deleteObject(RedisContants.DISH_CATEGORY + dishDTO.getCategoryId());return Result.success();}@ApiOperation("修改状态")@PostMapping("/status/{status}")public Result changeStatus(@PathVariable Integer status,Long id) {log.info("修改状态 参数为{},{}",status,id);dishService.changeStatus(status,id);Dish dish = dishService.getById(id);//删除缓存redisCache.deleteObject(RedisContants.DISH_CATEGORY + dish.getCategoryId());return Result.success();}@ApiOperation("删除菜品")@DeleteMappingpublic Result delete(String ids) {log.info("删除菜品 参数为{}",ids);String[] split = ids.split(",");List<Long> list = Arrays.stream(split).map((str) -> Long.parseLong(str)).collect(Collectors.toList());dishService.deleteById(list);Collection<String> keys = redisCache.keys(RedisContants.DISH_CATEGORY + "*");//删除所有的菜品redisCache.deleteObject(keys);return Result.success();}}
这里按道理来说,其实还是要给redis这样的key要至少设置时间才行,不然就会有问题
我这里的修改,比较要注意的是,我这里的keys,我在批量删除的时候,我的业务逻辑是,直接所有的缓存都删除
也就是以 dish:category:分类id,开头的所有都要删除,所以我们需要得到所有这样的keys的集合,也就是
Collection<String> keys = redisCache.keys(RedisContants.DISH_CATEGORY + "*");
通过这个,然后直接删除,通过集合的方式,直接删除
Redis实现模拟手机验证码登录
注意,我这里实现的redisCache是我封装好的redis操作类,在我另外一个博客里边,你可以直接操作redis,都没关系,我这里只是为了省事
需求分析
使用使用手机验证码登录
我们需要来捋清楚整个的流程
首先我们有三件大事要做
第一件事,完成发送验证码功能
第二件事,完成登录功能
第三件事,完成登录之后的校验功能
发送验证码功能
这个很好完成,因为我们这里是模拟短信,实际上,再实际中,我们要接入第三方短信业务,我们这里就当后端为用户端,省略实际发送的地方
流程
先校验手机号,手机号校验通过,
生成验证码
保存到redis中
返回
短信登录注册
首先依然是校验手机号
然后是从redis中获取验证码,校验验证码
然后判断用户是否是新用户如果是的化,直接快捷注册到数据库中
将用户的信息保存到redis中
最后返回用户
校验
首先是前端发带有token的header过来
然后我们从redis中查询用户
判断用户是否存在,如果存在放行
不存在,拦截!
代码实现
发送验证码
接口分析
url: /user/code
请求参数 (Body) phone
返回值: 无
UserController
/*** 发送手机验证码*/@PostMapping("code")public Result sendCode(@RequestParam("phone") String phone, HttpSession session) {return userService.sendCode(phone,session);}
IUserService
public interface IUserService extends IService<User> {/*** 发送验证码* @param phone* @param session*/Result sendCode(String phone, HttpSession session);
}
UserServiceImpl
@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {@Autowiredprivate RedisCache redisCache;/*** 发送验证码* @param phone* @param session*/@Overridepublic Result sendCode(String phone, HttpSession session) {//先校验手机号if(RegexUtils.isPhoneInvalid(phone)) {return Result.fail("校验不通过");}String code = RandomUtil.randomString(6);//存到redis中redisCache.setCacheObject(RedisConstants.LOGIN_CODE_KEY + phone,code,2, TimeUnit.MINUTES);log.info("验证码: {}",code);return Result.ok();}
}
这里的校验工具类,在我另外一篇博客里边
https://blog.csdn.net/weixin_52232901/article/details/135957348?csdn_share_tail=%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22135957348%22%2C%22source%22%3A%22weixin_52232901%22%7D
发送验证码还挺简单的,只要把验证码放到redis上就 ok,这个redis的作用就类似于session,而为什么不用session呢,就是怕我们有几个端,这样子就好用一些,类似于这种验证码的东西,最好还是放在一个服务器里边,这样就不会有一致性问题
短信登录 + 注册
接口分析
url: /user/login
请求方法: post
请求参数: Body
参数名 | 类型 | 举例 |
---|---|---|
code | String | 131222 |
phone | String | 13168885555 |
返回值: token
UserController
/*** 登录功能* @param loginForm 登录参数,包含手机号、验证码;或者手机号、密码*/@PostMapping("/login")public Result login(@RequestBody LoginFormDTO loginForm){return userService.login(loginForm);}
IUserService
public interface IUserService extends IService<User> {/*** 发送验证码* @param phone* @param session*/Result sendCode(String phone, HttpSession session);/*** 短信登录* @param loginForm* @return*/Result login(LoginFormDTO loginForm);
}
UserServiceImpl
@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {@Autowiredprivate RedisCache redisCache;@Overridepublic Result login(LoginFormDTO loginForm) {//从redis中查String code = redisCache.getCacheObject(RedisConstants.LOGIN_CODE_KEY + loginForm.getPhone());if(Objects.isNull(code)) {return Result.fail("错误!");}//先校验验证码是否相同if(!code.equals(loginForm.getCode())) {return Result.fail("验证码错误!");}//校验手机号if(RegexUtils.isPhoneInvalid(loginForm.getPhone())) {return Result.fail("手机号不正确!");}//查用户是否是新用户,是的化,创建新用户LambdaQueryWrapper<User> userWrapper = new LambdaQueryWrapper<>();userWrapper.eq(User::getPhone,loginForm.getPhone());User user = getOne(userWrapper);//是否是新用户if(Objects.isNull(user)) {user = User.builder().phone(loginForm.getPhone()).nickName(SystemConstants.USER_NICK_NAME_PREFIX + RandomUtil.randomString(10)).build();save(user);}//保存到redis中String token = UUID.randomUUID().toString();UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);//保存redisCache.setCacheObject(RedisConstants.LOGIN_USER_KEY + token,userDTO,RedisConstants.LOGIN_USER_TTL.intValue(),TimeUnit.MINUTES);return Result.ok(token);}
}
这里是先查redis中的验证码,然后校验一波,通过的化,就查询数据库是否是新用户,这里的查的方法,我用的lambda 的wrapper,这个lambda,很好用,我可以不用知道字段真正的名字是什么,而是使用User.getXXX就能获得响应的字段,建议大家学这个东西,蛮好用的
然后这里的生成一个新的User对象,是lombok的@Builder注解,可以这么构造,我觉得他很优雅,所以我就用了
最后这里的token生成,是直接用UUID生成的,我估计这里可以使用其他的token生成的方法,都可以
最后把用户对象保存到redis中,返回token
校验
校验我们得用到拦截器,这个事实上是SpringMvc的
public class RefreshTokenInterceptor implements HandlerInterceptor {private RedisCache redisCache;public RefreshTokenInterceptor(RedisCache redisCache) {this.redisCache = redisCache;}@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {String token = request.getHeader("authorization");//为空的化,拦截if(StrUtil.isBlank(token)) {response.setStatus(401);return false;}String userkey = RedisConstants.LOGIN_USER_KEY + token;UserDTO userDTO = redisCache.getCacheObject(userkey);//没查到这个用户,拦截if(Objects.isNull(userDTO)) {response.setStatus(401);return true;}//保存到ThreadLocal里边UserHolder.saveUser(userDTO);//刷新redis缓存redisCache.expire(userkey,RedisConstants.LOGIN_USER_TTL, TimeUnit.MINUTES);return true;}
}
配置
@Configuration
public class MvcConfig implements WebMvcConfigurer {@Autowiredprivate RedisCache redisCache;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new LoginInterceptor(redisCache)).excludePathPatterns("/shop/**","/voucher/**","/shop-type/**","/upload/**","/blog/hot","/user/code","/user/login");}
}
我这里的不用登录的接口都写在这
测试
我们再来粗略的捋一下整个的流程,做到心中有数
第一: 发送验证码到后端(模拟发送到用户手机),存验证码到redis中
第二: 登录,得到redis中的验证码,做一个校验,校验通过,查数据库,是否是新用户,如果是的化,快速注册到数据库中, 然后用户信息保存到redis中,最后返回token
第三: 校验,先检查是否带着token,带着token去查redis,如果有这个用户信息,同行,否则,不同行
发送验证码
输入验证码登录
登录成功!
我们测试一下是否可以过滤
没问题,过滤成功,带着autorization
可以访问个人页面说明没问题!!!
总结
这整个流程比较复杂,但是逻辑还算清楚,其中我们可以改其中的发送验证码的接口,我们可以自己接入第三方服务,然后这个验证码存储也不一定要在redis中,只要有一个数据库就行,最好是类似于redis这样的,这样就没有一致性问题,然后还有一个问题就是我这里的token,是很简单的生成,如果要生成复杂的token的化,就应该自己再写入,这里的校验还行