99 lines
2.8 KiB
JavaScript
99 lines
2.8 KiB
JavaScript
const swagger = require("../../_docs/swagger.json");
|
||
const auth = require("../service/biz_auth_verify");
|
||
const proxy = require("../service/biz_proxy_service");
|
||
|
||
|
||
|
||
/**
|
||
* 从 ctx 请求头中提取 Token(不含 query)
|
||
* - Authorization: Bearer <token>
|
||
* - Authorization: <token>(无 Bearer 前缀时整段作为 token)
|
||
* - X-Api-Token / X-Token
|
||
*/
|
||
function extractToken(ctx) {
|
||
|
||
let x_token = ctx.headers['authorization'] || ''
|
||
if (x_token.startsWith("Bearer ")) {
|
||
x_token = x_token.slice(7).trim();
|
||
}
|
||
return x_token;
|
||
}
|
||
|
||
/**
|
||
* 提取 swagger tags 第一项作为 feature 名(用于套餐功能点校验)
|
||
*/
|
||
function pickFeature(spec) {
|
||
if (spec.tags && spec.tags.length > 0) {
|
||
return spec.tags[0];
|
||
}
|
||
return null;
|
||
}
|
||
|
||
/** 不参与转发的文档路径(与 framework 实际路由重叠或仅为说明) */
|
||
function should_skip_proxy_path(route_path) {
|
||
return (
|
||
route_path.startsWith("/admin_api") ||
|
||
route_path.startsWith("/api/auth")
|
||
);
|
||
}
|
||
|
||
/**
|
||
* 构建转发路由表(供 framework.addRoutes 注册)
|
||
*/
|
||
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}`;
|
||
|
||
routes[routeKey] = async (ctx) => {
|
||
// 1. 提取 Token
|
||
const token = extractToken(ctx);
|
||
if (!token) {
|
||
ctx.fail("缺少 Token");
|
||
return;
|
||
}
|
||
|
||
// 2. 鉴权:Token + 用户 + 订阅 + 套餐功能点 + 接口权限 + 调用量
|
||
const feature = pickFeature(spec);
|
||
const authResult = await auth.verifyRequest({ token, feature, api_path: path });
|
||
if (!authResult.ok) {
|
||
ctx.fail(authResult.message || "鉴权失败");
|
||
return;
|
||
}
|
||
// 3. 组装 query,并注入 token 对应 key(上游要求参数名为 key)
|
||
const query = { ...ctx.query };
|
||
if (!query.key && authResult.context && authResult.context.token_key) {
|
||
query.key = authResult.context.token_key;
|
||
}
|
||
|
||
// 4. 转发到上游
|
||
const result = await proxy.forwardRequest({
|
||
api_path: path,
|
||
method: method.toUpperCase(),
|
||
query,
|
||
body: ctx.getBody(),
|
||
headers: ctx.headers || {},
|
||
auth_ctx: authResult.context,
|
||
});
|
||
|
||
// 5. 根据上游 Success 字段决定响应方式
|
||
const upstream = result.data;
|
||
if (upstream && upstream.Code === 200) {
|
||
ctx.success(upstream);
|
||
} else {
|
||
ctx.fail(upstream && upstream.Text ? upstream.Text : "上游请求失败", upstream);
|
||
}
|
||
};
|
||
}
|
||
}
|
||
|
||
return routes;
|
||
}
|
||
|
||
module.exports = { buildProxyRoutes };
|