Files
wenzi/docs/reports/architecture/OPENAPI_CONFIG.md

23 KiB
Raw Blame History

🦟 蚊子项目 - OpenAPI 3.0 文档配置

📋 概述

蚊子项目使用SpringDoc OpenAPI生成OpenAPI 3.0规范的API文档支持自动生成和实时更新。

🚀 快速开始

1. 添加依赖

<!-- pom.xml -->
<properties>
    <springdoc.version>2.3.0</springdoc.version>
</properties>

<dependencies>
    <!-- SpringDoc OpenAPI -->
    <dependency>
        <groupId>org.springdoc</groupId>
        <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
        <version>${springdoc.version}</version>
    </dependency>
    
    <!-- Kubernetes支持可选 -->
    <dependency>
        <groupId>org.springdoc</groupId>
        <artifactId>springdoc-openapi-starter-common</artifactId>
        <version>${springdoc.version}</version>
    </dependency>
</dependencies>

2. 基础配置

# application-prod.yml
springdoc:
  api-docs:
    enabled: true
    path: /api-docs
    groups:
      enabled: true
  swagger-ui:
    enabled: true
    path: /swagger-ui.html
    display-operation-id: true
    display-request-duration: true
    show-extensions: true
    show-common-extensions: true
    default-models-expand-depth: 2
    default-model-expand-depth: 2
    try-it-out-enabled: true
    persist-authorization: true
    tags-sorter: alpha
    operations-sorter: alpha
  group-configs:
    - group: public
      display-name: Public APIs
      paths-to-match: /api/v1/**
    - group: internal
      display-name: Internal APIs
      paths-to-match: /api/v1/internal/**
    - group: admin
      display-name: Admin APIs
      paths-to-match: /api/v1/admin/**

3. OpenAPI配置类

package com.mosquito.project.config;

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.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 org.springframework.context.annotation.Profile;

import java.util.List;

/**
 * OpenAPI配置类
 */
@Configuration
@Profile("prod")
public class OpenApiConfig {

    @Value("${spring.application.name}")
    private String applicationName;
    
    @Value("${spring.application.version}")
    private String applicationVersion;
    
    @Value("${springdoc.api-docs.server.url}")
    private String serverUrl;

    @Bean
    public OpenAPI mosquitoOpenAPI() {
        // 安全方案定义
        SecurityScheme apiKeyScheme = new SecurityScheme()
            .type(SecurityScheme.Type.APIKEY)
            .in(SecurityScheme.In.HEADER)
            .name("X-API-Key")
            .description("API密钥认证");
        
        SecurityScheme bearerAuthScheme = new SecurityScheme()
            .type(SecurityScheme.Type.HTTP)
            .scheme("bearer")
            .bearerFormat("JWT")
            .description("JWT Token认证");

        // 安全要求
        SecurityRequirement apiKeyRequirement = new SecurityRequirement()
            .addList("API Key");
        
        SecurityRequirement bearerAuthRequirement = new SecurityRequirement()
            .addList("Bearer Auth");

        // 服务器配置
        Server server = new Server()
            .url(serverUrl)
            .description("生产环境服务器");

        // 组件配置
        Components components = new Components()
            .addSecuritySchemes("API Key", apiKeyScheme)
            .addSecuritySchemes("Bearer Auth", bearerAuthScheme);

        return new OpenAPI()
            .info(new Info()
                .title("蚊子项目 API文档")
                .description("蚊子项目推广活动管理系统的API接口文档")
                .version(applicationVersion)
                .contact(new Contact()
                    .name("蚊子项目团队")
                    .email("support@mosquito.com")
                    .url("https://mosquito.com"))
                .license(new License()
                    .name("MIT License")
                    .url("https://opensource.org/licenses/MIT")))
            .servers(List.of(server))
            .components(components)
            .addSecurityItem(apiKeyRequirement)
            .addSecurityItem(bearerAuthRequirement);
    }
}

4. 开发环境配置

package com.mosquito.project.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.oas.annotations.EnableOpenApi;
import springfox.documentation.service.*;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.Docket;

import java.util.Collections;
import java.util.List;

/**
 * Swagger配置开发环境
 */
@Configuration
@EnableOpenApi
@Profile("dev")
public class SwaggerConfig {

    @Bean
    public Docket api() {
        return new Docket(DocumentationType.OAS_30)
            .apiInfo(apiInfo())
            .securitySchemes(Collections.singletonList(apiKey()))
            .securityContexts(Collections.singletonList(securityContext()))
            .select()
            .apis(RequestHandlerSelectors.basePackage("com.mosquito.project.controller"))
            .paths(PathSelectors.any())
            .build();
    }

    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
            .title("蚊子项目 API文档")
            .description("蚊子项目推广活动管理系统的API接口文档")
            .version("2.0.0")
            .contact(new Contact(
                "蚊子项目团队",
                "https://mosquito.com",
                "support@mosquito.com"))
            .license("MIT License")
            .licenseUrl("https://opensource.org/licenses/MIT")
            .build();
    }

    private ApiKey apiKey() {
        return new ApiKey("API Key", "X-API-Key", "header");
    }

    private SecurityContext securityContext() {
        return SecurityContext.builder()
            .securityReferences(defaultAuth())
            .operationSelector(operationContext -> true)
            .build();
    }

    private List<SecurityReference> defaultAuth() {
        AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
        return Collections.singletonList(
            new SecurityReference("API Key", new AuthorizationScope[]{authorizationScope})
        );
    }
}

📖 API注解示例

1. Controller注解

package com.mosquito.project.controller;

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.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/v1/activities")
@Tag(name = "活动管理", description = "活动相关的API接口")
@SecurityRequirement(name = "API Key")
public class ActivityController {

    /**
     * 创建活动
     */
    @PostMapping
    @Operation(
        summary = "创建新活动",
        description = "创建一个新的推广活动返回活动ID",
        tags = {"活动管理"},
        operationId = "createActivity"
    )
    @ApiResponses(value = {
        @ApiResponse(
            responseCode = "201",
            description = "活动创建成功",
            content = @Content(
                mediaType = "application/json",
                schema = @Schema(implementation = Activity.class)
            )
        ),
        @ApiResponse(
            responseCode = "400",
            description = "请求参数错误",
            content = @Content(
                mediaType = "application/json",
                schema = @Schema(implementation = ApiResponse.class)
            )
        ),
        @ApiResponse(
            responseCode = "401",
            description = "API密钥无效",
            content = @Content(
                mediaType = "application/json",
                schema = @Schema(implementation = ApiResponse.class)
            )
        )
    })
    public ResponseEntity<ApiResponse<Activity>> createActivity(
        @Parameter(
            name = "request",
            description = "活动创建请求",
            required = true,
            schema = @Schema(implementation = CreateActivityRequest.class)
        )
        @RequestBody CreateActivityRequest request) {
        // 实现逻辑
    }

    /**
     * 获取活动详情
     */
    @GetMapping("/{id}")
    @Operation(
        summary = "获取活动详情",
        description = "根据活动ID获取活动的详细信息",
        tags = {"活动管理"},
        operationId = "getActivityById"
    )
    @ApiResponses(value = {
        @ApiResponse(
            responseCode = "200",
            description = "活动详情",
            content = @Content(
                mediaType = "application/json",
                schema = @Schema(implementation = Activity.class)
            )
        ),
        @ApiResponse(
            responseCode = "404",
            description = "活动不存在",
            content = @Content(
                mediaType = "application/json",
                schema = @Schema(implementation = ApiResponse.class)
            )
        )
    })
    public ResponseEntity<ApiResponse<Activity>> getActivity(
        @Parameter(
            name = "id",
            description = "活动ID",
            required = true,
            example = "1"
        )
        @PathVariable Long id) {
        // 实现逻辑
    }

    /**
     * 更新活动
     */
    @PutMapping("/{id}")
    @Operation(
        summary = "更新活动信息",
        description = "更新指定活动的信息",
        tags = {"活动管理"},
        operationId = "updateActivity"
    )
    @ApiResponses(value = {
        @ApiResponse(
            responseCode = "200",
            description = "更新成功",
            content = @Content(
                mediaType = "application/json",
                schema = @Schema(implementation = Activity.class)
            )
        ),
        @ApiResponse(
            responseCode = "404",
            description = "活动不存在"
        )
    })
    public ResponseEntity<ApiResponse<Activity>> updateActivity(
        @PathVariable Long id,
        @RequestBody UpdateActivityRequest request) {
        // 实现逻辑
    }

    /**
     * 删除活动
     */
    @DeleteMapping("/{id}")
    @Operation(
        summary = "删除活动",
        description = "删除指定的活动",
        tags = {"活动管理"},
        operationId = "deleteActivity"
    )
    @ApiResponses(value = {
        @ApiResponse(
            responseCode = "204",
            description = "删除成功"
        ),
        @ApiResponse(
            responseCode = "404",
            description = "活动不存在"
        )
    })
    public ResponseEntity<Void> deleteActivity(@PathVariable Long id) {
        // 实现逻辑
    }

    /**
     * 获取排行榜
     */
    @GetMapping("/{id}/leaderboard")
    @Operation(
        summary = "获取活动排行榜",
        description = "获取指定活动的排行榜数据,支持分页",
        tags = {"活动管理"},
        operationId = "getLeaderboard"
    )
    @Parameters({
        @Parameter(
            name = "id",
            description = "活动ID",
            required = true
        ),
        @Parameter(
            name = "page",
            description = "页码从0开始",
            required = false,
            schema = @Schema(type = "integer", defaultValue = "0")
        ),
        @Parameter(
            name = "size",
            description = "每页大小",
            required = false,
            schema = @Schema(type = "integer", defaultValue = "20", maximum = "100")
        ),
        @Parameter(
            name = "topN",
            description = "只显示前N名如果设置则忽略分页",
            required = false,
            schema = @Schema(type = "integer")
        )
    })
    @ApiResponses(value = {
        @ApiResponse(
            responseCode = "200",
            description = "排行榜数据",
            content = @Content(
                mediaType = "application/json",
                schema = @Schema(implementation = LeaderboardResponse.class)
            )
        )
    })
    public ResponseEntity<ApiResponse<LeaderboardResponse>> getLeaderboard(
        @PathVariable Long id,
        @RequestParam(defaultValue = "0") int page,
        @RequestParam(defaultValue = "20") int size,
        @RequestParam(required = false) Integer topN) {
        // 实现逻辑
    }
}

2. 模型注解

package com.mosquito.project.dto;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import java.time.LocalDateTime;

/**
 * 活动创建请求
 */
@Data
@Schema(description = "活动创建请求")
public class CreateActivityRequest {

    @Schema(
        description = "活动名称",
        example = "新年推广活动",
        required = true
    )
    @NotBlank(message = "活动名称不能为空")
    @Size(min = 2, max = 100, message = "活动名称长度必须在2-100个字符之间")
    private String name;

    @Schema(
        description = "活动开始时间",
        example = "2024-01-01T10:00:00",
        required = true
    )
    @NotNull(message = "开始时间不能为空")
    private LocalDateTime startTime;

    @Schema(
        description = "活动结束时间",
        example = "2024-01-31T23:59:59",
        required = true
    )
    @NotNull(message = "结束时间不能为空")
    private LocalDateTime endTime;

    @Schema(
        description = "活动描述",
        example = "新年期间的用户推广活动"
    )
    private String description;

    @Schema(
        description = "活动状态",
        example = "draft",
        allowableValues = {"draft", "active", "completed", "cancelled"}
    )
    private String status;
}
package com.mosquito.project.dto;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.time.LocalDateTime;
import java.util.List;

/**
 * 活动响应
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "活动响应")
public class Activity {

    @Schema(description = "活动ID", example = "1")
    private Long id;

    @Schema(description = "活动名称", example = "新年推广活动")
    private String name;

    @Schema(description = "活动开始时间", example = "2024-01-01T10:00:00")
    private LocalDateTime startTime;

    @Schema(description = "活动结束时间", example = "2024-01-31T23:59:59")
    private LocalDateTime endTime;

    @Schema(description = "活动状态", example = "active")
    private String status;

    @Schema(description = "活动描述")
    private String description;

    @Schema(description = "创建时间", example = "2024-01-01T08:00:00")
    private LocalDateTime createdAt;

    @Schema(description = "更新时间", example = "2024-01-01T08:00:00")
    private LocalDateTime updatedAt;
}

3. 枚举注解

package com.mosquito.project.domain;

import io.swagger.v3.oas.annotations.media.Schema;

/**
 * 活动状态枚举
 */
@Schema(description = "活动状态")
public enum ActivityStatus {

    @Schema(description = "草稿状态")
    DRAFT("draft", "草稿"),

    @Schema(description = "进行中")
    ACTIVE("active", "进行中"),

    @Schema(description = "已完成")
    COMPLETED("completed", "已完成"),

    @Schema(description = "已取消")
    CANCELLED("cancelled", "已取消");

    private final String code;
    private final String description;

    ActivityStatus(String code, String description) {
        this.code = code;
        this.description = description;
    }

    public String getCode() {
        return code;
    }

    public String getDescription() {
        return description;
    }
}

🌐 访问API文档

Swagger UI

开发环境: http://localhost:8080/swagger-ui.html
测试环境: https://test-api.mosquito.com/swagger-ui.html
生产环境: https://api.mosquito.com/swagger-ui.html

OpenAPI JSON

http://localhost:8080/api-docs
https://api.mosquito.com/api-docs

OpenAPI YAML

http://localhost:8080/api-docs.yaml
https://api.mosquito.com/api-docs.yaml

🔒 安全配置

1. API密钥认证

@SecurityRequirement(name = "API Key")
@Operation(summary = "需要API密钥的接口")
public ResponseEntity<?> securedEndpoint() {
    // 实现逻辑
}

2. JWT认证

@SecurityRequirement(name = "Bearer Auth")
@Operation(summary = "需要JWT Token的接口")
public ResponseEntity<?> jwtSecuredEndpoint() {
    // 实现逻辑
}

3. 多重安全要求

@Operation(
    summary = "多种认证方式",
    security = {
        @SecurityRequirement(name = "API Key"),
        @SecurityRequirement(name = "Bearer Auth")
    }
)
public ResponseEntity<?> multipleAuthEndpoint() {
    // 实现逻辑
}

📚 导出API文档

1. 导出JSON格式

curl -o openapi.json http://localhost:8080/api-docs

2. 导出YAML格式

curl -o openapi.yaml http://localhost:8080/api-docs.yaml

3. 使用OpenAPI Generator生成客户端

# 生成TypeScript客户端
openapi-generator-cli generate \
  -i openapi.json \
  -g typescript-axios \
  -o ./client/typescript

# 生成Java客户端
openapi-generator-cli generate \
  -i openapi.json \
  -g java \
  -o ./client/java

# 生成Python客户端
openapi-generator-cli generate \
  -i openapi.json \
  -g python \
  -o ./client/python

🧪 测试API文档

1. 使用Swagger UI测试

// 在浏览器中访问Swagger UI
// 1. 点击 "Authorize" 按钮
// 2. 输入API密钥: "your-api-key"
// 3. 点击 "Authorize"
// 4. 现在可以使用 "Try it out" 功能测试API

2. 使用Postman测试

// Postman Pre-request Script
pm.request.headers.add({
    key: 'X-API-Key',
    value: 'your-api-key'
});

// 导入OpenAPI到Postman
// 1. 打开Postman
// 2. File -> Import
// 3. 选择 openapi.json 文件
// 4. Postman会自动创建Collection

🔧 自定义配置

1. 自定义响应示例

@Operation(
    summary = "创建活动",
    responses = {
        @ApiResponse(
            responseCode = "201",
            description = "活动创建成功",
            content = @Content(
                mediaType = "application/json",
                schema = @Schema(implementation = Activity.class),
                examples = @ExampleObject(
                    name = "示例响应",
                    value = "{\"id\":1,\"name\":\"新年推广活动\",\"status\":\"active\"}"
                )
            )
        )
    }
)

2. 自定义请求示例

@Operation(
    summary = "创建活动",
    requestBody = @RequestBody(
        description = "活动创建请求",
        content = @Content(
            mediaType = "application/json",
            schema = @Schema(implementation = CreateActivityRequest.class),
            examples = {
                @ExampleObject(
                    name = "标准请求",
                    value = "{\"name\":\"新年推广活动\",\"startTime\":\"2024-01-01T10:00:00\",\"endTime\":\"2024-01-31T23:59:59\"}"
                )
            }
        )
    )
)

3. 自定义错误响应

@Operation(
    summary = "创建活动",
    responses = {
        @ApiResponse(
            responseCode = "400",
            description = "请求参数错误",
            content = @Content(
                mediaType = "application/json",
                schema = @Schema(implementation = ErrorResponse.class),
                examples = @ExampleObject(
                    name = "参数错误示例",
                    value = "{\"code\":\"VALIDATION_ERROR\",\"message\":\"活动名称不能为空\",\"details\":{\"name\":\"活动名称不能为空\"}}"
                )
            )
        )
    }
)

📊 API文档最佳实践

1. 分组组织

@Tag(name = "活动管理", description = "活动相关的API接口")
@Tag(name = "用户管理", description = "用户相关的API接口")
@Tag(name = "分享功能", description = "分享相关的API接口")

2. 清晰的描述

@Operation(
    summary = "创建新活动", // 简短的标题
    description = """
    创建一个新的推广活动。
    
    **注意事项:**
    - 活动名称不能为空
    - 开始时间必须早于结束时间
    - 活动时长不能超过90天
    """ // 详细的描述
)

3. 错误处理

@ApiResponses(value = {
    @ApiResponse(responseCode = "200", description = "操作成功"),
    @ApiResponse(responseCode = "400", description = "请求参数错误"),
    @ApiResponse(responseCode = "401", description = "API密钥无效"),
    @ApiResponse(responseCode = "403", description = "权限不足"),
    @ApiResponse(responseCode = "404", description = "资源不存在"),
    @ApiResponse(responseCode = "429", description = "请求过于频繁"),
    @ApiResponse(responseCode = "500", description = "服务器内部错误")
})

🔄 自动化文档更新

1. CI/CD集成

# .github/workflows/docs.yml
name: Update API Documentation

on:
  push:
    branches: [main]

jobs:
  update-docs:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Generate OpenAPI Spec
        run: |
          curl -o openapi.json http://localhost:8080/api-docs
          curl -o openapi.yaml http://localhost:8080/api-docs.yaml
      
      - name: Commit Documentation
        run: |
          git config --local user.email "action@github.com"
          git config --local user.name "GitHub Action"
          git add openapi.json openapi.yaml
          git commit -m "Update API documentation"
          git push

2. 自动生成客户端SDK

#!/bin/bash
# generate-client-sdks.sh

# 生成TypeScript客户端
echo "Generating TypeScript client..."
openapi-generator-cli generate \
  -i openapi.json \
  -g typescript-axios \
  -o ./client/typescript

# 生成Java客户端
echo "Generating Java client..."
openapi-generator-cli generate \
  -i openapi.json \
  -g java \
  -o ./client/java

echo "Client SDKs generated successfully!"

OpenAPI文档配置版本: v2.0.0
最后更新: 2026-01-22
维护团队: API Team