Redis
下载地址 - https://github.com/tporadowski/redis/releases
Redis 是完全开源的,遵守 BSD 协议,是一个高性能的 key-value
内存
[但是也是可以进行持久化操作的] 数据库[非关系型数据库 - nosql]。分布式的数据库性能极高 – Redis能读的速度是110000次/s,写的速度是81000次/s 。
配置文件
redis.windows.conf - linux中的文件就是redis.conf文件
配置redis的密码
requirepass success
redis的数据库的编号
# 设置数据库的编号.默认的数据库的编号是0,范围是从[0.15] # select 数据库编号 # 缺点 - 可读性不是特别高.需要程序员自己记录号数据是在哪个数据库编号中的.因为不同的编号的数据库的数据是隔离的. databases 16
启动redis的服务器端
进入到/Users/admin/Downloads/Redis-x64-5.0.10 - redis解压目录
不推荐的方式 , 导致redis.windows.conf配置文件没有生效.选用的默认的配置文件.
redis-server.exe
读取redis.windows.conf配置文件
redis-server.exe redis.windows.conf
端口号 - 6379
启动redis客户端
进入redis解压目录
redis-cli.exe
远程连接
admindeMacBook-Pro:~ admin$ redis-cli -h localhost -p 6379 -a success
本地连接
redis-cli.exe -a success
命令
查看所有的key
keys *
清空数据库
flushdb
删除key
del key
Redis数据类型
String: 字符串
# 存储 set username admin # 根据key来获取 get username
Hash: 散列
hash 特别适合用于存储对象
hmset student id 200 name "tom" age 23
遍历所有的key和value hgetall student 根据key来获取 hget student name
List: 列表 - 有序允许重复的
lpush course java lpush course python lpush course redis lrange course 0 2 llen course lindex course 1
Set: 集合 - 无序不可重复
sadd name admin sadd name tom sadd name admin smembers name 1) "admin" 2) "tom"
Sorted Set: 有序集合,但是仍然是不可重复的
Springboot整合redis
导入依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
application-dev.yml
spring: redis: host: localhost port: 6379 database: 1 password: success
测试RedisTemplate - API
操作String
/**
* 操作String - ValueOperations
*/
@Test
public void testString(){
ValueOperations vop = redisTemplate.opsForValue();
//放入数据
//vop.set("username","admin");
//根据key来获取
String value = (String) vop.get("username");
System.out.println(value);
}
/**
* 数据放在redis - 设置过期时间的
* 测试一下设置过期时间
*/
@Test
public void testStringExpired(){
ValueOperations vop = redisTemplate.opsForValue();
//放入数据
vop.set("username","admin",5, TimeUnit.SECONDS);
}
操作list列表
@Test
public void testSetList(){
ListOperations<String,List<Phone>> lop = redisTemplate.opsForList();
List<Phone> phoneList = phoneMapper.findAll();
lop.leftPush("phones",phoneList);
}
@Test
public void testGetList(){
ListOperations<String,List<Phone>> lop = redisTemplate.opsForList();
//获取redis的个数
System.out.println(lop.size("phones"));//1
List<List<Phone>> list = lop.range("phones",0,1);
List<Phone> phoneList = list.get(0);
for (Phone phone : phoneList) {
System.out.println(phone);
}
}
改变redis序列化的方式
package tech.aistar.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
* 本类用来演示: 修改redis默认的key,value的序列化方式
* 序列化使用的jdkSerializeable, 存储二进制字节码, 所以自定义序列化类
* * @param redisConnectionFactory
*
* @author: success
*/
@Configuration
public class RedisConfig {
/**
* redisTemplate 序列化使用的jdkSerializeable, 存储二进制字节码, 所以自定义序列化类
* @param redisConnectionFactory
* @return
*/
@Bean
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
// 使用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);
// 设置value的序列化规则和 key的序列化规则
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
应用场景
验证码放入到session空间 - 缺点
- session空间无法设置过期时间
- session空间是服务器内部的空间 - 内存 - 比较昂贵
验证码放入到redis中.
redisTemplate.opsForValue().set("code",String.valueOf(n),20, TimeUnit.SECONDS);
String sesCode = (String) redisTemplate.opsForValue().get("code");
传统的模式 - 登录成功之后的用户放入到session中
session.setAttribute("user",u);
用户服务 - 部署在服务器A
购物车服务 - 部署在服务器B
系统架构的演变
单体架构
单机就是所有的业务全部写在一个项目中,部署服务到一台服务器上,所有的请求业务都由这台服务器处理。显然,当业务增长到一定程度的时候,服务器的硬件会无法满足业务需求。自然而然地想到一个程序不行就部署多个喽,
缺点:
- 所有的请求的压力都由一台服务器来处理
- 因为所有的业务都在一个项目中,一旦其中某个业务崩溃了,会导致整个项目运行失败.
- 如果后期仅仅只是某个业务中的一小块需要改动,那么是需要将整个项目重新打包部署.
微服务架构
Springboot框架就是专门用来进行微服务开发的 - 微服务的”脚手架” - 快速搭建微服务的项目工程.
![]()
- 微服务的应用不一定是分散在多个服务器上也可以是同一个服务
- 微服务 - 架构的风格,将一个大的项目模块,拆分成若干个子的业务模块.并且每个子的业务模块都是一个独立的工程,可以进行单独的部署.好处 - 解耦合了各个模块.
分布式架构
将一个大的项目模块,拆分成若干个子的业务模块,但是不同的业务模块分散部署在不同的服务器上的 - 为了分散服务器的压力.
“着重于部署的方式”
“分布式一定是微服务的” - “微服务不一定是分布式”
“不同的人干不同的事情” - “饭店 - 厨师,洗碗工,买菜,洗菜,上菜 - 各个子业务模块”
集群架构
集群模式是不同服务器部署同一套服务对外访问,实现服务的负载均衡。区别集群的方式是根据部署多台服务器业务是否相同
“好多人干同一件事情”
“集群是为了高可用,高性能,高并发 - 三高”
“分布式的每个节点都可以进行集群”
相同的服务部署在不同的服务器上 - 不同的服务器上部署相同的服务.
电商系统 - 用户模块,订单模块,支付模块 - “拆的过程”
比如 - 订单服务 - 部署在服务器A,支付模块部署在服务器B,用户模块部署在服务器C - “分布式部署的过程”
为了防止哪天服务器C哪天不可用,会容易发生雪崩效应.因为服务器A中的订单服务依赖于服务器C的用户服务的.
可以对分布式上的每个节点进行集群操作.可以将用户模块同时部署在服务器C,服务器B,服务器N上…
一旦进行了集群,后期可以再配合一些负载均衡的策略.比如
订单服务 - 服务器A和服务器C上都存在.
原则 - 先微服务架构,然后进行分布式部署,最后再做集群.
客户端请求 -> 网关[负载均衡] -> 服务器A/服务器C
常见的集群
- 分布式的每个节点[微服务]可以进行集群
- redis做集群
- mysql做集群
- es集群
SOA架构
Springcloud
jjwt
Java Json Web Token[令牌]
JJWT是一个提供端到端的JWT创建和验证的Java库
三部分
Header(头部) —— base64编码的Json字符串
{ 'typ': 'JWT', 'alg': 'HS256' } ewogJ3R5cCc6ICdKV1QnLAogJ2FsZyc6ICdIUzI1NicKfQ==
Payload(载荷) —— base64编码的Json字符串 - 真正的有效的数据.比如登录的信息.
{ "sub": "1234567890", "name": "John Doe", "admin": true } ewogInN1YiI6ICIxMjM0NTY3ODkwIiwKICJuYW1lIjogIkpvaG4gRG9lIiwKICJhZG1pbiI6IHRydWUKfQ==
Signature(签名)—— 使用指定算法,通过Header和Payload加盐计算的字符串
JWT的第三部分是一个签证信息,这个签证信息由三部分组成:Base64编码后的header、Base64编码后的payload和一个自定义的私人密钥secret: HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret) 不可逆的 HMACSHA256(ewogJ3R5cCc6ICdKV1QnLAogJ2FsZyc6ICdIUzI1NicKfQ==.ewogInN1YiI6ICIxMjM0NTY3ODkwIiwKICJuYW1lIjogIkpvaG4gRG9lIiwKICJhZG1pbiI6IHRydWUKfQ==,盐值加密) 比如 - 设置的盐 - success 签证 - token值 - 2602287a3aec62e36a3bc1a474f8c028270822f50800841a2d9135eb1a876cd2
具体操作
导入依赖
<dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency>
工具类
package tech.aistar.util; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import tech.aistar.model.entity.Users; import java.util.Date; public class JWTUtils { /** * 设置过期时间,一周 */ private static final long EXPIRE = 7 * 24 * 60 * 60 * 1000; //private static final long EXPIRE = 2000; /** * 设置秘钥 */ private static final String SECRET = "success";//盐值 /** * 令牌前缀 */ private static final String TOKEN_PREFIX = "aistar"; /** * 令牌主题 */ private static final String SUBJECT = "admin";
/** * 根据用户信息来生成令牌(token) * @param user * @return */ public static String geneJsonWebToken(Users user){ //头部省略不写了
// {
// “typ”: “JJWT”
// “alg”: “HS256”
// }
String token = Jwts.builder().setSubject(SUBJECT).claim("id",user.getId())//载荷 - 有效信息 - 登录用户的有效信息,是为了校验用户是否登录的信息 .claim("name",user.getUsername()) .setIssuedAt(new Date())//颁布时间 //过期时间 .setExpiration(new Date(System.currentTimeMillis() + EXPIRE)) //生成签名 - token值 .signWith(SignatureAlgorithm.HS256,SECRET).compact();//签名 token = TOKEN_PREFIX + token; return token; } /** * 校验token的方法 * @param token * @return */ public static Claims checkJWT(String token){ try{ final Claims claims = Jwts.parser().setSigningKey(SECRET) .parseClaimsJws(token.replace(TOKEN_PREFIX,"")).getBody(); return claims; }catch (Exception e){ return null; } }
}
测试
package tech.aistar.jjwt; import io.jsonwebtoken.Claims; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import tech.aistar.mapper.UsersMapper; import tech.aistar.model.entity.Users; import tech.aistar.util.JWTUtils; /** * 本类用来演示: * * @author: success * @date: 2021/10/8 1:40 下午 */ @SpringBootTest public class TestJJwt { @Autowired private UsersMapper usersMapper; @Test public void createToken(){ Users users = usersMapper.findByEmail("849962874@qq.com"); System.out.println(users); //令牌 String token = JWTUtils.geneJsonWebToken(users); /* * 如果一旦登录成功,在服务器端就会生成token,并且将这个token返回给客户端 * 客户端需要将这个token放在h5的本地存储中. * 后续的请求需要带上token值[载荷信息]. * * aistareyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZG1pbiIsImlkIjoxLCJuYW1lIjoiYWRtaW4iLCJlbWFpbCI6Ijg0OTk2Mjg3NEBxcS5jb20iLCJpYXQiOjE2MzM2NzE3ODYsImV4cCI6MTYzNDI3NjU4Nn0.kvk1CKSpHatz4CQgkXIaYLOOVGLe-3vg-h3je6CYsyk */ System.out.println(token); } @Test public void testParseToken(){ //server接收到客户端的请求中的token - 首先要去除 String token = "aistareyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZG1pbiIsImlkIjoxLCJuYW1lIjoiYWRtaW4iLCJlbWFpbCI6Ijg0OTk2Mjg3NEBxcS5jb20iLCJpYXQiOjE2MzM2NzIzMTEsImV4cCI6MTYzNDI3NzExMX0.WULibokcNM5U5cDW07eQsj8lyVj_qf7FImKlhAFJEtk"; Claims c = JWTUtils.checkJWT(token); System.out.println("c:"+c); //获取载荷中的单个key String email = (String) c.get("email"); System.out.println(email); } }
流程
登录成功代码片段
a. 在server端生成token,并且返回给client端 - 保存在本地存储中.
并且在
//生成一个token String token = JWTUtils.geneJsonWebToken(u); return new Result("200","邮箱发送成功",token);
后续的请求,比如点击我的购物车,我的订单,我的个人中心都是需要校验token
思考在哪里进行校验呢? - 拦截器
fastjson
将java对象转换成json字符串.
<dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.78</version> </dependency>
package tech.aistar.fastjson; import com.alibaba.fastjson.JSON; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import tech.aistar.mapper.UsersMapper; import tech.aistar.model.entity.Users; /** * 本类用来演示: * * @author: success * @date: 2021/10/8 2:53 下午 */ @SpringBootTest public class TestFastJSon { @Autowired private UsersMapper usersMapper; @Test public void testJavaToJsonString(){ Users users = usersMapper.findByEmail("849962874@qq.com"); String jsonStr = JSON.toJSONString(users); System.out.println(jsonStr); } }
//设置文档的输出类型
response.setContentType("application/json;charset=utf-8");
//获取文档输出流
PrintWriter out = response.getWriter();
String jsonResult = JSON.toJSONString(new Result("507","非法的token"));
out.println(jsonResult);
密码加密
导入依赖 - 权限认证的框架 - SpringSecurity,还有shiro
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
单侧
package tech.aistar.pwd; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; /** * 本类用来演示: 加密技术 * * @author: success * @date: 2021/10/8 3:18 下午 */ @SpringBootTest public class PasswordTest { //思考 - 为什么RedisTemplate可以直接使用 //springboot底层的自动化配置原理有关系. @Autowired private BCryptPasswordEncoder encoder; @Test public void testEncoder(){ //页面上输入的数据. String pwd = "admin123"; //加密之后保存到db中 //db的password的字段的长度修改长一点 //不可逆 - 底层随机盐 String encoderPwd = encoder.encode(pwd); //密码需要进行密文的处理的 //$2a$10$XHCcsh3SWU/gjOzht17CPOX2W4pGFiBogyUCfUVBGyIfuJRY6UX5m System.out.println(encoderPwd); } @Test public void testGet(){ //直接进行判断 boolean flag = encoder.matches("admin123","$2a$10$XHCcsh3SWU/gjOzht17CPOX2W4pGFiBogyUCfUVBGyIfuJRY6UX5m"); System.out.println(flag); } }
需要对拦截的请求处理一下 - 放行,否则会自动跳转默认的登录界面里面
package tech.aistar.config; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; /** * 本类用来演示: * * @author: success * @date: 2021/10/8 3:17 下午 */ @Configuration @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/**").permitAll() .anyRequest().authenticated() .and().csrf().disable(); } }
redis+token如何防止表单重复提交
/user/registerView - server端生成token[令牌] - 存储到redis中,并且将这个token存储到request作用域中.
转发到WEB-INF/register.jsp文件 - form表单
<input type='hidden' value='${token}' name='token'>
点击注册按钮 - 都可以将这个隐藏域的token值一起发送给server - /user/register
/user/register后端是可以拿到redis中的token,然后和form表单提交过来的token值进行比较.
如果一样,则直接顺利执行.执行注册操作之后,将redis中的token删除.
再次发送,隐藏域中的token会再次和redis中的token进行比较[已经被删除了] - 比较失败! - 重复提交了.
RabbitMQ
ActiveMQ:基于JMS,Apache
RocketMQ:(Rocket,火箭)阿里巴巴的产品,基于JMS,目前由Apache基于会维护
Kafka:分布式消息系统,亮点:吞吐量超级高,没秒中数十万的并发。
RabbitMQ:(Rabbit,兔子)由erlang语言开发,基于AMQP协议,在erlang语言特性的加持下,RabbitMQ稳定性要比其他的MQ产品好一些,而且erlang语言本身是面向高并发的编程的语言,所以RabbitMQ速度也非常快。且它基于AMQP协议,对分布式、微服务更友好。
RabbitMQ是实现了高级消息队列协议(AMQP)的开源消息代理软件(亦称面向消息的中间件)
特点
消息队列中间件是分布式系统中重要的组件,主要解决应用耦合,异步消息,流量削锋等问题实现高性能,高可用,可伸缩和最终一致性[架构]
使用较多的消息队列有ActiveMQ,RabbitMQ,ZeroMQ,Kafka,MetaMQ,RocketMQ。其使用场景为:异步处理,应用解耦,流量削锋和消息通讯四个场景。
安装
- Erlang - http://www.erlang.org/downloads
- RabbitMQ - https://github.com/rabbitmq/rabbitmq-server/releases
- 注意一下两者的版本,需要相互兼容.否则安装运行失败!