From 739d5670ba0bfce7868a529250023feb61f4d4c7 Mon Sep 17 00:00:00 2001 From: root Date: Tue, 20 Oct 2026 18:59:16 +0800 Subject: [PATCH] =?UTF-8?q?fix(billing):=20=E6=8F=92=E4=BB=B6=20HTTP=20?= =?UTF-8?q?=E8=B7=AF=E7=94=B1=E6=8C=89=E6=93=8D=E4=BD=9C=E6=8B=86=E8=B7=AF?= =?UTF-8?q?=E5=BE=84=20+=20=E6=AD=A3=E7=A1=AE=E8=A7=A3=E6=9E=90=E8=AF=B7?= =?UTF-8?q?=E6=B1=82=E4=BD=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 内核插件路由冲突仲裁按**路径**判重(不看 method),同一路径挂 4 个 method 会被 当成 4 个插件抢同一路径 ⇒ 规则 API 不可用。拆成 /rules(GET)、/rules/replace (PUT)、/rules/add(POST)、/rules/remove(DELETE)。 - 插件 HTTP 分发交给 handler 的是**整个请求表**(body 是原始字符串),而直调导出 拿到的是参数表;此前 PUT 把请求信封当成规则写进了 billing.json,规则全丢、价格表 清空。新增 reqData() 统一解出 JSON 载荷。 - 生产复验:GET 返回 4 条规则;PUT 后 billing.json 保持规则形状;localzen(free) 请求不计入 unpriced,codearts 未列价模型计入(预期收紧)。 (cherry picked from commit 79bc55cfbec86d26097b17c34d3a0601c72c071c) --- internal/lua/plugins/billing.lua | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/internal/lua/plugins/billing.lua b/internal/lua/plugins/billing.lua index aeb1c2f..62be325 100644 --- a/internal/lua/plugins/billing.lua +++ b/internal/lua/plugins/billing.lua @@ -346,6 +346,21 @@ do if not res then plugin.load_error = tostring(err) end end +-- 插件 HTTP 分发把**整个请求表**交给 handler(method/path/body/headers…), +-- body 是原始字符串;而直调导出(/call/...)拿到的就是参数表本身。这里统一 +-- 解出 JSON 载荷,避免"把请求信封当成规则写进 billing.json"(真实踩过: +-- PUT 之后 billing.json 变成了整个请求的转储,规则全丢、价格表清空)。 +local function reqData(req) + if type(req) ~= "table" then return {} end + if type(req.json) == "table" then return req.json end + if type(req.body) == "string" and req.body ~= "" then + local ok, dec = pcall(json.decode, req.body) + if ok and type(dec) == "table" then return dec end + end + if req.method == nil and req.path == nil then return req end + return {} +end + -- ---- 规则编辑器的 HTTP 面(挂在插件自己的 /api/plugins/http/billing/ 下)---- -- 规则属于插件,写盘也在插件内完成;内核没有 billing 端点。 function plugin.__http_rules_get(req) @@ -359,7 +374,7 @@ function plugin.__http_rules_get(req) end function plugin.__http_rules_put(req) - local body = (req and (req.json or req)) + local body = reqData(req) if type(body) ~= "table" then return { status = 400, json = { error = "请求体必须是 JSON 对象" } } end @@ -375,7 +390,7 @@ function plugin.__http_rules_put(req) end function plugin.__http_rules_add(req) - local body = (req and (req.json or req)) or {} + local body = reqData(req) or {} local copy = json.decode(json.encode(plugin.rules or { active = "", profiles = {} })) local target = nil for _, p in ipairs(copy.profiles or {}) do @@ -407,7 +422,7 @@ function plugin.__http_rules_add(req) end function plugin.__http_rules_delete(req) - local body = (req and (req.json or req)) or {} + local body = reqData(req) or {} local copy = json.decode(json.encode(plugin.rules or { active = "", profiles = {} })) local target = nil for _, p in ipairs(copy.profiles or {}) do @@ -448,14 +463,16 @@ function plugin.rules_put(args) return unwrap(plugin.__http_rules_put({ json = a function plugin.rules_add(args) return unwrap(plugin.__http_rules_add({ json = args or {} })) end function plugin.rules_delete(args) return unwrap(plugin.__http_rules_delete({ json = args or {} })) end +-- 每条路由的 path 必须唯一:内核的路由冲突仲裁按**路径**(不看 method)判重, +-- 同一路径挂 4 个 method 会被当成 4 个插件抢同一个路径。所以按操作拆路径。 plugin.http = { { path = "billing/rules", method = "GET", handler = "__http_rules_get", roles = { "admin" }, description = "计费规则读取" }, - { path = "billing/rules", method = "PUT", handler = "__http_rules_put", + { path = "billing/rules/replace", method = "PUT", handler = "__http_rules_put", roles = { "admin" }, description = "替换整份计费规则(插件自己写盘)" }, - { path = "billing/rules", method = "POST", handler = "__http_rules_add", + { path = "billing/rules/add", method = "POST", handler = "__http_rules_add", roles = { "admin" }, description = "新增/替换一条计费规则" }, - { path = "billing/rules", method = "DELETE", handler = "__http_rules_delete", + { path = "billing/rules/remove", method = "DELETE", handler = "__http_rules_delete", roles = { "admin" }, description = "删除一条计费规则" }, } @@ -1524,8 +1541,9 @@ plugin.ui = { return { "&": "&", "<": "<", ">": ">", '"': """ }[c]; }); } + var OP_PATH = { GET: "", PUT: "/replace", POST: "/add", DELETE: "/remove" }; function call(method, body) { - return fetch(API, { + return fetch(API + (OP_PATH[method] || ""), { method: method, credentials: "same-origin", headers: body ? { "Content-Type": "application/json" } : undefined,