163 lines
4.9 KiB
JavaScript
163 lines
4.9 KiB
JavaScript
/**
|
||
* 从 swagger.json 生成带参数的 API 接口清单 MD 文档
|
||
* 用法: node _docs/_gen_api_doc.js
|
||
*/
|
||
const fs = require("fs");
|
||
const path = require("path");
|
||
|
||
const swagger = require("./swagger.json");
|
||
const mdRaw = fs.readFileSync(path.join(__dirname, "接口说明文档-完整版-含营销等级.md"), "utf8");
|
||
|
||
// ========== 1. 解析套餐映射 ==========
|
||
const planMap = {};
|
||
let curPath = "";
|
||
for (const line of mdRaw.split("\n")) {
|
||
const m = line.match(/####.*`(POST|GET)\s+(.+?)`/);
|
||
if (m) { curPath = m[2].trim(); continue; }
|
||
const p = line.match(/对应套餐:\*\*(.+?)\*\*/);
|
||
if (p && curPath) { planMap[curPath] = p[1]; curPath = ""; }
|
||
}
|
||
|
||
// ========== 2. 解析 definitions ==========
|
||
function resolveRef(ref) {
|
||
if (!ref) return null;
|
||
const name = ref.replace("#/definitions/", "");
|
||
return swagger.definitions[name] || null;
|
||
}
|
||
|
||
function resolveType(prop) {
|
||
if (!prop) return "any";
|
||
if (prop.$ref) {
|
||
const name = prop.$ref.replace("#/definitions/", "");
|
||
return name;
|
||
}
|
||
if (prop.type === "array") {
|
||
if (prop.items) {
|
||
if (prop.items.$ref) return resolveType(prop.items) + "[]";
|
||
return (prop.items.type || "any") + "[]";
|
||
}
|
||
return "array";
|
||
}
|
||
let t = prop.type || "any";
|
||
if (prop.format) t += `(${prop.format})`;
|
||
return t;
|
||
}
|
||
|
||
function getModelFields(def) {
|
||
if (!def || !def.properties) return [];
|
||
const fields = [];
|
||
for (const [name, prop] of Object.entries(def.properties)) {
|
||
fields.push({
|
||
name,
|
||
type: resolveType(prop),
|
||
desc: (prop.description || "").trim() || "-",
|
||
});
|
||
}
|
||
return fields;
|
||
}
|
||
|
||
// ========== 3. 按 tag 分组 ==========
|
||
const tagNameMap = { "朋友": "好友", "/shop": "微信小店", "管理": "管理/授权" };
|
||
function normTag(t) { return tagNameMap[t] || t; }
|
||
|
||
const tagOrder = [
|
||
"登录", "用户", "好友", "标签", "消息", "消息回调",
|
||
"群管理", "朋友圈", "收藏", "支付", "公众号/小程序",
|
||
"企业微信", "视频号", "设备", "微信小店", "其他", "同步消息", "管理/授权",
|
||
];
|
||
|
||
const groups = {};
|
||
for (const [apiPath, methods] of Object.entries(swagger.paths)) {
|
||
for (const [method, spec] of Object.entries(methods)) {
|
||
const tag = (spec.tags && spec.tags[0]) || "未分类";
|
||
if (!groups[tag]) groups[tag] = [];
|
||
|
||
const params = spec.parameters || [];
|
||
const queryParams = params.filter((p) => p.in === "query");
|
||
const bodyParam = params.find((p) => p.in === "body");
|
||
|
||
let bodyModelName = "";
|
||
let bodyFields = [];
|
||
if (bodyParam && bodyParam.schema && bodyParam.schema.$ref) {
|
||
bodyModelName = bodyParam.schema.$ref.replace("#/definitions/", "");
|
||
const def = resolveRef(bodyParam.schema.$ref);
|
||
bodyFields = getModelFields(def);
|
||
}
|
||
|
||
groups[tag].push({
|
||
method: method.toUpperCase(),
|
||
path: apiPath,
|
||
summary: spec.summary || "",
|
||
plan: planMap[apiPath] || "-",
|
||
queryParams,
|
||
bodyModelName,
|
||
bodyFields,
|
||
});
|
||
}
|
||
}
|
||
|
||
const sortedTags = Object.keys(groups).sort((a, b) => {
|
||
const ia = tagOrder.indexOf(normTag(a));
|
||
const ib = tagOrder.indexOf(normTag(b));
|
||
return (ia === -1 ? 99 : ia) - (ib === -1 ? 99 : ib);
|
||
});
|
||
|
||
// ========== 4. 生成 MD ==========
|
||
const totalApis = Object.values(groups).reduce((s, a) => s + a.length, 0);
|
||
const planCount = {};
|
||
for (const apis of Object.values(groups)) {
|
||
for (const a of apis) {
|
||
planCount[a.plan] = (planCount[a.plan] || 0) + 1;
|
||
}
|
||
}
|
||
|
||
let out = "# API 接口清单(按模块)\n\n";
|
||
out += `> 接口总数:**${totalApis}**\n\n`;
|
||
|
||
out += "## 套餐统计\n\n";
|
||
out += "| 套餐 | 接口数 |\n|---|---:|\n";
|
||
for (const p of ["初级版", "高级版", "定制版", "白标/OEM"]) {
|
||
if (planCount[p]) out += `| ${p} | ${planCount[p]} |\n`;
|
||
}
|
||
out += "\n---\n\n";
|
||
|
||
let secIdx = 0;
|
||
for (const tag of sortedTags) {
|
||
const apis = groups[tag];
|
||
secIdx++;
|
||
const displayTag = normTag(tag);
|
||
out += `## ${secIdx}. ${displayTag}(${apis.length} 个接口)\n\n`;
|
||
|
||
apis.forEach((a, i) => {
|
||
const num = `${secIdx}.${i + 1}`;
|
||
out += `### ${num} \`${a.method} ${a.path}\` - ${a.summary}\n\n`;
|
||
out += `- 套餐:**${a.plan}**\n\n`;
|
||
|
||
// Query 参数
|
||
if (a.queryParams.length > 0) {
|
||
out += "**Query 参数**\n\n";
|
||
out += "| 参数名 | 类型 | 说明 |\n|---|---|---|\n";
|
||
for (const q of a.queryParams) {
|
||
out += `| \`${q.name}\` | ${q.type || "string"} | ${q.description || "-"} |\n`;
|
||
}
|
||
out += "\n";
|
||
}
|
||
|
||
// Body 请求体
|
||
if (a.bodyFields.length > 0) {
|
||
out += `**请求体 (${a.bodyModelName})**\n\n`;
|
||
out += "| 字段名 | 类型 | 说明 |\n|---|---|---|\n";
|
||
for (const f of a.bodyFields) {
|
||
out += `| \`${f.name}\` | \`${f.type}\` | ${f.desc} |\n`;
|
||
}
|
||
out += "\n";
|
||
}
|
||
|
||
out += "---\n\n";
|
||
});
|
||
}
|
||
|
||
const outPath = path.join(__dirname, "API接口清单-按模块.md");
|
||
fs.writeFileSync(outPath, out, "utf8");
|
||
console.log(`done: ${outPath}, ${out.split("\n").length} lines`);
|