面试常问框架知识(动手做才能深刻)

常用架构知识

一、分布式系统,系统间如何通信?

1、Socket通信,基于TCP/UDP二进制通讯;效率最高,编程最复杂,需要自定义通讯格式;
2、JavaEE体系中的RMI或EJB,在Socket基础之上封装的实现,直接面象Java对象编程,编程相对简单,不需要考虑低层实现,效率也不错,但只能是Java系统间通信
3、基于HTTP的通信,即服务端提供可访问URL,客户端模拟http请求完成通信;可跨平台跨语言,通讯效率相对较低,编程较简单
4、hessian,remoting on HTTP,类似于RMI与Socket的关系;
5、JMS,异步通信
6、WebService,可跨平台跨语言,工具丰富,复杂通信相对编程简单,通信效率低

使用场景

  • Http连接使用的是“请求一响应”的方式,当请求连接数少,或者一个请求一个响应这种场景可以考虑Http请求,比如web应用。
  • socket连接是长连接,适合请求较多,或者服务器端主动向客户端推送数据这种场景,比如QQ等。
  • RPC
    1)内部子系统较多。
    2)接口访问量巨大。
    3)接口非常多;
    像dubbo框架采用TCP协议,二进制传输,Google的grpc 这种 rpc 库使用的 是http2.0协议 (二进制分帧)。
    HTTP带请求头,相比之下,大大精简了传输内容。
    这也就是为什么后端进程间通常会采用 自定义tcp协议 的 rpc 来进行通信的原因。
    据说相较于HTTP1.1协议传输(HTTP1.1是字符协议),在大规模访问情况下,效率差十几倍。流行的RPC架构封装了 “服务发现”,“负载均衡”,“熔断降级” 一类面向服务的高级特性。单纯使用http调用则缺少了这些特性。

二、微服务系统之间如何通信?

1、微服务通信方式

同步:RPC ,REST等。
异步:消息队列,要考虑消息的可靠传输、高性能,以及编程模型的变化等。

2、RestTemplate通信的三种方式:
第一种方式:

1.product端我们新建一个controller,然后创建一个简单的待访问类【ClientController】

@RestController
public class ClientController {
        @GetMapping("/msg")
        public  String msg(){
            return "我是product端传来的消息!";
        }
}

启动测试可以正常访问
在这里插入图片描述

2.order端我们创建一个controller ,新建一个【OrderCotroller】

@RestController
public class OrderCotroller {
    @RequestMapping("getProductMsg")
    public String getProductMsg(){
        RestTemplate restTemplate=new RestTemplate();
        String forObject = restTemplate.getForObject("http://localhost:8080/msg", String.class);
        System.out.println("我们是order调用端:"+forObject);
        return forObject;
    }
}

然后启动,我们访问 【http://localhost:8090/getProductMsg】,发现可以正常访问,如下图:
在这里插入图片描述
在这里插入图片描述

第二种方式:

场景:若对方是多个节点,或者说是多台服务器做的负载均衡,我们是不知道其具体的地址的,这时候我们可以采用第二种方式
product端不变,修改我们的order端,此种方式是通过服务端的名字进行访问的

@RestController
public class OrderController {
 
    @Autowired
    private LoadBalancerClient loadBalancerClient;
 
    @RequestMapping("/getProductMsg")
    public String getProductMsg(){
        ServiceInstance product = loadBalancerClient.choose("PRODUCT");
        String host = product.getHost();
        int port = product.getPort();
        String formatStr = String.format("http://%s:%s", host,port)+"/msg";
        RestTemplate restTemplate=new RestTemplate();
        String forObject = restTemplate.getForObject(formatStr, String.class);
        System.out.println("我们是order调用端:"+forObject);
        return forObject;
    }
}

然后启动,我们访问 【http://localhost:8090/getProductMsg】,发现可以正常访问,如下图:
在这里插入图片描述

第三种方式:

1.新建一个【RestTemplateConfig】配置类

@Component
public class RestTemplateConfig {
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
}

2.然后进行引入并调用

@Autowired
private LoadBalancerClient loadBalancerClient;
@Autowired
private RestTemplate restTemplate;
 
@RequestMapping("/getProductMsg2")
    public String getProductMsg2(){
        String forObject = restTemplate.getForObject("http://PRODUCT/msg", String.class);
        System.out.println("我们是order调用端:"+forObject);
        return forObject;
}

然后启动,我们访问 【http://localhost:8090/getProductMsg】,发现可以正常访问,如下图:
在这里插入图片描述

三、消息队列中间件如何选型?

1.协议:AMQP、STOMP、MQTT、私有协议等。
2.消息是否需要持久化。
3.吞吐量。
4.高可用支持,是否单点。
5.分布式扩展能力。
6.消息堆积能力和重放能力。
7.开发便捷,易于维护。
8.社区成熟度。

四、springcloud 五大组件?

1、Eureka

在这里插入图片描述

作用:实现服务治理(服务注册与发现)
简介:Spring Cloud Eureka是Spring Cloud Netflix项目下的服务治理模块。
由两个组件组成:Eureka服务端和Eureka客户端。
Eureka服务端用作服务注册中心。支持集群部署。
Eureka客户端是一个java客户端,用来处理服务注册与发现。
在应用启动时,Eureka客户端向服务端注册自己的服务信息,同时将服务端的服务信息缓存到本地。客户端会和服务端周期性的进行心跳交互,以更新服务租约和服务信息。

2、Ribbon
在这里插入图片描述

作用:Ribbon,主要提供客户侧的软件负载均衡算法。
简介:Spring Cloud Ribbon是一个基于HTTP和TCP的客户端负载均衡工具,它基于Netflix Ribbon实现。通过Spring Cloud的封装,可以让我们轻松地将面向服务的REST模版请求自动转换成客户端负载均衡的服务调用。

注意看上图,关键点就是将外界的rest调用,根据负载均衡策略转换为微服务调用。Ribbon有比较多的负载均衡策略,以后专门讲解。

3、Hystrix

在这里插入图片描述

作用:断路器,保护系统,控制故障范围。
简介:为了保证其高可用,单个服务通常会集群部署。由于网络原因或者自身的原因,服务并不能保证100%可用,如果单个服务出现问题,调用这个服务就会出现线程阻塞,此时若有大量的请求涌入,Servlet容器的线程资源会被消耗完毕,导致服务瘫痪。服务与服务之间的依赖性,故障会传播,会对整个微服务系统造成灾难性的严重后果,这就是服务故障的“雪崩”效应。

4、Zuul
在这里插入图片描述

作用:api网关,路由,负载均衡等多种作用
简介:类似nginx,反向代理的功能,不过netflix自己增加了一些配合其他组件的特性。
在微服务架构中,后端服务往往不直接开放给调用端,而是通过一个API网关根据请求的url,路由到相应的服务。当添加API网关后,在第三方调用端和服务提供方之间就创建了一面墙,这面墙直接与调用方通信进行权限控制,后将请求均衡分发给后台服务端。

5、Config
在这里插入图片描述

作用:配置管理
简介:SpringCloud Config提供服务器端和客户端。服务器存储后端的默认实现使用git,因此它轻松支持标签版本的配置环境,以及可以访问用于管理内容的各种工具。

这个还是静态的,得配合Spring Cloud Bus实现动态的配置更新。

五、Spring Boot的核心注解有哪些?

  • SpringBootApplication
    实现配置文件,扫描包,自动配置
  • Component、@Service、@Controller、@Repository
    将类注入到spring容器中
  • @ResponseBody
    可以作用在方法上或类上,表示该方法的返回结果直接写入 HTTP response body 中而不会被解析为跳转路径,即不会经过视图解析器,返回什么数据即在页面输入什么数据。
  • @RestController
    该注解是@Controller和@ResponseBody的结合体,一般用于类,作用等于在类上面添加了@ResponseBody和@Controller
  • @AutoWired、@Qualifier、@Resource
    这3个注解都是基于注解方式进行自动装配,在容器里面将查找到的bean返回,一般@AutoWired用得最多,@Qualifier则需要配合@AutoWired使用,@Resource则是可以通过名字进行自动装配
  • @RequestMapping、@GetMapping、@PostMapping
    这3个注解功能也是类似的,通过这3个注解来映射请求,也就是通过它来指定控制器可以处理哪些URL请求,用在方法上,可以通过配置的url进行访问
  • @Value
    用于获取bean的属性,一般用于读取配置文件的数据,作用在变量上
  • @ConfigurationProperties
    用于注入Bean属性,然后再通过当前Bean获取注入值,作用在类上
  • @PropertySource
    用于指定要读取的配置文件,可以和@Value或@ConfigurationProperties配合使用;
    注意:@PropertySource不支持yml文件读取。
  • @Configuration、@Bean
    @Configuration作用于类上面,表明这是一个配置类,@Bean产生一个Bean对象加入Spring IOC容器
    注意:@Configuration标注在类上,相当于把该类作为spring的xml配置文件中,作用为:配置spring容器(应用上下文)
  • @RequestParam、@RequestBody、@PathVariable、@RequestHeader、@CookieValue
注解说明
RequestParam获取查询参数。即url?name=这种形式
PathVariable获取路径参数。即url/{id}这种形式。
RequestParam获取Body的参数,一般用于post获取参数
RequestHeader获取请求头的信息
CookieValue获取Cookie的信息

六、Spring容器中的Bean是否线程安全

容器本身并没有提供Bean的线程安全策略,因此可以说Spring容器中的Bean本身不具备线程安全的特性,但是具体还是要结合具体scope的Bean去研究。
Spring的bean作用域(scope)类型:
singleton
prototype
request
session
global-session
线程安全这个问题,要从单例与原型Bean分别进行说明:

  • 原型Bean:对于原型Bean,每次创建一个新对象,也就是线程之间并不存在Bean共享,自然是不会有线程安全的问题
  • 单例Bean:对于单例Bean,所有线程都共享一个单例实例Bean,因此是存在资源的竞争。如果单例Bean是一个无状态Bean,也就是线程中的操作不会对Bean的成员执行 查询 以外的操作,那么这个单例Bean是线程安全的。比如Spring mvc的 Controller 、 Service 、 Dao 等,这些Bean大多是无状态的,只关注于方法本身
    bean 分为 有状态 bean 和无状态 bean ,有状态 bean 即类定义了成员变量,可能被多个线程同时访问,则会出现线程安全问题;无状态 bean 每个线程访问不会产生线程安全问题,因为各个线程栈及方法栈资源都是独立的,不共享。即是,无状态 bean 可以在多线程环境下共享,有状态 bean不能

Spring中的Bean默认是单例模式的,框架并没有对bean进行多线程的封装处理。

实际上大部分时间Bean是无状态的(比如Dao) 所以说在某种程度上来说Bean其实是安全的。

但是如果Bean是有状态的,那就需要开发人员自己来进行线程安全的保证,最简单的办法就是改变bean的作用域,把 singleton 改为 protopyte 这样每次请求Bean就相当于是 new Bean() 这样就可以保证线程的安全了。

  • 有状态就是有数据存储功能
  • 无状态就是不会保存数据
    Controller 、 Service 和 Dao 层本身并不是线程安全的,只是如果只是调用里面的方法,而且多线程调用一个实例的方法,会在内存中复制变量,这是自己的线程的工作内存,是安全的。

Java虚拟机栈是线程私有的,它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。

局部变量的固有属性之一就是封闭在执行线程中。 它们位于执行线程的栈中,其他线程无法访问这个栈。

所以其实任何无状态单例都是线程安全的。

Spring的根本就是通过大量这种单例构建起系统,以事务脚本的方式提供服务。

@Controller、@Service是不是线程安全的?
默认配置下不是的。 因为默认情况下@Controller没有加上@Scope,没有加@Scope就是默认值singleton,单例的 。意思就是系统只会初始化一次 Controller 容器,所以每次请求的都是同一个 Controller 容器,当然是非线程安全的。举个栗子:

@RestController
public class TestController {
    private int var = 0;
    @GetMapping(value = "/test_var")
    public String test() {
        System.out.println("普通变量var:" + (++var));
        return "普通变量var:" + var ;
    }
}  

在postman里面发三次请求,结果如下:

普通变量var:1   
普通变量var:2  
普通变量var:3   

说明它不是线程安全的。可以给它加上 @Scope 注解,如下:

@RestController
@Scope(value = "prototype") // 加上@Scope注解,有2个取值:单例-singleton 多实例-prototype
public class TestController {
    private int var = 0;
    @GetMapping(value = "/test_var")
    public String test() {
        System.out.println("普通变量var:" + (++var));
        return "普通变量var:" + var ;
    }
}

这样一来,每个请求都单独创建一个 Controller 容器,所以各个请求之间是线程安全的,三次请求结果:

普通变量var:1
普通变量var:1
普通变量var:1

加了 @Scope 注解的 prototype 实例一定就是线程安全的吗?

@RestController
@Scope(value = "prototype") // 加上@Scope注解,有2个取值:单例-singleton 多实例-prototype
public class TestController {
    private int var = 0;
    private static int staticVar = 0;
?
    @GetMapping(value = "/test_var")
    public String test() {
        System.out.println("普通变量var:" + (++var)+ "---静态变量staticVar:" + (++staticVar));
        return "普通变量var:" + var + "静态变量staticVar:" + staticVar;
    }
}

三次请求结果:

普通变量var:1---静态变量staticVar:1
普通变量var:1---静态变量staticVar:2
普通变量var:1---静态变量staticVar:3

虽然每次都是单独创建一个 Controller 但是扛不住它变量本身是 static 的,所以说,即便是加上 @Scope 注解也不一定能保证 Controller 100%的线程安全。所以是否线程安全在于怎样去定义变量以及 Controller 的配置。来个全乎一点的实验,代码如下:

@RestController
@Scope(value = "singleton") // prototype singleton
public class TestController {
 
    private int var = 0; // 定义一个普通变量
 
    private static int staticVar = 0; // 定义一个静态变量
 
    @Value("${test-int}")
    private int testInt; // 从配置文件中读取变量
 
    ThreadLocal<Integer> tl = new ThreadLocal<>(); // 用ThreadLocal来封装变量
 
    @Autowired
    private User user; // 注入一个对象来封装变量
 
    @GetMapping(value = "/test_var")
    public String test() {
        tl.set(1);
        System.out.println("先取一下user对象中的值:"+user.getAge()+"===再取一下hashCode:"+user.hashCode());
        user.setAge(1);
        System.out.println("普通变量var:" + (++var) + "===静态变量staticVar:" + (++staticVar) + "===配置变量testInt:" + (++testInt)
                + "===ThreadLocal变量tl:" + tl.get()+"===注入变量user:" + user.getAge());
        return "普通变量var:" + var + ",静态变量staticVar:" + staticVar + ",配置读取变量testInt:" + testInt + ",ThreadLocal变量tl:"
                + tl.get() + "注入变量user:" + user.getAge();
    }
}@RestController
@Scope(value = "prototype") // prototype singleton
public class TestController {
 
    private int var = 0; // 定义一个普通变量
 
    private static int staticVar = 0; // 定义一个静态变量
 
    @Value("${test-int}")
    private int testInt; // 从配置文件中读取变量
 
    ThreadLocal<Integer> tl = new ThreadLocal<>(); // 用ThreadLocal来封装变量
 
    @Autowired
    private User user; // 注入一个对象来封装变量
 
    @GetMapping(value = "/test_var")
    public String test() {
        tl.set(1);
        System.out.println("先取一下user对象中的值:"+user.getAge()+"===再取一下hashCode:"+user.hashCode());
        user.setAge(1);
        System.out.println("普通变量var:" + (++var) + "===静态变量staticVar:" + (++staticVar) + "===配置变量testInt:" + (++testInt)
                + "===ThreadLocal变量tl:" + tl.get()+"===注入变量user:" + user.getAge());
        return "普通变量var:" + var + ",静态变量staticVar:" + staticVar + ",配置读取变量testInt:" + testInt + ",ThreadLocal变量tl:"
                + tl.get() + "注入变量user:" + user.getAge();
    }
}

补充 Controller 以外的代码:

config里面自己定义的Bean: User

@Configuration
public class MyConfig {
    @Bean
    public User user(){
        return new User();
    }
}
三次http请求结果如下:

先取一下user对象中的值:0===再取一下hashCode:241165852
普通变量var:1===静态变量staticVar:1===配置变量testInt:1===ThreadLocal变量tl:1===注入变量user:1
先取一下user对象中的值:1===再取一下hashCode:241165852
普通变量var:2===静态变量staticVar:2===配置变量testInt:2===ThreadLocal变量tl:1===注入变量user:1
先取一下user对象中的值:1===再取一下hashCode:241165852
普通变量var:3===静态变量staticVar:3===配置变量testInt:3===ThreadLocal变量tl:1===注入变量user:1
复制代码

可以看到,在单例模式下 Controller 中只有用 ThreadLocal 封装的变量是线程安全的。可以看到3次请求结果里面只有 ThreadLocal 变量值每次都是从 0+1=1 的,其他的几个都是累加的,而 user 对象呢,默认值是0,第二交取值的时候就已经是1了,关键它的 hashCode 是一样的,说明每次请求调用的都是同一个 user 对象。

下面将 TestController 上的 @Scope 注解的属性改一下改成多实例的: @Scope(value = “prototype”) ,其他都不变,再次请求,结果如下:

先取一下user对象中的值:0===再取一下hashCode:853315860
普通变量var:1===静态变量staticVar:1===配置变量testInt:1===ThreadLocal变量tl:1===注入变量user:1
先取一下user对象中的值:1===再取一下hashCode:853315860
普通变量var:1===静态变量staticVar:2===配置变量testInt:1===ThreadLocal变量tl:1===注入变量user:1
先取一下user对象中的值:1===再取一下hashCode:853315860
普通变量var:1===静态变量staticVar:3===配置变量testInt:1===ThreadLocal变量tl:1===注入变量user:1

分析这个结果发现,多实例模式下普通变量,取配置的变量还有 ThreadLocal 变量都是线程安全的,而静态变量和 user (看它的 hashCode 都是一样的)对象中的变量都是非线程安全的。也就是说尽管 TestController 是每次请求的时候都初始化了一个对象,但是静态变量始终是只有一份的,而且这个注入的 user 对象也是只有一份的。静态变量只有一份这是当然的咯,那么有没有办法让 user 对象可以每次都new一个新的呢?当然可以:

public class MyConfig {
    @Bean
    @Scope(value = "prototype")
    public User user(){
        return new User();
    }    
}

在config里面给这个注入的Bean加上一个相同的注解 @Scope(value = “prototype”) 就可以了,再来请求一下:

先取一下user对象中的值:0===再取一下hashCode:1612967699
普通变量var:1===静态变量staticVar:1===配置变量testInt:1===ThreadLocal变量tl:1===注入变量user:1
先取一下user对象中的值:0===再取一下hashCode:985418837
普通变量var:1===静态变量staticVar:2===配置变量testInt:1===ThreadLocal变量tl:1===注入变量user:1
先取一下user对象中的值:0===再取一下hashCode:1958952789
普通变量var:1===静态变量staticVar:3===配置变量testInt:1===ThreadLocal变量tl:1===注入变量user:1

可以看到每次请求的 user 对象的 hashCode 都不是一样的,每次赋值前取 user 中的变量值也都是默认值0。

七、ThreadLocal vs 线程同步机制

ThreadLocal 和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。
线程同步机制

  • 在同步机制中,通过对象的 锁机制 保证同一时间只有一个线程访问变量。这时该变量是多个线程共享的,使用同步机制要求程序慎密地分析什么时候对变量进行读写,什么时候需要锁定某个对象,什么时候释放对象锁等繁杂的问题,程序设计和编写难度相对较大。
    ThreadLocal
  • ThreadLocal 则从另一个角度来解决多线程的并发访问。 ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突 。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。 ThreadLocal 提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进 ThreadLocal 。
  • 由于 ThreadLocal 中可以持有任何类型的对象,低版本JDK所提供的 get() 返回的是 Object 对象,需要强制类型转换。但JDK 5.0通过泛型很好的解决了这个问题,在一定程度地简化 ThreadLocal 的使用
    概括起来说,对于多线程资源共享的问题, 同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式 。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。
    总结
  • 在 @Controller/@Service 等容器中,默认情况下,scope值是单例- singleton 的,也是线程不安全的
  • 尽量不要在 @Controller/@Service 等容器中定义静态变量,不论是单例( singleton )还是多实例( prototype )都是线程不安全的
  • 默认注入的Bean对象,在不设置scope的时候也是线程不安全的
  • 一定要定义变量的话,用 ThreadLocal 来封装,这个是线程安全的

八、事务控制

1.涉及到@Transactional对于事务的控制,传播行为,然后说一下Spring事务的回滚机制。
2.springboot面相切面编程-前置增强、后置增强、环绕增强(advice接口),实现implements MethodBeforeAdvice等接口

九、Nacos与Eureka的不同

1、Na cos与Eureka的保护方式不同
Eureka保护方式:当在短时间内, 统计续约失败的比例, 如果达到一定阈值, 则会触发 自我保护的机制, 在该机制下, EurekaServer不会剔除任何的微服务, 等到正常后, 再退出自我保护机制。自我保护开关(eureka.server.enable-self-preservation: false)
Nacos保护方式:当域名健康实例(Instance)占总服务实例(Instance)的比例小 于阈值时,无论实例(Instance)是否健康,都会将这个实例(Instance)返回给 客户端。这样做虽然损失了一部分流量,但是保证了集群的剩余健康实例(Instance) 能正常工作。

十、SpringBoot+Vue前后端分离(登录状态管理和校验)

1、问题

本文内容:前后端开发分离,如何实现用户登录状态监控和校验,要解决的几个问题:

A用户登录A机器成功,此时A用户登录B机器,A机器的A用户登录状态需要下线,即保证一个用户只能同时在线一台机器;
A用户登录成功,一周之内不需要再次登录;

2、实现原理

  • 用户首次访问站点,后端从请求体中读取cookie,此时cookie中的token为空,用户需要进行登录;
  • 用户首次登录成功,后端的response向浏览器写入cookie,在cookie中存储该用户唯一的并且有时效性的token;
  • 用户二次访问站点,如果token没有失效(过期或更新,过期:是指我们设定的一周的有效期;更新:是指用户在其他设备/浏览器登录后,redis数据库会更新token),不做处理;如果失效,重新设定路由指向登录页,引导用户重新登录;
  • 如果用户退出操作,调用退出接口,后端的response会主动删除浏览器中的cookie;

3、概述

常规的PC站点开发,如果有访问权限控制,需要用户登录才能访问,因此合理安全的方案至关重要,本文方案的具体实现过程如下:

  • 前端请求后端的每一个接口,需要对接口返回值的code码进行分类处理,我们定义,在任意页面的任意接口返回值code码为11115时,表示用户登录身份失效,此时需要重新登录,用路由控制直接跳转到登录页面;
  • 后端在接收任意一个请求时,需要在Spring MVC拦截器Interceptor中做处理,对用户登录状态进行判断,读取浏览器中的cookie和redis数据库进行对比,如果cookie中的token有效,返回正常结果,如果token失效,返回11115 code码;

4、代码实现

前端代码

 fetch().then((Response)=>{
      let data = Response.json();//res是一个json文件,使用 json() 读取并解析数据,返回一个被解析为JSON格式的promise对象
      data.then((res,rej)=>{
        if(res.code == 100){
        
        }else if(res.code == 11115){
          //用户登录失效,token失效,跳转到登录页面
          history.replace('/login')
          window.location.reload()
        }else{
        
        }
      })
    }).catch((e)=>{
    
    })

后端代码
每个接口的拦截,使用Interceptor拦截器

//HandlerInterceptor接口
package org.springframework.web.servlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public interface HandlerInterceptor {
    boolean preHandle(HttpServletRequest var1, HttpServletResponse var2, Object var3) throws Exception;
    void postHandle(HttpServletRequest var1, HttpServletResponse var2, Object var3, ModelAndView var4) throws Exception;
    void afterCompletion(HttpServletRequest var1, HttpServletResponse var2, Object var3, Exception var4) throws Exception;
}

//HandlerInterceptor接口的实现
public class AuthInterceptor implements HandlerInterceptor {
	//引入 声明
    @Autowired
    private RedisManage redisManage;

	//重写
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object o) throws Exception {
    	String Origin = request.getHeader("Origin");
    	if(Origin != null){
	    	response.setHeader("Access-Control-Allow-Origin",Origin);
	        response.setHeader("Access-Control-Allow-Credentials", "true");
	        response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
	        response.setHeader("Access-Control-Max-Age", "3600");
	        response.setHeader("Access-Control-Allow-Headers", "DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type, Accept-Language, Origin, Accept-Encoding");
    	}
        String url = request.getRequestURI();
    	boolean isLogout = false;
        String tokenStr = "";//测试用
        Cookie[] cookies = request.getCookies();
        if(cookies == null) {
            isLogout = true;
        }else {
        	tokenStr = cookies.getName();//没有这个方法cookies.getName(),此处举个例子
        }
        if(tokenStr == ‘’) {
            isLogout = true;
        }else {
        	//查询redis数据库
            if(redisManage.getValue(token) == null){
                isLogout = true;
            }else{
                String tokenInRedis = (String) redisManage.getValue(token);
                if(tokenInRedis != token) {
                    isLogout = true;
                }
            }
        }
        if(isLogout) {
            //清空session
            request.getSession().invalidate();
            //清空cookies信息
            Cookie tokenInRedis = new Cookie("token","");
            tokenInRedis.setMaxAge(0);
            tokenInRedis.setHttpOnly(true);
            //响应中写入cookie
            response.addCookie(tokenInRedis);
            ServiceResult serviceResult = new ServiceResult(60*60*24*7);
            response.getWriter().write(JSONObject.toJSONString(serviceResult));
            response.getWriter().flush();
            return false;
        }
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {

    }
}

登录类

public ServiceResult login(@RequestBody User user, HttpServletRequest request , HttpServletResponse response){
	ServiceResult serviceResult = userService.login(user);
	String userName = 'wanshaobo';
	if(serviceResult.getCode() == 100){
		String md5Token = MD5Util.md5(userName + U2AESKey + "REMIXED" + Math.random());
		Cookie cookie01 = new Cookie("token",md5Token);
		//7天过期时间,单位为秒
		U2TokenCookie.setMaxAge(60*60*24*7);
		U2TokenCookie.setHttpOnly(true);
		U2TokenCookie.setPath("/");
		response.addCookie(cookie01);
		//存储token到redis
		redisManage.putValueExpireTimes(userName,md5Token,60*60*24*7L);
	}
	return serviceResult;
}
**登出类**
```java
public ServiceResult login(@RequestBody User user, HttpServletRequest request , HttpServletResponse response){
	ServiceResult serviceResult = userService.login(user);
	String userName = 'wanshaobo';
	if(serviceResult.getCode() == 100){
		String md5Token = MD5Util.md5(userName + U2AESKey + "REMIXED" + Math.random());
		Cookie cookie01 = new Cookie("token",md5Token);
		//7天过期时间,单位为秒
		U2TokenCookie.setMaxAge(60*60*24*7);
		U2TokenCookie.setHttpOnly(true);
		U2TokenCookie.setPath("/");
		response.addCookie(cookie01);
		//存储token到redis
		redisManage.putValueExpireTimes(userName,md5Token,60*60*24*7L);
	}
	return serviceResult;
}

(持续更新中…)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

菠菜很好吃

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值