SpringWebFlux使用RouteFunction方式定义api接口

package luck.spring.boot.webflux;

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpCookie;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.HttpHandler;
import org.springframework.util.MultiValueMap;
import org.springframework.web.reactive.function.server.HandlerStrategies;
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 org.springframework.web.reactive.result.view.freemarker.FreeMarkerViewResolver;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebExceptionHandler;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;

import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.reactive.function.server.RequestPredicates.accept;
import static org.springframework.web.reactive.function.server.RequestPredicates.queryParam;
import static org.springframework.web.reactive.function.server.RouterFunctions.route;
import static org.springframework.web.reactive.function.server.RouterFunctions.toHttpHandler;
import static org.springframework.web.reactive.function.server.ServerResponse.status;

@Slf4j
@Configuration
public class RouterFunctionConfig {

    // 定义复杂条件的路由,包含前置过滤器,后置过滤器,环绕过滤器
    @Bean
    public RouterFunction<ServerResponse> nestRouterFunction(RouterHandler handler) {
        // 定义路由
        RouterFunctions.Builder globalRouterBuilder = route();
        return globalRouterBuilder
                .GET("/luck", handler::simpleTestApi)
                // 以下路由的基础路径
                .path("/base/url",
                        // 定义嵌套路由,该嵌套路由的所有子路由的客户端都需要json类型的数据
                        baseUrlBuilder -> baseUrlBuilder.GET("/test", accept(APPLICATION_JSON), handler::simpleTestApi)
                                .nest(accept(APPLICATION_JSON),
                                        // 定义一个子路由,url为"/router/aaa",访问路由为/base/url/router/aaa
                                        // 并且/router/aaa这个路由必须有name=luck这个参数对,否则无法访问
                                        // 匹配之后的处理函数为HandlerFunction,是一个函数式编程
                                        nestBuilder -> nestBuilder.GET("/router/aaa", queryParam("name", "luck"), handler::simpleTestApi)
                                                .GET("/router/bbb", handler::simpleTestApi)
                                                .GET("/router/ccc", handler::simpleTestApi)
                                                // 在同层次的builder(nestBuilder)构建的所有路由的前置拦截器,只会拦截请求,无法处理响应
                                                .before(req -> {
                                                    MultiValueMap<String, HttpCookie> cookies = req.exchange().getRequest().getCookies();
                                                    if (!cookies.containsKey("cookie_lang")) {
                                                        log.error("不包含该cookie,不放行");
                                                        return req;
                                                    }
                                                    log.error("before:{}", req.exchange().getRequest().getURI().getPath());
                                                    return req;
                                                }))
                                // 该过滤器是针对于变量baseUrlBuilder这个builder构建的路由以及子路由进行过滤的一个过滤器
                                // 注意构建的层级,同一个Builder构建的过滤器,作用于同级别的所有路由以及子路由
                                // 由此可总结: baseUrlBuilder变量builder构建的路由有/base/url/test,还有嵌套路由nestBuilder变量builder构建的
                                //           子路由/base/url/router/[aaa,bb,ccc],这些路由都会经过这个过滤器
                                .filter((request, next) -> {
                                    boolean containsKey = request.queryParams().containsKey("bbb");
                                    if (!containsKey) {
                                        return status(HttpStatus.UNAUTHORIZED).build();
                                    }
                                    // 传递过下一个过滤器执行
                                    return next.handle(request);
                                })
                )
                // 在同层次的builder(globalRouterBuilder)构建的所有路由的后置拦截器,执行完目标方法之后的拦截器
                .after((req, resp) -> {
                    log.error("after过滤器,所有路由都会执行,包括嵌套路由");
                    req.exchange().getResponse().getHeaders().add("x-request-id", "response-request-id");
                    return resp;
                })
                // 在同层次的builder(globalRouterBuilder)构建的所有路由的过滤器
                .filter((request, next) -> {
                    log.error("全局过滤器:{}", request.uri().getPath());
                    // List<String> header = request.headers().header("Authorization");
                    // if (CollUtil.isEmpty(header)) {
                    //     return status(HttpStatus.UNAUTHORIZED).build();
                    // }
                    // 传递过下一个过滤器执行
                    return next.handle(request);
                })
                .build();
    }


    @Bean
    public RouterFunction<ServerResponse> routerFunction(RouterHandler routerHandler) {
        // 动态定义路由(接口)信息
        // RouterFunctions.route()静态方法
        return route()
                // 定义一个接口,请求方式为GET,URL为"/router/a"
                // 并且设置规则,客户端需要的响应类型为JSON,并且参数必须含有name=luck才能访问到,否则404
                // 请求的处理函数为一个HandlerFunction,是一个函数式接口,通过res->resp
                .GET("/router/a", accept(APPLICATION_JSON).and(queryParam("name", "luck")), routerHandler::simpleBody)
                .POST("/router/b", accept(APPLICATION_JSON), routerHandler::requestBody)
                .GET("/router/c", accept(APPLICATION_JSON), routerHandler::fromDataBody)
                .build();
    }

    /**
     * <pre>
     *      HttpHandler是一个约定,专门用来处理请求和响应的处理器,它主要并且是唯一的目的就是对不同的Http服务器进行最小的抽象
     *      说白了就是对不同Http服务器,HttpHandler接口就是Http服务器处理请求响应的处理类
     *
     *      // Netty服务器
     *      HttpHandler handler = null;
     *      ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(handler);
     *      HttpServer.create().host(host).port(port).handle(adapter).bind().block();
     *
     *      // Undertow服务器
     *      HttpHandler handler = null;
     *      UndertowHttpHandlerAdapter adapter = new UndertowHttpHandlerAdapter(handler);
     *      Undertow server = Undertow.builder().addHttpListener(port, host).setHandler(adapter).build();
     *      server.start();
     *
     *      // Tomcat服务器
     *      Servlet servlet = new TomcatHttpHandlerAdapter(handler);
     *      Tomcat server = new Tomcat();
     *      File base = new File(System.getProperty("java.io.tmpdir"));
     *      Context rootContext = server.addContext("", base.getAbsolutePath());
     *      Tomcat.addServlet(rootContext, "main", servlet);
     *      rootContext.addServletMappingDecoded("/", "main");
     *      server.setHost(host);
     *      server.setPort(port);
     *      server.start();
     * </pre>
     *
     * <pre>
     *     在HttpHandler约定之上,Spring还提供了一个通用的WebAPI,通过多个WebExceptionHandler
     *     多个WebFilter和一个WebHandler组成一条链来处理请求,这条链可以通过WebHttpHandlerBuilder来构建
     *     而WebHttpHandlerBuilder会包含Spring上下文,从容器中扫描WebExceptionHandler,WebFilter和一个WebHandler的Bean
     *     最终构建成HttpHandler来适配不同的Http服务器
     *     public HttpHandler build() {
     *          // 创建过滤器的WebHandler
     * 		    WebHandler decorated = new FilteringWebHandler(this.webHandler, this.filters);
     * 		    // 使用装饰模式,包装成一个可以处理异常的WebHandler
     * 		    decorated = new ExceptionHandlingWebHandler(decorated,  this.exceptionHandlers);
     *          // 创建HttpWebHandlerAdapter的适配器,它也是HttpHandler类型,就是将WebHandler对WebHandler进行适配
     * 		    HttpWebHandlerAdapter adapted = new HttpWebHandlerAdapter(decorated);
     * 		    // 在HttpHandler的基础上,赋予它管理session,编码解码等等的功能
     * 		    if (this.sessionManager != null) {
     * 		    	adapted.setSessionManager(this.sessionManager);
     *          }
     * 		    if (this.codecConfigurer != null) {
     * 		    	adapted.setCodecConfigurer(this.codecConfigurer);
     *          }
     * 		    if (this.applicationContext != null) {
     * 		    	adapted.setApplicationContext(this.applicationContext);
     *          }
     * 		    adapted.afterPropertiesSet();
     * 		    return (this.httpHandlerDecorator != null ? this.httpHandlerDecorator.apply(adapted) : adapted);
     *        }
     * </pre>
     */
    // 如果存在自定义HttpHandler接口Bean,那么所有的RouterFunction就会失效
    // 因为HttpHandler是Web服务器处理所有请求的处理类,存在这个类的时候,就不会在处理RouterFunction的bean
    // 如果不存在HttpHandler,最终也会将所有的RouterFunction组装,封装成HttpHandler
    // @Bean
    public HttpHandler httpHandler(RouterHandler routerHandler) {
        // 定义路由,也就是API接口,最终使用HttpHandler进行注册
        RouterFunction<ServerResponse> function = route()
                .POST("/router/aa", accept(APPLICATION_JSON), routerHandler::fileUpload)
                .POST("/router/bb", accept(APPLICATION_JSON), routerHandler::fileDownload)
                .GET("/router/cc", accept(APPLICATION_JSON), routerHandler::simpleTestApi)
                .build();
        HandlerStrategies strategies = HandlerStrategies.builder()
                // 添加编码解码器
                .codecs(configurer -> {
                })
                // 添加异常处理器
                .exceptionHandler(webExceptionHandler())
                // 添加过滤器
                // .webFilter(WebFilter())
                // 添加视图解析器
                .viewResolver(new FreeMarkerViewResolver())
                .build();
        // 将RouterFunction转换为HttpHandler类型
        return toHttpHandler(function, strategies);
    }

    @Bean
    public WebExceptionHandler webExceptionHandler() {
        return new WebExceptionHandler() {
            @Override
            public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
                log.error("出现异常了:{}", ex.getMessage());
                return exchange.getResponse().writeWith(
                        Mono.just(exchange.getResponse().bufferFactory().wrap(("Hello" + ex.getMessage()).getBytes(StandardCharsets.UTF_8))
                        ));
            }
        };
    }

    // 这是WebFlux的过滤器
    // @Bean
    public WebFilter WebFilter() {
        return new WebFilter() {
            @Override
            public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
                if (exchange.getRequest().getQueryParams().containsKey("luck")) {
                    return chain.filter(exchange);
                }
                log.error("没有luck请求参数,拦截请求");
                return Mono.empty();
            }
        };

    }
}





package luck.spring.boot.webflux;

import cn.hutool.core.util.RandomUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.FileSystemResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.codec.json.Jackson2CodecSupport;
import org.springframework.http.codec.multipart.FilePart;
import org.springframework.http.codec.multipart.FormFieldPart;
import org.springframework.http.codec.multipart.Part;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.web.reactive.function.BodyExtractors;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;

import java.io.File;

import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.reactive.function.server.ServerResponse.ok;

@Slf4j
@Component
public class RouterHandler {
    public static final String fileDir = "spring-boot-source-webflux-study\\src\\main\\resources";

    public Mono<ServerResponse> simpleBody(ServerRequest request) {
        // get请求没有body
        // Mono<String> body = request.bodyToMono(String.class);
        // 直接返回结果,将Mono中的数据响应映射为Mono<ServerResponse>类型
        return ok().contentType(APPLICATION_JSON).body(Mono.just("Luck"), String.class);
    }

    public Mono<ServerResponse> requestBody(ServerRequest request) {
        // 处理POST请求,获取Body中的数据,将Body中的数据转换为Mono类型
        // 一下两种方式都可以获取Body数据
        // Mono<Entity> body = request.bodyToMono(Entity.class);
        return request.body(BodyExtractors.toMono(Entity.class))
                // 得到Body中的数据,进行逻辑操作
                // 操作完成最终将结果进行映射为最终的响应类型Mono<ServerResponse>
                .flatMap(entity -> ok()
                        .contentType(APPLICATION_JSON)
                        .bodyValue(entity.setName(entity.getName() + RandomUtil.randomString(3))));
    }

    public Mono<ServerResponse> fromDataBody(ServerRequest request) {
        // 处理GET请求,获取表单类型的数据
        // 不是multipart/form-data,这个是处理文件请求
        // 而是x-www-from-urlencoded
        return request.formData()
                // 将表单数据进行逻辑处理,得到最终结果,最终映射为Mono<ServerResponse>类型
                .flatMap(map -> ok()
                        .contentType(APPLICATION_JSON)
                        .bodyValue(map));
    }

    public Mono<ServerResponse> fileUpload(ServerRequest request) {
        // 和BodyExtractors.toMultipartData()效果一样
        Mono<MultiValueMap<String, Part>> mono = request.multipartData();
        return mono.flatMap(map -> {
            map.toSingleValueMap().values().forEach(part -> {
                if (part instanceof FormFieldPart fieldPart) {
                    log.error("field value is {}", fieldPart.value());
                }
                if (part instanceof FilePart filePart) {
                    log.error("filename is {}", filePart.filename());
                    // 所有操作都是异步的,必须要订阅才会触发保存操作
                    filePart.transferTo(new File(fileDir, filePart.filename())).subscribe();
                }
            });
            return ok().contentType(APPLICATION_JSON).bodyValue("上传成功");
        });
    }

    public Mono<ServerResponse> fileDownload(ServerRequest request) {
        File file = new File(fileDir, "test.png");
        return ok()
                // 设置响应类型
                .contentType(MediaType.APPLICATION_OCTET_STREAM)
                .contentLength(file.length())
                // 设置文件头
                .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + file.getName())
                // 设置响应体
                .body(BodyInserters.fromResource(new FileSystemResource(file)));
    }

    public Mono<ServerResponse> simpleTestApi(ServerRequest request) {
        // 设置提示信息,和Hibernate类型,给编码解码器的提供商设置额外的信息
        // 例如,为json编解码器设置对应的响应分组
        log.error("hello simple test api");
        return ok()
                .contentType(APPLICATION_JSON)
                // 一种是map的形式,一种是单个设置的形式,key相同会被覆盖
                .hints(map -> {
                    map.put(Jackson2CodecSupport.JSON_VIEW_HINT, Entity.UpdateJsonView.class);
                })
                .hint(Jackson2CodecSupport.JSON_VIEW_HINT, Entity.AddJsonView.class)
                .bodyValue(new Entity("Luck", 100));
    }
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值