springboot 实现验证码 springboot验证码实现
淘宝搜:【天降红包222】领超级红包,京东搜:【天降红包222】
淘宝互助,淘宝双11微信互助群关注公众号 【淘姐妹】
package com.lzzy.meet.common.kaptcha; import com.google.code.kaptcha.Constants; import com.google.code.kaptcha.Producer; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.Re【【微信】】; import javax.imageio.ImageIO; import javax.【【微信】】.ServletOutputStream; import javax.【【微信】】.http.HttpServletRequest; import javax.【【微信】】.http.HttpServletResponse; import java.awt.image.【【微信】】; @Slf4j @Controller @Re【【微信】】("kaptcha") public class KaptchaController { @Autowired pri【【微信】】; @GetMapping("kaptcha-image") public 【【微信】】(HttpServletRequest request, HttpSer【【微信】】) throws Exception { response.setDateHeader("Expires", 0); response.setHeader("Cache-Control", "no-store, no-cache, must-re【【微信】】"); response.addHeader("Cache-Control", "post-check=0, pre-check=0"); response.setHeader("Pragma", "no-cache"); response.setContentType("image/jpeg"); String capText=producer.createText(); log.info("******************当前验证码为:{}******************", capText); // 将验证码存于session中 request.【【微信】】().setAttribute(Constants.KAPTCHA_SESSION_KEY, capText); 【【微信】】 bi=producer.createImage(capText); Ser【【微信】】=response.getOutputStream(); // 向页面输出验证码 ImageIO.write(bi, "jpg", out); try { // 清空缓存区 out.flush(); } finally { // 关闭输出流 out.close(); } } }
Redis学习笔记②实战篇_黑马点评项目
黑马react若文章内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系博主删除。
- 资料链接:(提取码:eh11)
- 在线视频:(视频合集共 175P,总时长:42:45:37)
- 项目源码地址:
- 这个是视频作者的代码地址
- 写该系列博客的目的旨在学习巩固知识,方便个人在线阅览。(记录学习时刻)
- 博客的内容主要来自黑马程序员官方免费公开提供的资料里的文档笔记中的 PPT。
我这篇博客是没有多少代码记录的,主要是理清思路和知识点。
- 对于视频中需要注意的地方会提一下。(比如代码错误,在测试高并发业务前需要进行的前置操作等)
- 但是代码中也有很多知识点,这点只能结合这视频看了。
这里推荐两篇博客,对于视频中内容记录的十分详细,有具体代码和具体分析
- 【Redis 笔记_基础篇_实战篇_黑马点评项目】:
- 【Redis 实战】:
- 《Redis 学习笔记①基础篇_Redis 快速入门》
- 《Redis 学习笔记②实战篇_黑马点评项目》
- 导入课前资料提供的 SQL 文件:hmdp.sql
- 注意事项:MySQL 的版本采用 5.7 及以上版本
创建一个数据库 hmdp
该库涉及到的表有:
- tb_user:用户表
- tb_user_info:用户详情表
- tb_shop:商户信息表
- tb_shop_type:商户类型表
- tb_blog:用户日记表(达人探店日记)
- tb_follow:用户关注表
- tb_【【微信】】:优惠券表
- tb_【【微信】】_order:优惠券的订单表
- 项目架构
这里不采用微服务架构的模式,因为这里我们关注的是 【【淘密令】】g>,所以这里采用的项目是单体项目。
不过这里我们这里的项目是前后端分离的,后端部署在 Tomcat 上,前端部署在 Nginx 服务器上。
尽管黑马点评项目是一个单体项目,但是将来我们还是会考虑到该项目的并发能力的,所以必须要保证项目的水平拓展能力(集群)。
- 导入后端项目
在资料中提供了一个项目源码:hm-dianping
将其该文件包复制到你的 idea 工作空间,然后再用 idea 打开即可
启动项目后,在浏览器访问:,如果可以看到数据则证明运行没有问题
- 注意事项:不要忘记修改 application.yaml 文件中的 MySQL、Redis 的地址信息
- 导入前端项目
在资料中提供了一个 文件夹将其复制到任意目录,要确保该目录不包含中文、特殊字符和空格
运行前端项目:在 nginx 所在目录下打开一个 CMD 窗口,输入命令:
然后访问 ,即可看到页面:
【短信验证码的登录注册功能】【【【淘密令】】g> 解决 Session 共享问题】
- 注:搭建项目 的内容被我放在了上一章节
- 发送短信验证码
- 短信验证码登录和注册功能
- 登录验证功能
上图中的请求是携带了 cookie 的,cookie 里是包含了 JSESSIONID 的。
服务端可以基于 JSESSIONID 来获得 session ,再从 session 里取出用户,进而来判断该用户是否存在。
但是这个流程里有一个问题,我们需要在每一个 controller 里来写这些业务逻辑。
我们可以加一个*(由 【【微信】】 提供)来统一判断信息,决定是否放行。
此外,session 以后要做分布式 session,考虑到系统负担和安全,我们可以在*拦截到之后,将 session 中的用户信息保存到 ThreadLocal 中。每一个进入 Tomcat 的请求都是一个独立的线程,那么将来 ThreadLocal 就会在线程内开启一个独立的空间去保存这些请求(请求中携带了对应的用户信息)。这样一来,不同的用户访问 controller,都是一个独立的线程,每一个线程都有自己的用户信息,相互独立不干扰,controller 从 ThreadLocal 中取出用户信息。
官方给的网盘文件里的前端资料有点小问题
我们需要将 文件内的 ****改为 (位于 methods 内的 login 方法的 axios.post().then() 中,第 87 行)。此外我们还需要将 文件中的 改为 (位于 methods 中的 query 方法内的 axios.get().then.catch() 中,第 164 行)。
session 共享问题:多台 Tomcat 并不共享 session 存储空间,当请求切换到不同 tomcat 服务时导致数据丢失的问题。
session 的替代方案 应该满足:数据共享;内存存储;key、value 结构(【【淘密令】】g> 恰好就满足这些情况)
Redis 代替 session 需要考虑的问题:
- 选择合适的数据结构
- 选择合适的 key
- 选择合适的存储粒度
保存登录的用户信息,可以使用 String 结构,以 JSON 字符串来保存,比较直观
KEY | VALUE |
---|---|
heima:user:1 | {name:“Jack”, age:21} |
heima:user:2 | {name:“Rose”, age:18} |
Hash 结构可以将对象中的每个字段独立存储,可以针对单个字段做 CRUD,并且内存占用更少
KEY | VALUE | |
field | value | |
heima:user:1 | name | Jack |
age | 21 | |
heima:user:2 | name | Rose |
age | 18 |
对于一些不会被*拦截的路径(比如用户一直访问不需要登录的首页),*就不会生效,它就不会刷新。
那么过了有效时间后,尽管用户一直在访问,但用户的登录状态也就消失了。
我们可以在原有的*上新增一个*,拦截一切路径。
【商家查询的缓存功能】【【【淘密令】】g> 的缓存实战方案】
缓存 就是数据交换的缓冲区(称作 Cache [ k ]),是存贮数据的临时地方,一般读写性能较高。
- 缓存的作用:降低后端负载;提高读写效率,降低响应时间
- 缓存的成本:数据一致性成本;代码维护成本;运维成本
- 根据 id 查询商铺的业务添加缓存
练习:给店铺类型查询业务添加缓存
店铺类型在首页和其它多个页面都会用到且不会经常发生改动,这种类型的数据适合存储在缓存中。
需求:修改 ShopTypeController 中的 【【微信】】 方法,添加查询缓存
-
相关 URL:(GET)
- 具体代码实现见:
内存淘汰 | 超时剔除 | 主动更新 | |
---|---|---|---|
说明 | 不用自己维护。利用 【【淘密令】】g> 的内存淘汰机制:?当内存不足时自动淘汰部分数据。?下次查询时更新缓存。 | 给缓存数据添加 TTL 时间,到期后自动删除缓存。下次查询时更新缓存。 | 编写业务逻辑,在修改数据库的同时,更新缓存。 |
一致性 | 差 | 一般 | 好 |
维护成本 | 无 | 低 | 高 |
业务场景
- 低一致性需求:使用内存淘汰机制。例如店铺类型的查询缓存
- 高一致性需求:主动更新,并以超时剔除作为兜底方案。例如店铺详情查询的缓存。
主动更新策略
- Cache Aside Pattern
- 由缓存的调用者,在更新数据库的提示更新缓存。
- 特点:一致性良好、实现难度一般。
- Read/Write Through Pattern
- 缓存与数据库整合为一个服务,由服务来维护一致性。
- 调用者调用该服务,无需关心缓存一致性问题。
- 特点:一致性优秀、实现复杂、性能一般。
- Write Behind Caching Pattern
- 调用者只操作缓存,由其他线程异步的将缓存数据持久化到数据库,保存最终一致。
- 特点:一致性差、性能好、实现复杂。
第 2 、3 种的方案维护起来比较复杂,也很难找到合适的第三方组件,且第 3 种方案很难保证一致性和可靠性。
综上所述,可控性较高的是第 1 种方案,企业也大多采用这种方案。
操作缓存和数据库时有三个问题需要考虑:
-
删除缓存还是更新缓存?
-
更新缓存:每次更新数据库都更新缓存,无效写操作较多,存在较大的线程安全问题。
-
删除缓存:更新数据库时让缓存失效,查询时再更新缓存,本质是延迟更新。
-
-
如何保证缓存与数据库的操作的同时成功或失败?
- 单体系统,将缓存与数据库操作放在一个事务
- 分布式系统,利用 TCC 等分布式事务方案
-
先操作缓存还是先操作数据库?
- 先删除缓存,再更新数据库(在满足原子性的情况下,安全问题概率较低)
- 先更新数据库,再删除缓存(安全问题概率较高)
先操作缓存?还是先操作数据库?
假设数据库和缓存里的数据是 v = 10。
-
第一种方案:先删除缓存,再输出数据库
异常情况介绍:在线程 1 删除缓存后,完成对数据库的更新(目标是更新为 v = 20)前。线程 2 恰好此时也查询了缓存,但是这时的缓存已经被线程 1 删除了,所以线程 1 它又直接去查询了数据库,并将数据库中的数据(v = 10)写入了缓存。在线程 2 进行完了上述的操作后,线程 1 才终于完成了对数据库中的数据的更新(v = 20)。此时,缓存中的数据为 v = 10,数据库中的数据为 v = 20,此时数据库和缓存中的数据不一致。
-
第二种方案:先操作数据库,再删除缓存
异常情况介绍:由于某种原因(不如过期时间到了),缓存此时恰好失效了,线程 1 查询不到缓存,线程 1 它需要再去数据库中查询数据后再写入缓存。但是就在线程 1 完成写入缓存的操作前,恰好此时线程 2 来更新数据库的数据(更新 v = 20),之后线程 2 又删除了缓存(此时缓存是空的,所以这里相当于删除了个寂寞)。在线程 2 完成这些操作后,线程 1 才终于将数据库中的旧数据写入了缓存(v = 10)。此时数据库中的数据(v = 20)和缓存中的数据(v = 10)不一致。
虽然上述两种方案都有安全问题,但是第二种方案的出现问题的概率是相对来说更低一些,因为缓存中更新比磁盘中的更新要快。
此外,还可以给缓存中的数据加超时时间,以应对异常情况的发生。
缓存更新策略的最佳实践方案:
- 低一致性需求:使用 Redis 自带的内存淘汰机制
- 高一致性需求:主动更新,并以超时剔除作为兜底方案
- 读操作:
- 缓存命中则直接返回
- 缓存未命中则查询数据库,并写入缓存,设定超时时间
- 写操作:
- 先写数据库,然后再删除缓存
- 要确保数据库与缓存操作的原子性
- 先写数据库,然后再删除缓存
案例:给查询商铺的缓存添加超时剔除和主动更新的策略
- 修改 ShopController 中的业务逻辑,满足下面的需求
- ① 根据 id 查询店铺时,如果缓存未命中,则查询数据库,将数据库结果写入缓存,并设置超时时间
- ② 根据 id 修改店铺时ÿ 版权声明:除非特别标注原创,其它均来自互联网,转载时请以链接形式注明文章出处。