中后台项目最怕的不是页面多,而是权限、菜单、接口、表单四条线各写各的:前端按钮藏了,接口还能调;菜单配好了,接口权限漏了;CRUD 页面生成出来了,表单和权限还要再补一遍。XYGo Admin 最近这轮调整,重点就是把这些容易返工的地方重新串起来,尤其是 RBAC 权限链路和 CRUD 代码生成流程。
RBAC 不再只停留在“前端隐藏按钮”
这次更新里最值得关注的是 RBAC 权限链路的分层处理:菜单权限、按钮权限、接口权限分开看,但不能割裂。
很多后台系统早期会这样做:
- 菜单权限决定用户能不能看到某个页面;
- 按钮权限决定用户能不能看到“新增”“删除”“导出”;
- 接口权限在后端中间件里校验请求是否允许。
问题在于,如果只做前两层,安全边界其实还在浏览器里。用户看不到删除按钮,不代表他不能手写一个 HTTP 请求去打删除接口。所以 XYGo Admin 这次强调将菜单、按钮、接口权限分层处理,实际是在把权限从“界面控制”推进到“服务端强制校验”。
可以这样理解权限链路:
用户 -> 角色 -> 菜单权限 -> 页面可见
-> 按钮权限 -> 操作入口可见
-> 接口权限 -> 请求是否真正放行
这条链路的关键不是多存几张表,而是每一次生成、配置、发布时都能少漏一环。
CRUD 生成流程要和权限一起长出来
来源摘要提到,后台模块在生成时会同步考虑权限、菜单、接口和表单页面。这是中后台工程里很现实的改动。
一个典型 CRUD 模块并不只是 list/create/update/delete 四个接口。它通常还包括:
- 左侧菜单入口;
- 列表页路由;
- 新增、编辑、删除、查询、导出等按钮;
- 后端接口路由;
- 表单字段、校验规则、默认值;
- 角色授权时可勾选的权限点。
如果代码生成器只生成页面和接口,权限点靠人工补,后期返工概率很高。尤其是多人协作时,A 生成页面,B 加接口,C 配菜单,D 再补角色权限,任何一步漏掉都会变成测试环境里的“为什么我看不到页面”或“为什么普通账号能删数据”。
这次调整的方向更像是让 CRUD 生成流程产出一组配套资产,而不是一堆孤立文件。
可以这样实践:用权限清单驱动菜单、按钮和接口
下面是一个可改造的最小示例,用 YAML 描述一个中后台模块的权限清单,再用一个 Python 脚本生成菜单、按钮和接口权限的种子数据。它不是 XYGo Admin 的官方接口,只是一个适合团队落地这类思路的实践样例。
先创建 permissions.yaml:
module: system_user
name: 用户管理
menu:
path: /system/users
title: 用户管理
permission: system:user:view
buttons:
- key: create
title: 新增用户
permission: system:user:create
- key: update
title: 编辑用户
permission: system:user:update
- key: delete
title: 删除用户
permission: system:user:delete
apis:
- method: GET
path: /api/system/users
permission: system:user:view
- method: POST
path: /api/system/users
permission: system:user:create
- method: PUT
path: /api/system/users/{id}
permission: system:user:update
- method: DELETE
path: /api/system/users/{id}
permission: system:user:delete
安装依赖并生成 SQL:
python -m venv .venv
source .venv/bin/activate
pip install pyyaml
python generate_permissions.py
generate_permissions.py:
import yaml
from pathlib import Path
config = yaml.safe_load(Path("permissions.yaml").read_text(encoding="utf-8"))
module = config["module"]
menu = config["menu"]
buttons = config.get("buttons", [])
apis = config.get("apis", [])
sql = []
sql.append(
"INSERT INTO admin_menu(module, title, path, permission) "
f"VALUES ('{module}', '{menu['title']}', '{menu['path']}', '{menu['permission']}');"
)
for button in buttons:
sql.append(
"INSERT INTO admin_permission(module, type, name, permission) "
f"VALUES ('{module}', 'button', '{button['title']}', '{button['permission']}');"
)
for api in apis:
sql.append(
"INSERT INTO admin_permission(module, type, name, permission, method, path) "
f"VALUES ('{module}', 'api', '{api['method']} {api['path']}', "
f"'{api['permission']}', '{api['method']}', '{api['path']}');"
)
Path("permissions.sql").write_text("\n".join(sql) + "\n", encoding="utf-8")
print("generated permissions.sql")
运行后会得到类似:
INSERT INTO admin_menu(module, title, path, permission) VALUES ('system_user', '用户管理', '/system/users', 'system:user:view');
INSERT INTO admin_permission(module, type, name, permission) VALUES ('system_user', 'button', '新增用户', 'system:user:create');
INSERT INTO admin_permission(module, type, name, permission) VALUES ('system_user', 'button', '编辑用户', 'system:user:update');
INSERT INTO admin_permission(module, type, name, permission) VALUES ('system_user', 'button', '删除用户', 'system:user:delete');
INSERT INTO admin_permission(module, type, name, permission, method, path) VALUES ('system_user', 'api', 'GET /api/system/users', 'system:user:view', 'GET', '/api/system/users');
这个例子的重点是:权限不靠开发者临场想,而是从模块定义里统一生成。实际接入 XYGo Admin 时,可以把这类清单接到项目已有的代码生成流程里,让模块生成时同时落下菜单、按钮、接口权限和页面骨架。
后端接口必须独立校验权限
前端按钮权限依然有价值,它能减少误操作,也能让界面更清爽。但后端接口权限必须单独存在。
可以这样实践一个很小的 Express 中间件,用权限码保护接口:
const express = require("express");
const app = express();
function requirePermission(permission) {
return function (req, res, next) {
const permissions = req.headers["x-user-permissions"]
? req.headers["x-user-permissions"].split(",")
: [];
if (!permissions.includes(permission)) {
return res.status(403).json({ message: "permission denied", permission });
}
next();
};
}
app.get(
"/api/system/users",
requirePermission("system:user:view"),
(req, res) => {
res.json([{ id: 1, name: "admin" }]);
}
);
app.delete(
"/api/system/users/:id",
requirePermission("system:user:delete"),
(req, res) => {
res.json({ deleted: req.params.id });
}
);
app.listen(3000, () => {
console.log("server listening on http://localhost:3000");
});
保存为 server.js 后运行:
npm init -y
npm install express
node server.js
测试无权限访问:
curl -i http://localhost:3000/api/system/users
测试有权限访问:
curl -i \
-H 'x-user-permissions: system:user:view,system:user:create' \
http://localhost:3000/api/system/users
真实项目里权限通常来自登录态、JWT、Session 或网关注入的用户上下文,而不是 HTTP Header。这里用 Header 只是为了让示例可以直接复制运行。
落地时要盯住三件事
这类调整最怕“生成时很完整,维护时又散掉”。建议团队采用时重点检查三点。
第一,权限码命名要稳定。比如统一使用 domain:resource:action,不要一会儿是 system:user:add,一会儿是 user:create。权限码一旦进入角色配置和审计日志,后续改名成本很高。
第二,代码生成不能绕过人工审查。CRUD 生成能减少重复劳动,但字段权限、敏感操作、批量删除、导出接口这些地方仍然要由开发者确认。生成器适合铺路,不适合替你判断业务风险。
第三,接口权限要进入测试。至少要覆盖“有权限通过、无权限拒绝、前端隐藏但接口不可越权”这几类用例。否则 RBAC 链路看起来完整,实际上仍可能在某个接口上漏网。
XYGo Admin 这轮更新的价值,不在于多了几个配置项,而在于把中后台项目最容易脱节的几条线拉回同一个流程里。对团队来说,真正的收益是少返工、少漏配、少在测试阶段追权限问题。采用时不要只看生成出来的页面漂不漂亮,更要看权限、菜单、接口、表单是不是能一起被创建、被审查、被维护。