Spring WebFlux 簡單業務代碼及其Swagger文檔


上一篇文章《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>-->


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM