前言
如果操作在一台Tomcat上,是没有问题的,但是当我们部署多台系统,配合Nginx的时候会出现用户登录的问题。
原因
由于Nginx 使用负载均衡策略,将请求分发到后端,
也就是说,在Tomcat 1 登录后,用户信息存在Tomcat1的Session里,下次请求又到了Tomcat2上,这时Tomcat2上Session里还没有用户信息,就要去登录。
解决
redis 安装教程自学
1、添加依赖:
<!-- spring data redis 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- commons-pool2 对象池依赖 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!-- spring-session 依赖 -->
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
2、配置 Redis
spring:
# thymeleaf关闭缓存
thymeleaf:
cache: false
# MySql数据源配置
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/secKill?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: 123456
# 最快的连接池
hikari:
# 连接池的名称
pool-name: DateHikariCP
# 最小空闲连接数
minimum-idle: 5
# 空闲连接存活最大时间 默认600000(10分钟)
idle-timeout: 1800000
# 最大连接数 默认10
maximum-pool-size: 10
# 从连接池返回的连接自动提交
auto-commit: true
# 连接最大存活时间 0代表永久存活,默认 1800000(分钟)
max-lifetime: 1800000
# 连接超时时间 默认30000(30秒)
connection-timeout: 30000
# 测试连接是否可以的查询语句
connection-test-query: SELECT 1
# Redis配置
redis:
host: 127.0.0.1
port: 6379
# 使用的数据库 0
database: 0
# 连接超时时间
timeout: 10000ms
lettuce:
# 连接池的配置
pool:
# 最大连接数 8
max-active: 8
# 最大连接的阻塞时间
max-wait: 10000ms
# 最大空闲连接
max-idle: 200
# 最小空闲连接
min-idle: 5
# mybatis-plus 配置
mybatis-plus:
# 配置 Mapper.xml的映射文件路径
mapper-locations: classpath*:/mapper/*.xml
# mybatis SQL 打印(接口方法在的包,不是 mapper.xml所在的包)
logging:
level:
com.example.seckill.mapper: DEBUG
这个时候 就OK 了,登录后,Session就会存进 Redis - 0(库) -spring Session 中
此时 注释掉我们代码中的
// /**
// * 校验用户成功后生成 cookie,将cookie 与用户存进 session中
// */
// // 使用UUID生成 cookie
// String cookie = UUIDUtil.uuid();
// // 将 cookie 和用户存进 session 中
// request.getSession().setAttribute(cookie,user);
// // 设置 cookie
// CookieUtil.setCookie(request,response,"userCookie",cookie);
再次登录 发现也是会存进Redis中。
1、添加依赖
<!-- spring data redis 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- commons-pool2 对象池依赖 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
2、redis 的配置 和方法1一样
3、Redsi配置类
package com.example.seckill.config;
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.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
* Redis 配置
*/
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
//key序列化
redisTemplate.setKeySerializer(new StringRedisSerializer());
//value序列化
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
//hash类型value序列化
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
//注入连接工厂
redisTemplate.setConnectionFactory(redisConnectionFactory);
return redisTemplate;
}
}
4、json工具类
这个很重要,如果没有的话 从redis中获取的user信息匹配不对,跳转不了 商品页面
package com.example.seckill.util;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.util.List;
/**
* Json工具类
*
* @author zhoubin
* @since 1.0.0
*/
public class JsonUtil {
private static ObjectMapper objectMapper = new ObjectMapper();
/**
* 将对象转换成json字符串
*
* @param obj
* @return
*/
public static String object2JsonStr(Object obj) {
try {
return objectMapper.writeValueAsString(obj);
} catch (JsonProcessingException e) {
//打印异常信息
e.printStackTrace();
}
return null;
}
/**
* 将字符串转换为对象
*
* @param 泛型
*/
public static <T> T jsonStr2Object(String jsonStr, Class<T> clazz) {
try {
return objectMapper.readValue(jsonStr.getBytes("UTF-8"), clazz);
} catch (JsonParseException e) {
e.printStackTrace();
} catch (JsonMappingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
/**
* 将json数据转换成pojo对象list
* Title: jsonToList
* Description:
*
* @param jsonStr
* @param beanType
* @return
*/
public static <T> List<T> jsonToList(String jsonStr, Class<T> beanType) {
JavaType javaType = objectMapper.getTypeFactory().constructParametricType(List.class, beanType);
try {
List<T> list = objectMapper.readValue(jsonStr, javaType);
return list;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
5、修改 UserServiceImpl代码
在验证用户后,将user 存进redis
并添加通过cookie 获取user的方法
package com.example.seckill.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.seckill.common.RespBean;
import com.example.seckill.common.RespBeanEnum;
import com.example.seckill.controller.parm.LoginRequestParam;
import com.example.seckill.exception.GlobalException;
import com.example.seckill.mapper.UserMapper;
import com.example.seckill.pojo.User;
import com.example.seckill.service.IUserService;
import com.example.seckill.util.CookieUtil;
import com.example.seckill.util.JsonUtil;
import com.example.seckill.util.MD5Util;
import com.example.seckill.util.UUIDUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
*
* 用户表 服务实现类
*
*
* @author jobob
* @since 2022-06-13
*/
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
@Autowired(required = false)
UserMapper userMapper;
@Autowired
private RedisTemplate redisTemplate;
@Override
public RespBean doLogin(LoginRequestParam param, HttpServletRequest request, HttpServletResponse response) {
String password = param.getPassword();
String mobile = param.getMobile();
User user = userMapper.selectById(mobile);
if (null == user){
throw new GlobalException(RespBeanEnum.LOGIN_ERROR);
}
if (!MD5Util.formPassToDBPass(password,user.getSalt()).equals(user.getPassword())){
throw new GlobalException(RespBeanEnum.LOGIN_ERROR);
}
/**
* 校验用户成功后生成 cookie,将cookie 与用户存进 session中
*/
// 使用UUID生成 cookie
String cookie = UUIDUtil.uuid();
// 将 cookie 和用户存进 session 中
// request.getSession().setAttribute(cookie,user);
redisTemplate.opsForValue().set("user:" + cookie, JsonUtil.object2JsonStr(user));
// 设置 cookie
CookieUtil.setCookie(request,response,"userCookie",cookie);
return RespBean.success(cookie);
}
@Override
public User getUserByCookie(String cookie, HttpServletRequest request, HttpServletResponse response) {
if (StringUtils.isEmpty(cookie)){
return null;
}
String userJson = (String) redisTemplate.opsForValue().get("user:" + cookie);
User user = JsonUtil.jsonStr2Object(userJson, User.class);
if (null != user){
// 设置 cookie
CookieUtil.setCookie(request,response,"userCookie",cookie);
}
return user;
}
}
6、商品Controller
package com.example.seckill.controller;
import com.example.seckill.common.RespBean;
import com.example.seckill.controller.parm.LoginRequestParam;
import com.example.seckill.pojo.User;
import com.example.seckill.service.IGoodsService;
import com.example.seckill.service.IUserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
@RequestMapping("/goods")
@Controller
@Slf4j
public class GoodsController {
@Autowired
IGoodsService goodsService;
@Autowired
IUserService userService;
/**
* 跳转商品页
* @param
* @param model
* @param cookie
* @return
*/
@RequestMapping("/toList")
public String toList(HttpServletRequest request, HttpServletResponse response, Model model, @CookieValue("userCookie") String cookie){
if (StringUtils.isEmpty(cookie)){ //如果 cookie为 空 跳转到 登录页面
return "login";
}
// 从session 中获取用户
// User user = (User) session.getAttribute(cookie);
User user = userService.getUserByCookie(cookie, request, response);
if (null == user){ // 如果用户信息为空 跳转登录
return "login";
}
// 将用户信息 传到前端页面
model.addAttribute("user",user);
return "goodsList";
}
}
以上避免每个接口都要去完成 根据Cookie获取User信息,避免代码冗余,特做优化:
1、UserArgumentResolvers类
package com.example.seckill.config;
import com.example.seckill.pojo.User;
import com.example.seckill.service.IUserService;
import com.example.seckill.util.CookieUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.MethodParameter;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 自定义用户参数
*/
@Component
public class UserArgumentResolvers implements HandlerMethodArgumentResolver {
@Autowired
IUserService userService;
@Override
public boolean supportsParameter(MethodParameter parameter) {
Class<?> parameterType = parameter.getParameterType();
return parameterType == User.class;
}
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);
String cookie = "486ceb27176a485f85e3c3d1f4f35e77";
// String cookie = CookieUtil.getCookieValue(request, "userCookie");
if (StringUtils.isEmpty(cookie)){ //如果 cookie为 空 跳转到 登录页面
return null;
}
return userService.getUserByCookie(cookie, request, response);
}
}
2、
package com.example.seckill.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.List;
/**
* MVC的配置类
*
*/
@Configuration
//@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Autowired
UserArgumentResolvers userArgumentResolvers;
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers){
resolvers.add(userArgumentResolvers);
}
}
3、goodController
package com.example.seckill.controller;
import com.example.seckill.common.RespBean;
import com.example.seckill.controller.parm.LoginRequestParam;
import com.example.seckill.pojo.User;
import com.example.seckill.service.IGoodsService;
import com.example.seckill.service.IUserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
@RequestMapping("/goods")
@Controller
@Slf4j
public class GoodsController {
@Autowired
IGoodsService goodsService;
@Autowired
IUserService userService;
/**
* 跳转商品页
* @param
* @param model
* @param
* @return
*/
@RequestMapping("/toList")
public String toList(Model model,User user){
// 将用户信息 传到前端页面
model.addAttribute("user",user);
return "goodsList";
}
}
问题:这里我遇到一个问题,每次生成Cookie存进Redis是正常的,但是登录成功后调用其他接口 通过CookieUtil 获取的Cookie并不是本次登录生成的Cookie 就很烦~ 我对这块不太了解 所以在保证redis中存在这个cookie ,将cookie写死了