上一篇文章《Spring 5 中函數式webmvc開發中的swagger文檔》中講了如何給傳統MVC開發模式中的RouterFunction
增加swagger文檔。這一篇講一下如何給函數式WebFlux開發增加Swagger文檔。
類似於MVC的webflux開發(基於
Controller
實現)的swagger和web mvc方法一樣,這里不講了
依賴變更
既然要從webmvc改造成webflux,就需要換一下依賴。
首先需要增加webflux依賴:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
接下來引入對應的doc依賴:
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-webflux-ui</artifactId>
<version>1.5.12</version>
</dependency>
業務代碼改造
我們首先把傳統web中的代碼改造成webFlux的。之前的Handler中的方法返回的都是org.springframework.web.reactive.function.server.ServerResponse
:
public ServerResponse getStations(ServerRequest req) {
Long entId = Long.valueOf(path(req, "entId"));
List<StationBO> stationBoList = modelBuildingService.getStations(entId);
List<StationVO> stationVoList = TransformUtils.transformList(stationBoList, StationVO.class);
return body(PagedResult.success(stationVoList));
}
public ServerResponse getDeviceTypes(ServerRequest req) {
String stationId = path(req, "stationId");
List<DeviceTypeBO> deviceTypeBoList = modelBuildingService.getDeviceTypes(stationId);
List<DeviceTypeVO> deviceTypeVoList = TransformUtils.transformList(deviceTypeBoList, DeviceTypeVO.class);
return body(PagedResult.success(deviceTypeVoList));
}
// 其他方法
其中path()
和body()
定義在基類中:
public abstract class BaseHandler {
protected ServerResponse body(Object body) {
return ServerResponse.ok().body(body);
}
protected String path(ServerRequest req, String path) {
return req.pathVariable(path);
}
protected <T> T req(ServerRequest req, Class<T> clazz) {
try {
return req.body(clazz);
} catch (ServletException | IOException e) {
throw new RuntimeException(e);
}
}
}
Reactor中有兩個實體,一個是Mono,表示單個對象,可以為空;一個是Flux,表示對象的集合(以序列的形式)。我們這里的接口都是返回的集合,所以應該改造成Flux。但是一來為了方便,二來我們的數據是一次性拿到,過程並不是響應式的,所以也無需用Flux,所以我們改造成Mono:
public Mono<ServerResponse> getStations(ServerRequest req) {
Long entId = Long.valueOf(path(req, "entId"));
List<StationBO> stationBoList = modelBuildingService.getStations(entId);
List<StationVO> stationVoList = TransformUtils.transformList(stationBoList, StationVO.class);
return body(PagedResult.success(stationVoList));
}
public Mono<ServerResponse> getDeviceTypes(ServerRequest req) {
String stationId = path(req, "stationId");
List<DeviceTypeBO> deviceTypeBoList = modelBuildingService.getDeviceTypes(stationId);
List<DeviceTypeVO> deviceTypeVoList = TransformUtils.transformList(deviceTypeBoList, DeviceTypeVO.class);
return body(PagedResult.success(deviceTypeVoList));
}
public Mono<ServerResponse> getRealTimeData(ServerRequest req) {
return req(req, RealTimeDataQueryVO.class)
.doOnNext(body -> {
log.info("請求參數{}", body);
})
.flatMap(body -> {
RealTimeDataResultBO resultBo = modelBuildingService.getRealTimeData(transferRealTimeDataQueryParam(body));
return body(Result.success(transferRealTimeDataResult(resultBo)));
});
}
可以看到,我們獲取數據的方式不是響應式的,這樣實際上並不能發揮出webFlux的能力。另外看到,Mono對象的生成都是通過基類來的,所以基類改造成了:
public abstract class BaseHandler {
protected Mono<ServerResponse> body(Object body) {
return ServerResponse.ok().bodyValue(body);
}
protected String path(ServerRequest req, String path) {
return req.pathVariable(path);
}
protected <T> Mono<T> req(ServerRequest req, Class<T> clazz) {
return req.bodyToMono(clazz);
}
}
這里用到了一些WebFlux的API,感興趣的可以獨自去了解,這里就先不介紹了。
RouterFunction的代碼幾乎不用改變,只要把引入的包從org.springframework.web.servlet.function
改到org.springframework.web.reactive.function.server
即可,之前的swagger就依然生效:
import com.enn.view.studio.web.handler.ModelBuildingHandler;
import com.enn.view.studio.web.vo.request.RealTimeDataQueryVO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.parameters.RequestBody;
import org.springdoc.core.annotations.RouterOperation;
import org.springdoc.core.annotations.RouterOperations;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerResponse;
import static org.springframework.web.reactive.function.server.RequestPredicates.*;
@Configuration
public class RoutingConfig {
@Bean
@RouterOperations({
@RouterOperation(
path = "/model/building/{entId}/stations",
beanClass = ModelBuildingHandler.class,
beanMethod = "getStations",
method = RequestMethod.GET,
operation = @Operation(
operationId = "getStations",
parameters = @Parameter(
name = "entId",
in = ParameterIn.PATH,
required = true,
description = "企業ID"
)
)
),
@RouterOperation(
path = "/model/building/devices/points/real-time-data",
beanClass = ModelBuildingHandler.class,
beanMethod = "getRealTimeData",
operation = @Operation(
operationId = "getRealTimeData",
requestBody = @RequestBody(
required = true,
description = "請求體",
content = @Content(
schema = @Schema(implementation = RealTimeDataQueryVO.class)
)
)
)
)
})
public RouterFunction<ServerResponse> getModelBuildingRouters(ModelBuildingHandler modelBuildingHandler) {
return RouterFunctions.nest(
path("/model/building"),
RouterFunctions.route(GET("/{entId}/stations"), modelBuildingHandler::getStations)
.andRoute(GET("/{stationId}/device-types"), modelBuildingHandler::getDeviceTypes)
.andRoute(POST("/devices/points/real-time-data"), modelBuildingHandler::getRealTimeData)
);
}
}
問題排查
如果增加了上面這個依賴,但是swagger頁面打不開,或者swagger頁面看不到
RouterFunction
的文檔(只能看到Controller
的文檔),通常都是因為依賴沖突。我本地的老項目引入上述依賴后調整了好幾天依賴才成功顯示出文檔來。
- 不要依賴springdoc-openapi-ui
依賴了springdoc-openapi-webflux-ui就把springdoc-openapi-ui移除掉
<!--<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-ui</artifactId>
<version>1.5.12</version>
</dependency>-->
- 不要依賴springdoc-openapi-webmvc-core
即使同時使用了mvc和webflux,也不要再依賴springdoc-openapi-webmvc-core
<!--<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-webmvc-core</artifactId>
<version>1.5.12</version>
</dependency>-->
- 不要依賴spring-boot-starter-web
這個可能因項目而異。我這邊需要刪掉這個依賴才能啟動
<!--<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>-->