Spring Gateway dynamic routes
需要一个demo后台服务
默认8080端口
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
@RestController
public class DemoController {
@GetMapping("/getV")
@ResponseBody
@ResponseStatus(HttpStatus.OK)
public ResponseEntity<?> get() {
return ResponseEntity.ok("success for get");
}
@PostMapping("/postV")
@ResponseBody
@ResponseStatus(HttpStatus.CREATED)
public ResponseEntity<?> post() {
return ResponseEntity.ok("success for post");
}
@PutMapping("/putV")
@ResponseBody
@ResponseStatus(HttpStatus.ACCEPTED)
public ResponseEntity<?> put() {
return ResponseEntity.ok("success for put");
}
}
配置网关
默认8082端口与后台Demo服务便于区分
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
<version>4.0.3</version>
</dependency>
@Configuration
public class RouteConfiguration1 {
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder routeLocatorBuilder) {
return routeLocatorBuilder.routes()
.route("get_route",
r-> r.path("/getV").and().method("GET").uri("http://localhost:8080")
)
.route("post_route",
r -> r.path("/postV").and().method("POST").uri("http://localhost:8080")
)
.build();
}
}
通过以上,我们可以直接访问 curl -X GET http://localhost:8082/getV
获取对应的后台Demo服务 http://localhost:8080/getV
$ curl -X GET http://localhost:8082/getV
success for get
通过routeLocatorBuilder.build()生成 RouteLocator
routeLocatorBuilder.routes()
.route()
.route().build()
将某些需要的编码抽取重构,生成一个新的 RouteLocator (RouteConfiguration1.java删除@Configuration, 避免RouteLocator Bean
冲突)
//@Configuration
public class RouteConfiguration1 {
// ...
}
@Configuration
public class RouteConfiguration2 {
@Getter
@Setter
@Builder
static class RouteConfig {
private String id;
private String path;
private String method;
private String uri;
}
public static List<RouteConfig> routeConfigs = new ArrayList<>();
static {
routeConfigs.add(new RouteConfig("get", "/getV", "GET", "http://localhost:8080"));
routeConfigs.add(new RouteConfig("post", "/postV", "POST", "http://localhost:8080"));
}
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder routeLocatorBuilder) {
RouteLocatorBuilder.Builder routesBuilder = routeLocatorBuilder.routes();
routeConfigs.forEach(e -> {
routesBuilder.route(e.getId(), r -> r.path(e.getPath()).and().method(e.getMethod()).uri(e.getUri()));
});
return routesBuilder.build();
}
}
RouteLocator
只有一个方法,
public interface RouteLocator {
Flux<Route> getRoutes();
}
升级存储持久库
又继续重构,此处加入mysql, reactive db
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-r2dbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
<version>4.0.3</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.github.jasync-sql</groupId>
<artifactId>jasync-r2dbc-mysql</artifactId>
<version>2.1.23</version>
</dependency>
spring:
r2dbc:
url: r2dbc:mysql://192.168.79.177:14306/testdb_spring
username: root
password: 123456
server:
port: 8082
@Getter
@Setter
@NoArgsConstructor
public class RouteDetail {
@Id
private Long id;
private String routeId;
private String pathValue;
private String uriValue;
private String methodValue;
public RouteDetail() {
}
public RouteDetail(String routeId, String path, String uri, String method) {
this.routeId = routeId;
this.pathValue = path;
this.uriValue = uri;
this.methodValue = method;
}
}
@Repository
public interface RouteDetailRepository extends R2dbcRepository<RouteDetail, Long> {
}
@Service
public class RouteLocatorService implements ApplicationEventPublisherAware {
@Resource
RouteDetailRepository routeDetailRepository;
private ApplicationEventPublisher applicationEventPublisher;
public Mono<RouteDetail> create(RouteDetail routeDetail) {
return routeDetailRepository.save(routeDetail)
.doOnSuccess(e -> publishEvent());
}
public Mono<RouteDetail> update(RouteDetail routeDetail) {
return routeDetailRepository.findById(routeDetail.getId())
.map(Optional::of).defaultIfEmpty(Optional.empty())
.flatMap(e -> {
if (e.isPresent()) {
return routeDetailRepository.save(routeDetail).doOnSuccess(s -> publishEvent());
}
return Mono.empty();
}
);
}
public Mono<Void> delete(Long id) {
return routeDetailRepository.deleteById(id)
.doOnSuccess(e -> publishEvent())
.then();
}
public Mono<RouteDetail> findById(Long id) {
return routeDetailRepository.findById(id);
}
public Flux<RouteDetail> findAll() {
return routeDetailRepository.findAll();
}
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
}
void publishEvent() {
applicationEventPublisher.publishEvent(new RefreshRoutesEvent(this));
}
}
@RestController
@RequestMapping("/api/rs")
public class RouteLocatorController {
@Resource
RouteLocatorService routeLocatorService;
@PostMapping("/")
@ResponseStatus(HttpStatus.CREATED)
public Mono<?> create(@RequestBody RouteDetail routeDetail) {
return routeLocatorService.create(new RouteDetail(routeDetail.getRouteId(), routeDetail.getPathValue()
, routeDetail.getUriValue(), routeDetail.getMethodValue()))
//optional: flow can be executed on seperate thread pools
.subscribeOn(Schedulers.boundedElastic());
}
@GetMapping("/")
@ResponseStatus(HttpStatus.OK)
public Mono<List<RouteDetail>> list() {
return routeLocatorService.findAll().collectList()
.subscribeOn(Schedulers.boundedElastic());
}
@GetMapping("/{id}")
@ResponseStatus(HttpStatus.OK)
public Mono<RouteDetail> get(@PathVariable Long id) {
return routeLocatorService.findById(id)
.subscribeOn(Schedulers.boundedElastic());
}
@DeleteMapping("/{id}")
@ResponseStatus(HttpStatus.NO_CONTENT)
public Mono<?> delete(@PathVariable Long id) {
return routeLocatorService.delete(id)
.subscribeOn(Schedulers.boundedElastic());
}
@PutMapping("/{id}")
@ResponseStatus(HttpStatus.ACCEPTED)
public Mono<?> update(@PathVariable Long id, @RequestBody RouteDetail routeDetail) {
return routeLocatorService.update(routeDetail)
.subscribeOn(Schedulers.boundedElastic());
}
}
@Configuration
public class RouteConfiguration3 {
@Bean
public RouteLocator routeLocator() {
return new CustomRouteLocator();
}
}
CREATE TABLE IF NOT EXISTS route_detail (
id INT NOT NULL AUTO_INCREMENT,
route_id VARCHAR(128),
`path_value` VARCHAR(50),
`method_value` VARCHAR(10),
uri_value VARCHAR(255),
PRIMARY KEY (id)
);
测试验证
$ curl -X GET http://localhost:8082/api/rs/
[{"id":5,"routeId":"put","pathValue":"/putV","uriValue":"http://localhost:8080","methodValue":"PUT"},{"id":6,"routeId":"get","pathValue":"/getV","uriValue":"http://localhost:8080","methodValue":"GET"}]
$ curl -X POST http://localhost:8082/postV
{"timestamp":"2023-03-07T03:17:42.509+00:00","path":"/postV","status":404,"error":"Not Found","message":null,"requestId":"0c259afb-13"}
$ curl -X POST -H "Content-Type: application/json" -d "{\"routeId\": \"post\",\"pathValue\": \"/postV\",\"uriValue\": \"http://localhost:8080\",\"methodValue\": \"POST\"}" http://localhost:8082/api/rs/
{"id":18,"routeId":"post","pathValue":"/postV","uriValue":"http://localhost:8080","methodValue":"POST"}
$ curl -X POST http://localhost:8082/postV
success for post
$ curl -X DELETE http://localhost:8082/api/rs/18
$ curl -X POST http://localhost:8082/postV
{"timestamp":"2023-03-07T03:19:13.764+00:00","path":"/postV","status":404,"error":"Not Found","message":null,"requestId":"74a8b30a-17"}