本文是看某課網關於 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);
}
}
複製代碼
隨着開源項目的進行,後期會寫多篇文章結合項目實戰詳細介紹這些技術,歡迎關注~