Spring Cloud 学习笔记 - 钢钢更新

Eureka 服务注册与发现

pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.imooc</groupId>
<artifactId>eureka</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>

<name>eureka</name>
<description>Demo project for Spring Boot</description>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.2.RELEASE</version>
<relativePath/>
</parent>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-cloud.version>Finchley.RELEASE</spring-cloud.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

</project>

application.yml

1
2
3
spring:
profiles:
active: eureka2

application-eureka1.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
eureka:
instance:
hostname: eureka1
client:
service-url:
defaultZone: http://eureka2:8762/eureka/ # 注册到其他Eureka节点
register-with-eureka: false
server:
enable-self-preservation: false
eviction-interval-timer-in-ms: 5000
spring:
application:
name: eureka1
#server:
# port: 8761

application-eureka2.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
eureka:
instance:
hostname: eureka2
client:
service-url:
defaultZone: http://eureka1:8761/eureka/
register-with-eureka: false
server:
enable-self-preservation: false
eviction-interval-timer-in-ms: 3000
spring:
application:
name: eureka2
#server:
# port: 8762

启动类EurekaApplication

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.imooc.eureka;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@SpringBootApplication
@EnableEurekaServer
public class EurekaApplication {

public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class, args);
}
}

Eureka控制台

应用模块改造 - product子项目

全局pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.imooc</groupId>
<artifactId>product</artifactId>
<version>0.0.1-SNAPSHOT</version>
<modules>
<module>common</module>
<module>server</module>
<module>client</module>
</modules>
<packaging>pom</packaging>

<name>product</name>
<description>Demo project for Spring Boot</description>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-cloud.version>Finchley.RELEASE</spring-cloud.version>
<product-common.version>0.0.1-SNAPSHOT</product-common.version>
</properties>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.imooc</groupId>
<artifactId>product-common</artifactId>
<version>${product-common.version}</version>
</dependency>
</dependencies>
</dependencyManagement>

</project>

server模块

pom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>product</artifactId>
<groupId>com.imooc</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>product-server</artifactId>

<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>

<dependency>
<groupId>com.imooc</groupId>
<artifactId>product-common</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-client</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
application.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
server:
port: 8080

spring:
application:
name: product
datasource:
driver-class-name: com.mysql.jdbc.Driver
username: root
password: welcome123
url: jdbc:mysql://localhost:3306/sell?characterEncoding=utf-8&useSSL=false
jpa:
show-sql: true

eureka:
client:
service-url:
defaultZone: http://eureka1:8761/eureka/
启动类 ProductApplication
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.imooc.product;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication
@EnableDiscoveryClient
public class ProductApplication {

public static void main(String[] args) {
SpringApplication.run(ProductApplication.class, args);
}
}

common模块

pom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>product</artifactId>
<groupId>com.imooc</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>product-common</artifactId>

<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
</project>
DecreaseStockInput.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.imooc.product.common;

import lombok.Data;

/**
* 减库存入参
*/
@Data
public class DecreaseStockInput {

private String productId;

private Integer productQuantity;

public DecreaseStockInput() {
}

public DecreaseStockInput(String productId, Integer productQuantity) {
this.productId = productId;
this.productQuantity = productQuantity;
}
}
ProductInfoOutput.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package com.imooc.product.common;

import lombok.Data;

import java.math.BigDecimal;


@Data
public class ProductInfoOutput {

private String productId;

/** 名字. */
private String productName;

/** 单价. */
private BigDecimal productPrice;

/** 库存. */
private Integer productStock;

/** 描述. */
private String productDescription;

/** 小图. */
private String productIcon;

/** 状态, 0正常1下架. */
private Integer productStatus;

/** 类目编号. */
private Integer categoryType;
}

client模块

pom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>product</artifactId>
<groupId>com.imooc</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>product-client</artifactId>

<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>

<dependency>
<groupId>com.imooc</groupId>
<artifactId>product-common</artifactId>
</dependency>
</dependencies>
</project>
ProductClient.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package com.imooc.product.client;

import com.imooc.product.common.DecreaseStockInput;
import com.imooc.product.common.ProductInfoOutput;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;

import java.util.List;


@FeignClient(name = "product", fallback = ProductClient.ProductClientFallback.class)
public interface ProductClient {

@PostMapping("/product/listForOrder")
List<ProductInfoOutput> listForOrder(@RequestBody List<String> productIdList);

@PostMapping("/product/decreaseStock")
void decreaseStock(@RequestBody List<DecreaseStockInput> decreaseStockInputList);

@Component
static class ProductClientFallback implements ProductClient {

@Override
public List<ProductInfoOutput> listForOrder(List<String> productIdList) {
return null;
}

@Override
public void decreaseStock(List<DecreaseStockInput> decreaseStockInputList) {

}
}
}

应用模块改造 - order子项目

全局pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.imooc</groupId>
<artifactId>order</artifactId>
<version>0.0.1-SNAPSHOT</version>
<modules>
<module>client</module>
<module>common</module>
<module>server</module>
</modules>
<packaging>pom</packaging>

<name>order</name>
<description>Demo project for Spring Boot</description>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.2.RELEASE</version>
<relativePath/>
</parent>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-cloud.version>Finchley.RELEASE</spring-cloud.version>
<product-client.version>0.0.1-SNAPSHOT</product-client.version>
<order-common.version>0.0.1-SNAPSHOT</order-common.version>
</properties>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.imooc</groupId>
<artifactId>product-client</artifactId>
<version>${product-client.version}</version>
</dependency>
<dependency>
<groupId>com.imooc</groupId>
<artifactId>order-common</artifactId>
<version>${order-common.version}</version>
</dependency>
</dependencies>
</dependencyManagement>

</project>

common模块

pom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>order</artifactId>
<groupId>com.imooc</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>order-common</artifactId>

<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
</project>

client模块

pom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>order</artifactId>
<groupId>com.imooc</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>order-client</artifactId>

</project>

server模块

pom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>order</artifactId>
<groupId>com.imooc</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>order-server</artifactId>

<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>

<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

<dependency>
<groupId>com.imooc</groupId>
<artifactId>product-client</artifactId>
</dependency>

<dependency>
<groupId>com.imooc</groupId>
<artifactId>order-common</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-client</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>

<!--<dependency>-->
<!--<groupId>org.springframework.cloud</groupId>-->
<!--<artifactId>spring-cloud-starter-sleuth</artifactId>-->
<!--</dependency>-->

<!--<dependency>-->
<!--<groupId>org.springframework.cloud</groupId>-->
<!--<artifactId>spring-cloud-sleuth-zipkin</artifactId>-->
<!--</dependency>-->

<!--包含sleuth和zipkin-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
bootstrap.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
spring:
rabbitmq:
host: 127.0.0.1
port: 5672 # 注意端口是5672,不是15672
username: guest
password: guest
application:
name: order
cloud:
config:
discovery:
enabled: true
service-id: config
profile: test
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/,http://localhost:8762/eureka/
启动类 OrderApplication.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.imooc.order;

import org.springframework.boot.SpringApplication;
import org.springframework.cloud.client.SpringCloudApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.ComponentScan;

//import org.springframework.cloud.netflix.feign.EnableFeignClients;

@EnableFeignClients(basePackages = "com.imooc.product.client")
//@SpringBootApplication
//@EnableDiscoveryClient
//@EnableCircuitBreaker
@SpringCloudApplication
@ComponentScan(basePackages = "com.imooc")
public class OrderApplication {

public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
}

Config 配置中心

RabbitMQ

Docker镜像

1
2
3
docker pull daocloud.io/library/rabbitmq:3.7-management

docker run -d --hostname my-rabbit -p 5672:5672 -p 15672:15672 daocloud.io/library/rabbitmq:3.7-management

RabbitMQ 控制台

http://localhost:15672/
guest/gutst

消息队列

pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.imooc</groupId>
<artifactId>config</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>config</name>
<description>Demo project for Spring Boot</description>

<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Finchley.RELEASE</spring-cloud.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>

<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
</dependencies>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

</project>

application.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
server:
port: 8090
spring:
rabbitmq:
host: 127.0.0.1
port: 5672 # 注意端口是5672,不是15672
username: guest
password: guest
application:
name: config
cloud:
config:
server:
git:
uri: git@github.com:wfg2513148/Spring-Cloud-Test.git
username: wfgdlut@gmail.com
password: UBVkUVNmv4xDqHgJyqyzctKy
# basedir: /Users/kwang/IdeaProjects/kw-SpringCloud-sell/config/basedir/
eureka:
client:
service-url:
defaultZone: http://eureka1:8761/eureka/,http://eureka2:8762/eureka/
management:
endpoints:
web:
exposure:
include: bus-refresh

配置类 GirlConfig.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.imooc.order.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Component;

@Data
@Component
@ConfigurationProperties("girl")
@RefreshScope
public class GirlConfig {

private String name;

private Integer age;
}

GirlController.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.imooc.order.controller;

import com.imooc.order.config.GirlConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/girl")
public class GirlController {

@Autowired
private GirlConfig girlConfig;

@GetMapping("print")
public String print() {
return "name: " + girlConfig.getName() + ", age: " + girlConfig.getAge();
}
}

启动类 ConfigApplication.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.imooc.config;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.config.server.EnableConfigServer;
import org.springframework.cloud.context.config.annotation.RefreshScope;

@SpringBootApplication
@EnableDiscoveryClient
@EnableConfigServer
@RefreshScope //如果在配置类里加了,启动类里就不需要加@RefreshScope这个注解了
public class ConfigApplication {

public static void main(String[] args) {
SpringApplication.run(ConfigApplication.class, args);
}

}

热更新接口服务 - RefreshController.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.imooc.config.controller;

import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.client.RestTemplate;

@Controller
public class RefreshController {

@RequestMapping("/refresh")
public void refresh() {
RestTemplate restTemplate = new RestTemplate();
HttpHeaders httpHeaders = new HttpHeaders();
// 设置content_type为json要不然会报415的错误
httpHeaders.add(HttpHeaders.CONTENT_TYPE, "application/json");
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(null, httpHeaders);
// 以post方法访问真正的刷新链接
ResponseEntity<String> stringResponseEntity = restTemplate.postForEntity("http://localhost:8090/actuator/bus-refresh",
request, String.class);
}
}

order-test.yml (Github repo)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
eureka:
client:
service-url:
defaultZone: http://localhost:8762/eureka/
server:
port: 8080
spring:
application:
name: order
datasource:
driver-class-name: com.mysql.jdbc.Driver
jpa:
show-sql: true
password: welcome123
url: jdbc:mysql://localhost:3306/sell?characterEncoding=utf-8&useSSL=false
username: root
env:
test16
girl:
name: gloria
age: 18

手动测试配置刷新

1
curl -v -X POST http://localhost/actuator/bus-refresh

Github webhooks

测试Github的webhooks时,网上说是要配置到/monitor这个地址上,实测发现会报错,这里重写了refresh服务的接口,采用get方式接收webhooks调用,然后再转成post方式去请求真实的配置刷新接口:http://kwang.natapp1.cc/refreshnatapp是个内网穿透工具)

测试配置热更新

修改Github上的yaml文件,无需重启任何服务,即可实现配置热刷新。

消息与异步

RabbitMQ

pom.xml

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

product-dev.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
username: root
password: welcome123
url: jdbc:mysql://localhost:3306/sell?characterEncoding=utf-8&useSSL=false
jpa:
show-sql: true
rabbitmq:
host: 127.0.0.1
port: 5672 # 注意端口是5672,不是15672
username: guest
password: guest
redis:
host: localhost
port: 6379
logging:
level:
org:
hibernate:
SQL: DEBUG

ProductServiceImpl.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
package com.imooc.product.service.impl;

import com.imooc.product.common.DecreaseStockInput;
import com.imooc.product.common.ProductInfoOutput;
import com.imooc.product.dataobject.ProductInfo;
import com.imooc.product.enums.ProductStatusEnum;
import com.imooc.product.enums.ResultEnum;
import com.imooc.product.exception.ProductException;
import com.imooc.product.repository.ProductInfoRepository;
import com.imooc.product.service.ProductService;
import com.imooc.product.utils.JsonUtil;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.transaction.Transactional;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;


@Service
public class ProductServiceImpl implements ProductService {

@Autowired
private ProductInfoRepository productInfoRepository;

@Autowired
private AmqpTemplate amqpTemplate;

@Override
public List<ProductInfo> findUpAll() {
return productInfoRepository.findByProductStatus(ProductStatusEnum.UP.getCode());
}

@Override
public List<ProductInfoOutput> findList(List<String> productIdList) {
return productInfoRepository.findByProductIdIn(productIdList).stream()
.map(e -> {
ProductInfoOutput output = new ProductInfoOutput();
BeanUtils.copyProperties(e, output);
return output;
})
.collect(Collectors.toList());
}

@Override
public void decreaseStock(List<DecreaseStockInput> decreaseStockInputList) {
List<ProductInfo> productInfoList = decreaseStockProcess(decreaseStockInputList);

//发送mq消息
List<ProductInfoOutput> productInfoOutputList = productInfoList.stream().map(e -> {
ProductInfoOutput output = new ProductInfoOutput();
BeanUtils.copyProperties(e, output);
return output;
}).collect(Collectors.toList());
amqpTemplate.convertAndSend("productInfo", JsonUtil.toJson(productInfoOutputList));

}

@Transactional
public List<ProductInfo> decreaseStockProcess(List<DecreaseStockInput> decreaseStockInputList) {
List<ProductInfo> productInfoList = new ArrayList<>();
for (DecreaseStockInput decreaseStockInput: decreaseStockInputList) {
Optional<ProductInfo> productInfoOptional = productInfoRepository.findById(decreaseStockInput.getProductId());
//判断商品是否存在
if (!productInfoOptional.isPresent()){
throw new ProductException(ResultEnum.PRODUCT_NOT_EXIST);
}

ProductInfo productInfo = productInfoOptional.get();
//库存是否足够
Integer result = productInfo.getProductStock() - decreaseStockInput.getProductQuantity();
if (result < 0) {
throw new ProductException(ResultEnum.PRODUCT_STOCK_ERROR);
}

productInfo.setProductStock(result);
productInfoRepository.save(productInfo);
productInfoList.add(productInfo);
}
return productInfoList;
}
}

ProductInfoReceiver.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package com.imooc.order.message;

import com.fasterxml.jackson.core.type.TypeReference;
import com.imooc.order.utils.JsonUtil;
import com.imooc.product.common.ProductInfoOutput;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.util.List;

@Component
@Slf4j
public class ProductInfoReceiver {

private static final String PRODUCT_STOCK_TEMPLATE = "product_stock_%s";

@Autowired
private StringRedisTemplate stringRedisTemplate;

@RabbitListener(queuesToDeclare = @Queue("productInfo"))
public void process(String message) {
//message => ProductInfoOutput
log.info("队列【{}】接受到的消息: {}", "productInfo", message);
ProductInfoOutput productInfoOutput = (ProductInfoOutput) JsonUtil.fromJson(message, ProductInfoOutput.class);
log.info("队列消息转换后结果:{}", productInfoOutput);

//存储到redis中
stringRedisTemplate.opsForValue().set(String.format(PRODUCT_STOCK_TEMPLATE, productInfoOutput.getProductId()),
String.valueOf(productInfoOutput.getProductStock()));

}
}

Spring Cloud Stream

pom.xml

1
2
3
4
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>

bootstrap.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
spring:
application:
name: order
cloud:
config:
discovery:
enabled: true
service-id: config
profile: test
stream:
bindings:
orderTopicInput:
group: order
destination: order-topic
content-type: application/json
orderTopicOutput:
group: order
destination: order-topic
content-type: application/json
orderTopicInput2:
group: order2
destination: order-topic2
content-type: application/json
orderTopicOutput2:
group: order2
destination: order-topic2
content-type: application/json
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/,http://localhost:8762/eureka/

StreamClient.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package com.imooc.order.message;

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 = "orderTopicInput";
String OUTPUT = "orderTopicOutput";
String INPUT2 = "orderTopicInput2";
String OUTPUT2 = "orderTopicOutput2";

@Input(StreamClient.INPUT)
SubscribableChannel input();

@Output(StreamClient.OUTPUT)
MessageChannel output();



@Input(StreamClient.INPUT2)
SubscribableChannel input2();

@Output(StreamClient.OUTPUT2)
MessageChannel output2();
}

StreamReceiver.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package com.imooc.order.message;

import com.imooc.order.dto.OrderDTO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.annotation.StreamListener;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.stereotype.Component;

@Component
@EnableBinding({StreamClient.class})
@Slf4j
public class StreamReceiver {

// @StreamListener(StreamClient.INPUT)
// public void process(Object message) {
// log.info("StreamReceiver: {}", message);
// }

/**
* 接收orderDTO对象
* @param message
*/
@StreamListener(StreamClient.INPUT)
@SendTo(StreamClient.INPUT2) //发送接收成功通知
public String process(OrderDTO message) {
log.info("StreamReceiver: {}", message);
return "received.";
}

/**
* 接受回执通知
* @param message
*/
@StreamListener(StreamClient.INPUT2)
public void process2(String message) {
log.info("StreamReceiver2: {}", message);
}
}

Zuul 服务网关

基本配置

pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.imooc</groupId>
<artifactId>api-gateway</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>api-gateway</name>
<description>Demo project for Spring Boot</description>

<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Finchley.RELEASE</spring-cloud.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

<repositories>
<repository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</pluginRepository>
<pluginRepository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
</pluginRepository>
</pluginRepositories>

</project>

bootstrap.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
spring:
application:
name: api-gateway
cloud:
config:
discovery:
enabled: true
service-id: config
profile: dev
eureka:
client:
service-url:
defaultZone: http://eureka1:8761/eureka/,http://eureka2:8762/eureka/
server:
port: 9000

api-gateway-dev.yml (config repo)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
zuul:
# 全部服务忽略敏感头(全部服务允许传递cookie)
sensitive-headers:
routes:
# myProduct:
# path: /myProduct/**
# serviceId: product
# 简洁写法
product: /myProduct/**
# 排除某些路由
# ignored-patterns:
# - /**/product/listForOrder
management:
endpoints:
web:
exposure:
include: "*"

热刷新 ZuulConfig.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.imooc.apigateway;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.cloud.netflix.zuul.filters.ZuulProperties;
import org.springframework.stereotype.Component;

@Component
public class ZuulConfig {

@ConfigurationProperties("zuul")
@RefreshScope
public ZuulProperties zuulProperties() {
return new ZuulProperties();
}
}

启动类 ApiGatewayApplication.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.imooc.apigateway;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;

@SpringBootApplication
@EnableZuulProxy
public class ApiGatewayApplication {

public static void main(String[] args) {
SpringApplication.run(ApiGatewayApplication.class, args);
}

}

过滤器

限流(令牌桶)

RateLimitFilter.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package com.imooc.apigateway.filter;

import com.google.common.util.concurrent.RateLimiter;
import com.imooc.apigateway.exception.RateLimitException;
import com.netflix.zuul.ZuulFilter;
import org.springframework.stereotype.Component;

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;


@Component
public class RateLimitFilter extends ZuulFilter{

private 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() {
if (!RATE_LIMITER.tryAcquire()) {
throw new RateLimitException();
}

return null;
}
}

设置http header

addResponseHeaderFilter.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package com.imooc.apigateway.filter;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletResponse;

import java.util.UUID;

import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.POST_TYPE;
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.SEND_RESPONSE_FILTER_ORDER;


@Component
public class addResponseHeaderFilter extends ZuulFilter{
@Override
public String filterType() {
return POST_TYPE;
}

@Override
public int filterOrder() {
return SEND_RESPONSE_FILTER_ORDER - 1;
}

@Override
public boolean shouldFilter() {
return true;
}

@Override
public Object run() {
RequestContext requestContext = RequestContext.getCurrentContext();
HttpServletResponse response = requestContext.getResponse();
response.setHeader("X-Foo", UUID.randomUUID().toString());
return null;
}
}

自定义条件拦截

TokenFilter.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package com.imooc.apigateway.filter;//package com.imooc.apigateway.filter;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
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() {
RequestContext requestContext = RequestContext.getCurrentContext();
HttpServletRequest request = requestContext.getRequest();

//这里从url参数里获取, 也可以从cookie, header里获取
String token = request.getParameter("token");
if (StringUtils.isEmpty(token)) {
requestContext.setSendZuulResponse(false);
requestContext.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
}
return null;
}
}

Login权限校验

买家过滤器

AuthBuyerFilter.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
package com.imooc.apigateway.filter;

import com.imooc.apigateway.constant.RedisConstant;
import com.imooc.apigateway.utils.CookieUtil;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.util.StringUtils;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;


public class AuthBuyerFilter extends ZuulFilter {

@Autowired
private StringRedisTemplate stringRedisTemplate;

RequestContext requestContext = RequestContext.getCurrentContext();
HttpServletRequest request = requestContext.getRequest();

@Override
public String filterType() {
return null;
}

@Override
public int filterOrder() {
return 0;
}

@Override
public boolean shouldFilter() {

if ("/order/order/create".equals(request.getRequestURI())) {
return true;
}
return false;
}

@Override
public Object run() throws ZuulException {

/**
* /order/order/create 只能买家访问(cookie里有openid)
*/
Cookie cookie = CookieUtil.get(request, "openid");
if (cookie == null || StringUtils.isEmpty((cookie.getValue()))) {
requestContext.setSendZuulResponse(false);
requestContext.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
}

return null;
}
}

卖家过滤器

AuthSellerFilter.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
package com.imooc.apigateway.filter;

import com.imooc.apigateway.constant.RedisConstant;
import com.imooc.apigateway.utils.CookieUtil;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.util.StringUtils;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;


public class AuthSellerFilter extends ZuulFilter {

@Autowired
private StringRedisTemplate stringRedisTemplate;

RequestContext requestContext = RequestContext.getCurrentContext();
HttpServletRequest request = requestContext.getRequest();

@Override
public String filterType() {
return null;
}

@Override
public int filterOrder() {
return 0;
}

@Override
public boolean shouldFilter() {

if ("/order/order/finish".equals(request.getRequestURI())) {
return true;
}
return false;
}

@Override
public Object run() throws ZuulException {

/**
* /order/order/finish 只能卖家访问(cookie里有token,并且对应的redis中有值)
*/
Cookie cookie = CookieUtil.get(request, "token");
if (cookie == null
|| StringUtils.isEmpty((cookie.getValue()))
|| StringUtils.isEmpty(stringRedisTemplate.opsForValue().get(String.format(RedisConstant.TOKEN_TEMPLATE, cookie.getValue())))) {
requestContext.setSendZuulResponse(false);
requestContext.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
}

return null;
}
}

其他

CookieUtil.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package com.imooc.apigateway.utils;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;


public class CookieUtil {

/**
* 设置cookie
* @param response
* @param name
* @param value
* @param maxAge
*/
public static void set(HttpServletResponse response,
String name,
String value,
int maxAge) {
Cookie cookie = new Cookie(name, value);
cookie.setPath("/");
cookie.setMaxAge(maxAge);
response.addCookie(cookie);
}

public static Cookie get(HttpServletRequest request,
String name) {
Cookie[] cookies = request.getCookies();
if (cookies != null) {
for (Cookie cookie: cookies) {
if (name.equals(cookie.getName())) {
return cookie;
}
}
}

return null;
}
}
CookieConstant.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.imooc.apigateway.constant;


public interface CookieConstant {

String TOKEN = "token";

String OPENID = "openid";

/**
* 过期时间(单位:s)
*/
Integer expire = 7200;
}
RedisConstant.java
1
2
3
4
5
6
7
package com.imooc.apigateway.constant;

public interface RedisConstant {

String TOKEN_TEMPLATE = "token_%s";

}
LoginController.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
package com.imooc.user.controller;

import com.imooc.user.VO.ResultVO;
import com.imooc.user.constant.CookieConstant;
import com.imooc.user.constant.RedisConstant;
import com.imooc.user.dataobject.UserInfo;
import com.imooc.user.enums.ResultEnum;
import com.imooc.user.enums.RoleEnum;
import com.imooc.user.service.UserService;
import com.imooc.user.utils.CookieUtil;
import com.imooc.user.utils.ResultVOUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.UUID;
import java.util.concurrent.TimeUnit;


@RestController
@RequestMapping("/login")
public class LoginController {

@Autowired
private UserService userService;

@Autowired
private StringRedisTemplate stringRedisTemplate;

/**
* 买家登录
* @param openid
* @param response
* @return
*/
@GetMapping("/buyer")
public ResultVO buyer(@RequestParam("openid") String openid,
HttpServletResponse response) {
//1. openid和数据库里的数据是否匹配
UserInfo userInfo = userService.findByOpenid(openid);
if (userInfo == null) {
return ResultVOUtil.error(ResultEnum.LOGIN_FAIL);
}

//2. 判断角色
if (RoleEnum.BUYER.getCode() != userInfo.getRole()) {
return ResultVOUtil.error(ResultEnum.ROLE_ERROR);
}

//3. cookie里设置openid=abc
CookieUtil.set(response, CookieConstant.OPENID, openid, CookieConstant.expire);

return ResultVOUtil.success();
}

@GetMapping("/seller")
public ResultVO seller(@RequestParam("openid") String openid,
HttpServletRequest request,
HttpServletResponse response) {
//判断是否已登录
Cookie cookie = CookieUtil.get(request, CookieConstant.TOKEN);
if (cookie != null &&
!StringUtils.isEmpty(stringRedisTemplate.opsForValue().get(String.format(RedisConstant.TOKEN_TEMPLATE, cookie.getValue())))) {
return ResultVOUtil.success();
}

//1. openid和数据库里的数据是否匹配
UserInfo userInfo = userService.findByOpenid(openid);
if (userInfo == null) {
return ResultVOUtil.error(ResultEnum.LOGIN_FAIL);
}

//2. 判断角色
if (RoleEnum.SELLER.getCode() != userInfo.getRole()) {
return ResultVOUtil.error(ResultEnum.ROLE_ERROR);
}

//3. redis设置key=UUID, value=xyz
String token = UUID.randomUUID().toString();
Integer expire = CookieConstant.expire;
stringRedisTemplate.opsForValue().set(String.format(RedisConstant.TOKEN_TEMPLATE, token),
openid,
expire,
TimeUnit.SECONDS);

//4. cookie里设置token=UUID
CookieUtil.set(response, CookieConstant.TOKEN, token, CookieConstant.expire);

return ResultVOUtil.success();
}
}

Hystrix 服务熔断与降级

Hystrix

pom.xml

1
2
3
4
5
6
7
8
9
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>

bootstrap.yml

1
2
3
4
5
6
7
8
9
10
11
12
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 1000
getProductInfoList:
execution:
isolation:
thread:
timeoutInMilliseconds: 3000

OrderApplication.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.imooc.order;

import org.springframework.boot.SpringApplication;
import org.springframework.cloud.client.SpringCloudApplication;
import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.ComponentScan;

//import org.springframework.cloud.netflix.feign.EnableFeignClients;

@EnableFeignClients(basePackages = "com.imooc.product.client")
//@SpringBootApplication
//@EnableDiscoveryClient
//@EnableCircuitBreaker
@SpringCloudApplication
@EnableHystrixDashboard
@ComponentScan(basePackages = "com.imooc")
public class OrderApplication {

public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
}

HystrixController.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
package com.imooc.order.controller;

import com.netflix.hystrix.contrib.javanica.annotation.DefaultProperties;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import java.util.Arrays;


@RestController
@DefaultProperties(defaultFallback = "defaultFallback")
public class HystrixController {

//超时配置
// @HystrixCommand(commandProperties = {
// @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "3000")
// })

// 断路器配置
// @HystrixCommand(commandProperties = {
// @HystrixProperty(name = "circuitBreaker.enabled", value = "true"), //设置熔断
// @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"), //请求数达到后才计算
// @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "10000"), //休眠时间窗
// @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "60"), //错误率
// })
@HystrixCommand
@GetMapping("/getProductInfoList")
public String getProductInfoList(@RequestParam("number") Integer number) {
if (number % 2 == 0) {
return "success";
}

RestTemplate restTemplate = new RestTemplate();
return restTemplate.postForObject("http://127.0.0.1:8005/product/listForOrder",
Arrays.asList("157875196366160022"),
String.class);

// throw new RuntimeException("发送异常了");
}

private String fallback() {
return "太拥挤了, 请稍后再试~~";
}

private String defaultFallback() {
return "默认提示:太拥挤了, 请稍后再试~~";
}
}

Hystrix Dashboard

控制台

http://localhost:8081/hystrix/

监控效果

http://localhost:8081/actuator/hystrix.stream

调用链路监控

sleuth

pom.xml

1
2
3
4
5
<!--已经包含了spring-cloud-starter-sleuth和spring-cloud-sleuth-zipkin-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>

bootstrap.yml

1
2
3
4
5
6
7
8
9
10
11
spring:
zipkin:
base-url: http://localhost:9411
sender:
type: web
sleuth:
sampler:
probability: 1
logging:
level:
org.springframework.cloud.openfeign: debug

zipkin

安装并运行zipkin

https://zipkin.io/pages/quickstart.html

1
docker run -d -p 9411:9411 openzipkin/zipkin

登录zipkin控制台

http://localhost:9411/zipkin/

测试

访问要测试的服务

登录zipkin控制台,看是否有调用链路记录生成。

本文标题:Spring Cloud 学习笔记 - 钢钢更新

文章作者:王方钢 / Kenny Wang

发布时间:2019年07月01日 - 11:07

最后更新:2020年01月23日 - 19:01

原始链接:https://wangfanggang.com/Java/Spring-Cloud/springcloud/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

王方钢 / Kenny Wang wechat
0%