一、session共享
考试系统是一个高并发项目,可能同时有百万级用户在线考试,因此生产环境有30台服务器,涉及到session共享的问题。
项目中,使用spring-session解决session共享问题。
spring-session借助redis实现session共享。(生产上用到了2台服务器分别部署了1个redis)
springboot项目使用spring-session使用方法:
1.pom.xml中引入jar包
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.6.RELEASE</version>
<relativePath/>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
</dependencies>
2.配置文件application.properties中,配置redis相关信息
spring.redis.database=1
spring.redis.host=127.0.0.1
spring.redis.port=37652
spring.redis.password=root
3.Application.java中增加注解启动spring-session
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 2 * 3600)
其中设置了过期时间是2小时。
4.这样就实现了session共享,后续可以编写controller方法、配置nginx,进行测试。
相关文章:
https://blog.youkuaiyun.com/qq_39669058/article/details/90235990
https://www.cnblogs.com/david1216/p/11468772.html
二、ThreadLocal传递参数
ThreadLocal是一个仅供当前线程使用的存储类,每个线程各自有一个ThreadLocalMap对象,可以set与get值。(与全局变量不同)
考试系统中,当一个controller接收到http请求时,会把其中的参数存入ThreadLocal,供后续方法使用;当所有方法结束时,清除该ThreadLocal中的内容;是通过拦截器实现的。
这样看起来简洁些,不需要把request对象或参数对象沿着方法传递下去。
大致记录如下:
1.编写一个存放ThreadLocal的java类
public class ProjectLocal {
public static ThreadLocal<ParamInfo> paramLocal = new ThreadLocal<>();
@Data
public static class ParamInfo{
private String name;
private String phone;
}
}
2.编写一个拦截器,从http请求的header中获取参数,new一个ThreadLocal对象并存入参数。
@Component
@Slf4j
public class ParamVerifyInterceptor extends HandlerInterceptorAdapter {
private Set<String> paramSet = new HashSet<>(Arrays.asList("name","phone","sign"));
@Autowired
private VerifyConfig verifyConfig;
//进入controller前执行的方法
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
//清空ThreadLocal中的内容,准备重新设置内容;仅在当次controller线程自身中使用
ProjectLocal.paramLocal.remove();
//从http请求的header中拿出参数来(通过header传参)
String paramString = request.getHeader("Params");
//如果没有参数,则返回
if(StringUtils.isEmpty(paramString)){
return true;
}
//转换为map
Map<String, String> params = paramsToMap(paramString);
String name = params.get("name");
String phone = params.get("phone");
String sign = params.get("sign");
//如果不需要验签,则返回
if(sign == null){
return true;
}
//验签;当然,会移除sign字段
//验证name与phone拼接后的字符串经过sha256Hex与md5Hex后,是否与sign相同
boolean verify = VerifyUtil.verifySign(params, VerifyConfig.getSecretKey(), sign);
if(verify){
//new一个javabean,将参数存入,供程序后续使用
ProjectLocal.paramLocal.set(new ProjectLocal.paramInfo());
ProjectLocal.paramLocal.get().setName(name);
ProjectLocal.paramLocal.get().setPhone(phone);
return true;
}
//验签失败,拒绝访问
else{
response.sendError(HttpServletResponse.SC_FORBIDDEN,"FORBIDDEN");
return false;
}
}
//执行完controller后执行的方法
@Override
public boolean afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
super.afterCompletion(request, response, handler, ex);
//清空ThreadLocal中的内容
ProjectLocal.paramLocal.remove();
}
}
3.注册该拦截器
@Configuration
public class WebAppConfig implements WebMvcConfigurer {
@Autowired
private ParamVerifyInterceptor paramVerifyInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry){
registry.addInterceptor(paramVerifyInterceptor).addPathPatterns("/exams/**/sign","exams/**/login");
}
@Bean
public LocaleResolver localeResolver(){
return new CustomLocaleResolver();
}
}
4.后续方法中,通过ThreadLocal获取需要的参数
String name = ParamLocal.get().getName();
String phone = ParamLocal.get().getPhone();
相关文章:
https://www.jianshu.com/p/3c5d7f09dfbd