kindergarten_java/reading-platform-frontend/scripts/fetch-openapi.js
Claude Opus 4.6 ddd3d8c152 feat: 套餐管理功能增强
新增功能:
- 后端新增套餐状态管理端点(下架、重新发布、撤销审核)
- 前端套餐详情页增加完整状态流转操作
- 前端套餐管理增加课程包添加/移除功能
- 修复套餐详情页空值引用错误
- 新增 collections.ts API 封装模块

后端变更:
- AdminCourseCollectionController 新增 archive/republish/withdraw 端点
- CourseCollectionService 新增对应服务方法

前端变更:
- collections.ts 新增 API 封装
- CollectionDetailView 增加状态管理按钮和课程包管理
- CollectionListView 增加状态筛选和操作按钮
- 修复 route 配置和 API 调用路径
- 合并远程更新,解决 TenantListView.vue 冲突

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-18 18:19:25 +08:00

90 lines
2.7 KiB
JavaScript

/**
* 拉取 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);
});