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>-->
原文地址:https://www.cnblogs.com/somefuture/p/15502897.html