信创联调血泪史:我用AI+国产库Schema,10秒生成“高仿真”API文档,前端和测试当场跪了! 一、痛点剖析为什么基于国产库Schema生成API样例这么难在动手写代码前咱得先搞清楚“敌人”长啥样。你以为用JDBC连上数据库getColumns()一调事情就完了太天真了1.1 国产库元数据的“方言陷阱”特性 Oracle/MySQL 达梦 DM8 人大金仓 KingbaseES 墨夶吐槽大文本 CLOB / LONGTEXT CLOB / TEXT CLOB / TEXT 驱动返回的 DATA_TYPE 可能是 2005也可能是 12判断逻辑得写一堆 if-else。空间数据 SDO_GEOMETRY GEOMETRY GEOMETRY 这种自定义类型JDBC标准接口根本认不出来得去查系统表字段注释 user_col_comments USER_COL_COMMENTS pg_description / 系统视图 拿注释的方法完全不同不封装好代码直接变成“意大利面条”。大小写 默认大写 默认大写 默认小写 表名传进去是 User查出来是 user匹配不上直接返回空1.2 “高仿真”Mock数据的“灵魂拷问”就算你拿到了字段类型你怎么生成Mock数据String 类型是生成 “test”还是生成 “13800138000”手机号BigDecimal 类型是生成 0.00还是生成 9999.99金额靠随机数那叫“垃圾数据”不叫“测试数据”真正的Mock数据必须结合字段的中文名Comment和业务语义让大模型来“脑补”最合理的值。graph TDA[国产库 Schema] -- B(JDBC 元数据采集)B -- C{方言适配器}C -- D[标准化 Table/Column 模型]D -- E[JSON Schema 映射]E -- F{AI Prompt 组装}F -- G[LLM 生成高仿真 Mock JSON]G -- H[OpenAPI 3.0 规范输出]二、架构设计AI驱动的API样例生成引擎老码农写代码绝不上来就撸main方法。咱得先定架构。这个引擎我分了四层保证高内聚、低耦合。层级 职责 核心技术栈 墨夶点评元数据采集层 扒光国产库的表结构、类型、注释 JDBC DatabaseMetaData 脏活累活都在这必须做方言兼容Schema映射层 将DB类型转为标准的JSON Schema 策略模式, 类型映射字典 承上启下把CLOB、GEOMETRY转成前端能懂的格式AI Prompt层 组装Prompt让大模型生成Mock数据 Spring AI, PromptTemplate 核心大脑Prompt写得不好AI就给你胡编乱造文档输出层 拼装成标准的 OpenAPI 3.0 JSON Swagger Core, Jackson 最终产物直接丢给Knife4j或YApi渲染三、核心代码实战极度详尽建议收藏反复观看老铁们前方高能代码量极大每一行都是生产环境打磨出来的。3.1 依赖准备Maven配置com.dameng DmJdbcDriver18 8.1.3.62 com.kingbase8 kingbase8 8.6.0!-- Spring AI (用于调用大模型) -- dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-openai-spring-boot-starter/artifactId version1.0.0-M1/version /dependency !-- OpenAPI/Swagger 模型构建 -- dependency groupIdio.swagger.core.v3/groupId artifactIdswagger-models/artifactId version2.2.19/version /dependency3.2 元数据采集层扒光国产库的“底裤”这是最脏最累的活。我们要用JDBC的 DatabaseMetaData 获取表和字段信息并兼容达梦和金仓的注释获取方式。package com.moda.apigen.collector;import lombok.Data;import lombok.Builder;import lombok.extern.slf4j.Slf4j;import org.springframework.jdbc.core.JdbcTemplate;import org.springframework.stereotype.Component;import java.sql.*;import java.util.ArrayList;import java.util.List;/**国产库 Schema 元数据采集器⚠️ 重点兼容 达梦DM8 和 人大金仓KingbaseES 的元数据差异*/Slf4jComponentpublic class DomesticSchemaCollector {private final JdbcTemplate jdbcTemplate; public DomesticSchemaCollector(JdbcTemplate jdbcTemplate) { this.jdbcTemplate jdbcTemplate; } /** 采集指定表的完整 Schema 信息包含字段、类型、注释 * param tableName 表名建议传小写金仓默认小写达梦默认大写内部做兼容 return 表结构模型 */ public TableSchema collectTableSchema(String tableName) { Connection conn null; try { conn jdbcTemplate.getDataSource().getConnection(); DatabaseMetaData metaData conn.getMetaData(); // 技巧获取当前连接的数据库产品名称用于方言判断 String dbName metaData.getDatabaseProductName().toLowerCase(); boolean isKingbase dbName.contains(kingbase); boolean isDM dbName.contains(dm); // ⚠️ 边界处理大小写问题 // 金仓默认小写达梦默认大写。如果用户传了 User可能两边都查不到 // 这里采用“大小写不敏感”的查询策略部分驱动支持不支持则查两次 String searchTableName isDM ? tableName.toUpperCase() : tableName.toLowerCase(); TableSchema.TableSchemaBuilder tableBuilder TableSchema.builder() .tableName(tableName) .columns(new ArrayList()); // 1. 获取表注释 (Table Comment) String tableComment getTableComment(conn, searchTableName, isKingbase, isDM); tableBuilder.tableComment(tableComment); // 2. 获取字段信息 (Columns) try (ResultSet rs metaData.getColumns(null, null, searchTableName, null)) { while (rs.next()) { String columnName rs.getString(COLUMN_NAME); int dataType rs.getInt(DATA_TYPE); // java.sql.Types String typeName rs.getString(TYPE_NAME); // 数据库原生类型名如 VARCHAR, CLOB, GEOMETRY int columnSize rs.getInt(COLUMN_SIZE); boolean isNullable rs.getInt(NULLABLE) DatabaseMetaData.columnNullable; // 获取字段注释核心坑点 String columnComment getColumnComment(conn, searchTableName, columnName, isKingbase, isDM); ColumnSchema column ColumnSchema.builder() .columnName(columnName) .jdbcType(dataType) .dbTypeName(typeName) .columnSize(columnSize) .nullable(isNullable) .comment(columnComment) .build(); tableBuilder.columns().add(column); } } return tableBuilder.build(); } catch (SQLException e) { log.error(采集表 {} 的 Schema 失败, tableName, e); throw new RuntimeException(Schema采集失败: e.getMessage()); } finally { if (conn ! null) { try { conn.close(); } catch (SQLException ignored) {} } } } /** 获取表注释方言兼容 */ private String getTableComment(Connection conn, String tableName, boolean isKingbase, boolean isDM) throws SQLException { String sql; if (isKingbase) { // 金仓兼容 PG使用 obj_description sql SELECT obj_description(c.oid) FROM pg_class c JOIN pg_namespace n ON n.oid c.relnamespace WHERE c.relname tableName AND n.nspname public; } else if (isDM) { // 达梦兼容 Oracle使用 USER_TAB_COMMENTS sql SELECT COMMENTS FROM USER_TAB_COMMENTS WHERE TABLE_NAME tableName ; } else { return ; } try (Statement stmt conn.createStatement(); ResultSet rs stmt.executeQuery(sql)) { return rs.next() ? rs.getString(1) : ; } } /** 获取字段注释方言兼容 */ private String getColumnComment(Connection conn, String tableName, String columnName, boolean isKingbase, boolean isDM) throws SQLException { String sql; if (isKingbase) { // 金仓关联 pg_attribute 和 pg_description sql String.format( SELECT d.description FROM pg_class c JOIN pg_attribute a ON a.attrelid c.oid LEFT JOIN pg_description d ON d.objoid c.oid AND d.objsubid a.attnum WHERE c.relname %s AND a.attname %s , tableName, columnName); } else if (isDM) { sql String.format(SELECT COMMENTS FROM USER_COL_COMMENTS WHERE TABLE_NAME %s AND COLUMN_NAME %s, tableName, columnName); } else { return ; } try (Statement stmt conn.createStatement(); ResultSet rs stmt.executeQuery(sql)) { return rs.next() ? rs.getString(1) : ; } } /** 领域模型表结构 */ Data Builder public static class TableSchema { private String tableName; private String tableComment; private ListColumnSchema columns; } /** 领域模型字段结构 */ Data Builder public static class ColumnSchema { private String columnName; private int jdbcType; private String dbTypeName; private int columnSize; private boolean nullable; private String comment; }}3.3 Schema映射层把DB类型“翻译”成JSON Schema前端和AI只认JSON Schemastring, integer, object。我们要把金仓的 CLOB、GEOMETRY 翻译成标准的 JSON Schema 格式并加上 description。package com.moda.apigen.mapper;import com.moda.apigen.collector.DomesticSchemaCollector.ColumnSchema;import com.moda.apigen.collector.DomesticSchemaCollector.TableSchema;import io.swagger.v3.oas.models.media.*;import lombok.extern.slf4j.Slf4j;import org.springframework.stereotype.Component;import java.sql.Types;/**DB Schema 到 OpenAPI JSON Schema 的映射器 设计思想将复杂的数据库类型降维打击成前端能理解的 JSON 结构*/Slf4jComponentpublic class JsonSchemaMapper {/** 将表结构转换为 OpenAPI 的 Schema 对象 */ public Schema? mapToOpenApiSchema(TableSchema tableSchema) { ObjectSchema objectSchema new ObjectSchema(); objectSchema.setName(tableSchema.getTableName()); objectSchema.setDescription(tableSchema.getTableComment()); for (ColumnSchema col : tableSchema.getColumns()) { Schema? propSchema mapColumnToSchema(col); objectSchema.addProperties(col.getColumnName(), propSchema); // ⚠️ 边界处理非空字段加入 required 列表 if (!col.isNullable()) { objectSchema.addRequiredItem(col.getColumnName()); } } return objectSchema; } /** 单个字段类型映射核心逻辑 */ private Schema? mapColumnToSchema(ColumnSchema col) { Schema? schema; String dbType col.getDbTypeName().toUpperCase(); // 1. 处理国产库特有的“大字段”和“空间字段” if (dbType.contains(CLOB) || dbType.contains(TEXT) || col.getJdbcType() Types.CLOB) { schema new StringSchema(); // 提示AI这是一个长文本字段 schema.setDescription(col.getComment() [长文本/富文本]); schema.setExample(这是一段很长的业务描述文本...); } else if (dbType.contains(BLOB) || dbType.contains(BYTEA) || col.getJdbcType() Types.BLOB) { schema new StringSchema(); schema.setFormat(byte); // Base64 编码 schema.setDescription(col.getComment() [二进制/Base64]); } else if (dbType.contains(GEOMETRY) || dbType.contains(GEOGRAPHY)) { // 避坑空间类型在 JSON 中通常用 WKT 字符串或 GeoJSON 对象表示 // 这里为了兼容前端映射为 String (WKT格式) schema new StringSchema(); schema.setDescription(col.getComment() [空间数据/WKT格式如: POINT(116.4 39.9)]); schema.setExample(POINT(116.4074 39.9042)); } // 2. 处理常规类型 else if (col.getJdbcType() Types.VARCHAR || col.getJdbcType() Types.CHAR) { schema new StringSchema(); schema.setMaxLength(col.getColumnSize()); schema.setDescription(col.getComment()); } else if (col.getJdbcType() Types.INTEGER || col.getJdbcType() Types.BIGINT) { schema new IntegerSchema(); schema.setFormat(col.getJdbcType() Types.BIGINT ? int64 : int32); schema.setDescription(col.getComment()); } else if (col.getJdbcType() Types.DECIMAL || col.getJdbcType() Types.NUMERIC) { schema new NumberSchema(); schema.setDescription(col.getComment() [金额/高精度]); } else if (col.getJdbcType() Types.DATE || col.getJdbcType() Types.TIMESTAMP) { schema new StringSchema(); schema.setFormat(col.getJdbcType() Types.DATE ? date : date-time); schema.setDescription(col.getComment()); } else if (col.getJdbcType() Types.BOOLEAN || col.getJdbcType() Types.BIT) { schema new BooleanSchema(); schema.setDescription(col.getComment()); } else { // 兜底未知类型全部当 String 处理 schema new StringSchema(); schema.setDescription(col.getComment() [未知类型: dbType ]); } return schema; }}3.4 AI Prompt层让大模型“脑补”高仿真Mock数据高光时刻这是整个引擎的灵魂我们把 JSON Schema 和 表的中文注释 喂给大模型让它生成符合业务逻辑的请求和响应样例。package com.moda.apigen.ai;import com.fasterxml.jackson.databind.JsonNode;import com.fasterxml.jackson.databind.ObjectMapper;import com.moda.apigen.collector.DomesticSchemaCollector.TableSchema;import io.swagger.v3.core.util.Json;import io.swagger.v3.oas.models.media.Schema;import lombok.RequiredArgsConstructor;import lombok.extern.slf4j.Slf4j;import org.springframework.ai.chat.ChatClient;import org.springframework.ai.chat.prompt.Prompt;import org.springframework.ai.chat.prompt.PromptTemplate;import org.springframework.stereotype.Component;import java.util.Map;/**AI Mock 数据生成器 核心设计利用 LLM 的语义理解能力生成“像真的一样”的测试数据*/Slf4jComponentRequiredArgsConstructorpublic class AiMockGenerator {private final ChatClient chatClient; private final ObjectMapper objectMapper; /** 生成高仿真的请求/响应 Mock JSON * param tableSchema 表结构 param openApiSchema 转换后的 JSON Schema param scene 场景如正常创建、更新、查询详情 return 生成的 JSON 字符串 */ public String generateMockJson(TableSchema tableSchema, Schema? openApiSchema, String scene) { // 将 OpenAPI Schema 转为 JSON 字符串作为 AI 的约束条件 String schemaJsonStr; try { schemaJsonStr Json.mapper().writeValueAsString(openApiSchema); } catch (Exception e) { log.error(Schema 序列化失败, e); return {}; } // Prompt 工程这是灵魂必须明确角色、约束、输出格式 String promptTemplate 你是一位拥有10年经验的资深测试架构师精通业务建模和测试数据构造。 【任务】 基于以下数据库表结构和 JSON Schema为 API 接口生成一个高仿真、符合业务语义的 {scene} Mock JSON 数据。 【输入信息】 表名及中文含义: {tableName} ({tableComment}) JSON Schema 约束: json {schemaJson} 【生成规则】 严格遵守 Schema字段名、类型、required 约束必须与 Schema 完全一致。 业务语义优先 看到 phone 或 手机生成真实的11位手机号如 13812345678。 看到 id_card 或 身份证生成符合校验规则的18位身份证号。 看到 amount 或 金额生成合理的金额如 99.50不要生成 0 或负数。 看到 geo 或 空间生成 WKT 格式如 POINT(116.4 39.9)。 看到 CLOB 或 长文本生成一段有意义的业务描述而不是 test123。 时间格式date-time 必须是 ISO8601 格式如 2024-05-20T10:00:00Z。 主键/ID如果是雪花算法ID生成19位数字字符串如果是UUID生成带横线的格式。 【输出要求】 只输出纯 JSON 字符串不要包含任何 Markdown 标记如 json不要任何解释说明 ; PromptTemplate template new PromptTemplate(promptTemplate); Prompt prompt template.create(Map.of( scene, scene, tableName, tableSchema.getTableName(), tableComment, tableSchema.getTableComment(), schemaJson, schemaJsonStr )); try { String response chatClient.call(prompt).getResult().getOutput().getContent(); // 清洗响应防止 AI 偶尔带出 markdown 标记 String cleanJson response.replaceAll(, ).replaceAll(, ).trim(); // ⚠️ 校验确保 AI 返回的是合法的 JSON objectMapper.readTree(cleanJson); return cleanJson; } catch (Exception e) { log.error(AI 生成 Mock 数据失败或格式不合法降级返回空对象, e); return {}; } }}3.5 文档输出层一键生成 OpenAPI 3.0 规范最后把 Schema 和 AI 生成的 Mock 数据拼装成标准的 OpenAPI JSON直接丢给 Knife4j 或 YApi 渲染javapackage com.moda.apigen.service;import com.moda.apigen.ai.AiMockGenerator;import com.moda.apigen.collector.DomesticSchemaCollector;import com.moda.apigen.collector.DomesticSchemaCollector.TableSchema;import com.moda.apigen.mapper.JsonSchemaMapper;import io.swagger.v3.oas.models.*;import io.swagger.v3.oas.models.info.Info;import io.swagger.v3.oas.models.media.Content;import io.swagger.v3.oas.models.media.MediaType;import io.swagger.v3.oas.models.media.Schema;import io.swagger.v3.oas.models.parameters.RequestBody;import io.swagger.v3.oas.models.responses.ApiResponse;import io.swagger.v3.oas.models.responses.ApiResponses;import lombok.RequiredArgsConstructor;import lombok.extern.slf4j.Slf4j;import org.springframework.stereotype.Service;import java.util.List;/**API 文档自动生成服务*/Slf4jServiceRequiredArgsConstructorpublic class ApiDocGeneratorService {private final DomesticSchemaCollector collector; private final JsonSchemaMapper schemaMapper; private final AiMockGenerator aiMockGenerator; /** 为指定表生成完整的 CRUD API 文档 * param tableName 表名 return OpenAPI 对象可序列化为 JSON/YAML */ public OpenAPI generateCrudApi(String tableName) { log.info( 开始为表 [{}] 生成 AI 驱动的 API 文档 , tableName); // 1. 采集 Schema TableSchema tableSchema collector.collectTableSchema(tableName); // 2. 映射为 JSON Schema Schema? openApiSchema schemaMapper.mapToOpenApiSchema(tableSchema); // 3. 让 AI 生成高仿真 Mock 数据 String createMockJson aiMockGenerator.generateMockJson(tableSchema, openApiSchema, 正常创建/新增); String queryMockJson aiMockGenerator.generateMockJson(tableSchema, openApiSchema, 查询详情响应); // 4. 组装 OpenAPI 3.0 规范 OpenAPI openAPI new OpenAPI(); openAPI.setInfo(new Info().title(tableSchema.getTableComment() API).version(1.0.0)); Paths paths new Paths(); // --- 生成 POST (创建) 接口 --- PathItem postPath new PathItem(); Operation postOp new Operation(); postOp.setSummary(创建 tableSchema.getTableComment()); postOp.setTags(List.of(tableSchema.getTableComment())); // 请求体 (Request Body) RequestBody requestBody new RequestBody(); requestBody.setRequired(true); Content reqContent new Content(); MediaType reqMediaType new MediaType(); reqMediaType.setSchema(openApiSchema); // 核心把 AI 生成的 Mock 数据塞进 example 里 reqMediaType.setExample(createMockJson); reqContent.addMediaType(application/json, reqMediaType); requestBody.setContent(reqContent); postOp.setRequestBody(requestBody); postPath.setPost(postOp); paths.addPathItem(/api/v1/ tableName.toLowerCase(), postPath); // --- 生成 GET (查询) 接口 --- PathItem getPath new PathItem(); Operation getOp new Operation(); getOp.setSummary(查询 tableSchema.getTableComment() 详情); getOp.setTags(List.of(tableSchema.getTableComment())); // 响应体 (Response) ApiResponses responses new ApiResponses(); ApiResponse resp200 new ApiResponse(); resp200.setDescription(成功); Content respContent new Content(); MediaType respMediaType new MediaType(); respMediaType.setSchema(openApiSchema); // 核心把 AI 生成的查询响应 Mock 数据塞进 example 里 respMediaType.setExample(queryMockJson); respContent.addMediaType(application/json, respMediaType); resp200.setContent(respContent); responses.addApiResponse(200, resp200); getOp.setResponses(responses); getPath.setGet(getOp); paths.addPathItem(/api/v1/ tableName.toLowerCase() /{id}, getPath); openAPI.setPaths(paths); log.info( API 文档生成完成); return openAPI; }}四、实战效果展示AI生成的“高仿真”数据长啥样假设我们在人大金仓里有一张表 t_loan_contract贷款合同表包含 CLOB 的合同条款和 GEOMETRY 的签约地点。AI 生成的 POST 请求 Mock 数据节选json{“contract_no”: “HT202405200001”,“borrower_name”: “张三”,“id_card”: “110105199001011234”,“loan_amount”: 50000.00,“sign_date”: “2024-05-20”,“contract_terms”: “第一条 借款金额…第二条 利率…此处省略5000字长文本AI自动脑补了合理的合同条款结构”,“sign_location”: “POINT(116.4074 39.9042)”}墨夶点评看到没id_card 是真实的18位格式loan_amount 是合理的金额contract_terms 不再是 “test”而是 AI 根据“合同条款”语义生成的长文本sign_location 是标准的 WKT 空间格式前端拿到这种数据联调简直如丝般顺滑五、避坑指南生产环境必须注意的“潜规则”老铁们代码跑通了只是第一步。要在生产环境稳如老狗下面这几个坑你必须提前填好。 坑1大模型的“幻觉”与 JSON 格式崩溃AI 有时候会“戏太多”在 JSON 前后加上 “好的这是您的数据” 这种废话导致 ObjectMapper 解析报错。墨夶建议Prompt 里必须用极其严厉的语气禁止输出 Markdown 和废话。代码里必须做正则清洗和 JSON 合法性校验失败了就重试一次retry1。 坑2金仓/达梦的“保留字”冲突如果你的表名或字段名刚好是数据库的保留字如 user, order, descJDBC 采集时可能会报错或者生成的 API 路径不符合 RESTful 规范。墨夶建议在生成 OpenAPI Path 时对表名做一层“安全过滤”遇到保留字自动加前缀如 tb_order。 坑3CLOB/BLOB 的性能陷阱在采集 Schema 时不要试图去读取 CLOB 的“默认值”或“样本数据”那会把 JDBC 连接撑爆。墨夶建议采集层只拿元数据Metadata绝不碰实际数据Data。Mock 数据全靠 AI 脑补。六、总结让数据库 Schema 成为 API 的“唯一真相源”老铁们看到这里你应该明白了API 文档不应该是开发“凭良心”手写的产物而应该是代码和数据库 Schema 的“自然投影”。在信创迁移这种“大换血”的场景下把国产库的 Schema 作为 Single Source of Truth结合 AI 的语义理解能力自动生成高仿真的 Mock 数据和 OpenAPI 规范。这不仅解放了后端的双手更拯救了前端和测试的头发6.1 墨夶的金句时间 “最好的 API 文档不是写得有多详细而是它和数据库 Schema 一样永远不会撒谎。” “用随机数造测试数据那叫敷衍用 AI 结合业务语义造数据那叫工程。” “信创迁移迁的不只是数据更是研发协同的’契约精神’。”