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,