package vip.mate.config; 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.security.SecurityRequirement; import io.swagger.v3.oas.models.security.SecurityScheme; import io.swagger.v3.oas.models.servers.Server; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.List; /** * SpringDoc OpenAPI 全局配置。 *

* 项目已依赖 springdoc-openapi-starter-webmvc-ui(见根 pom 的 * {@code springdoc.version}),但此前没有任何 OpenAPI 配置 Bean,导致 * Swagger UI 缺少标题/描述、缺少安全方案(Authorize 按钮不可用)。 * 本类补齐这些全局元信息,让现有 Controller 上已有的 * {@code @Tag} / {@code @Operation} 注解直接可用。 * *

安全方案

* 两种 token 都通过标准 {@code Authorization: Bearer } 头传入, * {@link JwtAuthFilter} 按 token 前缀分发:JWT 以 {@code eyJ} 开头, * Personal Access Token 以 {@code mc_} 开头(PAT_PREFIX)。因此 Swagger * UI 的 Authorize 按钮只需填入任意一种 token 即可。 * *

注意:Swagger 当前公开可访问

* {@link SecurityConfig#filterChain} 中 {@code /api/**} 要求认证,但 * {@code /swagger-ui*}、{@code /v3/api-docs*}、{@code /webjars/**} 落到 * {@code .anyRequest().permitAll()},即 Swagger UI 当前是公开的。如生产环境 * 需要收口,应在 SecurityConfig 显式加规则,而不是改本类。 * *

未做的事(与「全局配置 + 安全方案」范围一致)

* 不逐个 Controller 补 {@code @Parameter} / {@code @ApiResponse} / * {@code @Schema} / 公开端点的 {@code @SecurityRequirements({})} opt-out。 * 这些属于「关键端点注解」增强档,留作后续。 * * @author MateClaw Team */ @Configuration public class OpenApiConfig { /** HTTP Bearer 安全方案的引用键,与 {@link Components#getSecuritySchemes()} 中的登记名一致。 */ public static final String BEARER_AUTH = "bearerAuth"; @Bean public OpenAPI mateclawOpenAPI( @Value("${mateclaw.openapi.title:MateClaw REST API}") String title, @Value("${mateclaw.openapi.description:#{null}}") String description, @Value("${mateclaw.openapi.version:1.0}") String version, @Value("${mateclaw.openapi.server-url:}") String serverUrl) { Info info = new Info() .title(title) .version(version) .description(defaultIfBlank(description, "" + "MateClaw 多用户 AI Agent 平台的 REST API。" + "所有业务端点使用 /api/v1 前缀,绝大多数 JSON 响应走 {code,msg,data} 统一信封。" + "点击右上角 Authorize 并粘贴 JWT 或 Personal Access Token (mc_) 即可调试受保护端点。" + "完整人读文档见部署地址的 /docs 页面。")); Components components = new Components() .addSecuritySchemes(BEARER_AUTH, bearerScheme( "JWT 或 Personal Access Token。两种都通过 Authorization: Bearer 头传入," + "服务端按前缀分发:JWT 以 eyJ 开头,PAT 以 mc_ 开头。" + "EventSource 不支持自定义请求头,SSE 流式端点可用 ?token= 查询参数替代。")); OpenAPI openAPI = new OpenAPI() .info(info) .components(components) // 默认所有端点需要鉴权;公开端点(登录、SSE 等)在 SecurityConfig 中放行, // Swagger 上会仍标注锁图标,但不影响实际调用。 .addSecurityItem(new SecurityRequirement().addList(BEARER_AUTH)); if (serverUrl != null && !serverUrl.isBlank()) { openAPI.servers(List.of(new Server().url(serverUrl))); } // serverUrl 为空时不显式配置 —— SpringDoc 默认从请求 host 推导, // 避免「Try it out」打到错误地址。 return openAPI; } private SecurityScheme bearerScheme(String description) { return new SecurityScheme() .type(SecurityScheme.Type.HTTP) .scheme("bearer") .bearerFormat("JWT") .description(description); } private static String defaultIfBlank(String value, String fallback) { return (value == null || value.isBlank()) ? fallback : value; } }