Skip to content

Schema 构建

SchemaBuilderExampleDataGeneratorSwaggerConstants 三个类承担「schema 怎么造、示例怎么填、常量从哪来」三件事。

职责分工

  • SchemaBuilder:把 OpenAPI 的 Schema API 包装得更顺手,专注 schema 构造
  • ExampleDataGenerator:示例数据生成,与 schema 形状无关
  • SwaggerConstants:所有字符串常量与已知类型清单

三者都是 final class + 私有构造,禁止实例化。

SchemaBuilder 的核心方法

buildResultSchema

java
public static ObjectSchema buildResultSchema(Schema<?> dataSchema, String originalName) {
    ObjectSchema resultSchema = new ObjectSchema();
    resultSchema.addProperty(SwaggerConstants.RESULT_SUCCESS_FIELD, new BooleanSchema());
    resultSchema.addProperty(SwaggerConstants.RESULT_CODE_FIELD, new IntegerSchema());
    resultSchema.addProperty(SwaggerConstants.RESULT_MESSAGE_FIELD, new StringSchema());
    resultSchema.addProperty(SwaggerConstants.RESULT_DATA_FIELD,
            dataSchema != null ? dataSchema : new ObjectSchema());

    if (originalName != null && !originalName.isBlank()) {
        resultSchema.setName(String.format(SwaggerConstants.RESULT_NAME_TEMPLATE, originalName));
    }
    return resultSchema;
}

字段名全部来自 SwaggerConstants,与 Result.java 的实际字段一一对应。如果未来 Result 改字段名,只需改 SwaggerConstants 一处。

createEntityRefSchema

java
public static Schema<?> createEntityRefSchema(String entityName) {
    return new Schema<>().$ref(SwaggerConstants.COMPONENTS_SCHEMAS_PREFIX + entityName);
}

构造一个 $ref 引用,指向 #/components/schemas/<entityName>。OpenAPI 把所有命名 schema 集中放在 components.schemas,引用走 ref 而不是嵌入完整定义,避免文档膨胀。

createArraySchema

java
public static ArraySchema createArraySchema(String itemType) {
    ArraySchema arraySchema = new ArraySchema();
    arraySchema.setItems(createEntityRefSchema(itemType));
    return arraySchema;
}

数组 schema 的 items 同样走 $ref。组合 Result«List<UserDTO>» 的层级会是:Result(data=Array(items=$ref UserDTO))

buildObjectSchemaFromConfig

java
public static ObjectSchema buildObjectSchemaFromConfig(Map<String, String> fieldConfig) {
    ObjectSchema schema = new ObjectSchema();
    for (Map.Entry<String, String> entry : fieldConfig.entrySet()) {
        Schema<?> fieldSchema = createSchemaByType(entry.getValue());
        schema.addProperty(entry.getKey(), fieldSchema);
    }
    return schema;
}

createSchemaByType 支持 string / integer / boolean / array,遇到 array 默认填 UserDTO 引用。这是给框架自带演示场景用的,业务工程很少会直接调到。

ExampleDataGenerator 的核心方法

生成 Result 完整示例

java
public static Map<String, Object> generateCompleteResultExample(Object dataExample) {
    Map<String, Object> example = generateBaseResultExample();
    example.put(SwaggerConstants.RESULT_DATA_FIELD, dataExample != null ? dataExample : new LinkedHashMap<>());
    return example;
}

public static Map<String, Object> generateBaseResultExample() {
    Map<String, Object> example = new LinkedHashMap<>();
    example.put(SwaggerConstants.RESULT_SUCCESS_FIELD, SwaggerConstants.DEFAULT_SUCCESS);
    example.put(SwaggerConstants.RESULT_CODE_FIELD, SwaggerConstants.DEFAULT_SUCCESS_CODE);
    example.put(SwaggerConstants.RESULT_MESSAGE_FIELD, SwaggerConstants.DEFAULT_SUCCESS_MESSAGE);
    return example;
}

LinkedHashMap 而非普通 HashMap,保证字段在 Swagger UI 中的渲染顺序固定为「success → code → message → data」。

按实体类型生成示例

java
public static Object generateEntityExample(String entityType) {
    if (entityType == null || !SwaggerConstants.SUPPORTED_ENTITY_TYPES.contains(entityType)) {
        return new LinkedHashMap<>();
    }
    switch (entityType) {
        case SwaggerConstants.USER_DTO_TYPE:
            return generateUserExample();
        default:
            return new LinkedHashMap<>();
    }
}

SUPPORTED_ENTITY_TYPES 当前只有 UserDTO。未识别的类型返回空对象,避免空指针、避免误生成误导性的示例数据。

SwaggerConstants 的内容索引

分类关键常量
Result 字段RESULT_SUCCESS_FIELD / RESULT_CODE_FIELD / RESULT_MESSAGE_FIELD / RESULT_DATA_FIELD / RESULT_FIELDS
媒体类型JSON_MEDIA_TYPE_KEY = "json" / APPLICATION_JSON
Schema 引用COMPONENTS_SCHEMAS_PREFIX = "#/components/schemas/" / RESULT_VOID_SUFFIX / RESULT_NAME_TEMPLATE = "Result«%s»"
默认值DEFAULT_SUCCESS / DEFAULT_SUCCESS_CODE / DEFAULT_SUCCESS_MESSAGE
方法名特化SEARCH_USERS_FIELDS / USER_STATS_FIELDS / METHOD_SCHEMA_CONFIG
实体类型USER_DTO_TYPE / SUPPORTED_ENTITY_TYPES

字段名与 forge-apiResult.java 字段保持手工同步,这是一个已知的耦合点:未来如果 Result 改字段,必须改这里。

扩展点

让示例数据覆盖新的实体类型

业务方有 OrderDTO 想在文档里看到完整示例:

  1. 修改 SwaggerConstants.SUPPORTED_ENTITY_TYPES 加入 "OrderDTO"
  2. ExampleDataGenerator.generateEntityExample 的 switch 里加一个 case
  3. 编写 generateOrderExample() 方法返回示例 Map

理想情况下,框架应该让业务方在外部注入 Map<String, Supplier<Object>> 而无需修改源码。这是后续可演进的方向。

让 schema 包装识别新的容器类型

例如 PageResult<T> 这种「双层包装」的业务场景,文档里希望看到 Result«PageResult«UserDTO»»。需要改 GlobalResultOperationCustomizer.inferSchemaFromHandlerReturnType,识别 PageResult 类型并构造对应的嵌套 schema。

把字段名改成驼峰之外的格式

某些团队约定字段名是 snake_case。改动点:

  • SwaggerConstants 修改字段名常量
  • 注意 Result.java 也必须同步改,因为字段名是序列化键名

设计取舍

选项选择理由
工具类形态final + 私有构造阻止实例化,符合阿里规范
字段名来源集中在 SwaggerConstants改一处影响全模块;与 Result 的同步靠手工
示例数据内置 + switch 分发实现简单;扩展性差,待重构为可注入策略
LinkedHashMap保证字段渲染顺序固定
类型推断兜底不识别的类型用空对象避免误导性示例,保持文档的「不知道就空着」原则