今日Gatewayを使ってマイクロサービスドキュメントを統合したところ、Knife4jのドキュメントリクエストが異常だったのでデータパッケージを確認したところ、このようなパスがリクエストされていました。(ストリーム保存アシスタント: エラーの理由は、api-doc の取得方法が間違っているためです。私が何を言っているのか理解できない場合は、以下を読んでください。) 統合されたコードは直接 CV にあります
。いくつかの変更を行う必要があるようですが、特に重要なのは、ゲートウェイの 2 つの構成と他のサービスの構成ファイルがスタンドアロン サーバーの構成と一致していることです。ゲートウェイの構成ファイルは次のとおりです。
最初のファイルは Config です。
@Slf4j
@Component
@Primary
@AllArgsConstructor
public class SwaggerResourceConfig implements SwaggerResourcesProvider {
private final RouteLocator routeLocator;
private final GatewayProperties gatewayProperties;
@Override // 请求网关时就会执行此方法
public List<SwaggerResource> get() {
List<SwaggerResource> resources = new ArrayList<>();
List<String> routes = new ArrayList<>();
//获取所有路由的ID并加入到routes里
routeLocator.getRoutes().subscribe(route -> routes.add(route.getId()));
//过滤出配置文件中定义的路由->过滤出Path Route Predicate->根据路径拼接成api-docs路径->生成SwaggerResource
gatewayProperties.getRoutes().stream().filter(routeDefinition -> routes.contains(routeDefinition.getId())).forEach(route -> {
route.getPredicates().stream()
.filter(predicateDefinition -> ("Path").equalsIgnoreCase(predicateDefinition.getName()))
.forEach(predicateDefinition -> resources.add(swaggerResource(route.getId(),
predicateDefinition.getArgs().get(NameUtils.GENERATED_NAME_PREFIX + "0")
.replace("**", "v2/api-docs"))));
});
return resources;
}
private SwaggerResource swaggerResource(String name, String location) {
log.info("name:{},location:{}", name, location);
SwaggerResource swaggerResource = new SwaggerResource();
swaggerResource.setName(name);
swaggerResource.setLocation(location);
swaggerResource.setSwaggerVersion("2.0");
return swaggerResource;
}
}
2番目はハンドラーです
/**
* 自定义Swagger的各个配置节点
*/
@RestController
public class SwaggerHandler {
@Autowired(required = false)
private SecurityConfiguration securityConfiguration;
@Autowired(required = false)
private UiConfiguration uiConfiguration;
private final SwaggerResourcesProvider swaggerResources;
@Autowired
public SwaggerHandler(SwaggerResourcesProvider swaggerResources) {
this.swaggerResources = swaggerResources;
}
/**
* Swagger安全配置,支持oauth和apiKey设置
*/
@GetMapping("/swagger-resources/configuration/security")
public Mono<ResponseEntity<SecurityConfiguration>> securityConfiguration() {
return Mono.just(new ResponseEntity<>(
Optional.ofNullable(securityConfiguration).orElse(SecurityConfigurationBuilder.builder().build()), HttpStatus.OK));
}
/**
* Swagger UI配置
*/
@GetMapping("/swagger-resources/configuration/ui")
public Mono<ResponseEntity<UiConfiguration>> uiConfiguration() {
return Mono.just(new ResponseEntity<>(
Optional.ofNullable(uiConfiguration).orElse(UiConfigurationBuilder.builder().build()), HttpStatus.OK));
}
/**
* Swagger资源配置,微服务中这各个服务的api-docs信息
*/
@GetMapping("/swagger-resources")
public Mono<ResponseEntity> swaggerResources() {
return Mono.just((new ResponseEntity<>(swaggerResources.get(), HttpStatus.OK)));
}
}
エラー箇所
コンソールのネットワーク要求レコードから、最初にハンドラーの swagger-resource を要求して API-docs を取得し、次に API-docs を要求したことがわかります。
api-doc は、以下の図の青い URL (http:localhost:10000/v2/api-docs) を指します。この API-docs は、Postman やその他の API テスト ツールにドキュメントをインポートするためにも使用でき、非常に便利です。
SwaggerHandler が swagger-resource リクエストを受信すると、自動的に挿入された swaggerResources get メソッドが呼び出されます。この get メソッドは SwaggerResourceConfig で書き換えられるため、この get メソッドのポイントを壊します。
get メソッドは、自動挿入を通じてゲートウェイの RouteLocator と GatewayProperties を取得します。routeLocator には、デリゲート、ルート、およびキャッシュの 3 つのフィールドが含まれます。
キャッシュでは、ゲートウェイで構成したすべてのルートがチェーンを形成していることがわかります。Java で作成したルーティング設定がチェーン全体の最上位にあることがわかります。
以下のソース コードから、routeLocator の getRoutes メソッドが実際に上図の情報をキャッシュから直接並べ替えて返していることがわかります。これらのルートは Flux、つまり応答性の高いフローの形式で編成されているため、subscribe を使用してデータ フローをトリガーし、すべてのルート ID を独自に作成したリストに追加する必要があります。
ここでは、gatewaProperties の getRoutes メソッドを通じてルートが再度取得されますが、今回は構成ファイルで静的に宣言されたルートのみが存在することがわかります。
次のステップは一連のストリーム処理で、前のステップで抽出したルート ID セットにルート ID が含まれていない設定ファイルをフィルタリングし、残りの各設定ファイルを照合して述語が Path タイプであるかどうかを確認します。それはパスです。その場合、設定したパス値を取り出します。値はハッシュ テーブルに保存され、キーは「_genkey_0」です。この自動生成されたプレフィックスは、NameUtils を通じて取得できます。(詳細な構造については、下図のデバッガー コンソールの変数列を参照してください)
データが流出すると、処理後に 6 つの URL を取得したことがわかります。ゲートウェイ マイクロサービスのスワガーはこれらの URL を使用して、 json ファイルを取得し、各マイクロサービスのドキュメントを 1 つのドキュメントに集約します。(下図のデバッガー コンソールの変数列)
明らかに、現時点ではゲートウェイの構成が間違っているため、ゲートウェイの Swagger は正しい API ドキュメントを取得できません。したがって、パスマッピングが正しい限りは問題ありません。
解決
- ルーティング ルールを変更します (StripPrefix はプレフィックスを削除してから転送します)。
- SwaggerResource を取得するためのルールを変更する
現在の Swagger ドキュメントの場所は、localhost:10000/swagger-ui.html など、各マイクロサービス パスの下のルート パスです。このとき、SwaggerResource を取得するためのルールはパスによって照合されます。明らかに、ルート ディレクトリをマップすることは不可能です。 (ルート ディレクトリと一致させるには、ルート パスのリクエストと一致するようにルーティング ルールを変更する必要があります。これにより、マイクロサービスを区別できないだけでなく、すべてのリクエストがインターセプトされてしまいます)。
また、リソース内で一致するすべてのルート ID がドキュメントに追加されることにも注意してください。そのため、SwaggerResource を取得するためのルールを変更し、/api/xxx で始まるルートを削除する必要があります。
したがって、次の図に示すようにルートのセットを追加します。
StripPrefix=2 は、転送間のパス、つまり /swagger/ware の最初の 2 つのプレフィックスを削除し、転送パスがルート ディレクトリになります。
そして、getメソッドは次のように変更されます
@Override // 请求网关时就会执行此方法
public List<SwaggerResource> get() {
List<SwaggerResource> resources = new ArrayList<>();
List<String> routes = new ArrayList<>();
//获取所有路由的ID并加入到routes里
routeLocator.getRoutes().subscribe(route -> routes.add(route.getId()));
//过滤出配置文件中定义的路由->过滤出Path Route Predicate->根据路径拼接成api-docs路径->生成SwaggerResource
gatewayProperties.getRoutes().stream().filter(routeDefinition -> routes.contains(routeDefinition.getId())).forEach(route -> {
route.getPredicates().stream()
.filter(predicateDefinition ->{
boolean condition1 =("Path").equalsIgnoreCase(predicateDefinition.getName());
String url = predicateDefinition.getArgs().get(NameUtils.GENERATED_NAME_PREFIX + "0");
boolean condition2=false;
if(url.length()>9){
condition2 = ("/swagger/").equalsIgnoreCase(url.substring(0,9));
}
return condition1 && condition2;
})
.forEach(predicateDefinition -> resources.add(swaggerResource(route.getId(),
predicateDefinition.getArgs().get(NameUtils.GENERATED_NAME_PREFIX + "0")
.replace("**", "v2/api-docs"))));
});
return resources;
}
ハイライト 1: パスマッピング
私はかつて、swagger によって設定された pathMapping を変更することでマッピング パスを変更できるのではないかと考えました。実際、これは Swagger にアクセスするパスには影響しません。つまり、構成ファイルでテストする pathMapping を設定し、http://localhost:88/swagger-ui.htm または http://localhost:88/swagger/ware/v2/api-docs を使用します。今回はjsonデータにアクセスまたは取得できます。http://localhost:88/test/swagger-ui.htm または http://localhost:88/swagger/ware/test/v2/api-docs を使用すると 404 になります
では、パスマッピングは何に役立つのでしょうか?
簡単に言えば、この pathMapping は、Swagger テスト インターフェイスを使用してリクエストを送信するときに、このプレフィックスを追加することを指します (プレフィックス テストの図に示すように)。フロントエンド リクエストには通常、プレフィックスとして /api/product が含まれており、このプレフィックスはゲートウェイが使用されるときに除外されます。この機能は、バックエンド インターフェイスを直接リクエストするのではなく、フロントエンド リクエストをシミュレートすることです。
たとえば、次の例を見てください。この時点のベース URL は localhost:88/swagger/ware/ です。localhost:88 はゲートウェイのアドレスであり、プレフィックス /swagger/ware/ はリクエスト時に自動的に
削除されます。 forwardedパスは localhost:88/swagger/ware/ware/purchase/info/2 である必要がありますが、pathMapping をテストとして設定したため、baseurl と path の間に追加のテスト (pathMapping) があり、これは swagger を意味します。 リクエスト パスは Baseurl です
。 +パスマッピング+パス
ヒント 2: Java でルーティング ルールを記述する方法
前述の通り、javaを使用してルーティング設定を記述しました。ルーティング設定は以下のとおりです。以下のymlで設定したルーティングは同等のルーティングを設定できますが、上記の分析からもリンクの先頭にあることがわかります。RouteLocator は、上で SwaggerConfig で使用したものです。
@Configuration
public class TestConfig {
/*
* 通过RouteLocatorBuilder的routes,可以逐一建立路由,每调用route一次可建立一条路由规则.
* p的代表是PredicateSpec,可以透过它的predicate来进行断言,要实现的接口就是Java 8的Predicate,
* 通过exchange取得了路径,然后判断它是不是以/testRouteLocator/开头。
* */
@Bean
public RouteLocator routeLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route(p -> p
.predicate(exchange -> exchange.getRequest().getPath().subPath(0).toString().startsWith(("/testRouteLocator/")))
.filters(f -> f.rewritePath("/testRouteLocator/(?<remaining>.*)", "/${remaining}"))
.uri("lb://gulimall-product"))
.route(p -> p
.predicate(exchang->exchang.getRequest().getPath().toString().equals("/routelocator"))
.uri("lb://gulimall-product"))
.build();
}
}
要約する
これはゲートウェイ、knife4j、swagger を単純に統合しただけですが、多くのルーティング ルール (PrefixStrip)、ルーティングの記述 (yml と java)、レスポンシブ プログラミングなどが含まれます。このプロセスは私にとって非常に困難です。
なぜrouteLocatorとgatewaPropertiesの交差がルートの一致に使用されるのかはまだ少し不明です。元のブロガーの意図がわかりません。これを行わなければならないシナリオが思いつきません(gatewaPropertiesだけができると思います) SwaggerResource.Task の取得を完了するために使用されます)、彼がこのように書いたので、最初はこのように使用し、後で答えを調べます。