104 lines
3.9 KiB
JavaScript
104 lines
3.9 KiB
JavaScript
import { mkdir, writeFile } from "node:fs/promises";
|
|
import { dirname, resolve } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
const swaggerUrl = process.env.XUEBANG_SWAGGER_URL
|
|
?? "http://api.xuebangsoft.net/v2/api-docs";
|
|
const scriptDirectory = dirname(fileURLToPath(import.meta.url));
|
|
const projectDirectory = resolve(scriptDirectory, "..");
|
|
const catalogPath = resolve(
|
|
projectDirectory,
|
|
"src/main/resources/xuebang-get-api-catalog.json",
|
|
);
|
|
const referencePath = resolve(
|
|
projectDirectory,
|
|
"docs/xuebang-get-api-catalog.md",
|
|
);
|
|
|
|
const blockedPaths = new Map([
|
|
["/token/{clientId}", "AccessToken 由后端鉴权组件管理"],
|
|
["/api/user/findUserForLogin", "登录密码不得通过通用查询接口传输"],
|
|
["/api/wxapp/getStudentToken", "学生 Token 属于敏感凭证"],
|
|
]);
|
|
|
|
const response = await fetch(swaggerUrl);
|
|
if (!response.ok) {
|
|
throw new Error(`Unable to download ${swaggerUrl}: HTTP ${response.status}`);
|
|
}
|
|
const swagger = await response.json();
|
|
const operations = [];
|
|
|
|
for (const [path, pathItem] of Object.entries(swagger.paths ?? {})) {
|
|
if (!pathItem.get) continue;
|
|
const operation = pathItem.get;
|
|
const parameters = (operation.parameters ?? [])
|
|
.filter((parameter) => parameter.in !== "header")
|
|
.map((parameter) => ({
|
|
name: parameter.name,
|
|
in: parameter.in,
|
|
required: Boolean(parameter.required),
|
|
type: parameter.type ?? parameter.schema?.type ?? "string",
|
|
format: parameter.format ?? parameter.schema?.format ?? null,
|
|
description: parameter.description ?? "",
|
|
enumValues: parameter.enum ?? parameter.schema?.enum ?? [],
|
|
}));
|
|
const blockedReason = blockedPaths.get(path) ?? null;
|
|
operations.push({
|
|
operationId: operation.operationId,
|
|
path,
|
|
summary: operation.summary ?? operation.description ?? operation.operationId,
|
|
tags: operation.tags ?? [],
|
|
parameters,
|
|
pageable: parameters.some((parameter) => parameter.name === "pageNo")
|
|
&& parameters.some((parameter) => parameter.name === "pageSize"),
|
|
exposed: blockedReason === null,
|
|
blockedReason,
|
|
});
|
|
}
|
|
|
|
operations.sort((left, right) => left.path.localeCompare(right.path, "zh-CN"));
|
|
const ids = new Set();
|
|
for (const operation of operations) {
|
|
if (!operation.operationId) throw new Error(`Missing operationId: ${operation.path}`);
|
|
if (ids.has(operation.operationId)) throw new Error(`Duplicate operationId: ${operation.operationId}`);
|
|
ids.add(operation.operationId);
|
|
}
|
|
|
|
const generatedAt = new Date().toISOString();
|
|
const catalog = {
|
|
source: swaggerUrl,
|
|
generatedAt,
|
|
getCount: operations.length,
|
|
exposedCount: operations.filter((operation) => operation.exposed).length,
|
|
operations,
|
|
};
|
|
|
|
const markdown = [
|
|
"# 学邦 GET 接口目录",
|
|
"",
|
|
`- 来源:${swaggerUrl}`,
|
|
`- 生成时间:${generatedAt}`,
|
|
`- GET 接口:${catalog.getCount}`,
|
|
`- WorkBuddy 可查询:${catalog.exposedCount}`,
|
|
"",
|
|
"| operationId | 分类 | 接口说明 | 路径 | 必填参数 | 可分页 | 状态 |",
|
|
"|---|---|---|---|---|---|---|",
|
|
...operations.map((operation) => {
|
|
const required = operation.parameters
|
|
.filter((parameter) => parameter.required)
|
|
.map((parameter) => `\`${parameter.name}\``)
|
|
.join("、") || "无";
|
|
return `| \`${operation.operationId}\` | ${operation.tags.join(" / ")} | ${operation.summary} | \`${operation.path}\` | ${required} | ${operation.pageable ? "是" : "否"} | ${operation.exposed ? "可查询" : `后端专用:${operation.blockedReason}`} |`;
|
|
}),
|
|
"",
|
|
"完整参数定义以 `src/main/resources/xuebang-get-api-catalog.json` 为准。",
|
|
"",
|
|
].join("\n");
|
|
|
|
await mkdir(dirname(catalogPath), { recursive: true });
|
|
await mkdir(dirname(referencePath), { recursive: true });
|
|
await writeFile(catalogPath, `${JSON.stringify(catalog, null, 2)}\n`, "utf8");
|
|
await writeFile(referencePath, markdown, "utf8");
|
|
|
|
console.log(`Generated ${operations.length} GET operations (${catalog.exposedCount} exposed).`);
|