redis简介
????????Redis 是一个高性能的开源的、C语言写的Nosql(非关系型数据库),数据保存在内存中。
????????Redis 是以key-value形式存储,和传统的关系型数据库不一样。不一定遵循传统数据库的一些基本要求,比如 说,不遵循sql标准,事务,表结构等等,非关系型数据库严格上不是一种数据库,应该是一种数据结构化存储方 法的集合。
????????Java中数据结构:String,数组,list,set map…
????????Redis提供了很多的方法,可以用来存取各种数据结构的数据
什么是NoSql
????????NoSQL(NoSQL = Not Only SQL ),意即“不仅仅是SQL”,它泛指非关系型的数据库。随着互联网2003年之后 web2.0网站的兴起,传统的关系数据库在应付web2.0网站,特别是超大规模和高并发的交友类型的web2.0纯动 态网站已经显得力不从心,暴露了很多难以克服的问题,而非关系型的数据库则由于其本身的特点得到了非常迅 速的发展。
为什么用redis
????????1.数据保存在内存,存取速度快,并发能力强
????????2.它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、 zset(sorted set --有序集合)和 hash(哈希类型)。
????????3.redis的出现,很大程度补偿了memcached这类key/value存储的不足,在部分场合可以对关系数据库(如 MySQL)起到很好的补充作用。
????????4.它提供了Java,C/C++,C#,PHP,JavaScript等客户端,使用很方便。
????????5.Redis支持集群(主从同步)。数据可以主服务器向任意数量从的从服务器上同步,从服务器可以是关联其他从 服务器的主服务器。
????????6.支持持久化,可以将数据保存在硬盘的文件中
Redis使用场景
????????1:缓存,毫无疑问这是Redis当今最为人熟知的使用场景。再提升服务器性能方面非常有效。
????????2:排行榜,在使用传统的关系型数据库(mysql oracle 等)来做这个事儿,非常的麻烦,而利用Redis的 SortSet(有序集合)数据结构能够简单的搞定。
????????3:利用Redis中原子性的自增操作,我们可以统计类似用户点赞数、用户访问数等,这类操作如果用MySQL,频 繁的读写会带来相当大的压力;限速器比较典型的使用场景是限制某个用户访问某个API的频率,常用的有抢购 时,防止用户疯狂点击带来不必要的压力;
????????4: 一些频繁被访问的数据,经常被访问的数据如果放在关系型数据库,每次查询的开销都会很大,而放在redis 中,因为redis 是放在内存中的可以很高效的访问
spring boot整合redis—邮箱验证码
一、springboot整合redis 环境搭建
1 导入依赖
!-- !redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2 加入配置
#=======redis连接池配置============== #
# Redis数据库索引(默认为0)
spring.redis.database=0
# Redis服务器地址
spring.redis.host=127.0.0.1
# Redis服务器连接端口
spring.redis.port=6379
# Redis服务器连接密码(默认为空)
spring.redis.password=
# 连接池最大连接数(使用负值表示没有限制)
spring.redis.jedis.pool.max-active=20
# 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.jedis.pool.max-wait=-1
# 连接池中的最大空闲连接
spring.redis.jedis.pool.max-idle=10
# 连接池中的最小空闲连接
spring.redis.jedis.pool.min-idle=0
# 连接超时时间(毫秒)
spring.redis.timeout=5000
3 创建redis配置类RedisConfig
@Configuration
@EnableCaching // 启用缓存,使用 Lettuce,自动注入配置的方式
@AutoConfigureAfter(RedisAutoConfiguration.class)
public class RedisConfig extends CachingConfigurerSupport {
//缓存管理器
@Bean
@SuppressWarnings("all")
public CacheManager cacheManager(LettuceConnectionFactory factory) {
RedisCacheWriter writer = RedisCacheWriter.lockingRedisCacheWriter(factory);
RedisSerializationContext.SerializationPair pair =
RedisSerializationContext.SerializationPair.fromSerializer(
new Jackson2JsonRedisSerializer(Object.class));
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
return new RedisCacheManager(writer, config);
}
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(connectionFactory);
// String 序列化方式
StringRedisSerializer stringSerializer = new StringRedisSerializer();
// 使用Jackson2JsonRedisSerialize替换默认序列化
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new
Jackson2JsonRedisSerializer(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL,
JsonAutoDetect.Visibility.ANY);
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
// 设置key的序列化规则
redisTemplate.setKeySerializer(stringSerializer);
redisTemplate.setHashKeySerializer(stringSerializer);
// 设置value的序列化规则
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
return redisTemplate;
}
// 重新定义缓存 key 的生成策略
@Bean
@Override
public KeyGenerator keyGenerator() {
return new KeyGenerator() {
@Override public Object generate(Object target, Method method, Object... params){
StringBuilder sb = new StringBuilder();
sb.append(target.getClass().getName());
sb.append(method.getName());
for(Object obj:params){
sb.append(obj.toString());
}
return sb.toString();
}
};
}
}
二、验证码发送和验证
在serviceImpl中注入redis
?
//注入redis
@Autowired
RedisTemplate<String,Object> redisTemplate;
1.验证码发送
1.1 后端,在项目-01 的基础上修改
1.1.1? ?优化发送验证码方法sendCode,30-34行
- 在验证码发送成功后,将”code:email“作为键,code作为值,并设置失效时间是1分钟
- 将键、值、时间使用redisTemplate.opsForValue().set保存进去
- 当失效时间到了后,redis会自动清楚这个键值。
@Override
public ResultVo sendCode(String email) {
ResultVo rv = new ResultVo();
try {
//创建邮件对象
SimpleMailMessage message = new SimpleMailMessage();
//定义收件人邮箱
message.setTo(email);
//定义发件人邮箱
message.setFrom(sendEmail);
//定义邮件标题
message.setSubject("xxx公司验证码");
//定义邮件正文
Random random = new Random();
int code = random.nextInt(10000);
message.setText("你的验证码是"+code);
//发送邮件
javaMailSender.send(message);
System.out.println("发送成功");
rv.setSuccess(true);
rv.setInfo("发送成功");
//发送成功放入redis
//1.定义redis的键名
String key = "code:"+email;
//2.存入验证码并设置失效时间,分钟
redisTemplate.opsForValue().set(key,code+"",1, TimeUnit.MINUTES);
} catch (Exception e) {
e.printStackTrace();
System.out.println("发送失败");
rv.setInfo("发送失败");
}
return rv;
}
1.2 前端,绑定按钮,并倒计时
????????绑定发送验证码按钮,要做到一分钟一次请求,发送验证码后,一分钟后才能再次发送验证码
1.2.1 绑定按钮
<template>
<div>
<h1>修改密码</h1>
<span>邮箱 :</span>
<input type="text" v-model="userInfo.email" placeholder="请符合邮箱标准" />
<span>验证码 :</span>
<input type="text" v-model="userInfo.code" />
<button type="button" @click="sendCode()" :disabled="isDis">发送验证码{{num}}</button>
</div>
</template>
1.2.2 计时器和禁用按钮实现
????????重要的方法:计时器方法
var time = setInterval(function() {}
<template>
<div>
<h1>修改密码</h1>
<span>邮箱 :</span>
<input type="text" v-model="userInfo.email" placeholder="请符合邮箱标准" />
<span>验证码 :</span>
<input type="text" v-model="userInfo.code" />
<button type="button" @click="sendCode()" :disabled="isDis">发送验证码{{num}}</button>
<button type="button" @click="getPwdPage()">进入修改密码页面</button>
</div>
</template>
<script>
export default {
name: "testUpdatePwd",
data() {
return {
userInfo: {
email: "2013116602@qq.com",
code: ""
},
isDis: false,
num: "" //倒计时
}
},
methods: {
sendCode() { //发送验证码
var self = this;
//发送get请求
this.$http.get("/userInfo/updatePwd", {
params: this.userInfo
}).then(function(rs) {
console.log(rs.data);
alert(rs.data.info);
if (rs.data.success) {
//禁用发送按钮
self.isDis = true;
//按钮计时
self.num = 60;
var time = setInterval(function() {
self.num--;
//判断倒计时是否结束,放开发送按钮
if (self.num == 0) {
self.isDis = false;
self.num = "";
//清除计时器
clearInterval(time);
}
}, 1000)
}
}).catch(function(rs) {
console.log("连接服务器错误");
})
}
}
}
</script>
2.验证邮箱验证码
2.1.后端,写实现类和控制器接口
2.1.1.编写邮箱验证码验证方法validateCode
- 1.先把传进来的userInfo对象的email拿出了,组成键名,然后去redis中获取改键的值
- 2.如果值为空,则时间已经到了,失效了
- 3.不为空,就判断传进来的userInfo对象的code是否等于redis中获取带的值,等于则输入正确,然后返回true
- 4.否则输入错误
@Override
public ResultVo validateCode(UserInfo userInfo) {
ResultVo rv = new ResultVo();
//判断验证码是否失效
//1.定义键名
String key = "code:"+userInfo.getEmail();
Object obj = redisTemplate.opsForValue().get(key);
//2.判断这个key是否存在,如果时间有效则存在
if (obj == null){
rv.setInfo("验证码失效");
}else {
//3.验证码未失效,比较验证码
if (userInfo.getCode().equals(obj)){
rv.setInfo("输入正确");
rv.setSuccess(true);
}else {
rv.setInfo("验证码输入错误");
}
}
return rv;
}
2.1.2.写验证邮箱验证码的控制器接口
????????使用的是post请求,因为要提交的是表单的数据包装的userInfo
@RestController
@RequestMapping("/userInfo")
public class UserInfoController {
//注入service
@Autowired
private UserInfoService userInfoService;
//定义修改密码
@GetMapping("/updatePwd")
public ResultVo updatePwd(String email){
return userInfoService.sendCode(email);
}
//验证输入的验证码是否失效或正确
@PostMapping("/validateCode")
public ResultVo validateCode(UserInfo userInfo){
return userInfoService.validateCode(userInfo);
}
}
2.2 前端,绑定按钮,并验证
<template>
<div>
<h1>修改密码</h1>
<span>邮箱 :</span>
<input type="text" v-model="userInfo.email" placeholder="请符合邮箱标准" />
<span>验证码 :</span>
<input type="text" v-model="userInfo.code" />
<button type="button" @click="sendCode()" :disabled="isDis">发送验证码{{num}}</button>
<button type="button" @click="getPwdPage()">进入修改密码页面</button>
</div>
</template>
<script>
export default {
name: "",
data() {
return {
userInfo: {
email: "2013116602@qq.com",
code: ""
},
isDis: false,
num: "" //倒计时
}
},
methods: {
getPwdPage() { //进入修改密码页面
var self = this;
this.$http.post(
"/userInfo/validateCode",
this.$qs.stringify(this.userInfo)
).then(function(rs){
alert(rs.data.info);
if(rs.data.success){
//进入到修改密码页面
self.$router.push({"name":"testUpdatePwd",query:{"email":self.userInfo.email}})
}
}).catch(function(rs) {
console.log("连接服务器错误");
})
}
}
}
</script>
<style>
</style>
3.发送验证码和验证的代码
3.1 后端
userInfoServerImpl.java
@Service
public class UserInfoServiceImpl implements UserInfoService {
//注入邮件发送服务类
@Autowired
JavaMailSender javaMailSender;
@Value("${spring.mail.username}")
String sendEmail;
//注入redis
@Autowired
RedisTemplate<String,Object> redisTemplate;
@Override
public ResultVo sendCode(String email) {
ResultVo rv = new ResultVo();
try {
//创建邮件对象
SimpleMailMessage message = new SimpleMailMessage();
//定义收件人邮箱
message.setTo(email);
//定义发件人邮箱
message.setFrom(sendEmail);
//定义邮件标题
message.setSubject("xxx公司验证码");
//定义邮件正文
Random random = new Random();
int code = random.nextInt(10000);
message.setText("你的验证码是"+code);
//发送邮件
javaMailSender.send(message);
System.out.println("发送成功");
rv.setSuccess(true);
rv.setInfo("发送成功");
//发送成功放入redis
//1.定义redis的键名
String key = "code:"+email;
//2.存入验证码并设置失效时间,分钟
redisTemplate.opsForValue().set(key,code+"",1, TimeUnit.MINUTES);
} catch (Exception e) {
e.printStackTrace();
System.out.println("发送失败");
rv.setInfo("发送失败");
}
return rv;
}
@Override
public ResultVo validateCode(UserInfo userInfo) {
ResultVo rv = new ResultVo();
//判断验证码是否失效
//1.定义键名
String key = "code:"+userInfo.getEmail();
Object obj = redisTemplate.opsForValue().get(key);
//2.判断这个key是否存在,如果时间有效则存在
if (obj == null){
rv.setInfo("验证码失效");
}else {
//3.验证码未失效,比较验证码
if (userInfo.getCode().equals(obj)){
rv.setInfo("输入正确");
rv.setSuccess(true);
}else {
rv.setInfo("验证码输入错误");
}
}
return rv;
}
}
UserInfoService.java
public interface UserInfoService {
//邮件发送验证码
ResultVo sendCode(String email);
//验证验证码是否输入正确
ResultVo validateCode(UserInfo userInfo);
}
UserInfoController.java
@RestController
@RequestMapping("/userInfo")
public class UserInfoController {
//注入service
@Autowired
private UserInfoService userInfoService;
//定义修改密码
@GetMapping("/updatePwd")
public ResultVo updatePwd(String email){
return userInfoService.sendCode(email);
}
//验证输入的验证码是否失效或正确
@PostMapping("/validateCode")
public ResultVo validateCode(UserInfo userInfo){
return userInfoService.validateCode(userInfo);
}
}
3.2 前端
testEmailVaildate.vue组件
<template>
<div>
<h1>修改密码</h1>
<span>邮箱 :</span>
<input type="text" v-model="userInfo.email" placeholder="请符合邮箱标准" />
<span>验证码 :</span>
<input type="text" v-model="userInfo.code" />
<button type="button" @click="sendCode()" :disabled="isDis">发送验证码{{num}}</button>
<button type="button" @click="getPwdPage()">进入修改密码页面</button>
</div>
</template>
<script>
export default {
name: "testUpdatePwd",
data() {
return {
userInfo: {
email: "2013116602@qq.com",
code: ""
},
isDis: false,
num: "" //倒计时
}
},
methods: {
sendCode() { //发送验证码
var self = this;
//发送get请求
this.$http.get("/userInfo/updatePwd", {
params: this.userInfo
}).then(function(rs) {
console.log(rs.data);
alert(rs.data.info);
if (rs.data.success) {
//禁用发送按钮
self.isDis = true;
//按钮计时
self.num = 60;
var time = setInterval(function() {
self.num--;
//判断倒计时是否结束,放开发送按钮
if (self.num == 0) {
self.isDis = false;
self.num = "";
//清除计时器
clearInterval(time);
}
}, 1000)
}
}).catch(function(rs) {
console.log("连接服务器错误");
})
},
getPwdPage() { //进入修改密码页面
var self = this;
this.$http.post(
"/userInfo/validateCode",
this.$qs.stringify(this.userInfo)
).then(function(rs){
alert(rs.data.info);
if(rs.data.success){
//进入到修改密码页面
self.$router.push({"name":"testUpdatePwd",query:{"email":self.userInfo.email}})
}
}).catch(function(rs) {
console.log("连接服务器错误");
})
}
}
}
</script>
<style>
</style>
index.js
import Vue from 'vue'
import Router from 'vue-router'
/* 配置地址,叫组件
第一种:导入hello组件
*/
import hello from '@/components/demo/hello'
import testEmailVaildate from '../components/project/testEmailVaildate.vue'
import testUpdatePwd from '../components/project/testUpdatePwd.vue'
Vue.use(Router)
export default new Router({
// 切换到history模式
mode:"history",
routes: [
{
/* 斜杠:代表项目的默认地址 */
path: '/',
name: 'hello', /* 组件名称:保持名称唯一性 */
component: hello /* 引入组件的别名 */
},
{
path: '/testEmailVaildate', //*,通配所有的错误地址
name: 'testEmailVaildate',
component: testEmailVaildate
},
{
path: '/testUpdatePwd', //*,通配所有的错误地址
name: 'testUpdatePwd',
component: testUpdatePwd
},
]
})
三、修改密码
????????涉及了后端数据交互、使用了全自动的框架mybatis plus,引入了shiro权限认证,以及加密工具
1、导包
导入了mybatis plus,shrio的依赖
<!--mybatis-plus依赖-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.17</version>
</dependency>
<!--代码生成器模板引擎-->
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.2</version>
</dependency>
<!--代码生成器-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.3.2</version>
</dependency>
<!-- spring boot整合shiro所需要的依赖 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.2.2</version>
</dependency>
2.引入加密工具类
@Component
public class MDFive {
/**
*
* @param password 要加密的密码
* @param saltValue 盐值
* @return
*/
public String encrypt(String password,String saltValue){
//创建一个MD5的盐值对象
Object salt = new Md5Hash(saltValue);
//生成加密的字符串
Object result = new SimpleHash("MD5", password, salt, 1024);
return result+"";
}
}
3.根据user_info表完善实体类userInfo
📎user_info.sqlhttps://www.yuque.com/attachments/yuque/0/2022/sql/32505142/1662716539576-2cdc64e2-f912-490d-8744-b20646ac8914.sql
@Data
@TableName("user_info")
public class UserInfo {
//主键
@TableId(value = "user_id",type = IdType.AUTO)
private Integer userId;
//用户名
private String userName;
//密码
private String passPwd;
//盐值
private String salt;
//邮箱
private String email;
//电话
private String phone;
//验证码
@TableField(exist = false)
private String code;
}
4.使用mybatis plus来接管到
4.1.在基础包下新建dao包,并建立UserInfoDao接口
4.2.继承BaseMapper
public interface UserInfoDao extends BaseMapper<UserInfo> {
}
4.3.在启动类上扫描dao
@SpringBootApplication
@MapperScan("com.hqyj.dao")
public class IdeaVueApplication {
public static void main(String[] args) {
SpringApplication.run(IdeaVueApplication.class, args);
}
}
5.写修改密码实现接口类
public interface UserInfoService {
//邮件发送验证码
ResultVo sendCode(String email);
//验证验证码是否输入正确
ResultVo validateCode(UserInfo userInfo);
//修改密码
ResultVo updatePwd(UserInfo userInfo);
}
使用UUID来作为盐值,唯一性
@Service
public class UserInfoServiceImpl implements UserInfoService {
//注入邮件发送服务类
@Autowired
JavaMailSender javaMailSender;
@Value("${spring.mail.username}")
String sendEmail;
//注入redis
@Autowired
RedisTemplate<String,Object> redisTemplate;
//注入加密工具
@Autowired
private MDFive mdFive;
@Autowired
private UserInfoDao userInfoDao;
@Override
public ResultVo sendCode(String email) {
ResultVo rv = new ResultVo();
try {
//创建邮件对象
SimpleMailMessage message = new SimpleMailMessage();
//定义收件人邮箱
message.setTo(email);
//定义发件人邮箱
message.setFrom(sendEmail);
//定义邮件标题
message.setSubject("xxx公司验证码");
//定义邮件正文
Random random = new Random();
int code = random.nextInt(10000);
message.setText("你的验证码是"+code);
//发送邮件
javaMailSender.send(message);
System.out.println("发送成功");
rv.setSuccess(true);
rv.setInfo("发送成功");
//发送成功放入redis
//1.定义redis的键名
String key = "code:"+email;
//2.存入验证码并设置失效时间,分钟
redisTemplate.opsForValue().set(key,code+"",1, TimeUnit.MINUTES);
} catch (Exception e) {
e.printStackTrace();
System.out.println("发送失败");
rv.setInfo("发送失败");
}
return rv;
}
@Override
public ResultVo validateCode(UserInfo userInfo) {
ResultVo rv = new ResultVo();
//判断验证码是否失效
//1.定义键名
String key = "code:"+userInfo.getEmail();
Object obj = redisTemplate.opsForValue().get(key);
//2.判断这个key是否存在,如果时间有效则存在
if (obj == null){
rv.setInfo("验证码失效");
}else {
//3.验证码未失效,比较验证码
if (userInfo.getCode().equals(obj)){
rv.setInfo("输入正确");
rv.setSuccess(true);
}else {
rv.setInfo("验证码输入错误");
}
}
return rv;
}
@Override
public ResultVo updatePwd(UserInfo userInfo) {
ResultVo rv = new ResultVo();
//生成盐值,使用UUID,生成唯一的
String salt = UUID.randomUUID()+"";
//记录盐值
userInfo.setSalt(salt);
//生成加密的字符串
String pwd = mdFive.encrypt(userInfo.getUserPwd(),salt);
//记录加密的密码
userInfo.setUserPwd(pwd);
//创建条件构造器
QueryWrapper<UserInfo> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("email", userInfo.getEmail());
int num = userInfoDao.update(userInfo, queryWrapper);
if (num > 0 ){
rv.setSuccess(true);
rv.setInfo("修改成功");
}else {
rv.setInfo("修改失败");
}
return rv;
}
}
6.写修改密码的控制器接口
@RestController
@RequestMapping("/userInfo")
public class UserInfoController {
//注入service
@Autowired
private UserInfoService userInfoService;
//定义修改密码
@GetMapping("/updatePwd")
public ResultVo updatePwd(String email){
return userInfoService.sendCode(email);
}
//验证输入的验证码是否失效或正确
@PostMapping("/validateCode")
public ResultVo validateCode(UserInfo userInfo){
return userInfoService.validateCode(userInfo);
}
//验证输入的验证码是否失效或正确
@PostMapping("/updatePwd")
public ResultVo updatePwd(UserInfo userInfo){
return userInfoService.updatePwd(userInfo);
}
}
7.测试
?
?
?
这里前提是数据表中有当前邮箱的数据,否则找不到
|