- 会话管理
-
基于cookie传输sessionid: java tomcat容器session实现
移动端开发很多时候会把cookie禁用掉. -
基于token传输,类似sessionid: java代码session实现
- 分布式会话
- 基于cookie传输sessionid: java tomcat容器session实现迁移到redis
- 基于token传输类似sessionid: java代码session实现迁移到redis
- 基于cookie传输sessionid的单机版redis实现
-
安装redis
教程很多,而且安装过程和其他软件一样,不再阐述 -
pom.xml文件中引入如下依赖
<!-- 引入springboot对redis的依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!-- spring将自己对session的管理方式存储在redis中--> <dependency> <groupId>org.springframework.session</groupId> <artifactId>spring-session-data-redis</artifactId> <version>2.0.5.RELEASE</version> </dependency>
-
RedisConfig.java
// 把当前类对象作为一个bean存入spirng容器 @Component // maxInactiveIntervalInSeconds: 设置 Session 失效时间(默认1800,即30分钟,单位秒) // 使用Redis Session 之后,原SpringBoot的server.session.timeout属性不再生效。 @EnableRedisHttpSession(maxInactiveIntervalInSeconds = 3600) public class RedisConfig { }
-
配置application.yml文件
spring: # 配置springboot对redis的依赖 redis: host: 127.0.0.1 port: 6379 # 一共16个数据库,从0开始,使用database 10 database: 10 jedis: pool: # 最大连接数为50 max-active: 50 # 最少空闲连接数为20 min-idle: 20
-
将几个Model实现序列化(因为存入redis的数据默认是序列化的,也可以修改redis的默认存储方式为json,json方式也是最好的,该处暂时使用序列化)
// 举例,OrderModel、ItemModel、PromoModel同理 public class UserModel implements Serializable{ @NotBlank(message = "用户名不能为空") private String name; @NotNull(message = "性别不能不填写") private Byte gender; @NotNull(message = "年龄不能不填写") @Min(value = 0, message = "年龄必须大于0岁") @Max(value = 150, message = "年龄必须小于150岁") private Integer age; @NotBlank(message = "手机号不能为空") private String telephone; private String registerMode; private String thirdPartyId; @NotBlank(message = "密码不能为空") private String encrptPassword; }
-
单机版测试
-
修改gethost.js
var g_host="localhost:9000";
-
切换redis数据库到10
select 10
-
前端访问服务器,点击获取otp信息,即将手机号和验证码放入session中,由于配置了上述两个依赖,便将session信息放入redis中
- 访问服务器
- 获取验证码后,手机号和验证码放入session代码
// 用户获取otp短信接口 @PostMapping(value = "/getotp",consumes = { CONTENT_TYPE_FORMED}) public CommonReturnType getOtp(@RequestParam("telephone") String telephone){ //需要按照一定的规则生成OTP验证码 Random random = new Random(); int randomInt = random.nextInt(99999); randomInt += 10000; String otpCode = String.valueOf(randomInt); //将OTP验证码同对应用户的手机号关联,使用httpSession的方式绑定手机号和OTPCODE(键值对) httpServletRequest.getSession().setAttribute(telephone, otpCode); //将OTP验证码通过短信通道发送给用户,省略 System.out.println("telephone= " + telephone + "& otpCode= " + otpCode); return CommonReturnType.create(null); }
- 查看redis中的信息
127.0.0.1:6379> select 10 OK 127.0.0.1:6379[10]> keys * (empty array) 127.0.0.1:6379[10]> keys * 1) "spring:session:sessions:expires:be5ffd6a-aaaf-4ac3-bed1-7347bdf5e3f2" 2) "spring:session:sessions:be5ffd6a-aaaf-4ac3-bed1-7347bdf5e3f2" 3) "spring:session:expirations:1611133500000"
- 访问服务器
-
- 基于cookie传输sessionid的redis分布式实现(redis和数据库使用同一台服务器)
-
在原先的数据库服务器上安装redis
-
在项目根目录下通过mvn clean package命令打包,然后上传到应用服务器1和应用服务器2(实际应用中关掉一台服务器更新一台服务器jar包,这样服务器还能对外提供服务)
-
修改redis.conf文件
# redis绑定到本机ip,也就是说只有本机才能访问redis bind redis服务器ip
-
更改应用服务器1和应用服务器2的application.properties配置文件,绑定redis所在服务器ip
spring.redis.host=redis_server_ip
-
打开redis和数据库服务器的6379端口
# 打开6379端口 firewall-cmd --add-port=6379/tcp --permanent # 重新加载 firewall-cmd --reload # 查看 firewall-cmd --list-all
-
重新启动应用服务器1和应用服务器2的jar脚本文件、nginx服务器、数据库服务器、redis
./deploy.sh & ./nginx systenctl start mysql redis-server &
-
测试分布式环境登陆下单
可见每次都能下单成功,如果不使用redis存储session的话,由于是分布式环境,应用服务器采用负载均衡的模式,当用户登陆时session信息存储到一台服务器上,当用户请求比如下单时可能会路由到另一台服务器上,由于另一台服务器上没有session信息,所以在下单前的验证发现用户没有登陆,于是请求失败.
使用redis存储session,请求时session信息会从redis中获取,所以每次都能请求成功.
- 基于token传输(类似sessionid)的java代码分布式实现(基于redis)
-
token传输的本质
当用户登录的时候服务器端返回给客户端一个token(令牌),并将token和对应的用户信息存入到redis中,以及将token放到localStorage(类似cookie)中,当用户请求的时候,首先会从localStorage中取token,判断是否为空,如果不为空,则会根据token从redis中获取用户信息,从而判断用户是否登录.
即使从localStorage中获取token值,用户信息也有可能为空,因为redis中设定过期时间为1h.token一个小时后就失效了. -
UserController.java
@Resource private RedisTemplate redisTemplate; // 用户登陆接口 @PostMapping(value = "/login", consumes = { CONTENT_TYPE_FORMED}) public CommonReturnType login(@RequestParam("telephone") String telephone, @RequestParam("password") String password) throws BusinessException, NoSuchAlgorithmException { ... // 使用token的方法: 将原来的方法修改成若用户登陆验证成功,将对应的登陆信息和登陆凭证一起存入redis中 // 使用UUID生成登陆凭证token String uuidToken = UUID.randomUUID().toString().replace("-", ""); // 建立token和用户登陆态之间的联系 redisTemplate.opsForValue().set(uuidToken, userModel); // uuidToken的过期时间为1h redisTemplate.expire(uuidToken, 1, TimeUnit.HOURS); return CommonReturnType.create(uuidToken); }
-
login.html
$.ajax({ success:function (data) { if(data.status == "success"){ alert("登陆成功"); // 前端返回一个CommonReturnType对象,CommonReturnType有status和data两个参数,data.data就是token值 var token = data.data; // localStorage是html5新出的,比cookie更加安全且存储容量更大,没有4KB限制,现在基本上不用cookie,而用localStorage来完成对应的操作, // 本质上是key-value的json对象的数据库 window.localStorage["token"] = token; window.location.href="listitem.html"; }else{ alert("登陆失败,原因为"+data.data.errMsg); } }, error:function (data) { alert("登陆失败,原因为"+data.responseText); } });
-
OrderController.java
@PostMapping(value = "/createorder", consumes = { CONTENT_TYPE_FORMED}) public CommonReturnType createOrder(@RequestParam("itemId") Integer itemId, @RequestParam(value = "promoId", required = false) Integer promoId, @RequestParam("amount") Integer amount) throws BusinessException { // 使用token的方法 String token = httpServletRequest.getParameterMap().get("token")[0]; if(StringUtils.isEmpty(token)){ throw new BusinessException(EmBusinessError.USER_NOT_EXIST, "用户未登陆,不能下单"); } UserModel userModel = (UserModel)redisTemplate.opsForValue().get(token); if(userModel == null){ throw new BusinessException(EmBusinessError.USER_NOT_EXIST, "用户未登陆,不能下单"); } OrderModel orderModel = orderService.createOrder(userModel.getId(), itemId, promoId, amount); return CommonReturnType.create(null); }
-
打包及部署到服务器与之前操作类似.