/** * 拉取 OpenAPI 文档并修复 SpringDoc 生成的 oneOf schema 问题 * 解决 orval 报错: "oneOf must match exactly one schema in oneOf" */ import { writeFileSync } from 'fs'; import { join, dirname } from 'path'; import { fileURLToPath } from 'url'; const __dirname = dirname(fileURLToPath(import.meta.url)); const TARGET = 'http://localhost:8480/v3/api-docs'; const OUTPUT = join(__dirname, '../openapi.json'); async function fetchAndFix() { const res = await fetch(TARGET); if (!res.ok) throw new Error(`Failed to fetch: ${res.status} ${res.statusText}. 请确保后端已启动 (mvn spring-boot:run)`); let spec = await res.json(); const paths = spec.paths || {}; for (const path of Object.keys(paths)) { let newKey = path.replace(/\/v1\/v1\//g, '/v1/'); if (newKey === path) newKey = path.replace(/^\/api\/(?!v1\/)/, '/api/v1/'); if (newKey !== path) { paths[newKey] = paths[path]; delete paths[path]; } } fixOneOfInPaths(paths); inlineResultObjectArrayRef(paths); // 移除非法 schema 名 ResultObject[](含 [] 不符合 OpenAPI 规范) if (spec.components?.schemas) { delete spec.components.schemas['ResultObject[]']; } writeFileSync(OUTPUT, JSON.stringify(spec, null, 2)); console.log('OpenAPI spec written to:', OUTPUT); } function fixOneOfInPaths(paths) { for (const pathObj of Object.values(paths)) { for (const op of Object.values(pathObj)) { const res200 = op?.responses?.['200']; if (!res200?.content) continue; for (const media of Object.values(res200.content)) { if (media?.schema) fixSchema(media.schema); } } } } function fixSchema(schema) { if (!schema || typeof schema !== 'object') return; if (schema.oneOf && Array.isArray(schema.oneOf)) { schema.type = 'array'; schema.items = { type: 'object', additionalProperties: true }; delete schema.oneOf; } if (schema.properties) { for (const p of Object.values(schema.properties)) fixSchema(p); } if (schema.items) fixSchema(schema.items); } function inlineResultObjectArrayRef(paths) { const inlineSchema = { type: 'object', properties: { code: { type: 'integer', format: 'int32' }, message: { type: 'string' }, data: { type: 'array', items: { type: 'object', additionalProperties: true } }, }, }; for (const pathObj of Object.values(paths)) { for (const op of Object.values(pathObj)) { const res200 = op?.responses?.['200']; if (!res200?.content) continue; for (const media of Object.values(res200.content)) { if (media?.schema?.['$ref']?.endsWith('ResultObject[]')) { media.schema = { ...inlineSchema }; } } } } } fetchAndFix().catch((e) => { console.error(e); process.exit(1); });