1
This commit is contained in:
378
_docs/merge_biz_into_swagger.js
Normal file
378
_docs/merge_biz_into_swagger.js
Normal file
@@ -0,0 +1,378 @@
|
||||
/**
|
||||
* 将管理端 / 开放鉴权接口合并进 swagger.json(运行: node _docs/merge_biz_into_swagger.js)
|
||||
*/
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
|
||||
const swaggerPath = path.join(__dirname, "swagger.json");
|
||||
const doc = JSON.parse(fs.readFileSync(swaggerPath, "utf8"));
|
||||
|
||||
const TAG_ADMIN = ["管理端-业务订阅"];
|
||||
const TAG_OPEN_AUTH = ["开放接口-鉴权"];
|
||||
|
||||
function res200() {
|
||||
return { 200: { description: "框架统一包装;成功时 code=0,业务数据在 data" } };
|
||||
}
|
||||
|
||||
function post(summary, ref, tags = TAG_ADMIN) {
|
||||
const params = [
|
||||
{
|
||||
in: "body",
|
||||
name: "body",
|
||||
schema: ref ? { $ref: `#/definitions/${ref}` } : { type: "object", description: "JSON 请求体" },
|
||||
},
|
||||
];
|
||||
return { post: { tags, summary, parameters: params, responses: res200() } };
|
||||
}
|
||||
|
||||
function postEmpty(summary, tags = TAG_ADMIN) {
|
||||
return { post: { tags, summary, parameters: [], responses: res200() } };
|
||||
}
|
||||
|
||||
function get(summary, queryList, tags = TAG_ADMIN) {
|
||||
return {
|
||||
get: {
|
||||
tags,
|
||||
summary,
|
||||
parameters: queryList.map((q) => ({
|
||||
in: "query",
|
||||
name: q.n,
|
||||
type: q.t || "string",
|
||||
required: q.req !== false,
|
||||
description: q.d || "",
|
||||
})),
|
||||
responses: res200(),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const definitions = {
|
||||
BizAdminPageRequest: {
|
||||
type: "object",
|
||||
title: "BizAdminPageRequest",
|
||||
description:
|
||||
"通用分页/筛选:body 可直接为 param,或形如 { param: { pageOption, seachOption } }",
|
||||
properties: {
|
||||
param: {
|
||||
type: "object",
|
||||
properties: {
|
||||
pageOption: {
|
||||
type: "object",
|
||||
properties: {
|
||||
page: { type: "integer", example: 1 },
|
||||
pageSize: { type: "integer", example: 20 },
|
||||
},
|
||||
},
|
||||
seachOption: {
|
||||
type: "object",
|
||||
properties: {
|
||||
key: { type: "string", description: "与模型字段名一致,如 user_id、status" },
|
||||
value: { type: "string" },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
BizIdRequest: {
|
||||
type: "object",
|
||||
title: "BizIdRequest",
|
||||
properties: { id: { type: "integer", format: "int64" } },
|
||||
},
|
||||
BizUserRevokeTokensRequest: {
|
||||
type: "object",
|
||||
title: "BizUserRevokeTokensRequest",
|
||||
properties: {
|
||||
user_id: { type: "integer", format: "int64", description: "与 id 二选一" },
|
||||
id: { type: "integer", format: "int64" },
|
||||
},
|
||||
},
|
||||
BizUserAddRequest: {
|
||||
type: "object",
|
||||
title: "BizUserAddRequest",
|
||||
description: "写入字段见 biz_user 模型;可选自动创建 Token",
|
||||
properties: {
|
||||
name: { type: "string" },
|
||||
mobile: { type: "string" },
|
||||
email: { type: "string" },
|
||||
company_name: { type: "string" },
|
||||
status: { type: "string", enum: ["active", "disabled"] },
|
||||
auto_create_token: { type: "boolean", default: true },
|
||||
initial_token_name: { type: "string" },
|
||||
initial_token_expire_at: { type: "string", description: "如 2026-12-31 23:59:59" },
|
||||
},
|
||||
},
|
||||
BizUserEditRequest: {
|
||||
type: "object",
|
||||
title: "BizUserEditRequest",
|
||||
properties: {
|
||||
id: { type: "integer", format: "int64" },
|
||||
name: { type: "string" },
|
||||
mobile: { type: "string" },
|
||||
email: { type: "string" },
|
||||
company_name: { type: "string" },
|
||||
status: { type: "string", enum: ["active", "disabled"] },
|
||||
},
|
||||
required: ["id"],
|
||||
},
|
||||
BizSubscriptionOpenRequest: {
|
||||
type: "object",
|
||||
title: "BizSubscriptionOpenRequest",
|
||||
required: ["user_id", "plan_id", "start_time", "end_time"],
|
||||
properties: {
|
||||
user_id: { type: "integer", format: "int64" },
|
||||
plan_id: { type: "integer", format: "int64" },
|
||||
start_time: { type: "string" },
|
||||
end_time: { type: "string" },
|
||||
status: { type: "string", enum: ["pending", "active", "expired", "cancelled"] },
|
||||
renew_mode: { type: "string", enum: ["manual", "auto"] },
|
||||
payment_channel: { type: "string", enum: ["offline", "pay_link"] },
|
||||
payment_ref: { type: "string" },
|
||||
},
|
||||
},
|
||||
BizSubscriptionUpgradeRequest: {
|
||||
type: "object",
|
||||
title: "BizSubscriptionUpgradeRequest",
|
||||
required: ["subscription_id", "new_plan_id"],
|
||||
properties: {
|
||||
subscription_id: { type: "integer", format: "int64" },
|
||||
new_plan_id: { type: "integer", format: "int64" },
|
||||
start_time: { type: "string" },
|
||||
end_time: { type: "string" },
|
||||
},
|
||||
},
|
||||
BizSubscriptionRenewRequest: {
|
||||
type: "object",
|
||||
title: "BizSubscriptionRenewRequest",
|
||||
required: ["subscription_id", "end_time"],
|
||||
properties: {
|
||||
subscription_id: { type: "integer", format: "int64" },
|
||||
end_time: { type: "string" },
|
||||
},
|
||||
},
|
||||
BizSubscriptionCancelRequest: {
|
||||
type: "object",
|
||||
title: "BizSubscriptionCancelRequest",
|
||||
required: ["subscription_id"],
|
||||
properties: { subscription_id: { type: "integer", format: "int64" } },
|
||||
},
|
||||
BizPaymentConfirmRequest: {
|
||||
type: "object",
|
||||
title: "BizPaymentConfirmRequest",
|
||||
required: ["subscription_id"],
|
||||
properties: {
|
||||
subscription_id: { type: "integer", format: "int64" },
|
||||
payment_ref: { type: "string" },
|
||||
},
|
||||
},
|
||||
BizTokenCreateRequest: {
|
||||
type: "object",
|
||||
title: "BizTokenCreateRequest",
|
||||
required: ["user_id", "expire_at"],
|
||||
properties: {
|
||||
user_id: { type: "integer", format: "int64" },
|
||||
token_name: { type: "string" },
|
||||
expire_at: { type: "string" },
|
||||
},
|
||||
},
|
||||
BizTokenIdRequest: {
|
||||
type: "object",
|
||||
title: "BizTokenIdRequest",
|
||||
required: ["id"],
|
||||
properties: { id: { type: "integer", format: "int64" } },
|
||||
},
|
||||
BizApiStatsByUserRequest: {
|
||||
type: "object",
|
||||
title: "BizApiStatsByUserRequest",
|
||||
required: ["user_id"],
|
||||
properties: {
|
||||
user_id: { type: "integer", format: "int64" },
|
||||
start_date: { type: "string", description: "DATEONLY YYYY-MM-DD" },
|
||||
end_date: { type: "string" },
|
||||
},
|
||||
},
|
||||
BizApiStatsByApiRequest: {
|
||||
type: "object",
|
||||
title: "BizApiStatsByApiRequest",
|
||||
required: ["api_path"],
|
||||
properties: {
|
||||
api_path: { type: "string" },
|
||||
start_date: { type: "string" },
|
||||
end_date: { type: "string" },
|
||||
},
|
||||
},
|
||||
BizApiStatsSummaryRequest: {
|
||||
type: "object",
|
||||
title: "BizApiStatsSummaryRequest",
|
||||
properties: {
|
||||
start_date: { type: "string" },
|
||||
end_date: { type: "string" },
|
||||
top_limit: { type: "integer", example: 10 },
|
||||
},
|
||||
},
|
||||
BizUsageWriteRequest: {
|
||||
type: "object",
|
||||
title: "BizUsageWriteRequest",
|
||||
description: "月度用量 biz_usage_monthly 字段",
|
||||
properties: {
|
||||
id: { type: "integer", format: "int64", description: "edit 必填" },
|
||||
user_id: { type: "integer", format: "int64" },
|
||||
plan_id: { type: "integer", format: "int64" },
|
||||
stat_month: { type: "string", example: "2026-04" },
|
||||
msg_count: { type: "integer" },
|
||||
mass_count: { type: "integer" },
|
||||
friend_count: { type: "integer" },
|
||||
sns_count: { type: "integer" },
|
||||
active_user_count: { type: "integer" },
|
||||
api_call_count: { type: "integer" },
|
||||
},
|
||||
},
|
||||
BizAuthVerifyRequest: {
|
||||
type: "object",
|
||||
title: "BizAuthVerifyRequest",
|
||||
required: ["token"],
|
||||
properties: {
|
||||
token: { type: "string", description: "明文 API Token" },
|
||||
feature: { type: "string", description: "swagger 路径对应 tags[0],用于套餐功能点" },
|
||||
api_path: { type: "string", description: "请求的接口 path,用于 allowed_apis 校验" },
|
||||
usage_delta: {
|
||||
type: "object",
|
||||
description: "可选用量上报",
|
||||
properties: {
|
||||
msg: { type: "integer" },
|
||||
mass: { type: "integer" },
|
||||
friend: { type: "integer" },
|
||||
sns: { type: "integer" },
|
||||
active_user: { type: "integer" },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
BizPlanWriteRequest: {
|
||||
type: "object",
|
||||
title: "BizPlanWriteRequest",
|
||||
description: "biz_plan 表字段;add 不需 id,edit 需 id",
|
||||
properties: {
|
||||
id: { type: "integer", format: "int64" },
|
||||
plan_code: { type: "string" },
|
||||
plan_name: { type: "string" },
|
||||
monthly_price: { type: "number" },
|
||||
auth_fee: { type: "number" },
|
||||
account_limit: { type: "integer" },
|
||||
active_user_limit: { type: "integer" },
|
||||
msg_quota: { type: "integer" },
|
||||
mass_quota: { type: "integer" },
|
||||
friend_quota: { type: "integer" },
|
||||
sns_quota: { type: "integer" },
|
||||
allowed_apis: { description: "JSON:路径字符串数组", type: "array", items: { type: "string" } },
|
||||
api_call_quota: { type: "integer" },
|
||||
enabled_features: { type: "object", description: "JSON 功能点开关" },
|
||||
status: { type: "string", enum: ["active", "inactive"] },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const paths = {};
|
||||
paths["/admin_api/biz_api_call_log/page"] = post("API 调用明细分页", "BizAdminPageRequest");
|
||||
paths["/admin_api/biz_api_stats/by_user"] = post("按用户统计接口调用", "BizApiStatsByUserRequest");
|
||||
paths["/admin_api/biz_api_stats/by_api"] = post("按接口路径统计调用", "BizApiStatsByApiRequest");
|
||||
paths["/admin_api/biz_api_stats/summary"] = post("调用量汇总与趋势", "BizApiStatsSummaryRequest");
|
||||
paths["/admin_api/biz_audit_log/page"] = post("审计日志分页", "BizAdminPageRequest");
|
||||
paths["/admin_api/biz_audit_log/export"] = post("审计日志导出", "BizAdminPageRequest");
|
||||
paths["/admin_api/biz_dashboard/summary"] = get("订阅/用户/Token 看板汇总", [], TAG_ADMIN);
|
||||
paths["/admin_api/biz_payment/confirm-offline"] = post("确认线下支付(订阅置 active)", "BizPaymentConfirmRequest");
|
||||
paths["/admin_api/biz_payment/confirm-link"] = post("确认链接支付", "BizPaymentConfirmRequest");
|
||||
paths["/admin_api/biz_plan/page"] = post("套餐分页", "BizAdminPageRequest");
|
||||
paths["/admin_api/biz_plan/add"] = post("新增套餐", "BizPlanWriteRequest");
|
||||
paths["/admin_api/biz_plan/edit"] = post("编辑套餐", "BizPlanWriteRequest");
|
||||
paths["/admin_api/biz_plan/del"] = post("删除套餐", "BizIdRequest");
|
||||
paths["/admin_api/biz_plan/detail"] = get("套餐详情", [{ n: "id", req: true, d: "套餐 id" }]);
|
||||
paths["/admin_api/biz_plan/all"] = get("套餐列表(下拉,最多 2000 条)", []);
|
||||
paths["/admin_api/biz_plan/toggle"] = post("上下线切换", "BizIdRequest");
|
||||
paths["/admin_api/biz_plan/export"] = post("套餐导出", "BizAdminPageRequest");
|
||||
paths["/admin_api/biz_plan/proxy_api_catalog"] = postEmpty("转发接口目录(配置 allowed_apis)");
|
||||
|
||||
paths["/admin_api/biz_subscription/page"] = post("订阅分页(含 user_name、plan_name)", "BizAdminPageRequest");
|
||||
paths["/admin_api/biz_subscription/detail"] = get("订阅详情", [{ n: "id", req: true }]);
|
||||
paths["/admin_api/biz_subscription/by_user"] = get("某用户订阅列表", [
|
||||
{ n: "user_id", req: true, d: "业务用户 id" },
|
||||
]);
|
||||
paths["/admin_api/biz_subscription/open"] = post("开通订阅", "BizSubscriptionOpenRequest");
|
||||
paths["/admin_api/biz_subscription/upgrade"] = post("变更套餐/时间", "BizSubscriptionUpgradeRequest");
|
||||
paths["/admin_api/biz_subscription/renew"] = post("续费(更新结束时间)", "BizSubscriptionRenewRequest");
|
||||
paths["/admin_api/biz_subscription/cancel"] = post("取消订阅", "BizSubscriptionCancelRequest");
|
||||
paths["/admin_api/biz_subscription/export"] = post("订阅导出 CSV 数据", "BizAdminPageRequest");
|
||||
|
||||
paths["/admin_api/biz_token/page"] = post("API Token 分页(不含 secret_cipher)", "BizAdminPageRequest");
|
||||
paths["/admin_api/biz_token/create"] = post("创建 Token,返回 plain_token", "BizTokenCreateRequest");
|
||||
paths["/admin_api/biz_token/revoke"] = post("吊销 Token", "BizTokenIdRequest");
|
||||
paths["/admin_api/biz_token/regenerate"] = post("重新生成密钥(返回新 plain_token)", "BizTokenIdRequest");
|
||||
paths["/admin_api/biz_token/export"] = post("Token 导出", "BizAdminPageRequest");
|
||||
|
||||
paths["/admin_api/biz_usage/page"] = post("月度用量分页", "BizAdminPageRequest");
|
||||
paths["/admin_api/biz_usage/add"] = post("新增用量记录", "BizUsageWriteRequest");
|
||||
paths["/admin_api/biz_usage/edit"] = post("编辑用量记录", "BizUsageWriteRequest");
|
||||
paths["/admin_api/biz_usage/del"] = post("删除用量记录", "BizIdRequest");
|
||||
paths["/admin_api/biz_usage/detail"] = get("用量详情", [{ n: "id", req: true }]);
|
||||
paths["/admin_api/biz_usage/export"] = post("用量导出", "BizAdminPageRequest");
|
||||
|
||||
paths["/admin_api/biz_user/page"] = post("业务用户分页(含 token_count)", "BizAdminPageRequest");
|
||||
paths["/admin_api/biz_user/add"] = post("新增用户(可选自动创建 Token)", "BizUserAddRequest");
|
||||
paths["/admin_api/biz_user/edit"] = post("编辑用户", "BizUserEditRequest");
|
||||
paths["/admin_api/biz_user/del"] = post("删除用户", "BizIdRequest");
|
||||
paths["/admin_api/biz_user/detail"] = get("用户详情(含 subscriptions、tokens[].plain_token)", [{ n: "id", req: true }]);
|
||||
paths["/admin_api/biz_user/all"] = get("全部用户(下拉)", []);
|
||||
paths["/admin_api/biz_user/disable"] = post("禁用用户", "BizIdRequest");
|
||||
paths["/admin_api/biz_user/enable"] = post("启用用户", "BizIdRequest");
|
||||
paths["/admin_api/biz_user/export"] = post("用户导出", "BizAdminPageRequest");
|
||||
paths["/admin_api/biz_user/revoke_all_tokens"] = post("吊销用户下全部 Token", "BizUserRevokeTokensRequest");
|
||||
|
||||
paths["/admin_api/sys_file/upload_img"] = {
|
||||
post: {
|
||||
tags: TAG_ADMIN,
|
||||
summary: "本地上传图片(multipart/form-data)",
|
||||
consumes: ["multipart/form-data"],
|
||||
parameters: [],
|
||||
responses: res200(),
|
||||
},
|
||||
};
|
||||
paths["/admin_api/sys_file/upload_oos_img"] = {
|
||||
post: {
|
||||
tags: TAG_ADMIN,
|
||||
summary: "上传图片到 OSS(multipart)",
|
||||
consumes: ["multipart/form-data"],
|
||||
parameters: [],
|
||||
responses: res200(),
|
||||
},
|
||||
};
|
||||
|
||||
paths["/api/auth/verify"] = {
|
||||
post: {
|
||||
tags: TAG_OPEN_AUTH,
|
||||
summary: "对外开放:Token 鉴权校验(含订阅/套餐/用量等)",
|
||||
parameters: [
|
||||
{
|
||||
in: "body",
|
||||
name: "body",
|
||||
schema: { $ref: "#/definitions/BizAuthVerifyRequest" },
|
||||
},
|
||||
],
|
||||
responses: res200(),
|
||||
},
|
||||
};
|
||||
|
||||
Object.assign(doc.definitions, definitions);
|
||||
Object.assign(doc.paths, paths);
|
||||
|
||||
const extraTags = [
|
||||
{ name: "管理端-业务订阅", description: "Base URL + /admin_api,需管理端登录" },
|
||||
{ name: "开放接口-鉴权", description: "Base URL + /api,如 /api/auth/verify" },
|
||||
];
|
||||
for (const t of extraTags) {
|
||||
if (!doc.tags.some((x) => x.name === t.name)) {
|
||||
doc.tags.push(t);
|
||||
}
|
||||
}
|
||||
|
||||
fs.writeFileSync(swaggerPath, JSON.stringify(doc, null, 4) + "\n", "utf8");
|
||||
console.log("merged admin biz paths + definitions into swagger.json");
|
||||
1556
_docs/swagger.json
1556
_docs/swagger.json
File diff suppressed because it is too large
Load Diff
@@ -5,15 +5,18 @@ const proxy = require("../service/biz_proxy_service");
|
||||
|
||||
|
||||
/**
|
||||
* 从请求中提取 Token
|
||||
* 支持 Authorization: Bearer xxx 和 query ?token=xxx
|
||||
* 从 ctx 请求头中提取 Token(不含 query)
|
||||
* - Authorization: Bearer <token>
|
||||
* - Authorization: <token>(无 Bearer 前缀时整段作为 token)
|
||||
* - X-Api-Token / X-Token
|
||||
*/
|
||||
function extractToken(ctx) {
|
||||
const authHeader = ctx.get("Authorization") || "";
|
||||
if (authHeader.startsWith("Bearer ")) {
|
||||
return authHeader.slice(7).trim();
|
||||
|
||||
let x_token = ctx.headers['authorization'] || ''
|
||||
if (x_token.startsWith("Bearer ")) {
|
||||
x_token = x_token.slice(7).trim();
|
||||
}
|
||||
return ctx.query.token || "";
|
||||
return x_token;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -26,6 +29,14 @@ function pickFeature(spec) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/** 不参与转发的文档路径(与 framework 实际路由重叠或仅为说明) */
|
||||
function should_skip_proxy_path(route_path) {
|
||||
return (
|
||||
route_path.startsWith("/admin_api") ||
|
||||
route_path.startsWith("/api/auth")
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建转发路由表(供 framework.addRoutes 注册)
|
||||
*/
|
||||
@@ -33,6 +44,9 @@ function buildProxyRoutes() {
|
||||
const routes = {};
|
||||
|
||||
for (const [path, methods] of Object.entries(swagger.paths)) {
|
||||
if (should_skip_proxy_path(path)) {
|
||||
continue;
|
||||
}
|
||||
for (const [method, spec] of Object.entries(methods)) {
|
||||
const routeKey = `${method.toUpperCase()} ${path}`;
|
||||
|
||||
@@ -51,10 +65,8 @@ function buildProxyRoutes() {
|
||||
ctx.fail(authResult.message || "鉴权失败");
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. 组装 query(去掉 token 参数,避免泄露)
|
||||
// 3. 组装 query
|
||||
const query = { ...ctx.query };
|
||||
delete query.token;
|
||||
|
||||
// 4. 转发到上游
|
||||
const result = await proxy.forwardRequest({
|
||||
@@ -68,7 +80,7 @@ function buildProxyRoutes() {
|
||||
|
||||
// 5. 根据上游 Success 字段决定响应方式
|
||||
const upstream = result.data;
|
||||
if (upstream && upstream.Success === true) {
|
||||
if (upstream && upstream.Code === 200) {
|
||||
ctx.success(upstream);
|
||||
} else {
|
||||
ctx.fail(upstream && upstream.Text ? upstream.Text : "上游请求失败", upstream);
|
||||
|
||||
@@ -49,5 +49,7 @@ module.exports = (db) => {
|
||||
underscored: true,
|
||||
}
|
||||
);
|
||||
|
||||
//biz_api_call_log.sync({ force: true });
|
||||
return biz_api_call_log;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user