本文是看某课网关于 SpringCloud 微服务实战的视频总结的笔记,其中涉及了
由于是随堂笔记,写的有点随意,大佬们见谅~
文中提到的大部分技术都会在我的一个开源项目中用到,这个项目后端业务逻辑部分已经基本写完了,就差权限验证、网关配置和后期优化啦,感兴趣的大佬可以看看。
项目地址: github.com/cachecats/c…
启动 SpringBoot 项目
java -jar test.jar 复制代码
启动 SpringBoot 项目并指定端口
java -jar -Dserver.port=8899 test.jar 复制代码
启动 SpringBoot 项目并指定后台运行
nohup java -jar test.jar > /dev/null 2>&1 & 复制代码
查看进程
ps -ef | grep eureka 复制代码
杀死进程
kill -9 进程号 复制代码
在本地安装项目到本地 maven 仓库
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
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); } } 复制代码
随着开源项目的进行,后期会写多篇文章结合项目实战详细介绍这些技术,欢迎关注~
项目地址: github.com/cachecats/c…