package cn.iocoder.yudao.framework.swagger.config; import com.github.xiaoymin.knife4j.spring.configuration.Knife4jAutoConfiguration; import io.swagger.v3.oas.models.Components; import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.info.Contact; import io.swagger.v3.oas.models.info.Info; import io.swagger.v3.oas.models.info.License; import io.swagger.v3.oas.models.media.IntegerSchema; import io.swagger.v3.oas.models.media.StringSchema; import io.swagger.v3.oas.models.parameters.Parameter; import io.swagger.v3.oas.models.security.SecurityRequirement; import io.swagger.v3.oas.models.security.SecurityScheme; import org.springdoc.core.customizers.OpenApiBuilderCustomizer; import org.springdoc.core.customizers.OperationCustomizer; import org.springdoc.core.customizers.ServerBaseUrlCustomizer; import org.springdoc.core.models.GroupedOpenApi; import org.springdoc.core.properties.SpringDocConfigProperties; import org.springdoc.core.providers.JavadocProvider; import org.springdoc.core.service.OpenAPIService; import org.springdoc.core.service.SecurityService; import org.springdoc.core.utils.PropertyResolverUtils; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Primary; import org.springframework.http.HttpHeaders; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.HEADER_TENANT_ID; /** * Swagger 自动配置类,基于 OpenAPI + Springdoc 实现。 * * 友情提示: * 1. Springdoc 文档地址:仓库 * 2. Swagger 规范,于 2015 更名为 OpenAPI 规范,本质是一个东西 * * @author 芋道源码 */ @AutoConfiguration(before = Knife4jAutoConfiguration.class) // before 原因,保证覆写的 Knife4jOpenApiCustomizer 先生效!相关 https://github.com/YunaiV/ruoyi-vue-pro/issues/954 讨论 @ConditionalOnClass({OpenAPI.class}) @EnableConfigurationProperties(SwaggerProperties.class) @ConditionalOnProperty(prefix = "springdoc.api-docs", name = "enabled", havingValue = "true", matchIfMissing = true) // 设置为 false 时,禁用 @Import(Knife4jOpenApiCustomizer.class) public class YudaoSwaggerAutoConfiguration { // ========== 全局 OpenAPI 配置 ========== @Bean public OpenAPI createApi(SwaggerProperties properties) { Map securitySchemas = buildSecuritySchemes(); OpenAPI openAPI = new OpenAPI() // 接口信息 .info(buildInfo(properties)) // 接口安全配置 .components(new Components().securitySchemes(securitySchemas)) .addSecurityItem(new SecurityRequirement().addList(HttpHeaders.AUTHORIZATION)); securitySchemas.keySet().forEach(key -> openAPI.addSecurityItem(new SecurityRequirement().addList(key))); return openAPI; } /** * API 摘要信息 */ private Info buildInfo(SwaggerProperties properties) { return new Info() .title(properties.getTitle()) .description(properties.getDescription()) .version(properties.getVersion()) .contact(new Contact().name(properties.getAuthor()).url(properties.getUrl()).email(properties.getEmail())) .license(new License().name(properties.getLicense()).url(properties.getLicenseUrl())); } /** * 安全模式,这里配置通过请求头 Authorization 传递 token 参数 */ private Map buildSecuritySchemes() { Map securitySchemes = new HashMap<>(); SecurityScheme securityScheme = new SecurityScheme() .type(SecurityScheme.Type.APIKEY) // 类型 .name(HttpHeaders.AUTHORIZATION) // 请求头的 name .in(SecurityScheme.In.HEADER); // token 所在位置 securitySchemes.put(HttpHeaders.AUTHORIZATION, securityScheme); return securitySchemes; } /** * 自定义 OpenAPI 处理器 */ @Bean @Primary // 目的:以我们创建的 OpenAPIService Bean 为主,避免一键改包后,启动报错! public OpenAPIService openApiBuilder(Optional openAPI, SecurityService securityParser, SpringDocConfigProperties springDocConfigProperties, PropertyResolverUtils propertyResolverUtils, Optional> openApiBuilderCustomizers, Optional> serverBaseUrlCustomizers, Optional javadocProvider) { return new OpenAPIService(openAPI, securityParser, springDocConfigProperties, propertyResolverUtils, openApiBuilderCustomizers, serverBaseUrlCustomizers, javadocProvider); } // ========== 分组 OpenAPI 配置 ========== /** * 所有模块的 API 分组 */ @Bean public GroupedOpenApi allGroupedOpenApi() { return buildGroupedOpenApi("all", ""); } public static GroupedOpenApi buildGroupedOpenApi(String group) { return buildGroupedOpenApi(group, group); } public static GroupedOpenApi buildGroupedOpenApi(String group, String path) { return GroupedOpenApi.builder() .group(group) .pathsToMatch("/admin-api/" + path + "/**", "/app-api/" + path + "/**") .addOperationCustomizer((operation, handlerMethod) -> operation .addParametersItem(buildTenantHeaderParameter()) .addParametersItem(buildSecurityHeaderParameter())) .addOperationCustomizer(buildOperationIdCustomizer()) .build(); } /** * 构建 Tenant 租户编号请求头参数 * * @return 多租户参数 */ private static Parameter buildTenantHeaderParameter() { return new Parameter() .name(HEADER_TENANT_ID) // header 名 .description("租户编号") // 描述 .in(String.valueOf(SecurityScheme.In.HEADER)) // 请求 header .schema(new IntegerSchema()._default(1L).name(HEADER_TENANT_ID).description("租户编号")); // 默认:使用租户编号为 1 } /** * 构建 Authorization 认证请求头参数 * * 解决 Knife4j Authorize 未生效,请求header里未包含参数 * * @return 认证参数 */ private static Parameter buildSecurityHeaderParameter() { return new Parameter() .name(HttpHeaders.AUTHORIZATION) // header 名 .description("认证 Token") // 描述 .in(String.valueOf(SecurityScheme.In.HEADER)) // 请求 header .schema(new StringSchema()._default("Bearer test1").name(HEADER_TENANT_ID).description("认证 Token")); // 默认:使用用户编号为 1 } /** * 核心:自定义OperationId生成规则,组合「类名前缀 + 方法名」 * * @see app-api 前缀不生效,都是使用 admin-api */ private static OperationCustomizer buildOperationIdCustomizer() { return (operation, handlerMethod) -> { // 1. 获取控制器类名(如 UserController) String className = handlerMethod.getBeanType().getSimpleName(); // 2. 提取类名前缀(去除 Controller 后缀,如 UserController -> User) String classPrefix = className.replaceAll("Controller$", ""); // 3. 获取方法名(如 list) String methodName = handlerMethod.getMethod().getName(); // 4. 组合生成 operationId(如 User_list) String operationId = classPrefix + "_" + methodName; // 5. 设置自定义 operationId operation.setOperationId(operationId); return operation; }; } }