Commit afc80a49d43fc6a348e811ac2e3c12a67dc5a8eb
0 parents
gateway-service
Showing
57 changed files
with
2743 additions
and
0 deletions
doc/xtrade-gateway说明.docx
0 → 100644
No preview for this file type
pom.xml
0 → 100644
1 | +++ a/pom.xml | |
1 | +<?xml version="1.0" encoding="UTF-8"?> | |
2 | +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | |
3 | + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> | |
4 | + <modelVersion>4.0.0</modelVersion> | |
5 | + <parent> | |
6 | + <groupId>org.springframework.boot</groupId> | |
7 | + <artifactId>spring-boot-starter-parent</artifactId> | |
8 | + <version>2.2.5.RELEASE</version> | |
9 | + <relativePath/> <!-- lookup parent from repository --> | |
10 | + </parent> | |
11 | + <groupId>com.diligrp</groupId> | |
12 | + <artifactId>xtrade-gateway-service</artifactId> | |
13 | + <version>1.0.0</version> | |
14 | + <name>gateway-service</name> | |
15 | + <description>Demo project for Spring Boot</description> | |
16 | + | |
17 | + <properties> | |
18 | + <java.version>11</java.version> | |
19 | + <spring-cloud.version>Hoxton.SR3</spring-cloud.version> | |
20 | + <spring-cloud-alibaba.version>2.2.0.RELEASE</spring-cloud-alibaba.version> | |
21 | + <mybatis-starter.version>2.1.2</mybatis-starter.version> | |
22 | + </properties> | |
23 | + | |
24 | + <dependencies> | |
25 | + <dependency> | |
26 | + <groupId>com.diligrp</groupId> | |
27 | + <artifactId>xtrade-shared-spring-boot-starter</artifactId> | |
28 | + <version>0.0.1</version> | |
29 | + </dependency> | |
30 | + <dependency> | |
31 | + <groupId>org.springframework.boot</groupId> | |
32 | + <artifactId>spring-boot-configuration-processor</artifactId> | |
33 | + <optional>true</optional> | |
34 | + </dependency> | |
35 | + <!-- <dependency> | |
36 | + <groupId>org.springframework.boot</groupId> | |
37 | + <artifactId>spring-boot-starter-cache</artifactId> | |
38 | + </dependency> | |
39 | + <dependency> | |
40 | + <groupId>net.sf.ehcache</groupId> | |
41 | + <artifactId>ehcache</artifactId> | |
42 | + </dependency>--> | |
43 | + <dependency> | |
44 | + <groupId>org.mybatis.spring.boot</groupId> | |
45 | + <artifactId>mybatis-spring-boot-starter</artifactId> | |
46 | + <version>${mybatis-starter.version}</version> | |
47 | + </dependency> | |
48 | + <dependency> | |
49 | + <groupId>org.reflections</groupId> | |
50 | + <artifactId>reflections</artifactId> | |
51 | + <version>0.9.11</version> | |
52 | + </dependency> | |
53 | + <dependency> | |
54 | + <groupId>org.springframework.cloud</groupId> | |
55 | + <artifactId>spring-cloud-starter-gateway</artifactId> | |
56 | + </dependency> | |
57 | + <dependency> | |
58 | + <groupId>org.projectlombok</groupId> | |
59 | + <artifactId>lombok</artifactId> | |
60 | + <optional>true</optional> | |
61 | + </dependency> | |
62 | + <dependency> | |
63 | + <groupId>com.alibaba.cloud</groupId> | |
64 | + <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> | |
65 | + </dependency> | |
66 | + <dependency> | |
67 | + <groupId>com.alibaba.cloud</groupId> | |
68 | + <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> | |
69 | + </dependency> | |
70 | + <dependency> | |
71 | + <groupId>org.springframework.boot</groupId> | |
72 | + <artifactId>spring-boot-starter-test</artifactId> | |
73 | + <scope>test</scope> | |
74 | + </dependency> | |
75 | + <dependency> | |
76 | + <groupId>org.springframework.boot</groupId> | |
77 | + <artifactId>spring-boot-starter-data-redis</artifactId> | |
78 | + </dependency> | |
79 | + <dependency> | |
80 | + <groupId>org.apache.commons</groupId> | |
81 | + <artifactId>commons-pool2</artifactId> | |
82 | + </dependency> | |
83 | + <dependency> | |
84 | + <groupId>com.alibaba.nacos</groupId> | |
85 | + <artifactId>nacos-api</artifactId> | |
86 | + <version>1.2.0</version> | |
87 | + </dependency> | |
88 | + <dependency> | |
89 | + <groupId>com.alibaba.nacos</groupId> | |
90 | + <artifactId>nacos-client</artifactId> | |
91 | + <version>1.2.0</version> | |
92 | + </dependency> | |
93 | + | |
94 | + </dependencies> | |
95 | + | |
96 | + <dependencyManagement> | |
97 | + <dependencies> | |
98 | + <dependency> | |
99 | + <groupId>org.springframework.cloud</groupId> | |
100 | + <artifactId>spring-cloud-dependencies</artifactId> | |
101 | + <version>${spring-cloud.version}</version> | |
102 | + <type>pom</type> | |
103 | + <scope>import</scope> | |
104 | + </dependency> | |
105 | + <dependency> | |
106 | + <groupId>com.alibaba.cloud</groupId> | |
107 | + <artifactId>spring-cloud-alibaba-dependencies</artifactId> | |
108 | + <version>${spring-cloud-alibaba.version}</version> | |
109 | + <type>pom</type> | |
110 | + <scope>import</scope> | |
111 | + </dependency> | |
112 | + </dependencies> | |
113 | + </dependencyManagement> | |
114 | + | |
115 | + <build> | |
116 | + <plugins> | |
117 | + <plugin> | |
118 | + <groupId>org.springframework.boot</groupId> | |
119 | + <artifactId>spring-boot-maven-plugin</artifactId> | |
120 | + </plugin> | |
121 | + <plugin> | |
122 | + <groupId>org.apache.maven.plugins</groupId> | |
123 | + <artifactId>maven-compiler-plugin</artifactId> | |
124 | + <configuration> | |
125 | + <source>11</source> | |
126 | + <target>11</target> | |
127 | + </configuration> | |
128 | + </plugin> | |
129 | + </plugins> | |
130 | + </build> | |
131 | + | |
132 | +</project> | ... | ... |
sql/gateway-1.0.0.sql
0 → 100644
1 | +++ a/sql/gateway-1.0.0.sql | |
1 | +CREATE TABLE `xtrade_gateway`.`t_route` | |
2 | +( | |
3 | + `id` bigint(0) NOT NULL AUTO_INCREMENT COMMENT '主键', | |
4 | + `service_id` varchar(20) NOT NULL COMMENT '服务id名称', | |
5 | + `url` varchar(100) NOT NULL COMMENT '目标url', | |
6 | + `description` varchar(100) NOT NULL DEFAULT 1 COMMENT '描述字段', | |
7 | + `created_time` datetime(0) NULL ON UPDATE CURRENT_TIMESTAMP(0) COMMENT '创建时间', | |
8 | + `modified_time` datetime(0) NULL ON UPDATE CURRENT_TIMESTAMP(0) COMMENT '更新时间', | |
9 | + `is_del` smallint(1) NOT NULL DEFAULT 1 COMMENT '1:删除 0:正常', | |
10 | + PRIMARY KEY (`id`) | |
11 | +) COMMENT = '路由表'; | |
12 | + | |
13 | +ALTER TABLE `xtrade_gateway`.`t_route` | |
14 | + ADD UNIQUE INDEX `uni_service_id`(`service_id`) USING BTREE COMMENT 'service_id唯一索引'; | |
15 | + | |
16 | +CREATE TABLE `xtrade_gateway`.`t_attr_config` | |
17 | +( | |
18 | + `id` bigint(0) NOT NULL COMMENT '主键', | |
19 | + `service_id` varchar(20) NOT NULL COMMENT '关联的服务id名称', | |
20 | + `type` smallint(1) NOT NULL COMMENT '1:predicate 2:filter 详见枚举GatewayAttrType', | |
21 | + `attr_name` varchar(20) NOT NULL COMMENT 'predicate或者filter的名称', | |
22 | + `attr_args` varchar(100) NULL COMMENT 'predicate或者filter的所属参数,json格式', | |
23 | + `description` varchar(100) NULL COMMENT '描述字段', | |
24 | + `sort_order` smallint(2) NOT NULL DEFAULT 0 COMMENT '排序字段', | |
25 | + `created_time` datetime(0) NULL ON UPDATE CURRENT_TIMESTAMP(0) COMMENT '创建时间', | |
26 | + `modified_time` datetime(0) NULL ON UPDATE CURRENT_TIMESTAMP(0) COMMENT '更新时间', | |
27 | + `is_del` smallint(1) NOT NULL DEFAULT 1 COMMENT '1:删除 0:正常', | |
28 | + PRIMARY KEY (`id`) | |
29 | +) COMMENT = 'predicate和filter的配置表'; | |
30 | + | |
31 | +ALTER TABLE `xtrade_gateway`.`t_attr_config` | |
32 | + MODIFY COLUMN `order` smallint(2) NOT NULL DEFAULT 0 COMMENT '排序字段' AFTER `desc`; | |
33 | + | |
34 | +ALTER TABLE `xtrade_gateway`.`t_attr_config` | |
35 | + ADD UNIQUE INDEX `uni_service_id&attr_name`(`service_id`, `attr_name`) USING BTREE COMMENT 'service_id和attr_name唯一约束'; | ... | ... |
src/main/java/com/diligrp/xtrade/gateway/XtradeGatewayApplication.java
0 → 100644
1 | +++ a/src/main/java/com/diligrp/xtrade/gateway/XtradeGatewayApplication.java | |
1 | +package com.diligrp.xtrade.gateway; | |
2 | + | |
3 | +import org.mybatis.spring.annotation.MapperScan; | |
4 | +import org.springframework.boot.SpringApplication; | |
5 | +import org.springframework.boot.autoconfigure.SpringBootApplication; | |
6 | +import org.springframework.cloud.client.discovery.EnableDiscoveryClient; | |
7 | +import org.springframework.cloud.context.config.annotation.RefreshScope; | |
8 | + | |
9 | +@SpringBootApplication | |
10 | +@EnableDiscoveryClient | |
11 | +@RefreshScope | |
12 | +@MapperScan(basePackages = {"com.diligrp.xtrade.gateway.repository.dao"}) | |
13 | +public class XtradeGatewayApplication { | |
14 | + | |
15 | + public static void main(String[] args) { | |
16 | + SpringApplication.run(XtradeGatewayApplication.class, args); | |
17 | + } | |
18 | + | |
19 | +} | ... | ... |
src/main/java/com/diligrp/xtrade/gateway/api/Api.java
0 → 100644
1 | +++ a/src/main/java/com/diligrp/xtrade/gateway/api/Api.java | |
1 | +package com.diligrp.xtrade.gateway.api; | |
2 | + | |
3 | +import lombok.Data; | |
4 | + | |
5 | +import java.io.Serializable; | |
6 | + | |
7 | +/** | |
8 | + * @Auther: miaoguoxin | |
9 | + * @Date: 2019/3/31 16:24 | |
10 | + * @Description: 只用于网关api缓存的对象 | |
11 | + */ | |
12 | +@Data | |
13 | +public class Api implements Serializable { | |
14 | + /**服务名称*/ | |
15 | + private String serviceId; | |
16 | + | |
17 | + private String patternUrl; | |
18 | + //并发量 | |
19 | + private int replenishRate = 2; | |
20 | + //容量 | |
21 | + private int burstCapacity = 5; | |
22 | + | |
23 | + private Integer state; | |
24 | +} | ... | ... |
src/main/java/com/diligrp/xtrade/gateway/api/ApiManager.java
0 → 100644
1 | +++ a/src/main/java/com/diligrp/xtrade/gateway/api/ApiManager.java | |
1 | +package com.diligrp.xtrade.gateway.api; | |
2 | + | |
3 | +import com.diligrp.xtrade.gateway.config.property.AuthPathProperties; | |
4 | +import org.springframework.beans.factory.annotation.Autowired; | |
5 | +import org.springframework.http.server.PathContainer; | |
6 | +import org.springframework.stereotype.Component; | |
7 | +import org.springframework.web.util.pattern.PathPattern; | |
8 | + | |
9 | +import java.util.List; | |
10 | +import java.util.Optional; | |
11 | +import java.util.function.Consumer; | |
12 | +import java.util.function.Function; | |
13 | +import java.util.function.Predicate; | |
14 | + | |
15 | +/** | |
16 | + * @Auther: miaoguoxin | |
17 | + * @Date: 2019/3/26 0026 15:37 | |
18 | + * @Description: 管理api动态配置的工具 | |
19 | + */ | |
20 | +@Component | |
21 | +public class ApiManager { | |
22 | + | |
23 | + @Autowired | |
24 | + private AuthPathProperties authPathProperties; | |
25 | + | |
26 | + public boolean isExcludePathMatch(String path) { | |
27 | + //没有配置排除path的话,表示不拦截任何path | |
28 | + PathContainer pathContainer = PathContainer.parsePath(path); | |
29 | + return Optional.ofNullable(authPathProperties.getExcludePathPatterns()) | |
30 | + .map(pathPatterns -> pathPatterns | |
31 | + .stream() | |
32 | + .anyMatch(pattern -> pattern.matches(pathContainer))) | |
33 | + .orElse(true); | |
34 | + } | |
35 | + | |
36 | +} | ... | ... |
src/main/java/com/diligrp/xtrade/gateway/application/TestAggregationApplication.java
0 → 100644
1 | +++ a/src/main/java/com/diligrp/xtrade/gateway/application/TestAggregationApplication.java | |
1 | +package com.diligrp.xtrade.gateway.application; | |
2 | + | |
3 | +import com.diligrp.xtrade.gateway.common.annotation.DispatchMapping; | |
4 | +import com.diligrp.xtrade.gateway.domain.TestRequestDto; | |
5 | +import com.diligrp.xtrade.gateway.support.dispatch.DispatchContext; | |
6 | +import com.diligrp.xtrade.gateway.support.dispatch.RequestDispatcher; | |
7 | +import org.springframework.stereotype.Component; | |
8 | +import org.springframework.validation.annotation.Validated; | |
9 | + | |
10 | +import javax.validation.groups.Default; | |
11 | + | |
12 | +/** | |
13 | + * @Auther: miaoguoxin | |
14 | + * @Date: 2020/4/15 09:40 | |
15 | + * @Description: 聚合服务应用层 | |
16 | + * {@link RequestDispatcher} | |
17 | + */ | |
18 | +@Component | |
19 | +@DispatchMapping | |
20 | +public class TestAggregationApplication { | |
21 | + | |
22 | + @DispatchMapping("/test") | |
23 | + public String test(DispatchContext<TestRequestDto> dispatchContext){ | |
24 | + return "ffff"; | |
25 | + } | |
26 | + | |
27 | + @DispatchMapping("test2") | |
28 | + public void test2(@Validated(value = Default.class) DispatchContext<TestRequestDto> dispatchContext){ | |
29 | + | |
30 | + } | |
31 | + | |
32 | +} | ... | ... |
src/main/java/com/diligrp/xtrade/gateway/common/annotation/DispatchMapping.java
0 → 100644
1 | +++ a/src/main/java/com/diligrp/xtrade/gateway/common/annotation/DispatchMapping.java | |
1 | +package com.diligrp.xtrade.gateway.common.annotation; | |
2 | + | |
3 | +import java.lang.annotation.Documented; | |
4 | +import java.lang.annotation.ElementType; | |
5 | +import java.lang.annotation.Retention; | |
6 | +import java.lang.annotation.RetentionPolicy; | |
7 | +import java.lang.annotation.Target; | |
8 | + | |
9 | +/** | |
10 | + * @Auther: miaoguoxin | |
11 | + * @Date: 2020/4/15 09:24 | |
12 | + * @Description: 用于聚合服务映射 | |
13 | + */ | |
14 | +@Target({ElementType.METHOD, ElementType.TYPE}) | |
15 | +@Retention(RetentionPolicy.RUNTIME) | |
16 | +@Documented | |
17 | +public @interface DispatchMapping { | |
18 | + /** | |
19 | + * 映射路径 | |
20 | + * @param | |
21 | + * @return | |
22 | + * @author miaoguoxin | |
23 | + * @date 2020/4/15 | |
24 | + */ | |
25 | + String value() default ""; | |
26 | +} | ... | ... |
src/main/java/com/diligrp/xtrade/gateway/common/constant/GatewayAttrType.java
0 → 100644
1 | +++ a/src/main/java/com/diligrp/xtrade/gateway/common/constant/GatewayAttrType.java | |
1 | +package com.diligrp.xtrade.gateway.common.constant; | |
2 | + | |
3 | +import com.diligrp.xtrade.shared.type.IEnumType; | |
4 | + | |
5 | +import java.util.Arrays; | |
6 | + | |
7 | +/** | |
8 | + * @Auther: miaoguoxin | |
9 | + * @Date: 2018/12/9 20:27 | |
10 | + * @Description: 网关属性类型(类型含义需参见spring cloud gateway官方文档) | |
11 | + */ | |
12 | +public enum GatewayAttrType implements IEnumType { | |
13 | + PREDICATE(1,"断言配置"), | |
14 | + FILTER(2,"过滤器"), | |
15 | + ; | |
16 | + /** | |
17 | + * 编码 | |
18 | + */ | |
19 | + private int value; | |
20 | + | |
21 | + private String desc; | |
22 | + | |
23 | + | |
24 | + GatewayAttrType(int value, String desc) { | |
25 | + this.value = value; | |
26 | + this.desc=desc; | |
27 | + } | |
28 | + | |
29 | + public int getValue() { | |
30 | + return value; | |
31 | + } | |
32 | + | |
33 | + /** | |
34 | + * 获取枚举 | |
35 | + * @param value | |
36 | + * @return | |
37 | + */ | |
38 | + public static GatewayAttrType getByValue(int value) { | |
39 | + return Arrays.stream(values()) | |
40 | + .filter(e -> e.getValue() == value).findFirst() | |
41 | + .orElse(null); | |
42 | + } | |
43 | + | |
44 | + public String getDesc() { | |
45 | + return desc; | |
46 | + } | |
47 | + | |
48 | + @Override | |
49 | + public int getCode() { | |
50 | + return this.value; | |
51 | + } | |
52 | + | |
53 | + @Override | |
54 | + public String getName() { | |
55 | + return this.desc; | |
56 | + } | |
57 | +} | ... | ... |
src/main/java/com/diligrp/xtrade/gateway/common/constant/GatewayConst.java
0 → 100644
1 | +++ a/src/main/java/com/diligrp/xtrade/gateway/common/constant/GatewayConst.java | |
1 | +package com.diligrp.xtrade.gateway.common.constant; | |
2 | + | |
3 | +import java.util.concurrent.atomic.AtomicBoolean; | |
4 | + | |
5 | +/** | |
6 | + * @Auther: miaoguoxin | |
7 | + * @Date: 2019/3/25 0025 18:08 | |
8 | + * @Description: 网关所需的一些常量配置 | |
9 | + */ | |
10 | +public class GatewayConst { | |
11 | + /**用于标记聚合服务mapping是否已经初始化(防止重复加载)*/ | |
12 | + public static final AtomicBoolean HAS_INIT_MAPPING = new AtomicBoolean(false); | |
13 | + /**请求体缓存Key*/ | |
14 | + public static final String CACHE_REQUEST_BODY_OBJECT_KEY = "cachedRequestBodyObj"; | |
15 | + /**缓存Api配置key*/ | |
16 | + public static final String CACHE_API_OBJECT_KEY = "cachedApiConfigObject"; | |
17 | + | |
18 | + public static final int CACHE_BODY_FILTER_ORDER = -10; | |
19 | + | |
20 | + public static final int AUTH_FILTER_ORDER = -9; | |
21 | +} | ... | ... |
src/main/java/com/diligrp/xtrade/gateway/common/utils/CacheUtils.java
0 → 100644
1 | +++ a/src/main/java/com/diligrp/xtrade/gateway/common/utils/CacheUtils.java | |
1 | +//package com.diligrp.xtrade.gateway.common.utils; | |
2 | +// | |
3 | +//import net.sf.ehcache.Cache; | |
4 | +//import net.sf.ehcache.CacheManager; | |
5 | +//import net.sf.ehcache.Element; | |
6 | +//import org.springframework.beans.factory.annotation.Autowired; | |
7 | +//import org.springframework.stereotype.Component; | |
8 | +// | |
9 | +///** | |
10 | +// * @Auther: miaoguoxin | |
11 | +// * @Date: 2020/4/2 13:25 | |
12 | +// * @Description: | |
13 | +// */ | |
14 | +//@Component | |
15 | +//public class CacheUtils { | |
16 | +// | |
17 | +// private static CacheManager cacheManager; | |
18 | +// | |
19 | +// @Autowired | |
20 | +// public void setCacheManager(CacheManager cacheManager) { | |
21 | +// CacheUtils.cacheManager = cacheManager; | |
22 | +// } | |
23 | +// | |
24 | +// /** | |
25 | +// * 获取缓存 | |
26 | +// * | |
27 | +// * @param cacheName | |
28 | +// * @param key | |
29 | +// * @return | |
30 | +// */ | |
31 | +// public Object getCache(String cacheName, String key) { | |
32 | +// Cache cache = cacheManager.getCache(cacheName); | |
33 | +// if (cache == null) { | |
34 | +// return null; | |
35 | +// } | |
36 | +// return cache.get(key).getObjectValue(); | |
37 | +// } | |
38 | +// | |
39 | +// /** | |
40 | +// * 保存缓存--没有则创建一个 | |
41 | +// * | |
42 | +// * @param cacheName | |
43 | +// * @param key | |
44 | +// * @param value | |
45 | +// */ | |
46 | +// public void putCache(String cacheName, String key, Object value) { | |
47 | +// Cache cache = cacheManager.getCache(cacheName); | |
48 | +// if (cache == null) { | |
49 | +// cacheManager.addCache(cacheName); | |
50 | +// cache = cacheManager.getCache(cacheName); | |
51 | +// cache.put(new Element(key, value)); | |
52 | +// } | |
53 | +// cache.put(new Element(key, value)); | |
54 | +// } | |
55 | +// | |
56 | +// /** | |
57 | +// * 删除缓存 | |
58 | +// * @param cacheName | |
59 | +// */ | |
60 | +// public void removeCache(String cacheName,String key){ | |
61 | +// Cache cache = cacheManager.getCache(cacheName); | |
62 | +// if(cache!=null){ | |
63 | +// cache.remove(key); | |
64 | +// } | |
65 | +// } | |
66 | +// | |
67 | +// /** | |
68 | +// * 替换缓存 | |
69 | +// * @param cacheName | |
70 | +// * @param key | |
71 | +// * @param value | |
72 | +// */ | |
73 | +// public void replaceCache(String cacheName,String key,String value){ | |
74 | +// Cache cache = cacheManager.getCache(cacheName); | |
75 | +// if(cache!=null){ | |
76 | +// cache.replace(new Element(key,value)); | |
77 | +// } | |
78 | +// } | |
79 | +//} | ... | ... |
src/main/java/com/diligrp/xtrade/gateway/common/utils/PathUtils.java
0 → 100644
1 | +++ a/src/main/java/com/diligrp/xtrade/gateway/common/utils/PathUtils.java | |
1 | +package com.diligrp.xtrade.gateway.common.utils; | |
2 | + | |
3 | +import org.springframework.http.server.PathContainer; | |
4 | +import org.springframework.util.CollectionUtils; | |
5 | +import org.springframework.web.util.pattern.PathPattern; | |
6 | +import org.springframework.web.util.pattern.PathPatternParser; | |
7 | + | |
8 | +import java.util.List; | |
9 | +import java.util.Optional; | |
10 | +import java.util.concurrent.CopyOnWriteArrayList; | |
11 | + | |
12 | +/** | |
13 | + * @Auther: miaoguoxin | |
14 | + * @Date: 2020/4/15 10:37 | |
15 | + */ | |
16 | +public class PathUtils { | |
17 | + private static PathPatternParser pathPatternParser = new PathPatternParser(); | |
18 | + | |
19 | + /** | |
20 | + * 多个路径拼接成一个标准uri,ex: /test + gateway = /test/gateway | |
21 | + * @author miaoguoxin | |
22 | + * @date 2020/4/15 | |
23 | + */ | |
24 | + public static String concatUri(String... paths){ | |
25 | + if (paths==null || paths.length ==0){ | |
26 | + return ""; | |
27 | + } | |
28 | + StringBuilder sb = new StringBuilder(); | |
29 | + for (String path : paths) { | |
30 | + sb.append("/").append(path).append("/"); | |
31 | + } | |
32 | + String s = removeExtraSlashOfUrl(sb.toString()); | |
33 | + if (s.endsWith("/")){ | |
34 | + s= s.substring(0,s.length()-1); | |
35 | + } | |
36 | + return s; | |
37 | + } | |
38 | + | |
39 | + /** | |
40 | + * 去除多余的斜杠 | |
41 | + * @author miaoguoxin | |
42 | + * @date 2020/4/15 | |
43 | + */ | |
44 | + public static String removeExtraSlashOfUrl(String url) { | |
45 | + if (url == null || url.length() == 0) { | |
46 | + return url; | |
47 | + } | |
48 | + return url.replaceAll("(?<!(http:|https:))/+", "/"); | |
49 | + } | |
50 | + | |
51 | + /** | |
52 | + * 判断uri是否匹配 | |
53 | + * @author miaoguoxin | |
54 | + * @date 2020/4/15 | |
55 | + */ | |
56 | + public static boolean isUriMatch(String path, List<PathPattern> pathPatterns){ | |
57 | + PathContainer pathContainer = PathContainer.parsePath(path); | |
58 | + return Optional.ofNullable(pathPatterns) | |
59 | + .map(patterns -> patterns | |
60 | + .stream() | |
61 | + .anyMatch(pattern -> { | |
62 | + return pattern.matches(pathContainer); | |
63 | + })) | |
64 | + .orElse(true); | |
65 | + } | |
66 | + | |
67 | + | |
68 | + public static String getMatchUri(String path, List<PathPattern> pathPatterns) { | |
69 | + PathContainer pathContainer = PathContainer.parsePath(path); | |
70 | + return Optional.ofNullable(pathPatterns) | |
71 | + .map(patterns -> { | |
72 | + PathPattern pathPattern = patterns.stream() | |
73 | + .filter(p -> p.matches(pathContainer)) | |
74 | + .findFirst().orElse(null); | |
75 | + if (pathPattern != null) { | |
76 | + return pathPattern.getPatternString(); | |
77 | + } | |
78 | + return ""; | |
79 | + }).orElse(""); | |
80 | + } | |
81 | + | |
82 | + /** | |
83 | + * 转换路径格式 | |
84 | + * @author miaoguoxin | |
85 | + * @date 2020/4/15 | |
86 | + */ | |
87 | + public static List<PathPattern> assemblePath(String[] paths,List<PathPattern> pathPatterns){ | |
88 | + if (paths == null || paths.length == 0){ | |
89 | + return null; | |
90 | + } | |
91 | + if (pathPatterns == null){ | |
92 | + pathPatterns = new CopyOnWriteArrayList<>(); | |
93 | + } | |
94 | + if (!CollectionUtils.isEmpty(pathPatterns)){ | |
95 | + pathPatterns.clear(); | |
96 | + } | |
97 | + for (String path : paths) { | |
98 | + PathPattern pathPattern = pathPatternParser.parse(path); | |
99 | + pathPatterns.add(pathPattern); | |
100 | + } | |
101 | + return pathPatterns; | |
102 | + } | |
103 | + | |
104 | +} | ... | ... |
src/main/java/com/diligrp/xtrade/gateway/common/utils/ResponseUtils.java
0 → 100644
1 | +++ a/src/main/java/com/diligrp/xtrade/gateway/common/utils/ResponseUtils.java | |
1 | +package com.diligrp.xtrade.gateway.common.utils; | |
2 | + | |
3 | +import com.diligrp.xtrade.shared.domain.Message; | |
4 | +import com.diligrp.xtrade.shared.util.JsonUtils; | |
5 | +import org.springframework.http.HttpStatus; | |
6 | +import org.springframework.http.MediaType; | |
7 | +import org.springframework.http.server.reactive.ServerHttpResponse; | |
8 | +import reactor.core.publisher.Flux; | |
9 | +import reactor.core.publisher.Mono; | |
10 | + | |
11 | +import java.nio.charset.StandardCharsets; | |
12 | + | |
13 | +/** | |
14 | + * @Auther: miaoguoxin | |
15 | + * @Date: 2020/4/14 16:46 | |
16 | + */ | |
17 | +public class ResponseUtils { | |
18 | + | |
19 | + | |
20 | + public static Mono<Void> writeForbidden(ServerHttpResponse response){ | |
21 | + Message message = Message | |
22 | + .builder() | |
23 | + .code(HttpStatus.FORBIDDEN.value()) | |
24 | + .message(HttpStatus.FORBIDDEN.toString()) | |
25 | + .build(); | |
26 | + return writeResponse(response,message); | |
27 | + } | |
28 | + | |
29 | + public static Mono<Void> writeResponse(ServerHttpResponse response,Message message){ | |
30 | + String respJson = JsonUtils.toJsonString(message); | |
31 | + response.getHeaders().setContentLength( | |
32 | + respJson.getBytes(StandardCharsets.UTF_8).length); | |
33 | + response.getHeaders().setContentType(MediaType.APPLICATION_JSON); | |
34 | + return response.writeWith( | |
35 | + Flux.just(respJson) | |
36 | + .map(bx -> response.bufferFactory() | |
37 | + .wrap(bx.getBytes(StandardCharsets.UTF_8)) | |
38 | + ) | |
39 | + ); | |
40 | + } | |
41 | +} | ... | ... |
src/main/java/com/diligrp/xtrade/gateway/common/utils/ValidateUtils.java
0 → 100644
1 | +++ a/src/main/java/com/diligrp/xtrade/gateway/common/utils/ValidateUtils.java | |
1 | +package com.diligrp.xtrade.gateway.common.utils; | |
2 | + | |
3 | +import com.diligrp.xtrade.gateway.exception.GatewayParamNotValidException; | |
4 | +import org.springframework.beans.factory.annotation.Autowired; | |
5 | +import org.springframework.stereotype.Component; | |
6 | + | |
7 | +import javax.validation.ConstraintViolation; | |
8 | +import javax.validation.Validation; | |
9 | +import javax.validation.Validator; | |
10 | +import javax.validation.groups.Default; | |
11 | +import java.util.Arrays; | |
12 | +import java.util.HashSet; | |
13 | +import java.util.Set; | |
14 | + | |
15 | +/** | |
16 | + * @Auther: miaoguoxin | |
17 | + * @Date: 2020/4/15 12:44 | |
18 | + * @Description: 用于参数校验的工具类 | |
19 | + */ | |
20 | +@Component | |
21 | +public class ValidateUtils { | |
22 | + private static Validator validator; | |
23 | + | |
24 | + | |
25 | + @Autowired | |
26 | + public void setValidator(Validator validator){ | |
27 | + ValidateUtils.validator = validator; | |
28 | + } | |
29 | + | |
30 | + public static void validate(Object params){ | |
31 | + validate(params, new Class[]{}); | |
32 | + } | |
33 | + | |
34 | + public static void validate(Object params, Class... groups) { | |
35 | + Set<Class> groupSet = new HashSet<>(); | |
36 | + groupSet.add(Default.class); | |
37 | + if (groups != null && groups.length > 0) { | |
38 | + groupSet.addAll(Arrays.asList(groups)); | |
39 | + } | |
40 | + Set<ConstraintViolation<Object>> set = validator.validate(params, groupSet.toArray(new Class[0])); | |
41 | + if (set != null && !set.isEmpty()) { | |
42 | + for (ConstraintViolation<Object> violation : set) { | |
43 | + throw new GatewayParamNotValidException(violation.getMessage()); | |
44 | + } | |
45 | + } | |
46 | + | |
47 | + } | |
48 | +} | ... | ... |
src/main/java/com/diligrp/xtrade/gateway/config/CacheManagerConfiguration.java
0 → 100644
1 | +++ a/src/main/java/com/diligrp/xtrade/gateway/config/CacheManagerConfiguration.java | |
1 | +//package com.diligrp.xtrade.gateway.config; | |
2 | +// | |
3 | +//import net.sf.ehcache.CacheManager; | |
4 | +//import org.springframework.beans.factory.annotation.Value; | |
5 | +//import org.springframework.context.annotation.Bean; | |
6 | +//import org.springframework.context.annotation.Configuration; | |
7 | +// | |
8 | +///** | |
9 | +// * @Auther: miaoguoxin | |
10 | +// * @Date: 2020/4/2 13:28 | |
11 | +// * @Description: | |
12 | +// */ | |
13 | +//@Configuration | |
14 | +//public class CacheManagerConfiguration { | |
15 | +// @Value("${spring.cache.ehcache.config}") | |
16 | +// private String ehcaheConfigName; | |
17 | +// | |
18 | +// @Bean | |
19 | +// public CacheManager cacheManager(){ | |
20 | +// String classesPath = getClassesPath(); | |
21 | +// return CacheManager.create(classesPath+ehcaheConfigName); | |
22 | +// } | |
23 | +// | |
24 | +// private static String getClassesPath(){ | |
25 | +// return Thread.currentThread().getContextClassLoader().getResource("").getPath(); | |
26 | +// } | |
27 | +// | |
28 | +//} | ... | ... |
src/main/java/com/diligrp/xtrade/gateway/config/ExceptionConfiguration.java
0 → 100644
1 | +++ a/src/main/java/com/diligrp/xtrade/gateway/config/ExceptionConfiguration.java | |
1 | +package com.diligrp.xtrade.gateway.config; | |
2 | + | |
3 | +import com.diligrp.xtrade.gateway.exception.GlobalGatewayErrorHandler; | |
4 | +import org.springframework.beans.factory.ObjectProvider; | |
5 | +import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler; | |
6 | +import org.springframework.context.annotation.Bean; | |
7 | +import org.springframework.context.annotation.Configuration; | |
8 | +import org.springframework.context.annotation.Primary; | |
9 | +import org.springframework.core.Ordered; | |
10 | +import org.springframework.core.annotation.Order; | |
11 | +import org.springframework.http.codec.ServerCodecConfigurer; | |
12 | +import org.springframework.web.reactive.result.view.ViewResolver; | |
13 | + | |
14 | +import java.util.Collections; | |
15 | +import java.util.List; | |
16 | + | |
17 | +/** | |
18 | + * @Auther: miaoguoxin | |
19 | + * @Date: 2018/12/12 21:53 | |
20 | + * @Description: 全局异常处理配置 | |
21 | + */ | |
22 | +@Configuration | |
23 | +public class ExceptionConfiguration { | |
24 | + @Primary | |
25 | + @Bean | |
26 | + @Order(Ordered.HIGHEST_PRECEDENCE) | |
27 | + public ErrorWebExceptionHandler errorWebExceptionHandler(ObjectProvider<List<ViewResolver>> viewResolversProvider, | |
28 | + ServerCodecConfigurer serverCodecConfigurer) { | |
29 | + GlobalGatewayErrorHandler jsonExceptionHandler = new GlobalGatewayErrorHandler(); | |
30 | + jsonExceptionHandler.setViewResolvers(viewResolversProvider.getIfAvailable(Collections::emptyList)); | |
31 | + jsonExceptionHandler.setMessageWriters(serverCodecConfigurer.getWriters()); | |
32 | + jsonExceptionHandler.setMessageReaders(serverCodecConfigurer.getReaders()); | |
33 | + return jsonExceptionHandler; | |
34 | + } | |
35 | + | |
36 | +} | ... | ... |
src/main/java/com/diligrp/xtrade/gateway/config/GatewayResourceLoaderConfiguration.java
0 → 100644
1 | +++ a/src/main/java/com/diligrp/xtrade/gateway/config/GatewayResourceLoaderConfiguration.java | |
1 | +package com.diligrp.xtrade.gateway.config; | |
2 | + | |
3 | +import com.alibaba.cloud.nacos.NacosConfigProperties; | |
4 | +import com.alibaba.nacos.api.NacosFactory; | |
5 | +import com.alibaba.nacos.api.PropertyKeyConst; | |
6 | +import com.alibaba.nacos.api.config.ConfigService; | |
7 | +import com.alibaba.nacos.api.exception.NacosException; | |
8 | +import com.diligrp.xtrade.gateway.exception.GatewayServiceException; | |
9 | +import com.diligrp.xtrade.gateway.route.DynamicRouteLoaderIntf; | |
10 | +import com.diligrp.xtrade.gateway.route.impl.CloudRouteResourceLoader; | |
11 | +import com.diligrp.xtrade.gateway.route.impl.MysqlRouteResourceLoader; | |
12 | +import org.apache.logging.log4j.util.Strings; | |
13 | +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; | |
14 | +import org.springframework.context.annotation.Bean; | |
15 | +import org.springframework.context.annotation.Configuration; | |
16 | +import org.springframework.core.env.Environment; | |
17 | + | |
18 | +import java.util.Properties; | |
19 | + | |
20 | +/** | |
21 | + * @Auther: miaoguoxin | |
22 | + * @Date: 2020/4/10 13:20 | |
23 | + * @Description: 路由资源加载器配置 | |
24 | + */ | |
25 | +@Configuration(proxyBeanMethods = false) | |
26 | +public class GatewayResourceLoaderConfiguration { | |
27 | + | |
28 | + @Bean | |
29 | + public ConfigService configService(Environment environment,NacosConfigProperties configProperties){ | |
30 | + String username = environment.getProperty("spring.cloud.nacos.config.username"); | |
31 | + String password = environment.getProperty("spring.cloud.nacos.config.password"); | |
32 | + Properties properties = new Properties(); | |
33 | + properties.put(PropertyKeyConst.NAMESPACE, configProperties.getNamespace()); | |
34 | + if (Strings.isNotBlank(username)) { | |
35 | + properties.put(PropertyKeyConst.USERNAME, username); | |
36 | + properties.put(PropertyKeyConst.PASSWORD, password); | |
37 | + } | |
38 | + properties.put(PropertyKeyConst.SERVER_ADDR, configProperties.getServerAddr()); | |
39 | + ConfigService configService; | |
40 | + try { | |
41 | + configService = NacosFactory.createConfigService(properties); | |
42 | + } catch (NacosException e) { | |
43 | + throw new GatewayServiceException("init nacos configService failed", e); | |
44 | + } | |
45 | + return configService; | |
46 | + } | |
47 | + | |
48 | + @Bean(name = "cloudRouteResourceLoader") | |
49 | + @ConditionalOnProperty(prefix = "xtrade",name = "gateway-loader",havingValue = "cloud",matchIfMissing = true) | |
50 | + public DynamicRouteLoaderIntf cloudRouteResourceLoader(){ | |
51 | + return new CloudRouteResourceLoader(); | |
52 | + } | |
53 | + | |
54 | + | |
55 | + @Bean(name = "mysqlRouteResourceLoader") | |
56 | + @ConditionalOnProperty(prefix = "xtrade",name = "gateway-loader",havingValue = "mysql") | |
57 | + public DynamicRouteLoaderIntf mysqlRouteResourceLoader(){ | |
58 | + return new MysqlRouteResourceLoader(); | |
59 | + } | |
60 | + | |
61 | +} | ... | ... |
src/main/java/com/diligrp/xtrade/gateway/config/property/AuthPathProperties.java
0 → 100644
1 | +++ a/src/main/java/com/diligrp/xtrade/gateway/config/property/AuthPathProperties.java | |
1 | +package com.diligrp.xtrade.gateway.config.property; | |
2 | + | |
3 | +import com.diligrp.xtrade.gateway.common.utils.PathUtils; | |
4 | +import lombok.Data; | |
5 | +import org.springframework.boot.context.properties.ConfigurationProperties; | |
6 | +import org.springframework.stereotype.Component; | |
7 | +import org.springframework.util.CollectionUtils; | |
8 | +import org.springframework.web.util.pattern.PathPattern; | |
9 | +import org.springframework.web.util.pattern.PathPatternParser; | |
10 | + | |
11 | +import java.util.ArrayList; | |
12 | +import java.util.List; | |
13 | +import java.util.concurrent.CopyOnWriteArrayList; | |
14 | + | |
15 | +/** | |
16 | + * @Auther: miaoguoxin | |
17 | + * @Date: 2020/4/13 15:17 | |
18 | + */ | |
19 | +@Component | |
20 | +@ConfigurationProperties(prefix = "xtrade.auth") | |
21 | +@Data | |
22 | +public class AuthPathProperties { | |
23 | + /**不需要校验权限的path*/ | |
24 | + private String[] excludePaths; | |
25 | + /**转换path格式*/ | |
26 | + private List<PathPattern> excludePathPatterns; | |
27 | + | |
28 | + | |
29 | + /** | |
30 | + * 属性配置刷新的时候会从这里进入 | |
31 | + * @author miaoguoxin | |
32 | + * @date 2020/4/13 | |
33 | + */ | |
34 | + public void setExcludePaths(String[] excludePaths) { | |
35 | + this.excludePaths = excludePaths; | |
36 | + this.excludePathPatterns = PathUtils.assemblePath(this.excludePaths,this.excludePathPatterns); | |
37 | + } | |
38 | + | |
39 | + | |
40 | +} | ... | ... |
src/main/java/com/diligrp/xtrade/gateway/config/property/DispatchProperties.java
0 → 100644
1 | +++ a/src/main/java/com/diligrp/xtrade/gateway/config/property/DispatchProperties.java | |
1 | +package com.diligrp.xtrade.gateway.config.property; | |
2 | + | |
3 | +import lombok.Data; | |
4 | +import org.springframework.boot.context.properties.ConfigurationProperties; | |
5 | +import org.springframework.stereotype.Component; | |
6 | + | |
7 | +/** | |
8 | + * @Auther: miaoguoxin | |
9 | + * @Date: 2020/4/15 17:35 | |
10 | + */ | |
11 | +@Component | |
12 | +@ConfigurationProperties(prefix = "xtrade") | |
13 | +@Data | |
14 | +public class DispatchProperties { | |
15 | + /** 扫描包路径 */ | |
16 | + private String[] aggregationScanPackages; | |
17 | + | |
18 | + public DispatchProperties(String[] aggregationScanPackages) { | |
19 | + this.aggregationScanPackages = aggregationScanPackages; | |
20 | + } | |
21 | +} | ... | ... |
src/main/java/com/diligrp/xtrade/gateway/controller/TestController.java
0 → 100644
1 | +++ a/src/main/java/com/diligrp/xtrade/gateway/controller/TestController.java | |
1 | +package com.diligrp.xtrade.gateway.controller; | |
2 | + | |
3 | +import com.diligrp.xtrade.shared.domain.Message; | |
4 | +import com.google.common.collect.Lists; | |
5 | +import org.springframework.beans.factory.annotation.Autowired; | |
6 | +import org.springframework.beans.factory.annotation.Value; | |
7 | +import org.springframework.cloud.context.config.annotation.RefreshScope; | |
8 | +import org.springframework.cloud.gateway.config.GatewayProperties; | |
9 | +import org.springframework.cloud.gateway.route.RouteDefinition; | |
10 | +import org.springframework.http.server.PathContainer; | |
11 | +import org.springframework.web.bind.annotation.GetMapping; | |
12 | +import org.springframework.web.bind.annotation.RequestMapping; | |
13 | +import org.springframework.web.bind.annotation.RestController; | |
14 | +import org.springframework.web.util.pattern.PathPattern; | |
15 | +import org.springframework.web.util.pattern.PathPatternParser; | |
16 | + | |
17 | +import java.util.ArrayList; | |
18 | +import java.util.List; | |
19 | +import java.util.Optional; | |
20 | + | |
21 | +/** | |
22 | + * @Auther: miaoguoxin | |
23 | + * @Date: 2020/4/10 16:01 | |
24 | + * @Description: | |
25 | + */ | |
26 | +@RestController | |
27 | +@RequestMapping("/test") | |
28 | +@RefreshScope | |
29 | +public class TestController { | |
30 | + private static PathPatternParser pathPatternParser = new PathPatternParser(); | |
31 | + @Autowired | |
32 | + private GatewayProperties gatewayProperties; | |
33 | + @Value("${ribbon.ServerListRefreshInterval}") | |
34 | + private String interval; | |
35 | + | |
36 | + @GetMapping("/gateway") | |
37 | + public Message<List<RouteDefinition>> test() { | |
38 | + List<RouteDefinition> routes = gatewayProperties.getRoutes(); | |
39 | + return Message.builder().success(routes); | |
40 | + //throw new RuntimeException("gfdg"); | |
41 | + } | |
42 | + | |
43 | + @GetMapping("/test_dynamic") | |
44 | + public Message<String> test1() { | |
45 | + return Message.builder().success(interval); | |
46 | + } | |
47 | +} | ... | ... |
src/main/java/com/diligrp/xtrade/gateway/domain/TestRequestDto.java
0 → 100644
1 | +++ a/src/main/java/com/diligrp/xtrade/gateway/domain/TestRequestDto.java | |
1 | +package com.diligrp.xtrade.gateway.domain; | |
2 | + | |
3 | +import lombok.Data; | |
4 | + | |
5 | +import javax.validation.constraints.NotBlank; | |
6 | + | |
7 | +/** | |
8 | + * @Auther: miaoguoxin | |
9 | + * @Date: 2020/4/15 15:56 | |
10 | + */ | |
11 | +@Data | |
12 | +public class TestRequestDto { | |
13 | + @NotBlank(message = "{request.username.not.blank}") | |
14 | + private String username; | |
15 | + | |
16 | +} | ... | ... |
src/main/java/com/diligrp/xtrade/gateway/exception/GatewayParamNotValidException.java
0 → 100644
1 | +++ a/src/main/java/com/diligrp/xtrade/gateway/exception/GatewayParamNotValidException.java | |
1 | +package com.diligrp.xtrade.gateway.exception; | |
2 | + | |
3 | +/** | |
4 | + * @Auther: miaoguoxin | |
5 | + * @Date: 2020/4/15 13:34 | |
6 | + * @Description: 用于聚合服务操作参数校验失败 | |
7 | + */ | |
8 | +public class GatewayParamNotValidException extends RuntimeException { | |
9 | + | |
10 | + public GatewayParamNotValidException(String message) { | |
11 | + super(message); | |
12 | + } | |
13 | +} | ... | ... |
src/main/java/com/diligrp/xtrade/gateway/exception/GatewayServiceException.java
0 → 100644
1 | +++ a/src/main/java/com/diligrp/xtrade/gateway/exception/GatewayServiceException.java | |
1 | +package com.diligrp.xtrade.gateway.exception; | |
2 | + | |
3 | +import lombok.Getter; | |
4 | + | |
5 | +/** | |
6 | + * @Auther: miaoguoxin | |
7 | + * @Date: 2020/4/10 11:28 | |
8 | + * @Description: | |
9 | + */ | |
10 | +@Getter | |
11 | +public class GatewayServiceException extends RuntimeException { | |
12 | + private Object[] args; | |
13 | + | |
14 | + public GatewayServiceException() { | |
15 | + } | |
16 | + | |
17 | + public GatewayServiceException(String message) { | |
18 | + super(message); | |
19 | + } | |
20 | + | |
21 | + public GatewayServiceException(String message, Object[] args) { | |
22 | + super(message); | |
23 | + this.args = args; | |
24 | + } | |
25 | + | |
26 | + public GatewayServiceException(String message, Throwable cause, Object[] args) { | |
27 | + super(message, cause); | |
28 | + this.args = args; | |
29 | + } | |
30 | + | |
31 | + public GatewayServiceException(Throwable cause, Object[] args) { | |
32 | + super(cause); | |
33 | + this.args = args; | |
34 | + } | |
35 | + | |
36 | + public GatewayServiceException(String message, Throwable cause) { | |
37 | + super(message, cause); | |
38 | + } | |
39 | + | |
40 | + public GatewayServiceException(Throwable cause) { | |
41 | + super(cause); | |
42 | + } | |
43 | + | |
44 | + | |
45 | +} | ... | ... |
src/main/java/com/diligrp/xtrade/gateway/exception/GlobalGatewayErrorHandler.java
0 → 100644
1 | +++ a/src/main/java/com/diligrp/xtrade/gateway/exception/GlobalGatewayErrorHandler.java | |
1 | +package com.diligrp.xtrade.gateway.exception; | |
2 | + | |
3 | +import com.alibaba.fastjson.JSON; | |
4 | +import com.diligrp.xtrade.shared.domain.Message; | |
5 | +import lombok.extern.slf4j.Slf4j; | |
6 | +import org.springframework.beans.factory.annotation.Autowired; | |
7 | +import org.springframework.boot.web.reactive.error.ErrorAttributes; | |
8 | +import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler; | |
9 | +import org.springframework.cloud.gateway.support.NotFoundException; | |
10 | +import org.springframework.http.HttpStatus; | |
11 | +import org.springframework.http.MediaType; | |
12 | +import org.springframework.http.codec.HttpMessageReader; | |
13 | +import org.springframework.http.codec.HttpMessageWriter; | |
14 | +import org.springframework.http.server.reactive.ServerHttpRequest; | |
15 | +import org.springframework.util.Assert; | |
16 | +import org.springframework.web.reactive.function.BodyInserters; | |
17 | +import org.springframework.web.reactive.function.server.RequestPredicates; | |
18 | +import org.springframework.web.reactive.function.server.RouterFunctions; | |
19 | +import org.springframework.web.reactive.function.server.ServerRequest; | |
20 | +import org.springframework.web.reactive.function.server.ServerResponse; | |
21 | +import org.springframework.web.reactive.result.view.ViewResolver; | |
22 | +import org.springframework.web.server.ResponseStatusException; | |
23 | +import org.springframework.web.server.ServerWebExchange; | |
24 | +import reactor.core.publisher.Mono; | |
25 | + | |
26 | +import java.util.Collections; | |
27 | +import java.util.HashMap; | |
28 | +import java.util.List; | |
29 | +import java.util.Map; | |
30 | + | |
31 | +/** | |
32 | + * @Auther: miaoguoxin | |
33 | + * @Date: 2020/04/13 10:55 | |
34 | + * @Description: 处理网关本身抛出的异常 | |
35 | + */ | |
36 | +@Slf4j | |
37 | +public class GlobalGatewayErrorHandler implements ErrorWebExceptionHandler { | |
38 | + @Autowired | |
39 | + private ErrorAttributes errorAttributes; | |
40 | + /** | |
41 | + * MessageReader | |
42 | + */ | |
43 | + private List<HttpMessageReader<?>> messageReaders = Collections.emptyList(); | |
44 | + | |
45 | + /** | |
46 | + * MessageWriter | |
47 | + */ | |
48 | + private List<HttpMessageWriter<?>> messageWriters = Collections.emptyList(); | |
49 | + | |
50 | + /** | |
51 | + * ViewResolvers | |
52 | + */ | |
53 | + private List<ViewResolver> viewResolvers = Collections.emptyList(); | |
54 | + | |
55 | + /** | |
56 | + * 存储处理异常后的信息 | |
57 | + */ | |
58 | + private ThreadLocal<Map<String, Object>> exceptionHandlerResult = new ThreadLocal<>(); | |
59 | + | |
60 | + /** | |
61 | + * 参考AbstractErrorWebExceptionHandler | |
62 | + */ | |
63 | + public void setViewResolvers(List<ViewResolver> viewResolvers) { | |
64 | + this.viewResolvers = viewResolvers; | |
65 | + } | |
66 | + | |
67 | + /** | |
68 | + * 参考AbstractErrorWebExceptionHandler | |
69 | + */ | |
70 | + public void setMessageWriters(List<HttpMessageWriter<?>> messageWriters) { | |
71 | + Assert.notNull(messageWriters, "'messageWriters' must not be null"); | |
72 | + this.messageWriters = messageWriters; | |
73 | + } | |
74 | + | |
75 | + /** | |
76 | + * 参考AbstractErrorWebExceptionHandler | |
77 | + */ | |
78 | + public void setMessageReaders(List<HttpMessageReader<?>> messageReaders) { | |
79 | + Assert.notNull(messageReaders, "'messageReaders' must not be null"); | |
80 | + this.messageReaders = messageReaders; | |
81 | + } | |
82 | + | |
83 | + | |
84 | + @Override | |
85 | + public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) { | |
86 | + ServerHttpRequest request = exchange.getRequest(); | |
87 | + // Map<String, Object> result = this.getHttpResult(httpStatus); | |
88 | + //参考AbstractErrorWebExceptionHandler | |
89 | + if (exchange.getResponse().isCommitted()) { | |
90 | + return Mono.error(ex); | |
91 | + } | |
92 | + errorAttributes.storeErrorInformation(ex,exchange); | |
93 | + ServerRequest newRequest = ServerRequest.create(exchange, this.messageReaders); | |
94 | + return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse).route(newRequest) | |
95 | + .switchIfEmpty(Mono.error(ex)) | |
96 | + .flatMap((handler) -> handler.handle(newRequest)) | |
97 | + .flatMap((response) -> write(exchange, response)); | |
98 | + } | |
99 | + | |
100 | + private HttpStatus getHttpStatus(Throwable ex) { | |
101 | + HttpStatus httpStatus; | |
102 | + if (ex instanceof NotFoundException) { | |
103 | + httpStatus = HttpStatus.NOT_FOUND; | |
104 | + } else if (ex instanceof ResponseStatusException) { | |
105 | + ResponseStatusException responseStatusException = (ResponseStatusException) ex; | |
106 | + httpStatus = responseStatusException.getStatus(); | |
107 | + } else { | |
108 | + httpStatus = HttpStatus.INTERNAL_SERVER_ERROR; | |
109 | + } | |
110 | + return httpStatus; | |
111 | + } | |
112 | + | |
113 | + private Map<String, Object> getHttpResult(HttpStatus httpStatus) { | |
114 | + Message apiResult= Message.builder() | |
115 | + .code(httpStatus.value()) | |
116 | + .message(httpStatus.toString()) | |
117 | + .build(); | |
118 | + Map<String,Object> result=new HashMap<>(); | |
119 | + result.put("httpStatus",httpStatus); | |
120 | + result.put("body", JSON.toJSONString(apiResult)); | |
121 | + return result; | |
122 | + } | |
123 | + | |
124 | + /** | |
125 | + * 参考DefaultErrorWebExceptionHandler | |
126 | + */ | |
127 | + private Mono<ServerResponse> renderErrorResponse(ServerRequest request) { | |
128 | + Map<String, Object> errorAttrs = errorAttributes.getErrorAttributes(request, true); | |
129 | + String msg = String.format("%s %s", | |
130 | + errorAttrs.get("path"), errorAttrs.get("error")); | |
131 | + Message message = Message.builder() | |
132 | + .code((int)errorAttrs.get("status")) | |
133 | + .message(msg) | |
134 | + .build(); | |
135 | + if (message.getCode().equals(500)){ | |
136 | + log.error("[全局异常处理]异常请求路径:{},记录异常信息:{}", errorAttrs.get("path"),errorAttributes.getError(request)); | |
137 | + } | |
138 | + return ServerResponse.status(HttpStatus.OK) | |
139 | + .contentType(MediaType.APPLICATION_JSON) | |
140 | + .body(BodyInserters.fromValue(message)); | |
141 | + } | |
142 | + | |
143 | + /** | |
144 | + * 参考AbstractErrorWebExceptionHandler | |
145 | + */ | |
146 | + private Mono<? extends Void> write(ServerWebExchange exchange, | |
147 | + ServerResponse response) { | |
148 | + exchange.getResponse().getHeaders() | |
149 | + .setContentType(response.headers().getContentType()); | |
150 | + return response.writeTo(exchange, new ResponseContext()); | |
151 | + } | |
152 | + | |
153 | + | |
154 | + /** | |
155 | + * 参考AbstractErrorWebExceptionHandler | |
156 | + */ | |
157 | + private class ResponseContext implements ServerResponse.Context { | |
158 | + | |
159 | + @Override | |
160 | + public List<HttpMessageWriter<?>> messageWriters() { | |
161 | + return GlobalGatewayErrorHandler.this.messageWriters; | |
162 | + } | |
163 | + | |
164 | + @Override | |
165 | + public List<ViewResolver> viewResolvers() { | |
166 | + return GlobalGatewayErrorHandler.this.viewResolvers; | |
167 | + } | |
168 | + | |
169 | + } | |
170 | + | |
171 | +} | ... | ... |
src/main/java/com/diligrp/xtrade/gateway/filters/factory/DispatchGatewayFilterFactory.java
0 → 100644
1 | +++ a/src/main/java/com/diligrp/xtrade/gateway/filters/factory/DispatchGatewayFilterFactory.java | |
1 | +package com.diligrp.xtrade.gateway.filters.factory; | |
2 | + | |
3 | +import com.diligrp.xtrade.gateway.common.utils.ResponseUtils; | |
4 | +import com.diligrp.xtrade.gateway.support.dispatch.RequestDispatcher; | |
5 | +import com.diligrp.xtrade.shared.domain.Message; | |
6 | +import lombok.Data; | |
7 | +import org.reactivestreams.Publisher; | |
8 | +import org.springframework.beans.factory.annotation.Autowired; | |
9 | +import org.springframework.cloud.gateway.filter.GatewayFilter; | |
10 | +import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory; | |
11 | +import org.springframework.core.io.buffer.DataBuffer; | |
12 | +import org.springframework.http.HttpHeaders; | |
13 | +import org.springframework.http.MediaType; | |
14 | +import org.springframework.http.server.reactive.ServerHttpResponseDecorator; | |
15 | +import org.springframework.stereotype.Component; | |
16 | +import reactor.core.publisher.Mono; | |
17 | + | |
18 | +import static com.diligrp.xtrade.gateway.common.constant.GatewayConst.CACHE_REQUEST_BODY_OBJECT_KEY; | |
19 | +import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR; | |
20 | + | |
21 | +/** | |
22 | + * @Auther: miaoguoxin | |
23 | + * @Date: 2020/4/15 14:18 | |
24 | + * @Description: 分发过滤器filter,用来做网关本身的逻辑操作 | |
25 | + */ | |
26 | +@Component | |
27 | +public class DispatchGatewayFilterFactory extends AbstractGatewayFilterFactory<DispatchGatewayFilterFactory.Config> { | |
28 | + @Autowired | |
29 | + private RequestDispatcher requestDispatcher; | |
30 | + | |
31 | + public DispatchGatewayFilterFactory() { | |
32 | + super(Config.class); | |
33 | + } | |
34 | + | |
35 | + @Override | |
36 | + public GatewayFilter apply(Config config) { | |
37 | + return (exchange, chain) -> { | |
38 | + String rawPath = exchange.getRequest().getURI().getRawPath(); | |
39 | + String paramsJson = exchange.getAttribute(CACHE_REQUEST_BODY_OBJECT_KEY); | |
40 | + Message message = requestDispatcher.executeMethod(rawPath, paramsJson,exchange); | |
41 | + //throw new RuntimeException("gggg"); | |
42 | + return ResponseUtils.writeResponse( | |
43 | + new ServerHttpResponseDecorator(exchange.getResponse()) { | |
44 | + @Override | |
45 | + public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) { | |
46 | + //这里需要设置响应content-Type,不然后面的过滤器拿不到这个值会报错 | |
47 | + exchange.getAttributes().put(ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR, MediaType.APPLICATION_JSON_VALUE); | |
48 | + String originalResponseContentType = exchange | |
49 | + .getAttribute(ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR); | |
50 | + HttpHeaders httpHeaders = exchange.getResponse().getHeaders(); | |
51 | + httpHeaders.add(HttpHeaders.CONTENT_TYPE, originalResponseContentType); | |
52 | + return super.writeWith(body); | |
53 | + } | |
54 | + }, message); | |
55 | + }; | |
56 | + } | |
57 | + | |
58 | + @Data | |
59 | + public static class Config { | |
60 | + private String keyResolverName; | |
61 | + } | |
62 | + | |
63 | +} | ... | ... |
src/main/java/com/diligrp/xtrade/gateway/filters/global/AuthGlobalFilter.java
0 → 100644
1 | +++ a/src/main/java/com/diligrp/xtrade/gateway/filters/global/AuthGlobalFilter.java | |
1 | +package com.diligrp.xtrade.gateway.filters.global; | |
2 | + | |
3 | +import com.diligrp.xtrade.gateway.api.ApiManager; | |
4 | +import com.diligrp.xtrade.gateway.common.utils.ResponseUtils; | |
5 | +import com.diligrp.xtrade.shared.domain.Message; | |
6 | +import com.diligrp.xtrade.shared.util.JsonUtils; | |
7 | +import lombok.extern.slf4j.Slf4j; | |
8 | +import org.springframework.beans.factory.annotation.Autowired; | |
9 | +import org.springframework.cloud.gateway.filter.GatewayFilterChain; | |
10 | +import org.springframework.cloud.gateway.filter.GlobalFilter; | |
11 | +import org.springframework.core.Ordered; | |
12 | +import org.springframework.core.io.buffer.PooledDataBuffer; | |
13 | +import org.springframework.http.HttpStatus; | |
14 | +import org.springframework.http.MediaType; | |
15 | +import org.springframework.http.server.reactive.ServerHttpRequest; | |
16 | +import org.springframework.http.server.reactive.ServerHttpResponse; | |
17 | +import org.springframework.stereotype.Component; | |
18 | +import org.springframework.web.server.ServerWebExchange; | |
19 | +import reactor.core.Disposable; | |
20 | +import reactor.core.publisher.Flux; | |
21 | +import reactor.core.publisher.Mono; | |
22 | + | |
23 | +import java.nio.charset.Charset; | |
24 | +import java.nio.charset.StandardCharsets; | |
25 | +import java.util.Map; | |
26 | + | |
27 | +import static com.diligrp.xtrade.gateway.common.constant.GatewayConst.AUTH_FILTER_ORDER; | |
28 | +import static com.diligrp.xtrade.gateway.common.constant.GatewayConst.CACHE_REQUEST_BODY_OBJECT_KEY; | |
29 | +import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.CACHED_REQUEST_BODY_ATTR; | |
30 | + | |
31 | +/** | |
32 | + * @Auther: miaoguoxin | |
33 | + * @Date: 2020/4/2 13:50 | |
34 | + * @Description: 用于权限操作 | |
35 | + */ | |
36 | +@Component | |
37 | +@Slf4j | |
38 | +public class AuthGlobalFilter implements GlobalFilter, Ordered { | |
39 | + @Autowired | |
40 | + private ApiManager apiManager; | |
41 | + @Override | |
42 | + public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { | |
43 | + String rawPath = exchange.getRequest().getURI().getRawPath(); | |
44 | + ServerHttpResponse response = exchange.getResponse(); | |
45 | + //排除在外的Uri跳过权限 | |
46 | + if (apiManager.isExcludePathMatch(rawPath)){ | |
47 | + return chain.filter(exchange); | |
48 | + } | |
49 | + return ResponseUtils.writeForbidden(response); | |
50 | + } | |
51 | + | |
52 | + @Override | |
53 | + public int getOrder() { | |
54 | + return AUTH_FILTER_ORDER; | |
55 | + } | |
56 | + | |
57 | +} | ... | ... |
src/main/java/com/diligrp/xtrade/gateway/filters/global/CacheRequestBodyGlobalFilter.java
0 → 100644
1 | +++ a/src/main/java/com/diligrp/xtrade/gateway/filters/global/CacheRequestBodyGlobalFilter.java | |
1 | +package com.diligrp.xtrade.gateway.filters.global; | |
2 | + | |
3 | +import lombok.extern.slf4j.Slf4j; | |
4 | +import org.springframework.cloud.gateway.filter.GatewayFilterChain; | |
5 | +import org.springframework.cloud.gateway.filter.GlobalFilter; | |
6 | +import org.springframework.cloud.gateway.filter.factory.rewrite.CachedBodyOutputMessage; | |
7 | +import org.springframework.cloud.gateway.filter.factory.rewrite.ModifyRequestBodyGatewayFilterFactory; | |
8 | +import org.springframework.cloud.gateway.support.BodyInserterContext; | |
9 | +import org.springframework.core.Ordered; | |
10 | +import org.springframework.core.io.buffer.DataBuffer; | |
11 | +import org.springframework.http.HttpHeaders; | |
12 | +import org.springframework.http.ReactiveHttpOutputMessage; | |
13 | +import org.springframework.http.codec.HttpMessageReader; | |
14 | +import org.springframework.http.server.reactive.ServerHttpRequest; | |
15 | +import org.springframework.http.server.reactive.ServerHttpRequestDecorator; | |
16 | +import org.springframework.stereotype.Component; | |
17 | +import org.springframework.web.reactive.function.BodyInserter; | |
18 | +import org.springframework.web.reactive.function.BodyInserters; | |
19 | +import org.springframework.web.reactive.function.server.HandlerStrategies; | |
20 | +import org.springframework.web.reactive.function.server.ServerRequest; | |
21 | +import org.springframework.web.server.ServerWebExchange; | |
22 | +import reactor.core.publisher.Flux; | |
23 | +import reactor.core.publisher.Mono; | |
24 | + | |
25 | +import java.util.List; | |
26 | + | |
27 | +import static com.diligrp.xtrade.gateway.common.constant.GatewayConst.CACHE_BODY_FILTER_ORDER; | |
28 | +import static com.diligrp.xtrade.gateway.common.constant.GatewayConst.CACHE_REQUEST_BODY_OBJECT_KEY; | |
29 | + | |
30 | +/** | |
31 | + * @Auther: miaoguoxin | |
32 | + * @Date: 2020/4/13 11:49 | |
33 | + * @Description: 缓存请求body的过滤器,参考 | |
34 | + * {@link ModifyRequestBodyGatewayFilterFactory} | |
35 | + */ | |
36 | +@Component | |
37 | +@Slf4j | |
38 | +public class CacheRequestBodyGlobalFilter implements GlobalFilter, Ordered { | |
39 | + private static final List<HttpMessageReader<?>> messageReaders = HandlerStrategies | |
40 | + .withDefaults().messageReaders(); | |
41 | + | |
42 | + @Override | |
43 | + public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { | |
44 | + ServerRequest serverRequest = ServerRequest.create(exchange, | |
45 | + messageReaders); | |
46 | + Mono<String> modifiedBody = serverRequest.bodyToMono(String.class) | |
47 | + .flatMap(body -> { | |
48 | + exchange.getAttributes().put(CACHE_REQUEST_BODY_OBJECT_KEY, body); | |
49 | + return Mono.just(body); | |
50 | + }); | |
51 | + | |
52 | + BodyInserter<Mono<String>, ReactiveHttpOutputMessage> bodyInserter = BodyInserters.fromPublisher(modifiedBody, String.class); | |
53 | + HttpHeaders headers = new HttpHeaders(); | |
54 | + headers.putAll(exchange.getRequest().getHeaders()); | |
55 | + //由bodyInserter重新设置content-length | |
56 | + headers.remove(HttpHeaders.CONTENT_LENGTH); | |
57 | + | |
58 | + CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage( | |
59 | + exchange, headers); | |
60 | + return bodyInserter.insert(outputMessage, new BodyInserterContext()) | |
61 | + // .log("modify_request", Level.INFO) | |
62 | + .then(Mono.defer(() -> { | |
63 | + ServerHttpRequest decorator = this.decorate(exchange, headers, | |
64 | + outputMessage); | |
65 | + return chain.filter(exchange.mutate().request(decorator).build()); | |
66 | + })); | |
67 | + } | |
68 | + | |
69 | + private ServerHttpRequestDecorator decorate(ServerWebExchange exchange, HttpHeaders headers, | |
70 | + CachedBodyOutputMessage outputMessage) { | |
71 | + return new ServerHttpRequestDecorator(exchange.getRequest()) { | |
72 | + @Override | |
73 | + public HttpHeaders getHeaders() { | |
74 | + long contentLength = headers.getContentLength(); | |
75 | + HttpHeaders httpHeaders = new HttpHeaders(); | |
76 | + httpHeaders.putAll(headers); | |
77 | + if (contentLength > 0) { | |
78 | + httpHeaders.setContentLength(contentLength); | |
79 | + } else { | |
80 | + httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked"); | |
81 | + } | |
82 | + return httpHeaders; | |
83 | + } | |
84 | + | |
85 | + @Override | |
86 | + public Flux<DataBuffer> getBody() { | |
87 | + return outputMessage.getBody(); | |
88 | + } | |
89 | + }; | |
90 | + } | |
91 | + | |
92 | + @Override | |
93 | + public int getOrder() { | |
94 | + return CACHE_BODY_FILTER_ORDER; | |
95 | + } | |
96 | + | |
97 | +} | ... | ... |
src/main/java/com/diligrp/xtrade/gateway/filters/global/ResponseReadBodyGlobalFilter.java
0 → 100644
1 | +++ a/src/main/java/com/diligrp/xtrade/gateway/filters/global/ResponseReadBodyGlobalFilter.java | |
1 | +package com.diligrp.xtrade.gateway.filters.global; | |
2 | + | |
3 | +import lombok.extern.slf4j.Slf4j; | |
4 | +import org.reactivestreams.Publisher; | |
5 | +import org.springframework.cloud.gateway.filter.GatewayFilterChain; | |
6 | +import org.springframework.cloud.gateway.filter.GlobalFilter; | |
7 | +import org.springframework.cloud.gateway.filter.NettyWriteResponseFilter; | |
8 | +import org.springframework.core.Ordered; | |
9 | +import org.springframework.core.io.buffer.DataBuffer; | |
10 | +import org.springframework.http.HttpHeaders; | |
11 | +import org.springframework.http.HttpStatus; | |
12 | +import org.springframework.http.MediaType; | |
13 | +import org.springframework.http.ReactiveHttpOutputMessage; | |
14 | +import org.springframework.http.codec.HttpMessageReader; | |
15 | +import org.springframework.http.server.reactive.ServerHttpRequest; | |
16 | +import org.springframework.http.server.reactive.ServerHttpResponse; | |
17 | +import org.springframework.http.server.reactive.ServerHttpResponseDecorator; | |
18 | +import org.springframework.stereotype.Component; | |
19 | +import org.springframework.web.reactive.function.client.ClientResponse; | |
20 | +import org.springframework.web.reactive.function.server.HandlerStrategies; | |
21 | +import org.springframework.web.server.ServerWebExchange; | |
22 | +import reactor.core.publisher.Flux; | |
23 | +import reactor.core.publisher.Mono; | |
24 | + | |
25 | +import java.nio.charset.StandardCharsets; | |
26 | +import java.util.List; | |
27 | +import java.util.Objects; | |
28 | +import java.util.function.BiFunction; | |
29 | + | |
30 | +import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR; | |
31 | + | |
32 | +/** | |
33 | + * @auther: miaoguoxin | |
34 | + * @date: 2018/12/26 21:26 | |
35 | + * @description: 读取响应体过滤器 | |
36 | + */ | |
37 | +@Component | |
38 | +@Slf4j | |
39 | +public class ResponseReadBodyGlobalFilter implements GlobalFilter, Ordered { | |
40 | + | |
41 | + private final List<HttpMessageReader<?>> messageReaders; | |
42 | + | |
43 | + public ResponseReadBodyGlobalFilter() { | |
44 | + messageReaders = HandlerStrategies.withDefaults().messageReaders(); | |
45 | + } | |
46 | + | |
47 | + @Override | |
48 | + public Mono<Void> filter(ServerWebExchange exchange, | |
49 | + GatewayFilterChain chain) { | |
50 | + long startTime = System.currentTimeMillis(); | |
51 | + //重写原始响应 | |
52 | + BodyHandlerServerHttpResponseDecorator responseDecorator = new BodyHandlerServerHttpResponseDecorator( | |
53 | + initBodyHandler(exchange, startTime), exchange.getResponse()); | |
54 | + return chain.filter(exchange.mutate().response(responseDecorator).build()); | |
55 | + } | |
56 | + | |
57 | + @Override | |
58 | + public int getOrder() { | |
59 | + //需要在NettyWriteResponseFilter之前执行 | |
60 | + return NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER - 1; | |
61 | + } | |
62 | + | |
63 | + /** | |
64 | + * 响应body处理,添加响应的打印 | |
65 | + * | |
66 | + * @param exchange | |
67 | + * @param startTime | |
68 | + * @return | |
69 | + */ | |
70 | + private BodyHandlerServerHttpResponseDecorator.BodyHandlerFunction initBodyHandler(ServerWebExchange exchange, long startTime) { | |
71 | + return (resp, body) -> { | |
72 | + ServerHttpRequest request = exchange.getRequest(); | |
73 | + //拦截 | |
74 | + String trace = request.getHeaders().getFirst("trace"); | |
75 | + | |
76 | + MediaType originalResponseContentType = MediaType.parseMediaType(Objects.requireNonNull(exchange.getAttribute(ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR))); | |
77 | + HttpHeaders httpHeaders = new HttpHeaders(); | |
78 | + httpHeaders.setContentType(originalResponseContentType); | |
79 | + | |
80 | + ClientResponse clientResponse = this.prepareClientResponse( exchange.getResponse().getStatusCode(),body, httpHeaders); | |
81 | + Mono<String> bodyMono = clientResponse.bodyToMono(String.class); | |
82 | + return bodyMono.flatMap(respBody -> { | |
83 | + //打印返回响应日志 | |
84 | + log.debug("[Trace:{}]-gateway response:ct=[{}], status=[{}],headers=[{}],body=[{}]", | |
85 | + trace, System.currentTimeMillis() - startTime, resp.getStatusCode(), resp.getHeaders(), respBody); | |
86 | +// String errRespString = this.getErrRespString(resp.getStatusCode()); | |
87 | +// if (!Strings.isNullOrEmpty(errRespString)) { | |
88 | +// resp.setStatusCode(HttpStatus.OK); | |
89 | +// respBody = errRespString; | |
90 | +// } | |
91 | + HttpHeaders headers = resp.getHeaders(); | |
92 | + //特别声明:响应体改变必须设置contentLength,且长度要保持一致,(经过测试,如果过短则会截断,过长则会导致超时。) | |
93 | + headers.setContentLength(respBody.getBytes(StandardCharsets.UTF_8).length); | |
94 | + return resp.writeWith(Flux.just(respBody).map(bx -> resp.bufferFactory().wrap(bx.getBytes()))); | |
95 | + }).then(); | |
96 | + }; | |
97 | + } | |
98 | + | |
99 | + | |
100 | + | |
101 | + private ClientResponse prepareClientResponse(HttpStatus httpStatus, Publisher<? extends DataBuffer> body, | |
102 | + HttpHeaders httpHeaders) { | |
103 | + ClientResponse.Builder builder; | |
104 | + builder = ClientResponse.create(httpStatus, messageReaders); | |
105 | + return builder.headers(headers -> headers.putAll(httpHeaders)) | |
106 | + .body(Flux.from(body)).build(); | |
107 | + } | |
108 | + | |
109 | + | |
110 | + protected static class BodyHandlerServerHttpResponseDecorator extends ServerHttpResponseDecorator{ | |
111 | + | |
112 | + interface BodyHandlerFunction extends BiFunction<ServerHttpResponse, Publisher<? extends DataBuffer>, Mono<Void>> { | |
113 | + | |
114 | + } | |
115 | + | |
116 | + /** | |
117 | + * body 处理拦截器 | |
118 | + */ | |
119 | + private BodyHandlerServerHttpResponseDecorator.BodyHandlerFunction bodyHandler = initDefaultBodyHandler(); | |
120 | + /** | |
121 | + * 构造函数 | |
122 | + * | |
123 | + * @param bodyHandler | |
124 | + * @param delegate | |
125 | + */ | |
126 | + public BodyHandlerServerHttpResponseDecorator(BodyHandlerServerHttpResponseDecorator.BodyHandlerFunction bodyHandler, ServerHttpResponse delegate) { | |
127 | + super(delegate); | |
128 | + if (bodyHandler != null) { | |
129 | + this.bodyHandler = bodyHandler; | |
130 | + } | |
131 | + } | |
132 | + | |
133 | + @Override | |
134 | + public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) { | |
135 | + //body 拦截处理器处理响应 | |
136 | + return bodyHandler.apply(getDelegate(), body); | |
137 | + } | |
138 | + | |
139 | + @Override | |
140 | + public Mono<Void> writeAndFlushWith( | |
141 | + Publisher<? extends Publisher<? extends DataBuffer>> body) { | |
142 | + return writeWith(Flux.from(body).flatMapSequential(p -> p)); | |
143 | + } | |
144 | + | |
145 | + | |
146 | + /** | |
147 | + * 默认body拦截处理器 | |
148 | + * | |
149 | + * @return | |
150 | + */ | |
151 | + private BodyHandlerServerHttpResponseDecorator.BodyHandlerFunction initDefaultBodyHandler() { | |
152 | + return ReactiveHttpOutputMessage::writeWith; | |
153 | + } | |
154 | + } | |
155 | +} | ... | ... |
src/main/java/com/diligrp/xtrade/gateway/filters/web/ReactiveContextWebFilter.java
0 → 100644
1 | +++ a/src/main/java/com/diligrp/xtrade/gateway/filters/web/ReactiveContextWebFilter.java | |
1 | +package com.diligrp.xtrade.gateway.filters.web; | |
2 | + | |
3 | +import com.diligrp.xtrade.gateway.support.context.ReactiveExchangeContextHolder; | |
4 | +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; | |
5 | +import org.springframework.http.server.reactive.ServerHttpRequest; | |
6 | +import org.springframework.stereotype.Component; | |
7 | +import org.springframework.web.server.ServerWebExchange; | |
8 | +import org.springframework.web.server.WebFilter; | |
9 | +import org.springframework.web.server.WebFilterChain; | |
10 | +import reactor.core.publisher.Mono; | |
11 | +import reactor.core.publisher.SignalType; | |
12 | + | |
13 | +/** | |
14 | + * @Auther: miaoguoxin | |
15 | + * @Date: 2020/4/16 09:17 | |
16 | + * @Description: 操作reactive request和response上下文的过滤器 | |
17 | + */ | |
18 | +@Component | |
19 | +@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE) | |
20 | +public class ReactiveContextWebFilter implements WebFilter { | |
21 | + @Override | |
22 | + public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) { | |
23 | + ServerHttpRequest request = exchange.getRequest(); | |
24 | + String path = request.getURI().getRawPath(); | |
25 | + if (path.contains("favicon.ico")) { | |
26 | + return exchange.getResponse().setComplete(); | |
27 | + } | |
28 | + ReactiveExchangeContextHolder.put(exchange); | |
29 | + return chain.filter(exchange).doFinally(signalType -> { | |
30 | + ReactiveExchangeContextHolder.remove(); | |
31 | + }); | |
32 | + } | |
33 | +} | ... | ... |
src/main/java/com/diligrp/xtrade/gateway/predicates/ReadBodyRoutePredicateFactory.java
0 → 100644
1 | +++ a/src/main/java/com/diligrp/xtrade/gateway/predicates/ReadBodyRoutePredicateFactory.java | |
1 | +package com.diligrp.xtrade.gateway.predicates; | |
2 | + | |
3 | +import com.diligrp.xtrade.gateway.filters.global.CacheRequestBodyGlobalFilter; | |
4 | +import lombok.Data; | |
5 | +import lombok.extern.slf4j.Slf4j; | |
6 | +import org.springframework.cloud.gateway.handler.AsyncPredicate; | |
7 | +import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory; | |
8 | +import org.springframework.cloud.gateway.support.ServerWebExchangeUtils; | |
9 | +import org.springframework.http.HttpHeaders; | |
10 | +import org.springframework.http.codec.HttpMessageReader; | |
11 | +import org.springframework.http.server.reactive.ServerHttpRequest; | |
12 | +import org.springframework.stereotype.Component; | |
13 | +import org.springframework.web.reactive.function.server.HandlerStrategies; | |
14 | +import org.springframework.web.reactive.function.server.ServerRequest; | |
15 | +import org.springframework.web.server.ServerWebExchange; | |
16 | +import reactor.core.Disposable; | |
17 | +import reactor.core.publisher.Mono; | |
18 | + | |
19 | +import java.time.Duration; | |
20 | +import java.util.List; | |
21 | +import java.util.function.Consumer; | |
22 | +import java.util.function.Function; | |
23 | +import java.util.function.Predicate; | |
24 | + | |
25 | +import static com.diligrp.xtrade.gateway.common.constant.GatewayConst.CACHE_REQUEST_BODY_OBJECT_KEY; | |
26 | + | |
27 | + | |
28 | +/** | |
29 | + * @Auther: miaoguoxin | |
30 | + * @Date: 2019/3/25 0025 10:47 | |
31 | + * @Description: 缓存请求体数据predicate | |
32 | + * {@link CacheRequestBodyGlobalFilter} | |
33 | + */ | |
34 | +@Slf4j | |
35 | +//@Component | |
36 | +@Deprecated | |
37 | +public class ReadBodyRoutePredicateFactory extends AbstractRoutePredicateFactory<ReadBodyRoutePredicateFactory.Config> { | |
38 | + private static final List<HttpMessageReader<?>> messageReaders = HandlerStrategies | |
39 | + .withDefaults().messageReaders(); | |
40 | + | |
41 | + public ReadBodyRoutePredicateFactory() { | |
42 | + super(Config.class); | |
43 | + } | |
44 | + | |
45 | + | |
46 | + @Override | |
47 | + public Predicate<ServerWebExchange> apply(Config config) { | |
48 | + throw new UnsupportedOperationException( | |
49 | + "ReadBodyRoutePredicateFactory is only async."); | |
50 | + } | |
51 | + | |
52 | + public AsyncPredicate<ServerWebExchange> applyAsync(Config config) { | |
53 | + return exchange -> { | |
54 | + ServerHttpRequest request = exchange.getRequest(); | |
55 | + HttpHeaders headers = request.getHeaders(); | |
56 | + log.info("请求headers:{}",headers); | |
57 | + String rawPath = request.getURI().getRawPath(); | |
58 | + //兼容没有content-type的情况 | |
59 | + String contentTypeStr = headers.getFirst("Content-Type"); | |
60 | + if (contentTypeStr == null) { | |
61 | + log.warn("consider your Content-Type empty?---" + rawPath); | |
62 | + return Mono.just(Boolean.TRUE); | |
63 | + } | |
64 | + //文件上传直接跳过 | |
65 | + if (contentTypeStr.startsWith("multipart/form-data")) { | |
66 | + return Mono.just(Boolean.TRUE); | |
67 | + } | |
68 | + // String cachedBody = exchange.getAttribute(CACHE_REQUEST_BODY_OBJECT_KEY); | |
69 | +// BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody, String.class); | |
70 | +// //因为请求体可能是null,这里自定义个缓存输出 | |
71 | +// CustomCacheBodyOutputMessage outputMessage = new CustomCacheBodyOutputMessage(exchange, headers); | |
72 | + return ServerWebExchangeUtils.cacheRequestBodyAndRequest(exchange, | |
73 | + (serverHttpRequest) -> ServerRequest | |
74 | + .create(exchange.mutate().request(serverHttpRequest) | |
75 | + .build(), messageReaders) | |
76 | + .bodyToMono(String.class) | |
77 | + .doOnNext(new Consumer<String>() { | |
78 | + @Override | |
79 | + public void accept(String objectValue) { | |
80 | + exchange.getAttributes().put(CACHE_REQUEST_BODY_OBJECT_KEY, objectValue); | |
81 | + } | |
82 | + }) | |
83 | + .map(objectValue -> Boolean.TRUE)); | |
84 | + }; | |
85 | + } | |
86 | + | |
87 | + @Data | |
88 | + public static class Config { | |
89 | + //需要跳过的uri | |
90 | + private String shouldSkipUri; | |
91 | + | |
92 | + } | |
93 | + | |
94 | +} | ... | ... |
src/main/java/com/diligrp/xtrade/gateway/repository/GatewayRepository.java
0 → 100644
1 | +++ a/src/main/java/com/diligrp/xtrade/gateway/repository/GatewayRepository.java | |
1 | +package com.diligrp.xtrade.gateway.repository; | |
2 | + | |
3 | +import com.diligrp.xtrade.gateway.common.constant.GatewayAttrType; | |
4 | +import com.diligrp.xtrade.gateway.exception.GatewayServiceException; | |
5 | +import com.diligrp.xtrade.gateway.repository.dao.AttrConfigDao; | |
6 | +import com.diligrp.xtrade.gateway.repository.dao.RouteDao; | |
7 | +import com.diligrp.xtrade.gateway.repository.entity.GatewayAttrConfig; | |
8 | +import com.diligrp.xtrade.gateway.repository.entity.GatewayConfig; | |
9 | +import com.diligrp.xtrade.shared.util.JsonUtils; | |
10 | +import com.diligrp.xtrade.shared.util.ReflectUtils; | |
11 | +import com.fasterxml.jackson.core.type.TypeReference; | |
12 | +import com.google.common.base.Strings; | |
13 | +import org.springframework.beans.factory.annotation.Autowired; | |
14 | +import org.springframework.cloud.gateway.filter.FilterDefinition; | |
15 | +import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition; | |
16 | +import org.springframework.cloud.gateway.route.RouteDefinition; | |
17 | +import org.springframework.stereotype.Repository; | |
18 | +import org.springframework.util.CollectionUtils; | |
19 | +import org.springframework.web.util.UriComponentsBuilder; | |
20 | +import reactor.core.publisher.Mono; | |
21 | + | |
22 | +import java.lang.reflect.InvocationTargetException; | |
23 | +import java.net.URI; | |
24 | +import java.net.URISyntaxException; | |
25 | +import java.util.ArrayList; | |
26 | +import java.util.Comparator; | |
27 | +import java.util.LinkedHashMap; | |
28 | +import java.util.List; | |
29 | +import java.util.Map; | |
30 | +import java.util.stream.Collectors; | |
31 | + | |
32 | +/** | |
33 | + * @Auther: miaoguoxin | |
34 | + * @Date: 2020/4/10 14:46 | |
35 | + */ | |
36 | +@Repository | |
37 | +public class GatewayRepository implements IGatewayRepository { | |
38 | + @Autowired | |
39 | + private RouteDao routeDao; | |
40 | + @Autowired | |
41 | + private AttrConfigDao attrConfigDao; | |
42 | + | |
43 | + @Override | |
44 | + public List<RouteDefinition> getAll() { | |
45 | + List<GatewayConfig> gatewayConfigs = routeDao.findAllForLoad(); | |
46 | + List<GatewayAttrConfig> allAttrConfigs = attrConfigDao.findAllForLoad(); | |
47 | + Map<String , List<GatewayAttrConfig>> attrMap = allAttrConfigs.stream() | |
48 | + .collect(Collectors.groupingBy(GatewayAttrConfig::getServiceId)); | |
49 | + gatewayConfigs.forEach(config->{ | |
50 | + List<GatewayAttrConfig> attrConfigs = attrMap.get(config.getServiceId()); | |
51 | + if (!CollectionUtils.isEmpty(attrConfigs)){ | |
52 | + attrConfigs.sort(Comparator.comparing(GatewayAttrConfig::getSortOrder)); | |
53 | + config.setAttrConfigs(attrConfigs); | |
54 | + } | |
55 | + }); | |
56 | + return gatewayConfigs.stream() | |
57 | + .map(this::generateRouteDefinition) | |
58 | + .collect(Collectors.toList()); | |
59 | + } | |
60 | + | |
61 | + private RouteDefinition generateRouteDefinition(GatewayConfig gatewayConfig) { | |
62 | + RouteDefinition definition = new RouteDefinition(); | |
63 | + definition.setId(gatewayConfig.getServiceId()); | |
64 | + try { | |
65 | + //必须要加上协议头才能访问 | |
66 | + URI uri; | |
67 | + if (gatewayConfig.getUrl().startsWith("lb")) { | |
68 | + uri = new URI(gatewayConfig.getUrl()); | |
69 | + } else { | |
70 | + uri = UriComponentsBuilder.fromHttpUrl(gatewayConfig.getUrl()).build().toUri(); | |
71 | + } | |
72 | + definition.setUri(uri); | |
73 | + } catch (URISyntaxException e) { | |
74 | + throw new GatewayServiceException("get url failed",e); | |
75 | + } | |
76 | + //设置predicate和filter | |
77 | + this.setPredicatesAndFilters(gatewayConfig, definition); | |
78 | + return definition; | |
79 | + } | |
80 | + | |
81 | + private void setPredicatesAndFilters(GatewayConfig gatewayConfig, RouteDefinition definition) { | |
82 | + List<GatewayAttrConfig> gatewayAttrConfigs = gatewayConfig.getAttrConfigs(); | |
83 | + if (gatewayAttrConfigs.isEmpty()) { | |
84 | + throw new GatewayServiceException("gateway attrs can not allow empty, please check"); | |
85 | + } | |
86 | + List<PredicateDefinition> predicateDefinitions = new ArrayList<>(); | |
87 | + List<FilterDefinition> filterDefinitions = new ArrayList<>(); | |
88 | + for (GatewayAttrConfig gatewayAttrConfig : gatewayAttrConfigs) { | |
89 | + if (GatewayAttrType.PREDICATE.getValue() == gatewayAttrConfig.getType()) { | |
90 | + predicateDefinitions.add(this.generatePredicateOrFilter(gatewayAttrConfig,PredicateDefinition.class)); | |
91 | + } else { | |
92 | + filterDefinitions.add(this.generatePredicateOrFilter(gatewayAttrConfig,FilterDefinition.class)); | |
93 | + } | |
94 | + } | |
95 | + definition.setPredicates(predicateDefinitions); | |
96 | + definition.setFilters(filterDefinitions); | |
97 | + } | |
98 | + | |
99 | + | |
100 | + private <T> T generatePredicateOrFilter(GatewayAttrConfig gatewayAttrConfig,Class<T> clazz) { | |
101 | + T target; | |
102 | + try { | |
103 | + target = clazz.getDeclaredConstructor().newInstance(); | |
104 | + //设置predicate或者filter的名称 | |
105 | + ReflectUtils.invokeMethod( | |
106 | + target, | |
107 | + "setName", | |
108 | + new Class[]{String.class}, | |
109 | + new Object[]{gatewayAttrConfig.getAttrName()}); | |
110 | + //设置predicate或者filter的参数 | |
111 | + if (!Strings.isNullOrEmpty(gatewayAttrConfig.getAttrArgs())) { | |
112 | + Map<String, String> args = JsonUtils.fromJsonString( | |
113 | + gatewayAttrConfig.getAttrArgs(), | |
114 | + new TypeReference<LinkedHashMap<String, String>>() {}); | |
115 | + ReflectUtils.invokeMethod( | |
116 | + target, | |
117 | + "setArgs", | |
118 | + new Class[]{Map.class}, | |
119 | + new Object[]{args} | |
120 | + ); | |
121 | + } | |
122 | + } catch (IllegalAccessException | |
123 | + | InvocationTargetException | |
124 | + | InstantiationException | |
125 | + |NoSuchMethodException e) { | |
126 | + throw new GatewayServiceException("init gateway resource failed", | |
127 | + e, | |
128 | + new Object[]{JsonUtils.toJsonString(gatewayAttrConfig)}); | |
129 | + } | |
130 | + return target; | |
131 | + } | |
132 | +} | ... | ... |
src/main/java/com/diligrp/xtrade/gateway/repository/IGatewayRepository.java
0 → 100644
1 | +++ a/src/main/java/com/diligrp/xtrade/gateway/repository/IGatewayRepository.java | |
1 | +package com.diligrp.xtrade.gateway.repository; | |
2 | + | |
3 | +import org.springframework.cloud.gateway.route.RouteDefinition; | |
4 | + | |
5 | +import java.util.List; | |
6 | + | |
7 | +/** | |
8 | + * @Auther: miaoguoxin | |
9 | + * @Date: 2020/4/10 14:39 | |
10 | + */ | |
11 | +public interface IGatewayRepository { | |
12 | + /** | |
13 | + * 获取所有路由资源配置 | |
14 | + * @param | |
15 | + * @return | |
16 | + * @author miaoguoxin | |
17 | + * @date 2020/4/10 | |
18 | + */ | |
19 | + List<RouteDefinition> getAll(); | |
20 | +} | ... | ... |
src/main/java/com/diligrp/xtrade/gateway/repository/dao/AttrConfigDao.java
0 → 100644
1 | +++ a/src/main/java/com/diligrp/xtrade/gateway/repository/dao/AttrConfigDao.java | |
1 | +package com.diligrp.xtrade.gateway.repository.dao; | |
2 | + | |
3 | +import com.diligrp.xtrade.gateway.repository.entity.GatewayAttrConfig; | |
4 | + | |
5 | +import java.util.List; | |
6 | + | |
7 | +/** | |
8 | + * @Auther: miaoguoxin | |
9 | + * @Date: 2020/4/10 15:06 | |
10 | + * @Description: t_attr_config mapper | |
11 | + */ | |
12 | +public interface AttrConfigDao { | |
13 | + | |
14 | + List<GatewayAttrConfig> findAllForLoad(); | |
15 | +} | ... | ... |
src/main/java/com/diligrp/xtrade/gateway/repository/dao/RouteDao.java
0 → 100644
1 | +++ a/src/main/java/com/diligrp/xtrade/gateway/repository/dao/RouteDao.java | |
1 | +package com.diligrp.xtrade.gateway.repository.dao; | |
2 | + | |
3 | +import com.diligrp.xtrade.gateway.repository.entity.GatewayConfig; | |
4 | + | |
5 | +import java.util.List; | |
6 | + | |
7 | +/** | |
8 | + * @Auther: miaoguoxin | |
9 | + * @Date: 2020/4/10 14:51 | |
10 | + * @Description: t_router mapper | |
11 | + */ | |
12 | +public interface RouteDao { | |
13 | + | |
14 | + List<GatewayConfig> findAllForLoad(); | |
15 | + | |
16 | +} | ... | ... |
src/main/java/com/diligrp/xtrade/gateway/repository/entity/GatewayAttrConfig.java
0 → 100644
1 | +++ a/src/main/java/com/diligrp/xtrade/gateway/repository/entity/GatewayAttrConfig.java | |
1 | +package com.diligrp.xtrade.gateway.repository.entity; | |
2 | + | |
3 | +import com.diligrp.xtrade.gateway.common.constant.GatewayAttrType; | |
4 | +import com.diligrp.xtrade.shared.domain.BaseDo; | |
5 | +import lombok.Data; | |
6 | +import lombok.EqualsAndHashCode; | |
7 | + | |
8 | +/** | |
9 | + * @Auther: miaoguoxin | |
10 | + * @Date: 2018/12/12 0012 17:43 | |
11 | + * @Description: 网关断言和过滤器配置实体 | |
12 | + */ | |
13 | +@EqualsAndHashCode(callSuper = true) | |
14 | +@Data | |
15 | +public class GatewayAttrConfig extends BaseDo{ | |
16 | + /**服务id名称*/ | |
17 | + private String serviceId; | |
18 | + /**类型 {@link GatewayAttrType}*/ | |
19 | + private Integer type; | |
20 | + /**predicate或者filter的名称*/ | |
21 | + private String attrName; | |
22 | + /** predicate或者filter所需的参数 json格式*/ | |
23 | + private String attrArgs; | |
24 | + /**描述*/ | |
25 | + private String description; | |
26 | + /**排序*/ | |
27 | + private Integer sortOrder; | |
28 | + /**1:删除 0:正常*/ | |
29 | + private Integer isDel; | |
30 | +} | ... | ... |
src/main/java/com/diligrp/xtrade/gateway/repository/entity/GatewayConfig.java
0 → 100644
1 | +++ a/src/main/java/com/diligrp/xtrade/gateway/repository/entity/GatewayConfig.java | |
1 | +package com.diligrp.xtrade.gateway.repository.entity; | |
2 | + | |
3 | +import com.diligrp.xtrade.shared.domain.BaseDo; | |
4 | +import lombok.Data; | |
5 | +import lombok.EqualsAndHashCode; | |
6 | + | |
7 | +import java.util.List; | |
8 | + | |
9 | +/** | |
10 | + * @Auther: miaoguoxin | |
11 | + * @Date: 2018/12/11 0011 14:53 | |
12 | + * @Description: 路由配置实体 | |
13 | + */ | |
14 | +@EqualsAndHashCode(callSuper = true) | |
15 | +@Data | |
16 | +public class GatewayConfig extends BaseDo { | |
17 | + /**服务id名称*/ | |
18 | + private String serviceId; | |
19 | + /**路由url*/ | |
20 | + private String url; | |
21 | + /** 1:删除 0:正常*/ | |
22 | + private Integer isDel; | |
23 | + /**描述*/ | |
24 | + private String description; | |
25 | + /**包含predicate和filter*/ | |
26 | + private List<GatewayAttrConfig> attrConfigs; | |
27 | +} | ... | ... |
src/main/java/com/diligrp/xtrade/gateway/route/DynamicRouteLoaderIntf.java
0 → 100644
1 | +++ a/src/main/java/com/diligrp/xtrade/gateway/route/DynamicRouteLoaderIntf.java | |
1 | +package com.diligrp.xtrade.gateway.route; | |
2 | + | |
3 | +/** | |
4 | + * @Auther: miaoguoxin | |
5 | + * @Date: 2020/4/2 14:01 | |
6 | + */ | |
7 | +public interface DynamicRouteLoaderIntf { | |
8 | + | |
9 | + void init(); | |
10 | + | |
11 | + void refreshApis(); | |
12 | + | |
13 | + void refreshRoutes(); | |
14 | + | |
15 | +} | ... | ... |
src/main/java/com/diligrp/xtrade/gateway/route/GatewayResourcesLoader.java
0 → 100644
1 | +++ a/src/main/java/com/diligrp/xtrade/gateway/route/GatewayResourcesLoader.java | |
1 | +package com.diligrp.xtrade.gateway.route; | |
2 | + | |
3 | +import org.springframework.beans.factory.annotation.Autowired; | |
4 | +import org.springframework.boot.ApplicationArguments; | |
5 | +import org.springframework.boot.ApplicationRunner; | |
6 | +import org.springframework.stereotype.Component; | |
7 | + | |
8 | +/** | |
9 | + * @Auther: miaoguoxin | |
10 | + * @Date: 2020/4/2 14:00 | |
11 | + * @Description: 初始化网关所需资源的入口 | |
12 | + */ | |
13 | +@Component | |
14 | +public class GatewayResourcesLoader implements ApplicationRunner { | |
15 | + @Autowired | |
16 | + private DynamicRouteLoaderIntf dynamicRouteLoader; | |
17 | + | |
18 | + @Override | |
19 | + public void run(ApplicationArguments args) throws Exception { | |
20 | + dynamicRouteLoader.init(); | |
21 | + } | |
22 | +} | ... | ... |
src/main/java/com/diligrp/xtrade/gateway/route/impl/CloudRouteResourceLoader.java
0 → 100644
1 | +++ a/src/main/java/com/diligrp/xtrade/gateway/route/impl/CloudRouteResourceLoader.java | |
1 | +package com.diligrp.xtrade.gateway.route.impl; | |
2 | + | |
3 | +import com.alibaba.nacos.api.config.ConfigService; | |
4 | +import com.alibaba.nacos.api.config.listener.Listener; | |
5 | +import com.alibaba.nacos.api.exception.NacosException; | |
6 | +import com.diligrp.xtrade.gateway.config.GatewayResourceLoaderConfiguration; | |
7 | +import com.diligrp.xtrade.gateway.exception.GatewayServiceException; | |
8 | +import com.diligrp.xtrade.gateway.route.DynamicRouteLoaderIntf; | |
9 | +import lombok.extern.slf4j.Slf4j; | |
10 | +import org.springframework.beans.factory.annotation.Autowired; | |
11 | +import org.springframework.beans.factory.annotation.Value; | |
12 | +import org.springframework.cloud.gateway.config.GatewayProperties; | |
13 | +import org.springframework.cloud.gateway.event.RefreshRoutesEvent; | |
14 | +import org.springframework.cloud.gateway.route.RouteDefinition; | |
15 | +import org.springframework.cloud.gateway.route.RouteDefinitionWriter; | |
16 | +import org.springframework.context.ApplicationContext; | |
17 | +import reactor.core.publisher.Mono; | |
18 | + | |
19 | +import javax.annotation.Resource; | |
20 | +import java.util.List; | |
21 | +import java.util.Timer; | |
22 | +import java.util.TimerTask; | |
23 | +import java.util.concurrent.Executor; | |
24 | +import java.util.concurrent.atomic.AtomicBoolean; | |
25 | + | |
26 | +/** | |
27 | + * @Auther: miaoguoxin | |
28 | + * @Date: 2020/4/2 14:02 | |
29 | + * @Description: 从云端配置中心加载 | |
30 | + * {@link GatewayResourceLoaderConfiguration} 配置 | |
31 | + */ | |
32 | +@Slf4j | |
33 | +public class CloudRouteResourceLoader implements DynamicRouteLoaderIntf { | |
34 | + /**标记当前gatewayproties的hash值,用于判断配置是否更新过*/ | |
35 | + private static int gatewayPropertiesHash; | |
36 | + /**防止重复初始化的标记*/ | |
37 | + private static AtomicBoolean hasInit = new AtomicBoolean(false); | |
38 | + | |
39 | + private final static String GROUP = "DEFAULT_GROUP"; | |
40 | + private final static String ROUTE_CONFIG_NAME = "route.properties"; | |
41 | + | |
42 | + | |
43 | + @Value("${spring.application.name}") | |
44 | + private String applicationName; | |
45 | + @Value("${spring.profiles.active}") | |
46 | + private String activeProfile; | |
47 | + @Value("${spring.cloud.nacos.config.file-extension}") | |
48 | + private String fileType; | |
49 | + @Autowired | |
50 | + private ApplicationContext applicationContext; | |
51 | + @Resource | |
52 | + private RouteDefinitionWriter routeDefinitionWriter; | |
53 | + @Autowired | |
54 | + private ConfigService configService; | |
55 | + @Autowired | |
56 | + private GatewayProperties gatewayProperties; | |
57 | + | |
58 | + @Override | |
59 | + public void init() { | |
60 | + if (hasInit.compareAndSet(false, true)) { | |
61 | + this.loadRoutes(); | |
62 | + //用来判断routes是否改变过,避免无效刷新 | |
63 | + gatewayPropertiesHash = gatewayProperties.getRoutes().hashCode(); | |
64 | + } | |
65 | + } | |
66 | + | |
67 | + @Override | |
68 | + public void refreshApis() { | |
69 | + | |
70 | + } | |
71 | + | |
72 | + @Override | |
73 | + public void refreshRoutes() { | |
74 | + this.notifyRefreshRoutes(); | |
75 | + } | |
76 | + | |
77 | + | |
78 | + /** | |
79 | + * 通过nacos配置中心监听配置变化, | |
80 | + * 完成路由资源装载 | |
81 | + * @author miaoguoxin | |
82 | + * @date 2020/4/10 | |
83 | + */ | |
84 | + private void loadRoutes() { | |
85 | + //只有放到标准的配置文件下,才能够成功刷新配置 | |
86 | + String configFile = String.format("%s-%s.%s", applicationName, activeProfile, fileType); | |
87 | + try { | |
88 | + configService.getConfigAndSignListener( | |
89 | + ROUTE_CONFIG_NAME, | |
90 | + GROUP, | |
91 | + 5000, | |
92 | + new Listener() { | |
93 | + @Override | |
94 | + public void receiveConfigInfo(String configInfo) { | |
95 | + Timer timer = new Timer(true); | |
96 | + TimerTask task = new TimerTask() { | |
97 | + @Override | |
98 | + public void run() { | |
99 | + notifyRefreshRoutes(); | |
100 | + } | |
101 | + }; | |
102 | + //由于属性刷新有延迟,这里延长点时间 | |
103 | + timer.schedule(task, 5000); | |
104 | + } | |
105 | + | |
106 | + @Override | |
107 | + public Executor getExecutor() { | |
108 | + return null; | |
109 | + } | |
110 | + }); | |
111 | + } catch (NacosException e) { | |
112 | + throw new GatewayServiceException("load route resource failed", e); | |
113 | + } | |
114 | + } | |
115 | + | |
116 | + | |
117 | + private void notifyRefreshRoutes() { | |
118 | + synchronized (CloudRouteResourceLoader.this){ | |
119 | + if (gatewayPropertiesHash == gatewayProperties.getRoutes().hashCode()) { | |
120 | + return; | |
121 | + } | |
122 | + List<RouteDefinition> routes = gatewayProperties.getRoutes(); | |
123 | + routes.forEach(definition -> | |
124 | + routeDefinitionWriter.save(Mono.just(definition)).subscribe()); | |
125 | + applicationContext.publishEvent(new RefreshRoutesEvent(this)); | |
126 | + gatewayPropertiesHash = gatewayProperties.getRoutes().hashCode(); | |
127 | + log.info("refresh route success"); | |
128 | + } | |
129 | + } | |
130 | + | |
131 | +} | ... | ... |
src/main/java/com/diligrp/xtrade/gateway/route/impl/MysqlRouteResourceLoader.java
0 → 100644
1 | +++ a/src/main/java/com/diligrp/xtrade/gateway/route/impl/MysqlRouteResourceLoader.java | |
1 | +package com.diligrp.xtrade.gateway.route.impl; | |
2 | + | |
3 | +import com.diligrp.xtrade.gateway.config.GatewayResourceLoaderConfiguration; | |
4 | +import com.diligrp.xtrade.gateway.repository.IGatewayRepository; | |
5 | +import com.diligrp.xtrade.gateway.route.DynamicRouteLoaderIntf; | |
6 | +import lombok.extern.slf4j.Slf4j; | |
7 | +import org.springframework.beans.factory.annotation.Autowired; | |
8 | +import org.springframework.cloud.gateway.event.RefreshRoutesEvent; | |
9 | +import org.springframework.cloud.gateway.route.RouteDefinition; | |
10 | +import org.springframework.cloud.gateway.route.RouteDefinitionWriter; | |
11 | +import org.springframework.context.ApplicationContext; | |
12 | +import reactor.core.publisher.Mono; | |
13 | + | |
14 | +import javax.annotation.Resource; | |
15 | +import java.util.List; | |
16 | + | |
17 | +/** | |
18 | + * @Auther: miaoguoxin | |
19 | + * @Date: 2020/4/10 13:18 | |
20 | + * @Description: 从数据库加载 | |
21 | + * 通过 {@link GatewayResourceLoaderConfiguration} 配置 | |
22 | + */ | |
23 | +@Slf4j | |
24 | +public class MysqlRouteResourceLoader implements DynamicRouteLoaderIntf { | |
25 | + @Autowired | |
26 | + private ApplicationContext applicationContext; | |
27 | + @Autowired | |
28 | + private IGatewayRepository gatewayRepository; | |
29 | + @Resource | |
30 | + private RouteDefinitionWriter routeDefinitionWriter; | |
31 | + | |
32 | + | |
33 | + public MysqlRouteResourceLoader() { | |
34 | + | |
35 | + } | |
36 | + | |
37 | + @Override | |
38 | + public void init() { | |
39 | + this.refreshRoutes(); | |
40 | + } | |
41 | + | |
42 | + @Override | |
43 | + public void refreshApis(){ | |
44 | + | |
45 | + } | |
46 | + | |
47 | + @Override | |
48 | + public void refreshRoutes() { | |
49 | + List<RouteDefinition> routeDefinitions = gatewayRepository.getAll(); | |
50 | + routeDefinitions.forEach(definition -> | |
51 | + routeDefinitionWriter.save(Mono.just(definition)).subscribe()); | |
52 | + applicationContext.publishEvent(new RefreshRoutesEvent(this)); | |
53 | + log.info("route resources load success"); | |
54 | + } | |
55 | +} | ... | ... |
src/main/java/com/diligrp/xtrade/gateway/support/context/ReactiveExchangeContextHolder.java
0 → 100644
1 | +++ a/src/main/java/com/diligrp/xtrade/gateway/support/context/ReactiveExchangeContextHolder.java | |
1 | +package com.diligrp.xtrade.gateway.support.context; | |
2 | + | |
3 | +import org.springframework.web.server.ServerWebExchange; | |
4 | + | |
5 | +/** | |
6 | + * @Auther: miaoguoxin | |
7 | + * @Date: 2020/4/16 09:09 | |
8 | + * @Description: 保存 ServerWebExchange 变量 | |
9 | + */ | |
10 | +public class ReactiveExchangeContextHolder { | |
11 | + /**让该变量可以在父子线程间传递*/ | |
12 | + private static final ThreadLocal<ServerWebExchange> REQUEST_THREAD_LOCAL = new InheritableThreadLocal<>(); | |
13 | + | |
14 | + public static void put(ServerWebExchange exchange){ | |
15 | + REQUEST_THREAD_LOCAL.set(exchange); | |
16 | + } | |
17 | + | |
18 | + public static ServerWebExchange get(){ | |
19 | + return REQUEST_THREAD_LOCAL.get(); | |
20 | + } | |
21 | + | |
22 | + public static void remove(){ | |
23 | + REQUEST_THREAD_LOCAL.remove(); | |
24 | + } | |
25 | +} | ... | ... |
src/main/java/com/diligrp/xtrade/gateway/support/context/ReactiveRequestBeanProcessor.java
0 → 100644
1 | +++ a/src/main/java/com/diligrp/xtrade/gateway/support/context/ReactiveRequestBeanProcessor.java | |
1 | +package com.diligrp.xtrade.gateway.support.context; | |
2 | + | |
3 | +import org.springframework.beans.BeansException; | |
4 | +import org.springframework.beans.factory.ObjectFactory; | |
5 | +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; | |
6 | +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; | |
7 | +import org.springframework.http.server.reactive.ServerHttpRequest; | |
8 | +import org.springframework.http.server.reactive.ServerHttpResponse; | |
9 | +import org.springframework.stereotype.Component; | |
10 | + | |
11 | +import java.io.Serializable; | |
12 | + | |
13 | +/** | |
14 | + * @Auther: miaoguoxin | |
15 | + * @Date: 2020/4/16 09:13 | |
16 | + * @Description: | |
17 | + */ | |
18 | +@Component | |
19 | +public class ReactiveRequestBeanProcessor implements BeanFactoryPostProcessor { | |
20 | + | |
21 | + @Override | |
22 | + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { | |
23 | + beanFactory.registerResolvableDependency(ServerHttpRequest.class,new ReactiveRequestObjectFactory()); | |
24 | + beanFactory.registerResolvableDependency(ServerHttpResponse.class,new ReactiveResponseObjectFactory()); | |
25 | + } | |
26 | + | |
27 | + private static class ReactiveRequestObjectFactory implements ObjectFactory<ServerHttpRequest>, Serializable { | |
28 | + | |
29 | + @Override | |
30 | + public ServerHttpRequest getObject() throws BeansException { | |
31 | + return ReactiveExchangeContextHolder.get().getRequest(); | |
32 | + } | |
33 | + } | |
34 | + | |
35 | + private static class ReactiveResponseObjectFactory implements ObjectFactory<ServerHttpResponse>, Serializable { | |
36 | + | |
37 | + @Override | |
38 | + public ServerHttpResponse getObject() throws BeansException { | |
39 | + return ReactiveExchangeContextHolder.get().getResponse(); | |
40 | + } | |
41 | + } | |
42 | +} | ... | ... |
src/main/java/com/diligrp/xtrade/gateway/support/dispatch/DispatchContext.java
0 → 100644
1 | +++ a/src/main/java/com/diligrp/xtrade/gateway/support/dispatch/DispatchContext.java | |
1 | +package com.diligrp.xtrade.gateway.support.dispatch; | |
2 | + | |
3 | +import lombok.Getter; | |
4 | +import org.springframework.http.server.reactive.ServerHttpRequest; | |
5 | +import org.springframework.http.server.reactive.ServerHttpResponse; | |
6 | + | |
7 | +/** | |
8 | + * @Auther: miaoguoxin | |
9 | + * @Date: 2020/4/16 09:20 | |
10 | + */ | |
11 | +@Getter | |
12 | +public class DispatchContext<T> { | |
13 | + private ServerHttpRequest request; | |
14 | + private ServerHttpResponse response; | |
15 | + /**请求参数*/ | |
16 | + private T param; | |
17 | + | |
18 | + public DispatchContext(ServerHttpRequest request, ServerHttpResponse response, T param) { | |
19 | + this.request = request; | |
20 | + this.response = response; | |
21 | + this.param = param; | |
22 | + } | |
23 | + | |
24 | +} | ... | ... |
src/main/java/com/diligrp/xtrade/gateway/support/dispatch/MappingMetaInfo.java
0 → 100644
1 | +++ a/src/main/java/com/diligrp/xtrade/gateway/support/dispatch/MappingMetaInfo.java | |
1 | +package com.diligrp.xtrade.gateway.support.dispatch; | |
2 | + | |
3 | +import lombok.Getter; | |
4 | +import org.springframework.validation.annotation.Validated; | |
5 | + | |
6 | +import java.lang.reflect.Method; | |
7 | + | |
8 | +/** | |
9 | + * @Auther: miaoguoxin | |
10 | + * @Date: 2020/4/15 09:57 | |
11 | + */ | |
12 | +@Getter | |
13 | +public class MappingMetaInfo { | |
14 | + /**方法所属的bean class*/ | |
15 | + private Class<?> targetClass; | |
16 | + /** 需要执行的方法 */ | |
17 | + private Method targetMethod; | |
18 | + /***/ | |
19 | + private Validated validated; | |
20 | + /**泛化参数类型 {@link DispatchContext#getParam()}*/ | |
21 | + private Class<?> parameterType; | |
22 | + | |
23 | + public MappingMetaInfo(Class<?> targetBean, Method targetMethod) { | |
24 | + this.targetClass = targetBean; | |
25 | + this.targetMethod = targetMethod; | |
26 | + } | |
27 | + | |
28 | + public MappingMetaInfo(Class<?> targetClass, Method targetMethod, Validated validated, Class<?> parameterType) { | |
29 | + this.targetClass = targetClass; | |
30 | + this.targetMethod = targetMethod; | |
31 | + this.validated = validated; | |
32 | + this.parameterType = parameterType; | |
33 | + } | |
34 | + | |
35 | + @Override | |
36 | + public String toString() { | |
37 | + return "MappingInfo{" + | |
38 | + "targetBean=" + targetClass.getName() + | |
39 | + ", targetMethod=" + targetMethod.getName() + | |
40 | + '}'; | |
41 | + } | |
42 | +} | ... | ... |
src/main/java/com/diligrp/xtrade/gateway/support/dispatch/MappingRegister.java
0 → 100644
1 | +++ a/src/main/java/com/diligrp/xtrade/gateway/support/dispatch/MappingRegister.java | |
1 | +package com.diligrp.xtrade.gateway.support.dispatch; | |
2 | + | |
3 | +import com.diligrp.xtrade.gateway.XtradeGatewayApplication; | |
4 | +import com.diligrp.xtrade.gateway.common.annotation.DispatchMapping; | |
5 | +import com.diligrp.xtrade.gateway.common.constant.GatewayConst; | |
6 | +import com.diligrp.xtrade.gateway.common.utils.PathUtils; | |
7 | +import com.diligrp.xtrade.gateway.config.property.DispatchProperties; | |
8 | +import lombok.extern.slf4j.Slf4j; | |
9 | +import org.reflections.Reflections; | |
10 | +import org.reflections.scanners.FieldAnnotationsScanner; | |
11 | +import org.reflections.scanners.MethodAnnotationsScanner; | |
12 | +import org.reflections.scanners.SubTypesScanner; | |
13 | +import org.reflections.util.ConfigurationBuilder; | |
14 | +import org.springframework.beans.factory.annotation.Autowired; | |
15 | +import org.springframework.boot.SpringApplication; | |
16 | +import org.springframework.context.ApplicationContext; | |
17 | +import org.springframework.context.ApplicationListener; | |
18 | +import org.springframework.context.event.ContextRefreshedEvent; | |
19 | +import org.springframework.stereotype.Component; | |
20 | +import org.springframework.validation.annotation.Validated; | |
21 | + | |
22 | +import java.lang.annotation.Annotation; | |
23 | +import java.lang.reflect.Method; | |
24 | +import java.lang.reflect.ParameterizedType; | |
25 | +import java.lang.reflect.Type; | |
26 | +import java.util.Set; | |
27 | + | |
28 | +/** | |
29 | + * @Auther: miaoguoxin | |
30 | + * @Date: 2020/4/15 09:29 | |
31 | + * @Description: 注册聚合服务方法映射 | |
32 | + */ | |
33 | +@Component | |
34 | +@Slf4j | |
35 | +public class MappingRegister implements ApplicationListener<ContextRefreshedEvent> { | |
36 | + @Autowired | |
37 | + private ApplicationContext applicationContext; | |
38 | + @Autowired | |
39 | + private DispatchProperties dispatchProperties; | |
40 | + | |
41 | + @Override | |
42 | + public void onApplicationEvent(ContextRefreshedEvent event) { | |
43 | + if (GatewayConst.HAS_INIT_MAPPING.compareAndSet(false, true)) { | |
44 | + String[] scanPackages = this.getScanPackages(); | |
45 | + Reflections reflections = new Reflections(new ConfigurationBuilder() | |
46 | + .forPackages(scanPackages) | |
47 | + .addScanners(new SubTypesScanner()) | |
48 | + .addScanners(new MethodAnnotationsScanner()) | |
49 | + .addScanners(new FieldAnnotationsScanner())); | |
50 | + try { | |
51 | + | |
52 | + Set<Method> methodMappings = reflections.getMethodsAnnotatedWith(DispatchMapping.class); | |
53 | + for (Method method : methodMappings) { | |
54 | + //只扫描参数为DispatchContext的方法 | |
55 | + Class<?>[] parameterTypes = method.getParameterTypes(); | |
56 | + if (parameterTypes.length != 1 || !DispatchContext.class.isAssignableFrom(parameterTypes[0])) { | |
57 | + continue; | |
58 | + } | |
59 | + DispatchMapping methodAnnotation = method.getAnnotation(DispatchMapping.class); | |
60 | + String methodPath = methodAnnotation.value(); | |
61 | + String classUriPath = ""; | |
62 | + //这里meta信息只传入bean的class,在dispatch中再获取真实的bean,为了能够多例bean使用 | |
63 | + Class<?> declaringClass = method.getDeclaringClass(); | |
64 | + if (declaringClass.isAnnotationPresent(DispatchMapping.class)) { | |
65 | + DispatchMapping classAnnotation = declaringClass.getAnnotation(DispatchMapping.class); | |
66 | + classUriPath = classAnnotation.value(); | |
67 | + } | |
68 | + String uri = PathUtils.concatUri(classUriPath, methodPath); | |
69 | + //获取泛型参数类型 | |
70 | + Class<?> parameterType = getParameterType(method); | |
71 | + //dispatch的时候判断校验参数 | |
72 | + Validated validated = getValidateAnnotation(method); | |
73 | + MappingMetaInfo mappingMetaInfo = new MappingMetaInfo(declaringClass, method, validated, parameterType); | |
74 | + RequestDispatcher.addMapping(uri, mappingMetaInfo); | |
75 | + log.info("register aggregation mapping 【{}】", uri); | |
76 | + } | |
77 | + } catch (Exception e) { | |
78 | + log.error("register mappingMetaInfo error:{}",e); | |
79 | + //发生异常直接退出,防止后续无法调用服务 | |
80 | + int exit = SpringApplication.exit(applicationContext, () -> 0); | |
81 | + System.exit(exit); | |
82 | + } | |
83 | + } | |
84 | + } | |
85 | + | |
86 | + private static Class<?> getParameterType(Method method) throws ClassNotFoundException { | |
87 | + Type[] genericParameterTypes = method.getGenericParameterTypes(); | |
88 | + if (genericParameterTypes.length == 1 && genericParameterTypes[0] instanceof ParameterizedType) { | |
89 | + ParameterizedType type = (ParameterizedType) genericParameterTypes[0]; | |
90 | + Type[] params = type.getActualTypeArguments(); | |
91 | + return Class.forName(params[0].getTypeName()); | |
92 | + } | |
93 | + return null; | |
94 | + } | |
95 | + | |
96 | + private static Validated getValidateAnnotation(Method method){ | |
97 | + Annotation[][] parameterAnnotations = method.getParameterAnnotations(); | |
98 | + for (Annotation[] parameterAnnotation : parameterAnnotations) { | |
99 | + for (Annotation annotation : parameterAnnotation) { | |
100 | + if (Validated.class == annotation.annotationType()){ | |
101 | + return (Validated)annotation; | |
102 | + } | |
103 | + } | |
104 | + } | |
105 | + return null; | |
106 | + } | |
107 | + | |
108 | + | |
109 | + private String[] getScanPackages() { | |
110 | + String[] scanPackages = dispatchProperties.getAggregationScanPackages(); | |
111 | + if (scanPackages == null || scanPackages.length == 0) { | |
112 | + return new String[]{XtradeGatewayApplication.class.getPackageName()}; | |
113 | + } | |
114 | + return scanPackages; | |
115 | + } | |
116 | + | |
117 | +} | ... | ... |
src/main/java/com/diligrp/xtrade/gateway/support/dispatch/RequestDispatcher.java
0 → 100644
1 | +++ a/src/main/java/com/diligrp/xtrade/gateway/support/dispatch/RequestDispatcher.java | |
1 | +package com.diligrp.xtrade.gateway.support.dispatch; | |
2 | + | |
3 | +import com.diligrp.xtrade.gateway.application.TestAggregationApplication; | |
4 | +import com.diligrp.xtrade.gateway.common.utils.PathUtils; | |
5 | +import com.diligrp.xtrade.gateway.common.utils.ValidateUtils; | |
6 | +import com.diligrp.xtrade.gateway.domain.TestRequestDto; | |
7 | +import com.diligrp.xtrade.gateway.exception.GatewayParamNotValidException; | |
8 | +import com.diligrp.xtrade.gateway.exception.GatewayServiceException; | |
9 | +import com.diligrp.xtrade.gateway.filters.factory.DispatchGatewayFilterFactory; | |
10 | +import com.diligrp.xtrade.gateway.route.DynamicRouteLoaderIntf; | |
11 | +import com.diligrp.xtrade.gateway.route.impl.MysqlRouteResourceLoader; | |
12 | +import com.diligrp.xtrade.shared.domain.Message; | |
13 | +import com.diligrp.xtrade.shared.type.SystemCode; | |
14 | +import com.diligrp.xtrade.shared.util.JsonUtils; | |
15 | +import com.google.common.base.Strings; | |
16 | +import lombok.extern.slf4j.Slf4j; | |
17 | +import org.bouncycastle.util.Arrays; | |
18 | +import org.springframework.beans.factory.annotation.Autowired; | |
19 | +import org.springframework.cloud.gateway.support.NotFoundException; | |
20 | +import org.springframework.context.ApplicationContext; | |
21 | +import org.springframework.stereotype.Component; | |
22 | +import org.springframework.validation.annotation.Validated; | |
23 | +import org.springframework.web.server.ResponseStatusException; | |
24 | +import org.springframework.web.server.ServerWebExchange; | |
25 | +import org.springframework.web.server.ServerWebInputException; | |
26 | +import org.springframework.web.util.pattern.PathPattern; | |
27 | + | |
28 | +import java.lang.reflect.InvocationTargetException; | |
29 | +import java.lang.reflect.Method; | |
30 | +import java.lang.reflect.ParameterizedType; | |
31 | +import java.lang.reflect.Type; | |
32 | +import java.util.List; | |
33 | +import java.util.Map; | |
34 | +import java.util.Optional; | |
35 | +import java.util.concurrent.ConcurrentHashMap; | |
36 | +import java.util.concurrent.CopyOnWriteArrayList; | |
37 | +import java.util.concurrent.atomic.AtomicBoolean; | |
38 | + | |
39 | +/** | |
40 | + * @Auther: miaoguoxin | |
41 | + * @Date: 2020/4/15 09:29 | |
42 | + * @Description: 映射分发器, 用于聚合服务执行对应方法 | |
43 | + * {@link DispatchGatewayFilterFactory} | |
44 | + */ | |
45 | +@Component | |
46 | +@Slf4j | |
47 | +public class RequestDispatcher { | |
48 | + /** | |
49 | + * key: uri value:{@link MappingMetaInfo} | |
50 | + */ | |
51 | + private static final Map<String, MappingMetaInfo> MAPPING_INFO_MAP = new ConcurrentHashMap<>(); | |
52 | + | |
53 | + private static final List<PathPattern> PATH_PATTERNS = new CopyOnWriteArrayList<>(); | |
54 | + | |
55 | + private static AtomicBoolean hasInit = new AtomicBoolean(false); | |
56 | + | |
57 | + @Autowired | |
58 | + private ApplicationContext applicationContext; | |
59 | + | |
60 | + /** | |
61 | + * 执行对应方法 | |
62 | + * | |
63 | + * @author miaoguoxin | |
64 | + * @date 2020/4/15 | |
65 | + */ | |
66 | + public Message executeMethod(String uri, String paramsJson, ServerWebExchange exchange) { | |
67 | + try { | |
68 | + if (hasInit.compareAndSet(false, true)) { | |
69 | + String[] paths = MAPPING_INFO_MAP.keySet().toArray(new String[0]); | |
70 | + PATH_PATTERNS.addAll(PathUtils.assemblePath(paths, PATH_PATTERNS)); | |
71 | + } | |
72 | + String matchUri = PathUtils.getMatchUri(uri, PATH_PATTERNS); | |
73 | + if (Strings.isNullOrEmpty(matchUri)) { | |
74 | + throw NotFoundException.create(true, String.format("%s not found", uri)); | |
75 | + } | |
76 | + //分发逻辑 | |
77 | + Object resultData = this.beginDispatch(matchUri, paramsJson,exchange); | |
78 | + return Message.builder().success(resultData); | |
79 | + } catch (Throwable e) { | |
80 | + return handleException(e); | |
81 | + } | |
82 | + } | |
83 | + | |
84 | + /** | |
85 | + * 注册mapping信息 | |
86 | + * @param | |
87 | + * @return | |
88 | + * @author miaoguoxin | |
89 | + * @date 2020/4/16 | |
90 | + */ | |
91 | + public static void addMapping(String uri, MappingMetaInfo mappingMetaInfo) { | |
92 | + Optional.ofNullable(MAPPING_INFO_MAP.put(uri, mappingMetaInfo)) | |
93 | + .ifPresent(info -> { | |
94 | + throw new GatewayServiceException( | |
95 | + String.format("mapping:【%s】 duplicate:【%s】", uri, info.toString())); | |
96 | + }); | |
97 | + | |
98 | + } | |
99 | + | |
100 | + /** | |
101 | + * 执行分发操作,参数为DispatchContext | |
102 | + * | |
103 | + * @author miaoguoxin | |
104 | + * @date 2020/4/15 | |
105 | + */ | |
106 | + private Object beginDispatch(String matchUri, String paramsJson, ServerWebExchange exchange) throws Throwable { | |
107 | + MappingMetaInfo mappingMetaInfo = MAPPING_INFO_MAP.get(matchUri); | |
108 | + Object targetBean = applicationContext.getBean(mappingMetaInfo.getTargetClass()); | |
109 | + Method targetMethod = mappingMetaInfo.getTargetMethod(); | |
110 | + Class<?> parameterType = mappingMetaInfo.getParameterType(); | |
111 | + Object paramData = null; | |
112 | + if (parameterType != null){ | |
113 | + paramData = JsonUtils.fromJsonString(paramsJson, parameterType); | |
114 | + Validated validated = mappingMetaInfo.getValidated(); | |
115 | + if (validated != null){ | |
116 | + ValidateUtils.validate(paramData,validated.value()); | |
117 | + } | |
118 | + } | |
119 | + | |
120 | + DispatchContext dispatchContext = new DispatchContext( | |
121 | + exchange.getRequest(), | |
122 | + exchange.getResponse(), | |
123 | + paramData); | |
124 | + return this.call(targetMethod, targetBean, dispatchContext); | |
125 | + } | |
126 | + | |
127 | + public Object call(Method method, Object target, Object params) throws Throwable { | |
128 | + try { | |
129 | + return method.invoke(target, params); | |
130 | + } catch (InvocationTargetException tex) { | |
131 | + throw tex.getCause() == null ? tex : tex.getCause(); | |
132 | + } | |
133 | + } | |
134 | + | |
135 | + private static Message handleException(Throwable ex) { | |
136 | + if (ex instanceof ResponseStatusException) { | |
137 | + int statusCode = ((ResponseStatusException) ex).getStatus().value(); | |
138 | + log.error("aggregation dispatch {}:{}", statusCode, ex.getMessage()); | |
139 | + return Message.builder() | |
140 | + .code(statusCode) | |
141 | + .message(ex.getMessage()) | |
142 | + .build(); | |
143 | + } else if (ex instanceof GatewayParamNotValidException) { | |
144 | + return Message.builder() | |
145 | + .code(SystemCode.ILLEGAL_PARAMS.getCode()) | |
146 | + .message(ex.getMessage()) | |
147 | + .build(); | |
148 | + } else { | |
149 | + log.error("aggregation dispatch error:{}", ex); | |
150 | + return Message.builder() | |
151 | + .code(SystemCode.SERVER_ERROR.getCode()) | |
152 | + .message(SystemCode.SERVER_ERROR.getName()) | |
153 | + .build(); | |
154 | + } | |
155 | + } | |
156 | + | |
157 | + | |
158 | +} | ... | ... |
src/main/resources/ValidationMessages.properties
0 → 100644
src/main/resources/ValidationMessages_zh_CN.properties
0 → 100644
1 | +++ a/src/main/resources/ValidationMessages_zh_CN.properties | |
1 | +request.username.not.blank=\u767B\u5F55\u7528\u6237\u8D26\u53F7\u4E0D\u80FD\u4E3A\u7A7A | |
2 | +request.password.not.blank=\u767B\u5F55\u7528\u6237\u5BC6\u7801\u4E0D\u80FD\u4E3A\u7A7A | |
3 | +request.date_interval={beginDateField}\u548C{endDateField}\u95F4\u9694\u6709\u8BEF | ... | ... |
src/main/resources/banner2.txt
0 → 100644
1 | +++ a/src/main/resources/banner2.txt | |
1 | +${AnsiColor.YELLOW} | |
2 | + ┏┓ ┏┓+ + | |
3 | + ┏┛┻━━━┛┻┓ + + | |
4 | + ┃ ┃ | |
5 | + ┃ ━ ┃ ++ + + + | |
6 | + ████━████ ┃+ | |
7 | + ┃ ┃ + | |
8 | + ┃ ┻ ┃ | |
9 | + ┃ ┃ + + | |
10 | + ┗━┓ ┏━┛ | |
11 | + ┃ ┃ | |
12 | + ┃ ┃ + + + + | |
13 | + ┃ ┃ Codes are far away from bugs with the animal protecting | |
14 | + ┃ ┃ + 神兽保佑,代码无bug | |
15 | + ┃ ┃ | |
16 | + ┃ ┃ + | |
17 | + ┃ ┗━━━┓ + + | |
18 | + ┃ ┣┓ | |
19 | + ┃ ┏┛ | |
20 | + ┗┓┓┏━┳┓┏┛ + + + + | |
21 | + ┃┫┫ ┃┫┫ | |
22 | + ┗┻┛ ┗┻┛+ + + + | |
23 | + Spring Boot :${spring-boot.version} 此代码已经开光! | ... | ... |
src/main/resources/bootstrap-dev.yml
0 → 100644
1 | +++ a/src/main/resources/bootstrap-dev.yml | |
1 | +spring: | |
2 | + cloud: | |
3 | + nacos: | |
4 | + discovery: | |
5 | + username: nacos | |
6 | + password: microtest | |
7 | + server-addr: apitest.51shiban.com:80/n | |
8 | + namespace: c86d8673-4d0a-469f-8bb8-1434c57c236c | |
9 | + config: | |
10 | + username: nacos | |
11 | + password: microtest | |
12 | + server-addr: apitest.51shiban.com:80/n | |
13 | + namespace: c86d8673-4d0a-469f-8bb8-1434c57c236c | ... | ... |
src/main/resources/bootstrap-gateway-tempalte.yml
0 → 100644
src/main/resources/bootstrap.yml
0 → 100644
1 | +++ a/src/main/resources/bootstrap.yml | |
1 | +server: | |
2 | + port: 8888 | |
3 | +spring: | |
4 | + application: | |
5 | + name: xtrade-gateway-service | |
6 | + profiles: | |
7 | + active: dev | |
8 | + cloud: | |
9 | + nacos: | |
10 | + config: | |
11 | + file-extension: properties | |
12 | + context-path: /nacos | |
13 | + extension-configs: | |
14 | + - dataId: route.properties | |
15 | + refresh: true | |
16 | + - dataId: common.properties | |
17 | + refresh: false | |
18 | + banner: | |
19 | + location: banner2.txt | |
20 | +mybatis: | |
21 | + mapper-locations: classpath:/mapper/*.xml | |
22 | + config-location: classpath:/mybatis-config.xml | |
23 | +xtrade: | |
24 | + json: | |
25 | + format: false | |
26 | + gateway-loader: cloud | |
27 | + aggregation-scan-packages: com.diligrp.xtrade.gateway.application | ... | ... |
src/main/resources/ehcache.xml
0 → 100644
1 | +++ a/src/main/resources/ehcache.xml | |
1 | +<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../config/ehcache.xsd"> | |
2 | + | |
3 | + <diskStore path="java.io.tmpdir"/> | |
4 | + | |
5 | + <!--defaultCache:echcache的默认缓存策略 --> | |
6 | + <defaultCache | |
7 | + maxElementsInMemory="100000" | |
8 | + eternal="false" | |
9 | + timeToIdleSeconds="120" | |
10 | + timeToLiveSeconds="120" | |
11 | + maxElementsOnDisk="100000" | |
12 | + diskExpiryThreadIntervalSeconds="120" | |
13 | + memoryStoreEvictionPolicy="LRU"> | |
14 | + <persistence strategy="localTempSwap"/> | |
15 | + </defaultCache> | |
16 | + <cache name="permissions" | |
17 | + maxElementsInMemory="10000" | |
18 | + eternal="false" | |
19 | + timeToIdleSeconds="120" | |
20 | + timeToLiveSeconds="7200" | |
21 | + maxElementsOnDisk="10000" | |
22 | + diskExpiryThreadIntervalSeconds="120" | |
23 | + memoryStoreEvictionPolicy="LRU"> | |
24 | + <persistence strategy="localTempSwap"/> | |
25 | + </cache> | |
26 | +</ehcache> | ... | ... |
src/main/resources/mapper/AttrConfigDao.xml
0 → 100644
1 | +++ a/src/main/resources/mapper/AttrConfigDao.xml | |
1 | +<?xml version="1.0" encoding="UTF-8" ?> | |
2 | +<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > | |
3 | +<mapper namespace="com.diligrp.xtrade.gateway.repository.dao.AttrConfigDao"> | |
4 | + <resultMap id="Base_Result_Map" type="com.diligrp.xtrade.gateway.repository.entity.GatewayAttrConfig"> | |
5 | + <id property="id" column="id"/> | |
6 | + <result property="serviceId" column="service_id"/> | |
7 | + <result property="type" column="type"/> | |
8 | + <result property="attrName" column="attr_name"/> | |
9 | + <result property="attrArgs" column="attr_args"/> | |
10 | + <result property="description" column="description"/> | |
11 | + <result property="sortOrder" column="sort_order"/> | |
12 | + <result property="isDel" column="is_del"/> | |
13 | + <result property="modifiedTime" column="modified_time"/> | |
14 | + <result property="createdTime" column="created_time"/> | |
15 | + </resultMap> | |
16 | + | |
17 | + <select id="findAllForLoad" resultMap="Base_Result_Map"> | |
18 | + SELECT service_id, | |
19 | + type, | |
20 | + attr_name, | |
21 | + attr_args, | |
22 | + sort_order | |
23 | + FROM t_attr_config | |
24 | + WHERE is_del = 0 | |
25 | + </select> | |
26 | +</mapper> | ... | ... |
src/main/resources/mapper/RouteDao.xml
0 → 100644
1 | +++ a/src/main/resources/mapper/RouteDao.xml | |
1 | +<?xml version="1.0" encoding="UTF-8" ?> | |
2 | +<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > | |
3 | +<mapper namespace="com.diligrp.xtrade.gateway.repository.dao.RouteDao"> | |
4 | + <resultMap id="Base_Result_Map" type="com.diligrp.xtrade.gateway.repository.entity.GatewayConfig"> | |
5 | + <id property="id" column="id"/> | |
6 | + <result property="url" column="url"/> | |
7 | + <result property="serviceId" column="service_id"/> | |
8 | + <result property="createdTime" column="created_time"/> | |
9 | + <result property="modifiedTime" column="modified_time"/> | |
10 | + <result property="description" column="description"/> | |
11 | + <result property="isDel" column="is_del"/> | |
12 | + </resultMap> | |
13 | + | |
14 | + <select id="findAllForLoad" resultMap="Base_Result_Map"> | |
15 | + SELECT | |
16 | + url,service_id | |
17 | + FROM | |
18 | + t_route | |
19 | + WHERE | |
20 | + is_del=0 | |
21 | + </select> | |
22 | +</mapper> | ... | ... |
src/main/resources/mybatis-config.xml
0 → 100644
1 | +++ a/src/main/resources/mybatis-config.xml | |
1 | +<?xml version="1.0" encoding="UTF-8" ?> | |
2 | +<!DOCTYPE configuration | |
3 | + PUBLIC "-//mybatis.org//DTD Config 3.0//EN" | |
4 | + "http://mybatis.org/dtd/mybatis-3-config.dtd"> | |
5 | + | |
6 | +<configuration> | |
7 | +<settings> | |
8 | + <!-- 只设置需要的,其他使用默认值 | |
9 | + 开启二级缓存,相当于关闭一级缓存,防止分布式一级缓存无法共享 --> | |
10 | + <setting name="cacheEnabled" value="true"/> | |
11 | + <!-- 在null时也调用 setter,适应于返回Map,3.2版本以上可用 --> | |
12 | + <setting name="callSettersOnNulls" value="true"/> | |
13 | + <!-- 打印sql语句 调试使用--> | |
14 | + <!-- <setting name="logImpl" value="STDOUT_LOGGING" />--> | |
15 | + </settings> | |
16 | + <!-- <plugins> | |
17 | + <plugin interceptor="com.simida.infrastructure.common.page.PageHelper"/> | |
18 | + </plugins>--> | |
19 | +</configuration> | |
0 | 20 | \ No newline at end of file | ... | ... |
src/test/java/com/diligrp/xtrade/gateway/XtradeGatewayApplicationTests.java
0 → 100644
1 | +++ a/src/test/java/com/diligrp/xtrade/gateway/XtradeGatewayApplicationTests.java | |
1 | +package com.diligrp.xtrade.gateway; | |
2 | + | |
3 | +import org.junit.jupiter.api.Test; | |
4 | +import org.springframework.boot.test.context.SpringBootTest; | |
5 | + | |
6 | +@SpringBootTest | |
7 | +class XtradeGatewayApplicationTests { | |
8 | + | |
9 | + @Test | |
10 | + void contextLoads() { | |
11 | + } | |
12 | + | |
13 | +} | ... | ... |