kindergarten_java/reading-platform-frontend/scripts/fetch-openapi.js

108 lines
3.4 KiB
JavaScript
Raw Normal View History

/**
* 拉取 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[]'];
}
// 修复成长记录 images 字段:统一为 array of string避免 SpringDoc 误生成为 string
fixGrowthRecordImagesSchema(spec.components?.schemas);
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);
}
/** 将 GrowthRecordCreateRequest/GrowthRecordUpdateRequest 的 images 统一为 array of string */
function fixGrowthRecordImagesSchema(schemas) {
if (!schemas) return;
const arrayOfString = {
type: 'array',
items: { type: 'string', description: '图片 URL' },
description: '图片 URL 列表',
};
for (const name of ['GrowthRecordCreateRequest', 'GrowthRecordUpdateRequest']) {
const s = schemas[name];
if (s?.properties?.images?.type === 'string') {
schemas[name].properties.images = arrayOfString;
}
}
}
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);
});