/** * 从 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`);