已知菜单表,如何配置接口权限
-- public.plat_menu definition -- Drop table -- DROP TABLE public.plat_menu; CREATE TABLE public.plat_menu ( id bigserial NOT NULL, menu_code varchar NULL, menu_name varchar NULL, list_order varchar NULL, created_by int8 DEFAULT 1 NULL, created_at timestamptz NULL, active bool DEFAULT true NULL, visible bool DEFAULT true NULL, subsys varchar DEFAULT 'opc'::character varying NULL, parent_id int8 DEFAULT '-1'::integer NULL, -- 父节点 page_type int4 DEFAULT 10 NULL -- 页面组件类型:10 菜单 20按扭 ); -- Column comments COMMENT ON COLUMN public.plat_menu.parent_id IS '父节点'; COMMENT ON COLUMN public.plat_menu.page_type IS '页面组
基于您提供的 plat_menu 表结构,这是一个标准的树形菜单表(支持父子级联),但缺少与后端接口的映射关系。以下是完整的接口权限配置方案:
一、现有结构分析
当前表只解决了前端菜单展示问题:
-
page_type=10:导航菜单(侧边栏/顶部) -
page_type=20:页面内按钮(增删改查按钮) -
parent_id:构建树形结构
缺少的部分:菜单/按钮对应哪些后端API接口(如 POST /api/users)。
二、扩展方案:增加接口映射表
1. 创建接口定义表
sql
复制
-- 存储所有需要权限控制的API
CREATE TABLE public.plat_api (
id bigserial PRIMARY KEY,
api_code varchar(100) NOT NULL UNIQUE, -- 接口编码:user:add
api_name varchar(100) NOT NULL, -- 接口名称:新增用户
api_url varchar(255) NOT NULL, -- 请求路径:/api/users
http_method varchar(10) NOT NULL, -- GET/POST/PUT/DELETE
description varchar(500),
created_at timestamptz DEFAULT NOW()
);
2. 创建菜单-接口关联表(多对多)
sql
复制
-- 建立菜单/按钮与接口的多对多关系
CREATE TABLE public.plat_menu_api (
id bigserial PRIMARY KEY,
menu_id int8 NOT NULL REFERENCES plat_menu(id),
api_id int8 NOT NULL REFERENCES plat_api(id),
created_at timestamptz DEFAULT NOW(),
UNIQUE(menu_id, api_id)
);
3. 标准RBAC表结构(如未存在)
sql
复制
-- 角色表
CREATE TABLE public.plat_role (
id bigserial PRIMARY KEY,
role_code varchar(50) NOT NULL UNIQUE,
role_name varchar(100) NOT NULL
);
-- 角色-菜单关联(功能权限)
CREATE TABLE public.plat_role_menu (
id bigserial PRIMARY KEY,
role_id int8 NOT NULL REFERENCES plat_role(id),
menu_id int8 NOT NULL REFERENCES plat_menu(id),
UNIQUE(role_id, menu_id)
);
-- 用户-角色关联
CREATE TABLE public.sys_user_role (
id bigserial PRIMARY KEY,
user_id int8 NOT NULL,
role_id int8 NOT NULL REFERENCES plat_role(id)
);
三、权限模型数据示例
场景:用户管理模块
sql
复制
-- 1. 插入菜单结构
INSERT INTO plat_menu (menu_name, menu_code, page_type, parent_id) VALUES
('系统管理', 'sys:mgmt', 10, -1),
('用户管理', 'sys:user', 10, 1), -- 假设系统管理id=1
('新增用户', 'sys:user:add', 20, 2), -- 按钮
('删除用户', 'sys:user:del', 20, 2),
('编辑用户', 'sys:user:edit', 20, 2);
-- 2. 注册接口
INSERT INTO plat_api (api_code, api_name, api_url, http_method) VALUES
('api:user:list', '查询用户列表', '/api/users', 'GET'),
('api:user:create', '新增用户', '/api/users', 'POST'),
('api:user:update', '更新用户', '/api/users/{id}', 'PUT'),
('api:user:delete', '删除用户', '/api/users/{id}', 'DELETE');
-- 3. 建立映射(按钮级权限控制)
-- "新增用户"按钮关联新增接口
INSERT INTO plat_menu_api (menu_id, api_id)
SELECT m.id, a.id
FROM plat_menu m, plat_api a
WHERE m.menu_code = 'sys:user:add' AND a.api_code = 'api:user:create';
-- "用户管理"菜单关联查询接口(进入页面就需要查列表)
INSERT INTO plat_menu_api (menu_id, api_id)
SELECT m.id, a.id
FROM plat_menu m, plat_api a
WHERE m.menu_code = 'sys:user' AND a.api_code = 'api:user:list';
四、后端权限校验实现
Spring Boot 方案(注解+AOP)
java
复制
@Component
public class PermissionService {
@Autowired
private PlatMenuMapper menuMapper;
/**
* 判断当前用户是否有权限访问该接口
*/
public boolean hasPermission(Long userId, String requestUrl, String method) {
// 1. 根据URL和方法找到接口ID
Long apiId = menuMapper.findApiIdByUrl(requestUrl, method);
if (apiId == null) {
return true; // 未注册的接口默认放行(或根据策略拦截)
}
// 2. 查询用户拥有的所有菜单ID(通过角色)
List userMenuIds = menuMapper.findMenuIdsByUserId(userId);
// 3. 检查该接口是否被任何已授权菜单关联
Long count = menuMapper.checkApiInMenus(apiId, userMenuIds);
return count > 0;
}
}
// 自定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RequiresPermission {
// 可留空,自动根据URL匹配;或显式指定apiCode
String value() default "";
}
// 拦截器实现
@Component
public class PermissionInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
// 获取当前登录用户
Long userId = getCurrentUserId();
String url = request.getRequestURI();
String method = request.getMethod();
if (!permissionService.hasPermission(userId, url, method)) {
response.setStatus(403);
return false;
}
return true;
}
}
数据库查询示例
sql
复制
-- 核心查询:用户是否有权限访问特定接口
SELECT COUNT(1)
FROM plat_role_menu rm
JOIN plat_menu_api ma ON rm.menu_id = ma.menu_id
JOIN plat_api api ON ma.api_id = api.id
WHERE rm.role_id IN (SELECT role_id FROM sys_user_role WHERE user_id = #{userId})
AND api.api_url = #{url}
AND api.http_method = #{method};
五、前端配合
动态路由 + 按钮级权限:
JavaScript
复制
// 1. 登录后获取用户权限树(包含menus和apis)
const userPermissions = await getUserPermissions();
// 2. 前端按钮显示控制(v-if)
// 3. API请求拦截(双重保险)
// 虽然后端会拦,但前端可提前预判,提升体验
六、进阶方案
表格
复制
| 方案 | 适用场景 | 特点 |
|---|---|---|
| 菜单-接口绑定(上述方案) | 标准RBAC | 配置清晰,颗粒度到按钮 |
| 数据权限 | 部门隔离 | 增加 data_scope 字段(全部/本部门/本人) |
| 接口注解 | 快速开发 | 不用维护plat_api表,直接注解 @RequireRole("admin") |
| 动态权限 | 复杂业务 | 使用Apache Shiro或Spring Security,支持运行时变更 |
数据权限扩展
如需控制"只能看本部门数据":
sql
复制
-- 角色表增加数据权限范围
ALTER TABLE plat_role ADD COLUMN data_scope int DEFAULT 1;
-- 1:全部 2:本部门 3:本部门及下级 4:仅本人
-- 用户表增加部门字段
ALTER TABLE sys_user ADD COLUMN dept_id int8;
核心逻辑:查询数据时自动拼接 WHERE dept_id IN (用户可见部门)。
这套方案实现了前端菜单可见性与后端接口访问控制的统一,通过 plat_menu_api 关联表解耦,既支持粗粒度(菜单级)也支持细粒度(按钮级)权限控制。
件类型:10 菜单 20按扭'; 已知菜单表,如何配置接口权限







