package com.baiyue.common.openapi; import com.fasterxml.jackson.databind.type.TypeFactory; import io.swagger.v3.core.converter.ModelConverter; import io.swagger.v3.oas.models.Components; import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.info.Info; import io.swagger.v3.oas.models.parameters.HeaderParameter; 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.OperationCustomizer; import org.springdoc.core.models.GroupedOpenApi; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; /** * OpenAPI 公共自动配置,根据服务配置生成统一的接口文档元数据和 JWT 安全方案。 */ @AutoConfiguration @EnableConfigurationProperties(OpenApiProperties.class) @ConditionalOnProperty(prefix = "baiyue.openapi", name = "enabled", havingValue = "true", matchIfMissing = true) public class BaiyueOpenApiAutoConfiguration { public static final String BEARER = "bearerAuth"; @Bean OpenAPI baiyueOpenApi(OpenApiProperties p) { return new OpenAPI() .info(new Info().title(p.getTitle()).description(p.getDescription()).version(p.getVersion())) .components(new Components().addSecuritySchemes(BEARER, new SecurityScheme().name(BEARER).type(SecurityScheme.Type.HTTP) .scheme("bearer").bearerFormat("JWT"))) .addSecurityItem(new SecurityRequirement().addList(BEARER)); } @Bean OperationCustomizer correlationHeaderCustomizer() { return (operation, method) -> operation .addParametersItem(header("X-Request-Id", "Request correlation ID / 请求关联 ID", false)) .addParametersItem(header("X-Trace-Id", "Distributed trace ID / 分布式追踪 ID", false)); } /** 保证 OpenAPI 与运行时 JSON 一致:所有 Java 长整型均声明为字符串。 */ @Bean ModelConverter longAsStringModelConverter() { return (type, context, chain) -> { if (type.getType() != null) { Class rawType = TypeFactory.defaultInstance().constructType(type.getType()).getRawClass(); if (rawType == Long.class || rawType == Long.TYPE) { return new io.swagger.v3.oas.models.media.StringSchema() .description("长整型以字符串传输,避免浏览器精度丢失") .example("190000000000000001"); } } return chain.hasNext() ? chain.next().resolve(type, context, chain) : null; }; } @Bean GroupedOpenApi platformApi(OperationCustomizer correlationHeaderCustomizer) { return GroupedOpenApi.builder().group("platform") .displayName("Platform APIs / 平台端接口") .pathsToMatch("/api/platform/**", "/api/auth/**", "/api/iam/platform/**") .addOperationCustomizer(correlationHeaderCustomizer).build(); } @Bean GroupedOpenApi merchantApi(OperationCustomizer correlationHeaderCustomizer) { return GroupedOpenApi.builder().group("merchant") .displayName("Merchant APIs / 商户端接口") .pathsToMatch("/api/merchant/**", "/api/orders/**", "/api/warehouse/**", "/api/firstleg/**", "/api/lastleg/**") .addOperationCustomizer(correlationHeaderCustomizer).build(); } @Bean GroupedOpenApi pdaApi(OperationCustomizer correlationHeaderCustomizer) { return GroupedOpenApi.builder().group("pda") .displayName("PDA APIs / PDA 作业接口") .pathsToMatch("/api/pda/**") .addOperationCustomizer(correlationHeaderCustomizer).build(); } private Parameter header(String name, String description, boolean required) { return new HeaderParameter().name(name).description(description).required(required) .schema(new io.swagger.v3.oas.models.media.StringSchema()); } }