Spring Boot函数式编程:轻量级路由函数替代传统Controller

一、函数式路由的崛起背景

在Spring Boot的传统开发模式中,我们习惯使用@RestController和@RequestMapping等注解来构建Web接口。这种方式简洁明了,但随着项目规模扩大,Controller类可能变得臃肿,注解配置也变得复杂。

函数式编程模型为Spring Web开发带来了全新的思路。它通过HandlerFunction和RouterFunction这两个核心概念,以声明式、函数式的方式定义路由,代码更加简洁、直观,特别适合构建轻量级、响应式的Web服务。

二、核心概念解析

2.1 HandlerFunction:请求处理的核心

HandlerFunction是一个函数接口,接收ServerRequest并返回ServerResponse。它类似于传统Controller中带有@RequestMapping注解的方法体。

bash 复制代码
// 函数式接口定义
@FunctionalInterface
public interface HandlerFunction<T extends ServerResponse> {
    T handle(ServerRequest request) throws Exception;
}

2.2 RouterFunction:路由定义器

RouterFunction用于将HTTP请求路由到对应的HandlerFunction。Spring提供了RouterFunctions工具类来简化路由定义。

三、实战:基于Servlet的阻塞式路由

3.1 定义HandlerFunction

bash 复制代码
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.function.*;

import java.util.List;

import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.servlet.function.ServerResponse.*;

@Component
public class PersonHandler {
    
    private final PersonRepository personRepository;
    
    public PersonHandler(PersonRepository personRepository) {
        this.personRepository = personRepository;
    }
    
    // 查询所有人
    public ServerResponse listPeople(ServerRequest request) {
        List<Person> people = personRepository.findAll();
        return ok()
            .contentType(APPLICATION_JSON)
            .body(people);
    }
    
    // 创建人员
    public ServerResponse createPerson(ServerRequest request) throws Exception {
        Person person = request.body(Person.class);
        personRepository.save(person);
        return ok().build();
    }
    
    // 根据ID查询
    public ServerResponse getPerson(ServerRequest request) {
        Long id = Long.valueOf(request.pathVariable("id"));
        Person person = personRepository.findById(id).orElse(null);
        
        if (person != null) {
            return ok()
                .contentType(APPLICATION_JSON)
                .body(person);
        } else {
            return notFound().build();
        }
    }
}

3.2 定义RouterFunction

bash 复制代码
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.function.*;

import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.servlet.function.RequestPredicates.accept;

@Configuration
public class RouterConfig {
    
    @Bean
    public RouterFunction<ServerResponse> personRouter(PersonHandler handler) {
        return RouterFunctions.route()
            .GET("/person/{id}", accept(APPLICATION_JSON), handler::getPerson)
            .GET("/person", accept(APPLICATION_JSON), handler::listPeople)
            .POST("/person", handler::createPerson)
            .build();
    }
}

四、实战:基于WebFlux的响应式路由

4.1 添加依赖

bash 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>

4.2 响应式HandlerFunction

bash 复制代码
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.*;
import reactor.core.publisher.Mono;

@Component
public class UserHandler {
    
    public Mono<ServerResponse> getById(ServerRequest request) {
        String id = request.pathVariable("id");
        return ServerResponse.ok()
            .bodyValue("Received ID: " + id);
    }
    
    public Mono<ServerResponse> getByParam(ServerRequest request) {
        String name = request.queryParam("name")
            .orElse("Guest");
        return ServerResponse.ok()
            .bodyValue("Hello, " + name);
    }
    
    public Mono<ServerResponse> createUser(ServerRequest request) {
        return request.bodyToMono(User.class)
            .flatMap(user -> 
                ServerResponse.ok()
                    .bodyValue("User created: " + user.getName())
            );
    }
}

4.3 响应式路由配置

bash 复制代码
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.function.server.*;

@Configuration
public class WebFluxRouterConfig {
    
    @Bean
    public RouterFunction<ServerResponse> routes(UserHandler handler) {
        return RouterFunctions.route()
            .GET("/user/{id}", handler::getById)
            .GET("/user/param", handler::getByParam)
            .POST("/user", handler::createUser)
            .build();
    }
}

五、进阶特性

5.1 参数验证

bash 复制代码
import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;

@Component
public class ValidatedPersonHandler {
    
    private final Validator validator;
    
    public Mono<ServerResponse> createPerson(ServerRequest request) {
        return request.bodyToMono(Person.class)
            .doOnNext(this::validate)
            .flatMap(person -> 
                ServerResponse.ok()
                    .bodyValue("Validated: " + person.getName())
            );
    }
    
    private void validate(Person person) {
        Errors errors = new BeanPropertyBindingResult(
            person, "person");
        validator.validate(person, errors);
        
        if (errors.hasErrors()) {
            throw new ServerWebInputException(
                "Validation failed: " + errors.toString());
        }
    }
}

5.2 嵌套路由

bash 复制代码
@Bean
public RouterFunction<ServerResponse> nestedRoutes(PersonHandler handler) {
    return RouterFunctions.route()
        .path("/api", builder -> builder
            .path("/v1", v1Builder -> v1Builder
                .path("/person", personBuilder -> personBuilder
                    .GET("/{id}", handler::getPerson)
                    .GET("/", handler::listPeople)
                    .POST("/", handler::createPerson)
                )
            )
        )
        .build();
}

5.3 路由过滤

bash 复制代码
@Bean
public RouterFunction<ServerResponse> filteredRoutes(PersonHandler handler) {
    return RouterFunctions.route()
        .before(request -> {
            // 前置处理:添加请求头
            System.out.println("Before handling: " + request.uri());
            return ServerRequest.from(request)
                .header("X-Request-Timestamp", 
                    String.valueOf(System.currentTimeMillis()))
                .build();
        })
        .GET("/person/{id}", handler::getPerson)
        .after((request, response) -> {
            // 后置处理:记录响应
            System.out.println("After handling: " + response.statusCode());
            return response;
        })
        .filter((request, next) -> {
            // 过滤器:身份验证
            if (!request.headers().header("Authorization").isEmpty()) {
                return next.handle(request);
            } else {
                return ServerResponse.status(HttpStatus.UNAUTHORIZED).build();
            }
        })
        .build();
}

5.4 静态资源重定向

bash 复制代码
@Bean
public RouterFunction<ServerResponse> spaRoutes() {
    // 匹配需要重定向到前端的请求
    RequestPredicate spaPredicate = RequestPredicates.path("/api/**")
        .or(RequestPredicates.path("/error"))
        .negate();
    
    // 重定向到前端资源
    return RouterFunctions.route()
        .resource(spaPredicate, 
            new ClassPathResource("static/index.html"))
        .build();
}

六、与传统Controller对比

特性 传统注解Controller 函数式路由
代码风格 面向对象 函数式编程
路由配置 注解驱动 代码声明式
灵活性 中等
学习曲线 平缓 较陡
测试便利性 中等
适用场景 传统Web应用 微服务、API网关

七、最佳实践建议

渐进式迁移:对于现有项目,可以先在新增接口中使用函数式路由,逐步替换

统一风格:在团队中保持一致性,要么全部使用注解,要么全部使用函数式

合理分层:将业务逻辑与路由逻辑分离,保持HandlerFunction的简洁性

充分利用组合:利用函数式编程的组合特性,创建可复用的路由组件

文档注释:由于缺乏注解的元数据,需要添加详细的文档注释

八、总结

Spring Boot的函数式路由为开发者提供了另一种构建Web应用的选择。它通过HandlerFunction和RouterFunction这两个核心抽象,实现了更加灵活、声明式的路由定义方式。虽然学习成本相对较高,但对于需要构建轻量级、高性能、响应式服务的场景,函数式路由无疑是一个强大的工具。

在选择技术方案时,应根据项目需求、团队技术栈和长期维护成本综合考虑。函数式路由特别适合:

构建API网关

微服务架构中的轻量级服务

需要高度可测试性的项目

对性能有严格要求的应用

无论选择哪种方式,Spring Boot都为我们提供了强大而灵活的工具,让Web开发变得更加高效和愉快。

实战演练

bash 复制代码
<!--web 模块 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

换成

bash 复制代码
<!--webflux 模块 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>

大家熟知的 controller

bash 复制代码
package com.cqcloud.platform.controller;

import com.cqcloud.platform.common.log.annotation.SysLog;
import com.cqcloud.platform.common.matter.utils.Result;
import com.cqcloud.platform.common.security.annotation.HasPermission;
import com.cqcloud.platform.entity.SysDept;
import com.cqcloud.platform.service.SysDeptService;
import com.cqcloud.platform.vo.SysTreeVo;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpHeaders;
import org.springframework.web.bind.annotation.*;

import java.util.List;

/**
 * 系统基础信息--部门管理模块
 *
 * @author weimeilayer@gmail.com ✨
 * @date 💓💕 2023年5月20日 🐬🐇 💓💕
 */
@RestController
@RequiredArgsConstructor
@RequestMapping("/dept")
@SecurityRequirement(name = HttpHeaders.AUTHORIZATION)
@Tag(description = "系统基础信息--部门管理模块操作接口", name = "系统基础信息--部门管理模块操作接口")
public class SysDeptController {

	private final SysDeptService sysDeptService;

	/**
	 * 通过ID查询
	 * @param id ID
	 * @return SysDept
	 */
	@GetMapping("/{id}")
	@Operation(summary = "通过ID查询", description = "通过ID查询")
	public Result<SysDept> getById(@PathVariable String id) {
		return Result.ok(sysDeptService.getById(id));
	}

	/**
	 * 添加部门
	 * @param sysDept 实体
	 * @return 是否成功
	 */
	@SysLog("添加部门")
	@PostMapping
	@HasPermission("sys_dept_add")
	@Operation(summary = "添加部门", description = "添加部门")
	public Result<Boolean> save(@Valid @RequestBody SysDept sysDept) {
		return Result.ok(sysDeptService.save(sysDept));
	}

	/**
	 * 编辑部门
	 * @param sysDept 实体
	 * @return 是否成功
	 */
	@SysLog("编辑部门")
	@PutMapping
	@HasPermission("sys_dept_edit")
	@Operation(summary = "编辑部门", description = "编辑部门")
	public Result<Boolean> update(@Valid @RequestBody SysDept sysDept) {
		return Result.ok(sysDeptService.updateById(sysDept));
	}

	/**
	 * 删除部门
	 * @param id 部门ID
	 * @return 是否成功
	 */
	@SysLog("删除部门")
	@DeleteMapping("/{id}")
	@HasPermission("sys_dept_del")
	@Operation(summary = "删除部门", description = "删除部门")
	public Result<?> removeById(@PathVariable String id) {
		return sysDeptService.removeDeptById(id) ? Result.success("删除成功") : Result.failed("删除失败");
	}

	/**
	 * 获取部门的本级及所有下级部门列表
	 * @return 部门的本级及所有下级部门列表
	 */
	@GetMapping(value = "/getDescendantList/{deptId}")
	@Operation(summary = "获取部门的本级及所有下级部门列表", description = "获取部门的本级及所有下级部门列表")
	public Result<List<SysDept>> getDescendantList(@PathVariable String deptId) {
		return Result.ok(sysDeptService.listDescendant(deptId));
	}

	/**
	 * 获取部门树集合
	 * @param parentId 父级ID
	 * @param lazyLoading 是否懒加载
	 * @return 部门树
	 */
	@GetMapping("/getDeptTree")
	@Parameters({ @Parameter(name = "parentId", description = "父级编号", example = "-1"),
			@Parameter(name = "lazyLoading", description = "是否懒加载", required = true, example = "true") })
	@Operation(summary = "获取部门树集合", description = "获取部门树集合")
	public Result<List<SysDept>> getDeptTree(String parentId, Boolean lazyLoading) {
		return Result.ok(sysDeptService.getDeptTree(parentId, lazyLoading));
	}

	/**
	 * 采用栈方式获取部门树
	 * @param parentId 父级ID
	 * @param lazyLoading 是否懒加载
	 * @return 部门树
	 */
	@GetMapping("/getTree")
	@Parameters({ @Parameter(name = "parentId", description = "父级编号", example = "-1"),
			@Parameter(name = "lazyLoading", description = "是否懒加载", required = true, example = "true") })
	@Operation(summary = "采用栈方式获取部门树", description = "采用栈方式获取部门树")
	public Result<List<SysTreeVo>> getTree(String parentId, Boolean lazyLoading) {
		return Result.ok(sysDeptService.getTree(parentId, lazyLoading));
	}

}

转换后的

bash 复制代码
package com.cqcloud.platform.config;


import com.cqcloud.platform.filter.OperationLogFilter;
import com.cqcloud.platform.handler.PermissionHandler;
import com.cqcloud.platform.handler.SysDeptHandler;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.function.RouterFunction;
import org.springframework.web.servlet.function.ServerResponse;

import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.servlet.function.RequestPredicates.*;
import static org.springframework.web.servlet.function.RouterFunctions.route;

/**
 * 部门管理模块路由配置
 * @author weimeilayer@gmail.com ✨
 * @date 💓💕 2023年5月20日 🐬🐇 💓💕
 */
@Configuration
@RequiredArgsConstructor
public class CompleteDeptRouterConfig {

    private final SysDeptHandler handler;
    private final PermissionHandler permissionHandler;
    private final OperationLogFilter logFilter;

    @Bean
    public RouterFunction<ServerResponse> deptRoutes() {
        return route()
                // 查询接口(只需要日志)
                .GET("/dept/{id}", accept(APPLICATION_JSON), handler::getById)
                .filter(logFilter.logOperation("查询部门详情"))

                .GET("/dept/getDescendantList/{deptId}", accept(APPLICATION_JSON), handler::getDescendantList)
                .filter(logFilter.logOperation("获取下级部门列表"))

                .GET("/dept/getDeptTree", accept(APPLICATION_JSON), handler::getDeptTree)
                .filter(logFilter.logOperation("获取部门树"))

                .GET("/dept/getTree", accept(APPLICATION_JSON), handler::getTree)
                .filter(logFilter.logOperation("获取部门树(栈方式)"))

                // 添加部门(权限+日志)
                .POST("/dept", accept(APPLICATION_JSON), handler::save)
                .filter(permissionHandler.requirePermission("sys_dept_add"))
                .filter(logFilter.logOperation("添加部门"))

                // 编辑部门(权限+日志)
                .PUT("/dept", accept(APPLICATION_JSON), handler::update)
                .filter(permissionHandler.requirePermission("sys_dept_edit"))
                .filter(logFilter.logOperation("编辑部门"))

                // 删除部门(权限+日志)
                .DELETE("/dept/{id}", handler::removeById)
                .filter(permissionHandler.requirePermission("sys_dept_del"))
                .filter(logFilter.logOperation("删除部门"))
                .build();
    }
}
bash 复制代码
package com.cqcloud.platform.filter;

import com.cqcloud.platform.common.log.annotation.SysLog;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.function.HandlerFilterFunction;
import org.springframework.web.servlet.function.ServerRequest;
import org.springframework.web.servlet.function.ServerResponse;

/**
 * 操作日志过滤器
 * @author weimeilayer@gmail.com ✨
 * @date 💓💕 2023年5月20日 🐬🐇 💓💕
 */
@Component
public class OperationLogFilter {

    /**
     * 创建日志过滤器
     * @param operation 操作描述
     */
    public HandlerFilterFunction<ServerResponse, ServerResponse> logOperation(String operation) {
        return (request, next) -> {
            // 记录操作前
            logBefore(operation, request);

            try {
                ServerResponse response = next.handle(request);
                // 记录操作成功
                logSuccess(operation, request, response);
                return response;
            } catch (Exception e) {
                // 记录操作失败
                logError(operation, request, e);
                throw e;
            }
        };
    }

    /**
     * 从@SysLog注解值创建过滤器
     */
    public HandlerFilterFunction<ServerResponse, ServerResponse> fromSysLog(SysLog sysLog) {
        return (request, next) -> {
            System.out.println("[操作日志] " + sysLog.value() + ": " + request.method() + " " + request.path());
            return next.handle(request);
        };
    }

    private void logBefore(String operation, ServerRequest request) {
        System.out.println("[操作开始] " + operation + " - " + request.method() + " " + request.path());
    }

    private void logSuccess(String operation, ServerRequest request, ServerResponse response) {
        System.out.println("[操作成功] " + operation + " - 状态码: " + response.statusCode());
    }

    private void logError(String operation, ServerRequest request, Exception e) {
        System.out.println("[操作失败] " + operation + " - 错误: " + e.getMessage());
    }
}
bash 复制代码
package com.cqcloud.platform.handler;

import com.cqcloud.platform.common.matter.utils.Result;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.function.HandlerFilterFunction;
import org.springframework.web.servlet.function.ServerRequest;
import org.springframework.web.servlet.function.ServerResponse;

import java.util.Map;

import static org.springframework.http.MediaType.APPLICATION_JSON;

/**
 * 权限检查处理器
 * @author weimeilayer@gmail.com ✨
 * @date 💓💕 2023年5月20日 🐬🐇 💓💕
 */
@Component
public class PermissionHandler {

    /**
     * 检查用户权限
     * @param permission 需要的权限编码
     * @return true=有权限,false=无权限
     */
    public boolean hasPermission(String permission) {
        // 实际项目中应从SecurityContext或Session中获取当前用户权限
        // 这里模拟返回true,实际需要实现具体逻辑
        return true;
    }

    /**
     * 创建权限过滤器
     * @param permission 权限编码
     */
    public HandlerFilterFunction<ServerResponse, ServerResponse> requirePermission(String permission) {
        return (request, next) -> {
            if (hasPermission(permission)) {
                return next.handle(request);
            } else {
                return ServerResponse.status(HttpStatus.FORBIDDEN)
                        .contentType(APPLICATION_JSON)
                        .body(Result.failed("权限不足,需要权限: " + permission));
            }
        };
    }

    /**
     * 检查多个权限(满足任意一个即可)
     * @param permissions 权限编码数组
     */
    public HandlerFilterFunction<ServerResponse, ServerResponse> requireAnyPermission(String... permissions) {
        return (request, next) -> {
            for (String permission : permissions) {
                if (hasPermission(permission)) {
                    return next.handle(request);
                }
            }
            return ServerResponse.status(HttpStatus.FORBIDDEN)
                    .contentType(APPLICATION_JSON)
                    .body(Result.failed("权限不足"));
        };
    }

    /**
     * 动态权限检查(从请求参数中获取权限编码)
     */
    public HandlerFilterFunction<ServerResponse, ServerResponse> dynamicPermissionCheck() {
        return (request, next) -> {
            // 从请求头、参数或路径中提取权限编码
            String requiredPermission = extractPermissionFromRequest(request);

            if (requiredPermission != null && !hasPermission(requiredPermission)) {
                return ServerResponse.status(HttpStatus.FORBIDDEN)
                        .contentType(APPLICATION_JSON)
                        .body(Result.failed("权限不足"));
            }
            return next.handle(request);
        };
    }

    private String extractPermissionFromRequest(ServerRequest request) {
        // 根据请求路径和方法映射权限
        Map<String, String> permissionMap = Map.of(
                "POST /dept", "sys_dept_add",
                "PUT /dept", "sys_dept_edit",
                "DELETE /dept", "sys_dept_del"
        );

        String key = request.method().name() + " " + request.path();
        return permissionMap.get(key);
    }
}
bash 复制代码
package com.cqcloud.platform.handler;

import com.cqcloud.platform.common.matter.utils.Result;
import com.cqcloud.platform.entity.SysDept;
import com.cqcloud.platform.service.SysDeptService;
import com.cqcloud.platform.vo.SysTreeVo;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.function.ServerRequest;
import org.springframework.web.servlet.function.ServerResponse;
import java.util.List;

import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.servlet.function.ServerResponse.*;

/**
 * 系统基础信息--部门管理模块
 * @author weimeilayer@gmail.com ✨
 * @date 💓💕 2023年5月20日 🐬🐇 💓💕
 */
@Component
@RequiredArgsConstructor
public class SysDeptHandler {

    private final SysDeptService deptService;

    // 通过ID查询
    public ServerResponse getById(ServerRequest request) {
        String id = request.pathVariable("id");
        SysDept dept = deptService.getById(id);
        return ok().contentType(APPLICATION_JSON)
                .body(Result.ok(dept));
    }

    // 添加部门
    public ServerResponse save(ServerRequest request) throws Exception {
        SysDept dept = request.body(SysDept.class);
        boolean success = deptService.save(dept);
        return ok().contentType(APPLICATION_JSON)
                .body(Result.ok(success));
    }

    // 编辑部门
    public ServerResponse update(ServerRequest request) throws Exception {
        SysDept dept = request.body(SysDept.class);
        boolean success = deptService.updateById(dept);
        return ok().contentType(APPLICATION_JSON)
                .body(Result.ok(success));
    }

    // 删除部门
    public ServerResponse removeById(ServerRequest request) {
        String id = request.pathVariable("id");
        boolean success = deptService.removeDeptById(id);

        if (success) {
            return ok().contentType(APPLICATION_JSON)
                    .body(Result.success("删除成功"));
        } else {
            return badRequest().contentType(APPLICATION_JSON)
                    .body(Result.failed("删除失败"));
        }
    }

    // 获取下级部门列表
    public ServerResponse getDescendantList(ServerRequest request) {
        String deptId = request.pathVariable("deptId");
        List<SysDept> list = deptService.listDescendant(deptId);
        return ok().contentType(APPLICATION_JSON)
                .body(Result.ok(list));
    }

    // 获取部门树(原getDeptTree)
    public ServerResponse getDeptTree(ServerRequest request) {
        String parentId = request.param("parentId").orElse("-1");
        Boolean lazyLoading = request.param("lazyLoading")
                .map(Boolean::parseBoolean)
                .orElse(true);

        List<SysDept> tree = deptService.getDeptTree(parentId, lazyLoading);
        return ok().contentType(APPLICATION_JSON)
                .body(Result.ok(tree));
    }

    // 获取部门树(栈方式)
    public ServerResponse getTree(ServerRequest request) {
        String parentId = request.param("parentId").orElse("-1");
        Boolean lazyLoading = request.param("lazyLoading")
                .map(Boolean::parseBoolean)
                .orElse(true);

        List<SysTreeVo> tree = deptService.getTree(parentId, lazyLoading);
        return ok().contentType(APPLICATION_JSON)
                .body(Result.ok(tree));
    }
}
bash 复制代码
package com.cqcloud.platform.swagger;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.media.Content;
import io.swagger.v3.oas.models.media.MediaType;
import io.swagger.v3.oas.models.media.Schema;
import io.swagger.v3.oas.models.parameters.RequestBody;
import io.swagger.v3.oas.models.responses.ApiResponse;
import lombok.RequiredArgsConstructor;
import org.springdoc.core.customizers.OpenApiCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.function.RouterFunction;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

/**
 * 函数式路由的Swagger文档处理器
 */
@Configuration
@RequiredArgsConstructor
public class SwaggerRouterCustomizer {
    
    @Bean
    public OpenApiCustomizer routerSwaggerCustomizer() {
        return openApi -> {
            // 自动为路由函数添加文档
            addDeptOperations(openApi);
            addDictOperations(openApi);
        };
    }
    
    /**
     * 部门管理API文档
     */
    private void addDeptOperations(OpenAPI openApi) {
        // GET /dept/{id}
        io.swagger.v3.oas.models.Operation getById = new io.swagger.v3.oas.models.Operation()
            .summary("通过ID查询")
            .description("通过ID查询部门详情")
            .addParametersItem(new io.swagger.v3.oas.models.parameters.Parameter()
                .name("id")
                .in(String.valueOf(ParameterIn.PATH))
                .required(true)
                .schema(new Schema().type("string")))
            .responses(new io.swagger.v3.oas.models.responses.ApiResponses()
                .addApiResponse("200", new ApiResponse()
                    .description("成功")
                    .content(new Content().addMediaType("application/json", 
                        new MediaType().schema(new Schema().$ref("#/components/schemas/Result<<SysDept>>"))))));
        
        // POST /dept
        io.swagger.v3.oas.models.Operation addDept = new io.swagger.v3.oas.models.Operation()
            .summary("添加部门")
            .description("添加新的部门")
            .requestBody(new RequestBody()
                .required(true)
                .content(new Content().addMediaType("application/json",
                    new MediaType().schema(new Schema().$ref("#/components/schemas/SysDept")))))
            .responses(new io.swagger.v3.oas.models.responses.ApiResponses()
                .addApiResponse("200", new ApiResponse()
                    .description("成功")
                    .content(new Content().addMediaType("application/json",
                        new MediaType().schema(new Schema().$ref("#/components/schemas/Result<<boolean>>"))))));
        
        // 添加到OpenAPI
        if (openApi.getPaths() == null) {
            openApi.setPaths(new io.swagger.v3.oas.models.Paths());
        }
        
        openApi.getPaths()
            .addPathItem("/dept/{id}", new io.swagger.v3.oas.models.PathItem().get(getById))
            .addPathItem("/dept", new io.swagger.v3.oas.models.PathItem().post(addDept));
    }
    
    private void addDictOperations(OpenAPI openApi) {
        // 类似方式添加字典管理的API文档
    }
}
相关推荐
Mr.朱鹏2 小时前
超时订单处理方案实战指南【完整版】
java·spring boot·redis·spring·rabbitmq·rocketmq·订单
趁月色小酌***2 小时前
JAVA 知识点总结2
java·开发语言
虾说羊2 小时前
java中的代理详解
java
野生技术架构师2 小时前
2025年Java面试八股文大全(附PDF版)
java·面试·pdf
Coder_Boy_2 小时前
SpringAI与LangChain4j的智能应用-(实践篇4)
java·人工智能·spring boot·langchain
CC.GG2 小时前
【Qt】常用控件----QWidget属性
java·数据库·qt
Drift_Dream2 小时前
Node.js 第二课:用核心模块构建你的第一个服务器
前端·后端
superman超哥2 小时前
仓颉Actor模型的实现机制深度解析
开发语言·后端·python·c#·仓颉