本文是看某课网关于 SpringCloud 微服务实战的视频总结的笔记,其中涉及了html
因为是随堂笔记,写的有点随意,大佬们见谅~java
文中提到的大部分技术都会在个人一个开源项目中用到,这个项目后端业务逻辑部分已经基本写完了,就差权限验证、网关配置和后期优化啦,感兴趣的大佬能够看看。git
项目地址:github.com/cachecats/c…github
启动 SpringBoot 项目web
java -jar test.jar
复制代码
启动 SpringBoot 项目并指定端口redis
java -jar -Dserver.port=8899 test.jar
复制代码
启动 SpringBoot 项目并指定后台运行spring
nohup java -jar test.jar > /dev/null 2>&1 &
复制代码
查看进程docker
ps -ef | grep eureka
复制代码
杀死进程json
kill -9 进程号
复制代码
在本地安装项目到本地 maven 仓库bootstrap
mvn -Dmaven.test.skip=true -U clean install
复制代码
选 CloudDiscovery -> Eureka Server
注意 SpringBoot 版本
在启动类 Application 上加注解
@EnableEurekaServer
复制代码
在配置文件 application.yml
注册服务
eureka:
client:
service-url:
defaultZone: http://localhost:8080/eureka/
复制代码
启动项目,浏览器输入地址 http://localhost:8080
便可看到项目正常启动,并将本身注册上去了
ApplicationName
是 UNKNOWN
,想改应用名字的话在 application.yml
作如下配置
eureka:
client:
service-url:
defaultZone: http://localhost:8080/eureka/
spring:
application:
name: eureka
复制代码
再启动项目,浏览器中查看,名字正确显示
若是不想让注册中心出如今注册列表中,配置 register-with-eureka: false
eureka:
client:
service-url:
defaultZone: http://localhost:8080/eureka/ #配置默认注册地址
register-with-eureka: false #不让该服务显示在应用列表中
spring:
application:
name: eureka #配置应用名字
复制代码
选 CloudDiscovery -> Eureka Discovery
注意 SpringBoot 和 SpringCloud 版本与server一致
Application
添加注解 @EnableDiscoveryClient
@SpringBootApplication
@EnableDiscoveryClient
public class ClientApplication {
public static void main(String[] args) {
SpringApplication.run(ClientApplication.class, args);
}
}
复制代码
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
spring:
application:
name: client
复制代码
http://clientname:8080/
eureka:
instance:
hostname: clientName
复制代码
这是 SpringCloud 的自我保护机制,就是无论这个服务在不在线,都把它当成在线。开发环境中为了调试方即可以关闭这个功能,注意生产环境必定要打开。
在 server 的 applicaitono.yml
中作以下配置
eureka:
server:
enable-self-preservation: false
复制代码
目前是 Client 注册到一个 Eureka Server 上,若是这个 Server 挂掉了怎么办呢?
能够启动多个 Eureka Server ,让他们相互注册。
这里演示启动三个 Eureka Server 相互注册,并把 Client 分别注册到这三个 Server 上。
分别在 8761, 8762, 8763 三个端口上启动 EurekaApplication
、EurekaApplication2
、EurekaApplication3
三个服务,在三个服务的 applicaiton.yml
中分别配置其余两个服务的地址。
如EurekaApplication
就配 http://localhost:8762/eureka/,http://localhost:8763/eureka/
,
EurekaApplication2
就配 http://localhost:8761/eureka/,http://localhost:8763/eureka/
,
EurekaApplication3
就配 http://localhost:8761/eureka/,http://localhost:8762/eureka/
,
EurekaApplication
的 applicaiton.yml
以下:
eureka:
client:
service-url:
defaultZone: http://localhost:8762/eureka/,http://localhost:8763/eureka/
复制代码
这样就把三个服务互相关联上了。
而后在 Client 的 applicaiton.yml
中把三个服务地址都配上
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/,http://localhost:8762/eureka/,http://localhost:8763/eureka/
复制代码
查看EurekaApplication
,发现注册到了8762 和 8763 上。三个server只要还有一个活着,服务就不会挂。
应用间通讯有两种主流通讯方式:
HTTP表明: SpringCloud
RPC表明: Dubbo
SpringCloud 中服务间两种 restful 调用方式
RestTemplate 调用一共有三种方法,下面一一介绍。
先在要提供数据的应用里写个 Controller 暴露接口,叫 ServerController
吧
@RestController
@RequestMapping("/product")
public class ServerController {
@GetMapping("/msg")
public String getMsg(){
return "I am product msg";
}
}
复制代码
而后在须要接收数据的应用写个 Controller ,叫 ClientController
直接使用 RestTemplate
手动写入提供数据的 url 地址
@RestController
@RequestMapping("/order")
@Slf4j
public class ClientController {
@GetMapping("/getmsg")
public String getMsg(){
RestTemplate restTemplate = new RestTemplate();
String result = restTemplate.getForObject("http://localhost:8080/product/msg", String.class);
log.info("result={}", result);
return result;
}
}
复制代码
不手动输入 url 地址,使用 LoadBalancerClient
经过应用名动态获取,而后再使用 RestTemplate
。
loadBalancerClient.choose("product");
这个 product
是提供数据的应用 id
@RestController
@RequestMapping("/order")
@Slf4j
public class ClientController {
@Autowired
LoadBalancerClient loadBalancerClient;
@GetMapping("/getmsg")
public String getMsg(){
ServiceInstance serviceInstance = loadBalancerClient.choose("product");
String url = String.format("http://%s:%s/product/msg", serviceInstance.getHost(), serviceInstance.getPort());
RestTemplate restTemplate = new RestTemplate();
String result = restTemplate.getForObject(url, String.class);
return result;
}
}
复制代码
用 @LoadBalanced
注解
新建 RestTemplateConfig
类
@Component
public class RestTemplateConfig {
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
复制代码
而后在 ClientController
中使用。
restTemplate.getForObject("http://product/product/msg", String.class);
url 中的两个 product
,第一个表示应用名,第二个是 api 的地址。若是 api 地址是 /abc
,那 url 就是 http://product/abc
@RestController
@RequestMapping("/order")
@Slf4j
public class ClientController {
@Autowired
RestTemplate restTemplate;
@GetMapping("/getmsg")
public String getMsg(){
String result = restTemplate.getForObject("http://product/product/msg", String.class);
return result;
}
}
复制代码
使用 Feign 有如下几个步骤
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
复制代码
注意
这里注意一个问题,有的视频和文章里引的依赖是 spring-cloud-starter-feign
,刚开始我引的也是这个,但是死活引不进来。这时到 maven 仓库 mvnrepository.com/ 里看一下,搜 spring-cloud-starter-feign
看到上面写着:
Spring Cloud Starter Feign (deprecated, please use spring-cloud-starter-openfeign)
说 spring-cloud-starter-feign
已经废弃了,请使用 spring-cloud-starter-openfeign
。
我用的 SpringCloud 版本比较高,可能不支持 spring-cloud-starter-feign
了。
在程序的入口类配置 @EnableFeignClients
注解
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
}
复制代码
找不到 @EnableFeignClients
的话请检查依赖是否引对,版本是否正确。
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
@FeignClient(name = "product")
@Component
public interface ProductClient {
@GetMapping("/product/msg")
String getMsg();
}
复制代码
接口上加 @FeignClient
注解,括号里的 name = "product"
声明了要去应用名为 product
的应用找数据(应用名大小写不敏感)。
@GetMapping("/product/msg")
注明请求方式和路径。
因此 getMsg()
方法的意思是要请求 product
应用里 api 为 /product/msg
的字符串。
@RestController
@RequestMapping("/order")
@Slf4j
public class ClientController {
@Autowired
ProductClient productClient;
@GetMapping("/getmsg")
public String getMsg(){
return productClient.getMsg();
}
}
复制代码
注入第三步建立的 ProductClient
,而后直接调用接口里定义的方法便可。
我这里注入 ProductClient
编辑器会报错,但不影响编译。
Could not autowire. No beans of 'ProductClient' type found
复制代码
看着不顺眼就在 ProductClient
上加了个 @Component
注解。
最后总结下 Feign :
本文用 Docker 安装 RabbitMQ,Docker教程看 这里
打开 RabbitMQ 官方下载页面 www.rabbitmq.com/download.ht… Docker
点击 Docker image
连接进入到详情页
能够看到最新版本是 3.7.7
,复制 3.7.7-management
,在命令行敲如下代码并运行
docker run -d --hostname my-rabbit -p 5672:5672 -p 15672:15672 rabbitmq:3.7.7-management
复制代码
使用 docker ps 来查看咱们正在运行的容器
Solo-mac:~ solo$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
345859e88ead rabbitmq:3.7.7-management "docker-entrypoint.s…" 2 minutes ago Up 2 minutes 4369/tcp, 5671/tcp, 0.0.0.0:5672->5672/tcp, 15671/tcp, 25672/tcp, 0.0.0.0:15672->15672/tcp goofy_volhard
复制代码
浏览器输入 http://localhost:15672
打开 RabbitMQ ,第一次会让输用户名密码,用户名和密码都是 guest
, 输入以后进入管理界面
到此 RabbitMQ 安装完成。
新建项目 config
勾选 Cloud Config -> Config Server 和 Cloud Discovery -> Eureka Discovery
复制代码
在启动类上添加注解
@SpringBootApplication
@EnableDiscoveryClient
@EnableConfigServer
public class ConfigApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigApplication.class, args);
}
}
复制代码
在 github 上或码云上新建一个项目,将 order
项目的 application.yml
配置文件传上去,用来测试。
配置项目的 application.yml
文件
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
server:
port: 8081
spring:
application:
name: config
cloud:
config:
server:
git:
uri: https://gitee.com/xxxxxxx
username: xxxxxx
password: xxxxxx
basedir: xxxxxx #本地的路径
复制代码
uri 是仓库地址,username 和 password 是仓库的用户名密码
配置完成后启动项目,能够在注册中心看到项目注册上去了,浏览器中访问 http://localhost:8081/order-a.yml
,也能正常读取 git 上的配置文件。
访问地址输入的后缀是 '/order-a.yml', 这里解释一下。
/{name}-{profiles}.yml
/{label}/{name}-{profiles}.yml
name: 服务名,这里是 order
profiles 环境
label 分支(branch) 不写的话默认是 master 分支
复制代码
用 order 项目做为客户端
pom.xml
文件里添加 config-client
依赖<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-client</artifactId>
</dependency>
复制代码
将 application.yml
更名为 bootstrap.yml
配置 bootstrap.yml
spring:
application:
name: order
cloud:
config:
discovery:
enabled: true
service-id: config #配置中心server的应用名
profile: dev
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
复制代码
配置完后启动项目,能够正常运行。
注意:
- 不要忘记改配置文件名为
bootstrap.yml
- 在本地配置文件中配置 eureka 的 service-url,而不是从 config 中读取,缘由是若是eureka 的端口号不是默认的 8761 ,会找不到。
- 若是git上有
order.yml
,order-dev.yml
,配置的是order-dev.yml
,那加载的时候也会默认加载order.yml
并将两个文件合并。利用这一特性,能够在order.yml
里写公共配置。
在 config 项目添加 spring cloud bus 依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
复制代码
启动项目,在 RabbitMQ 控制台查看,有一个链接,说明配置成功。
同上在 order 的 server 模块里添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
复制代码
运行再看 RabbitMQ ,出现两个链接
配置 config 项目的 application.yml
文件,将 bus-refresh
接口暴露出来
management:
endpoints:
web:
exposure:
include: "*"
复制代码
在 order 中新建一个 controller,用来读取远程配置里的 env 字段
@RestController
@RequestMapping("/env")
@RefreshScope
public class EnvController {
@Value("${env}")
private String env;
@GetMapping("/print")
public String print(){
return env;
}
}
复制代码
注意必定要加
@RefreshScope
注解,不然不会自动刷新配置
再次启动两个项目,访问 http://localhost:8899/env/print
,获得结果是 git 上配置的 env 的值。
更改 git 上 env 的值,发送 post 请求 http://127.0.0.1:8081/actuator/bus-refresh
刷新消息队列,再刷新 http://localhost:8899/env/print
会看到没有重启项目但 env 的值改变了。
git 配置
env: dev5
girl:
name: lili
age: 18
复制代码
新建类 GirlConfig
@Data
@Component
@ConfigurationProperties("girl")
@RefreshScope
public class GirlConfig {
private String name;
private Integer age;
}
复制代码
新建 GirlController
@RestController
public class GirlController {
@Autowired
GirlConfig girlConfig;
@GetMapping("girl/print")
public String print(){
return "name= " + girlConfig.getName() + ", age= " + girlConfig.getAge();
}
}
复制代码
浏览器输入 http://localhost:8899/girl/print
,获得结果 name= lili, age= 18
。
跟上面同样改变 git 的配置,发送 post 请求 http://127.0.0.1:8081/actuator/bus-refresh
刷新消息队列,能够看到获得的结果也跟着改变了。
若是发请求 http://127.0.0.1:8081/actuator/bus-refresh
返回值是 500,那就是 bus 没配好。最后可能的缘由是版本问题,把 SpringBoot
版本改为 2.0.0.BUILD-SNAPSHOT
,SpringCloud
版本改为 Finchley.BUILD-SNAPSHOT
应该就没问题了。
在 order 项目中演示
先在配置文件中配置 rabbitmq
的信息。这些配置能够放到远程 git 上
spring:
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
复制代码
接收消息有三种基本用法
myQueue
/** * RabbitMQ 消息接收者 */
@Slf4j
@Component
public class MqReceiver {
@RabbitListener(queues = "myQueue")
public void process(String msg){
log.info("reveicer: " + msg);
}
}
复制代码
建立消息发送者,简单起见在测试类里写个方法
/** * RabbitMQ 消息发送方 */
@Component
public class RabbitMQTest extends OrderApplicationTests {
@Autowired
AmqpTemplate amqpTemplate;
@Test
public void test1(){
amqpTemplate.convertAndSend("myQueue", "now " + new Date());
}
}
复制代码
运行测试,控制台成功打印出收到的消息。
先将方法一建立的队列 myQueue
删除,发送方不变,改一下接收方
@RabbitListener(queuesToDeclare = @Queue("myQueue"))
public void process(String msg){
log.info("reveicer: " + msg);
}
复制代码
用 queuesToDeclare
会自动建立队列。
先将队列 myQueue
删除,发送方不变,改一下接收方
@RabbitListener(bindings = @QueueBinding(
value = @Queue("myQueue"),
exchange = @Exchange("myExchange")
))
public void process(String msg){
log.info("reveicer: " + msg);
}
复制代码
假设订单服务有两个分组,数码供应商和水果供应商。下单以后是电脑的订单会被发给数码供应商,是水果的订单会被发给水果供应商。两个供应商各自接收各自的消息。
接收者
/** * 数码供应商接收消息 * @param msg */
@RabbitListener(bindings = @QueueBinding(
exchange = @Exchange("myOrder"),
key = "computer",
value = @Queue("computerOrder")
))
public void processComputer(String msg){
log.info("computerOrder reveicer: " + msg);
}
/** * 水果供应商接收消息 * @param msg */
@RabbitListener(bindings = @QueueBinding(
exchange = @Exchange("myOrder"),
key = "fruit",
value = @Queue("fruitOrder")
))
public void processFruit(String msg){
log.info("fruitOrder reveicer: " + msg);
}
复制代码
消息发送者
@Test
public void send(){
amqpTemplate.convertAndSend("myOrder", "computer", "now " + new Date());
}
复制代码
这里发送的是电脑的订单,convertAndSend()
三个参数依次是 exchange
, routingKey
, message
发送消息以后只有 computerOrder
接收到了消息。
查看 RabbitMQ 控制体台能够清楚的看到 exchange 和 queue 的关系
Spring Cloud Stream is a framework for building highly scalable event-driven microservices connected with shared messaging systems.
复制代码
Spring Cloud Stream 目前支持的消息中间件只有 RabbitMQ
和 Kafka
下面结合 RabbitMQ
演示 Spring Cloud Stream 的用法
引入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>
复制代码
配置 RabbitMQ,跟上节同样
spring:
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
复制代码
建立接口 StreamClient
import org.springframework.cloud.stream.annotation.Input;
import org.springframework.cloud.stream.annotation.Output;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.SubscribableChannel;
public interface StreamClient {
String INPUT = "messageInput";
String OUTPUT = "messageOut";
@Input(INPUT)
SubscribableChannel input();
@Output(OUTPUT)
MessageChannel output();
}
复制代码
建立消息接受者,这里先接收字符串
@Component
@EnableBinding(StreamClient.class)
@Slf4j
public class StreamReceiver {
@StreamListener(StreamClient.OUTPUT)
public void process(String obj){
log.info("StreamReceiver: " + obj);
}
}
复制代码
建立消息发送者
import com.solo.order.message.StreamClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Date;
@RestController
public class SendMessageController {
@Autowired
private StreamClient streamClient;
@GetMapping("/sendMessage")
public void send() {
String message = "now: " + new Date();
streamClient.output().send(MessageBuilder.withPayload(message).build());
}
}
复制代码
注意 MessageBuilder 别引错包了
若是同时开启了多个实例,有可能多个实例都收到消息,为避免这个问题,能够用消息分组。
在配置文件里添加
spring:
cloud:
#消息分组
stream:
bindings:
messageInput: #本身定义的队列名
group: order # group 名能够随意起
复制代码
改造消息接收者
/** * 接收对象 * @param dto */
@StreamListener(StreamClient.OUTPUT)
public void process(OrderDTO dto){
log.info("StreamReceiver: " + dto);
}
复制代码
改造消息发送者
/** * 发送对象 */
@GetMapping("/sendMessage")
public void send() {
OrderDTO dto = new OrderDTO();
dto.setOrderId("12345678");
streamClient.output().send(MessageBuilder.withPayload(dto).build());
}
复制代码
若是想在 MQ 控制台看到序列化以后的 json 字符串而不是对象名,更改配置以下
spring:
cloud:
#消息分组
stream:
bindings:
messageInput: #本身定义的队列名
group: order # group 名能够随意起
content-type: application/json #让mq里显示json字符串而不是对象
复制代码
添加 content-type: application/json
在 StreamClient 里添加两个接口
public interface StreamClient {
String INPUT = "messageInput";
String OUTPUT = "messageOut";
String INPUT2 = "messageInput2";
String OUTPUT2 = "messageOut2";
@Input(INPUT)
SubscribableChannel input();
@Output(OUTPUT)
MessageChannel output();
@Input(INPUT2)
SubscribableChannel input2();
@Output(OUTPUT2)
MessageChannel output2();
}
复制代码
消息接收者作以下更改
@StreamListener(StreamClient.OUTPUT)
@SendTo(StreamClient.OUTPUT2)
public String process(OrderDTO dto){
log.info("StreamReceiver: " + dto);
return "Received...";
}
@StreamListener(StreamClient.OUTPUT2)
public void process2(String msg){
log.info("StreamReceiver2: " + msg);
}
复制代码
主要是添加一个 @SendTo(StreamClient.OUTPUT2)
注解,而后返回须要的值。再定义一个接收 StreamClient.OUTPUT2
的接收者。
经过 Docker 安装并启动
docker run -d -p 6379:6379 redis:4.0.8
复制代码
mac 下的 redis 可视化工具:Redis Desktop Manager
,简称 RDM
先添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
复制代码
而后配置 redis 的地址和端口号
spring:
redis:
host: localhost
port: 6379
复制代码
服务网关的要素
经常使用网关方案
Zuul 的特色
Zuul 的四种过滤器 API
新建项目 api-gateway ,勾选 Cloud Config -> Config Client,CloudDiscovery -> Eureka Discovery,Cloud Routing -> Zuul 三个选项,点下一步完成建立
修改 application.properties
文件为 bootstrap.yml
并作以下配置
spring:
application:
name: api-gateway
cloud:
config:
discovery:
enabled: true
service-id: config
profile: dev
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
复制代码
入口类添加 @EnableZuulProxy
注解
@SpringBootApplication
@EnableZuulProxy
public class ApiGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(ApiGatewayApplication.class, args);
}
}
复制代码
在端口 9000 启动项目,就能够经过网关访问其余项目的 api 啦
如要访问 product 项目的 product/list
接口,直接在浏览器输入 http://localhost:9000/product/product/list
便可。
访问格式是 http://localhost:9000/应用id/api地址
bootstrap.yml
添加
zuul:
routes:
myProduct: #本身定义的名字
path: /myProduct/**
serviceId: product
复制代码
便可经过 http://localhost:9000/myProduct/product/list
访问上面的接口
简洁写法
zuul:
routes:
product: /myProduct/**
复制代码
排除掉 /product/list
,使它不能被访问
zuul:
routes:
# 简介写法
product: /myProduct/**
# 排除某些路由
ignored-patterns:
- /**/product/list
复制代码
默认会过滤掉 cookie,若是想拿到cookie,设置 sensitiveHeaders:
为空便可
zuul:
routes:
myProduct:
path: /myProduct/**
serviceId: product
sensitiveHeaders:
复制代码
全局设置敏感头
zuul:
# 全局设置敏感头
sensitive-headers:
复制代码
在 Git 上新建 api-gateway-dev.yml
将 zuul 的配置移到 git 上
新建配置类或直接在入口类上写前缀方式取配置
@SpringBootApplication
@EnableZuulProxy
public class ApiGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(ApiGatewayApplication.class, args);
}
@ConfigurationProperties("zuul")
@RefreshScope
public ZuulProperties ZuulProperties(){
return new ZuulProperties();
}
}
复制代码
下面用 Zuul 的 pre 过滤器实现请求的 token 校验
新建 TokenFilter
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import javax.servlet.http.HttpServletRequest;
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_DECORATION_FILTER_ORDER;
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_TYPE;
@Component
public class TokenFilter extends ZuulFilter {
@Override
public String filterType() {
return PRE_TYPE;
}
@Override
public int filterOrder() {
return PRE_DECORATION_FILTER_ORDER - 1;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() throws ZuulException {
RequestContext requestContext = RequestContext.getCurrentContext();
HttpServletRequest request = requestContext.getRequest();
//这里从url里获取,也能够从
String token = request.getParameter("token");
if (StringUtils.isEmpty(token)){
requestContext.setSendZuulResponse(false);
requestContext.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
}
return null;
}
}
复制代码
没有携带 token 的请求将会报 401 错误。
新建 AddResponseFilter
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletResponse;
import java.util.UUID;
@Component
public class AddResponseFilter extends ZuulFilter {
@Override
public String filterType() {
return FilterConstants.POST_TYPE;
}
@Override
public int filterOrder() {
return FilterConstants.SEND_RESPONSE_FILTER_ORDER - 1;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() throws ZuulException {
RequestContext requestContext = RequestContext.getCurrentContext();
HttpServletResponse response = requestContext.getResponse();
response.addHeader("X-Foo", UUID.randomUUID().toString());
return null;
}
}
复制代码
在返回头里加了 X-Foo
,重启项目请求接口发现值被成功添加了进去
import com.google.common.util.concurrent.RateLimiter;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.exception.ZuulException;
import com.solo.apigateway.exception.RateLimitException;
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_TYPE;
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.SERVLET_DETECTION_FILTER_ORDER;
/** * 限流拦截器. 令牌桶, 用 google 的 guava 实现 */
public class RateLimitFilter extends ZuulFilter {
public static final RateLimiter RATE_LIMITER = RateLimiter.create(100);
@Override
public String filterType() {
return PRE_TYPE;
}
@Override
public int filterOrder() {
return SERVLET_DETECTION_FILTER_ORDER - 1;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() throws ZuulException {
if (RATE_LIMITER.tryAcquire()){
throw new RateLimitException();
}
return null;
}
}
复制代码
待完善
跨域问题的解决方法有不少种,能够在单个接口上加注解,也能够在 Zuul 网关上统一处理
在接口上添加 @CrossOrigin
注解便可使这个接口实现跨域
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import java.util.Arrays;
/** * 跨域配置 */
@Configuration
public class CorsConfig {
@Bean
public CorsFilter corsFilter() {
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
final CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true); //是否支持 cookie 跨域
config.setAllowedHeaders(Arrays.asList("*"));
config.setAllowedOrigins(Arrays.asList("*"));
config.setAllowedMethods(Arrays.asList("*"));
config.setMaxAge(300l); //缓存时间。在这个时间段内,相同的跨域请求将再也不检查
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
}
复制代码
随着开源项目的进行,后期会写多篇文章结合项目实战详细介绍这些技术,欢迎关注~