• 【开发篇】十、Spring缓存:手机验证码的生成与校验


    1、缓存

    • 缓存是一种介于数据永久存储介质与数据应用之间的数据临时存储介质
    • 使用缓存可以有效的减少低速数据读取过程的次数(例如磁盘IO),提高系统性能
    • 缓存不仅可以用于提高永久性存储介质的数据读取效率,还可以提供临时的数据存储空间

    在这里插入图片描述

    注意最后这条,缓存的不一定就是从持久层数据库来的数据,也可以是程序运行的临时数据,理解别太狭义,如手机验证码,对应于下图的Cache1:

    在这里插入图片描述

    2、用HashMap模拟自定义缓存

    传统的一个查询接口,每次都查需要去和数据库交互,数据库压力大且容易产生性能瓶颈:

    在这里插入图片描述
    在这里插入图片描述

    在Service层引入一个Map类型的非局部变量,来模拟缓存:(这种使用一个Map来充当临时缓冲池的思想需要学习

    private HashMap<Integer,Book> cache = new HashMap<>();
    
    @Override
    public Book getBookById(Integer id){
    
    	Book book = cache.get(id);
    	
    	if(book == null){
    		book = bookDao.selectById(id);
    		cache.put(id,book);
    	}
    	
    	return book;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    此时调用之前的接口,除了第一次需要查数据库,后面直到服务重启,变量被回收,都不用再去查数据库。同理,写个手机验证码的demo代码:

    在这里插入图片描述

    看下效果:

    在这里插入图片描述
    在这里插入图片描述

    3、SpringBoot提供缓存的使用

    首先导入缓存技术对应的starter:

    <dependency>    
    	<groupId>org.springframework.bootgroupId>    
    	<artifactId>spring-boot-starter-cacheartifactId>
    dependency>
    
    • 1
    • 2
    • 3
    • 4

    配置配或者直接启动类上加@EnableCaching启动缓存

    @SpringBootApplication
    
    @EnableCaching  //!!!!
    
    public class SpringbootApplication {    
    
    	public static void main(String[] args) {
    	
    		SpringApplication.run(SpringbootApplication.class, args);    
    		
    	}
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    此时,上面用HashMap模拟缓存的Service码就可改为:

    @Cacheable(value="cacheSpace",key="#id")
    
    public Book getById(Integer id) {    
    
    	return bookDao.selectById(id);
    	
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    即先从cacheSpace这块缓存空间查看,有则返回,没有再查持久层@Cacheable注解,即以属性里的key值为键,以方法的返回值为value,既存又取,有则取,无则查后存。 以上是背后使用的缓存技术是SpringBoot默认的Simple。SpringBoot提供的缓存技术除了提供默认的缓存方案外,还可以对其他缓存技术进行整合,统一接口,方便缓存技术的开发与管理:

    • Generic
    • JCache
    • Ehcache
    • Hazelcast
    • Infinispan
    • Couchbase
    • Redis
    • Caffeine
    • Simple(默认)
    • memcached

    4、手机验证码案例完善

    引入@Cacheable注解后,重写并完善下这个验证码的案例。先写个工具类来生成验证码,这个工具类写的不优雅,重点备份下补0串的这种思想吧:

    public class CodeUtil {
    
        private static final String[] patch = {"000000","00000","0000","000","00","0",""};
    
        public static String generatorCode(String tel){
            int hash = tel.hashCode();
            int encryption = 20230927;  //加密常量码
            long result = hash ^ encryption;  //第一次加密,此时,我加密码写死,同一个电话号码的验证码会一直不变
            long nowTime = System.currentTimeMillis();
            result = result ^ nowTime;  //引入时间,二次加密
            long code = Math.abs(result % 1000000);  //取后六位
            String codeStr = code + "";  //也可能不够6位,比如000147,则上面的long code就是147
            return patch[codeStr.length()] + codeStr;  //加一个补0数组,根据字符串长度来取对应的补0串,最多补5个0,最少不补,这里为了适配数组下标从0开始,给array[0]给个值
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    注意根据字符串长度来取数组中对应的补0串时,长度为6,则取array[6],但数组下标从0开始,会越界:

    在这里插入图片描述

    此时有两种思路处理,一种是上面的,给数组加个下标为0的值,此时str.length()就和数组下标对应上了,也可以直接让str.length()-1:

    return patch[codeStr.length() -1 ] + codeStr;
    
    • 1

    Service层,生成验证码,并存缓存,不能用@Cacheable,它既存又取,这样验证码在缓存失效前都一样,可改为@CachePut只存不取

    @Service
    @Slf4j
    public class MsgServiceImpl implements MsgService {
    
        @Resource
        @Lazy  //解决下循环以来的问题
        MsgService msgService;
    
        /**
         * 返回验证码
         * @param tel
         * @return
         */
        @Override
        //@Cacheable(value = "telCode",key = "#tel")
        @CachePut(value = "telCode",key = "#tel")
        public String getCheckCode(String tel) {
            String checkCode = CodeUtil.generatorCode(tel);
            return checkCode;
        }
    
        /**
         * 校验验证码
         * @param tel
         * @param checkCode
         * @return
         */
        @Override
        public boolean verify(String tel, String checkCode) {
    
            return checkCode.equals(msgService.getCode(tel));
        }
    
        @Override
        @Cacheable(value = "telCode",key = "#tel")
        public String getCode(String tel){
            return null;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    关于校验方法里的取验证码,之前写需求用redis,直接redisTemplate.get了,但这里底层缓存技术是Simple,可单独写个方法,上面加@Caceable注解,然后校验方法里调用这个获取缓存值的方法即可实现(此时会有@Cacheable注解失效问题,注意出去绕一圈拿代理对象来调用)。还是利用了@Cacheable注解的特点,先查有无这个key,有就把value当作这个方法的返回值,没有再执行代码方法体。

    @Cacheable(value = "telCode",key = "#tel")
    public String getCode(String tel){
        return null;
    }
    
    • 1
    • 2
    • 3
    • 4

    Controller层随便写就行:

    @RestController
    @RequestMapping("/msg")
    public class MsgController {
    
        @Resource
        MsgService msgService;
    
        @GetMapping("{tel}")
        public String getCode(@PathVariable String tel){
            return msgService.getCheckCode(tel);
        }
    
        @PostMapping
        public Boolean verifyCode(@RequestParam String tel,String code){
            return msgService.verify(tel,code);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    重点关注:

    • 工具类中加密使用了一个补0数组的思想
    • @Cacheable既存又取,这样验证码在缓存失效前都一样,可改为@CachePut只存不取
    • @Cacheable注解因方法内部调用而失效的解决
    • 不用Redis,再取数据时,引入了一个方法,加@Cacheable注解并返回null,即查到这个key的值就返回,否则返回方法的返回值,即null
  • 相关阅读:
    好的代码是优质资产、莫让代码成为负债
    深度强化学习中Double DQN算法(Q-Learning+CNN)的讲解及在Asterix游戏上的实战(超详细 附源码)
    Redis - 分布式锁和事务
    独家 | 如何比较两个或多个分布形态(附链接)
    基础--Concurrenthashmap的实现原理分析
    Java判断时间间隔来决定任务执行
    最高分辨率,阜时全固态激光雷达SPAD芯片FL6031技术全解析
    如果再来一次,你还会选择互联网么?
    QT软件开发-基于FFMPEG设计视频播放器-支持软解与硬解(三)
    Nginx 惊群的原因和解决方案
  • 原文地址:https://blog.csdn.net/llg___/article/details/133350760