目前基本上的系统都会涉及到权限的控制,而且粒度都比较小,一般都要控制到具体窗口的具体操作上。而要达到这种要求,一个可行的数据库设计将显得非常有帮助。下面我们就设计一个通用型的数据库来达到权限的控制
操作系统:windows xp
数据库:mysql5.0
辅助设计工具:PowerDesigner15
/*==============================================================*/ /* DBMS name: MySQL 5.0 */ /* Created on: 2013-1-25 11:19:43 */ /*==============================================================*/ drop table if exists T_MENU; drop table if exists T_MENU_PRIVILEGE; drop table if exists T_PRIVILEGE; drop table if exists T_ROLE; drop table if exists T_USER; drop table if exists T_USER_ROLE; /*==============================================================*/ /* Table: T_MENU */ /*==============================================================*/ create table T_MENU ( MENUID int(4) not null auto_increment, NAME varchar(50), URL varchar(100) comment '目标网址', PARENTID bigint comment '父菜单', LFT int(4) comment '左边界', RGT int(4) comment '右边界', primary key (MENUID) ); alter table T_MENU comment '菜单表'; /*==============================================================*/ /* Table: T_MENU_PRIVILEGE */ /*==============================================================*/ create table T_MENU_PRIVILEGE ( MENU_PRIVILEGE_ID bigint not null auto_increment, MENUID int(4), PRIVILEGEID int(3), ROLEID int(3), primary key (MENU_PRIVILEGE_ID) ); alter table T_MENU_PRIVILEGE comment '角色菜单权限表'; /*==============================================================*/ /* Table: T_PRIVILEGE */ /*==============================================================*/ create table T_PRIVILEGE ( PRIVILEGEID int(3) not null auto_increment, NAME varchar(50) comment '权限名称(增加,删除……)', primary key (PRIVILEGEID) ); alter table T_PRIVILEGE comment '权限表'; /*==============================================================*/ /* Table: T_ROLE */ /*==============================================================*/ create table T_ROLE ( ROLEID int(3) not null auto_increment, ROLENAME varchar(20) comment '角色名称', primary key (ROLEID) ); alter table T_ROLE comment '角色表'; /*==============================================================*/ /* Table: T_USER */ /*==============================================================*/ create table T_USER ( USERID bigint not null auto_increment, USERNAME varchar(50), PASSWORD varchar(20), primary key (USERID) ); alter table T_USER comment '用户表'; /*==============================================================*/ /* Table: T_USER_ROLE */ /*==============================================================*/ create table T_USER_ROLE ( USER_ROLE_ID bigint not null auto_increment, USERID bigint, ROLEID int(3), primary key (USER_ROLE_ID) ); alter table T_USER_ROLE comment '用户角色'; alter table T_MENU_PRIVILEGE add constraint FK_Reference_3 foreign key (MENUID) references T_MENU (MENUID) on delete restrict on update restrict; alter table T_MENU_PRIVILEGE add constraint FK_Reference_4 foreign key (ROLEID) references T_ROLE (ROLEID) on delete restrict on update restrict; alter table T_MENU_PRIVILEGE add constraint FK_Reference_5 foreign key (PRIVILEGEID) references T_PRIVILEGE (PRIVILEGEID) on delete restrict on update restrict; alter table T_USER_ROLE add constraint FK_Reference_1 foreign key (USERID) references T_USER (USERID) on delete restrict on update restrict; alter table T_USER_ROLE add constraint FK_Reference_2 foreign key (ROLEID) references T_ROLE (ROLEID) on delete restrict on update restrict;
从脚本中可以清晰的看出每个表和字段的作用及数据类型,这里面着重要理解的是menu数据表中的左右边界问题,可以结合下面的图进行理解
1)添加子菜单AddChildMenu,每次添加的子菜单都在最前面,如果需要添加到后面,则可以采用下面的AppendMenu来实现,代码如下
CREATE DEFINER=`root`@`localhost` PROCEDURE `AddChildMenu`( IN P_PARENTID int(11), IN P_MENUNAME VARCHAR(50), IN P_MENUURL VARCHAR(100)) BEGIN DECLARE VAL_LFT INT(4); -- 获取父菜单的lft值 SELECT LFT INTO VAL_LFT FROM T_MENU WHERE MENUID=P_PARENTID; -- 将所有rgt大于当前父菜单lft值的菜单的rgt+2 UPDATE T_MENU SET RGT=RGT+2 WHERE RGT>VAL_LFT; -- 将所有lft大于当前父菜单lft值的菜单的lft+2 UPDATE T_MENU SET LFT=LFT+2 WHERE LFT>VAL_LFT; -- 插入新的子菜单lft=lft+1,rgt=lft+2 INSERT INTO T_MENU(NAME,URL,PARENTID,LFT,RGT) VALUES(P_MENUNAME,P_MENUURL,P_PARENTID,VAL_LFT+1,VAL_LFT+2); -- 显示结果 SELECT * FROM T_MENU; END;
调用示例 CALL AddChildMenu(1,'用户管理',');
2)、附加菜单AppendMenu,将菜单附加都某个菜单的后面,代码如下
CREATE DEFINER=`root`@`localhost` PROCEDURE `AppendMenu`( IN P_MENUID int(11), IN P_MENUNAME VARCHAR(50), IN P_MENUURL VARCHAR(100)) BEGIN DECLARE VAL_RGT INT(4); DECLARE VAL_PARENTID INT(11); -- 获取当前菜单的rgt值 SELECT RGT,PARENTID INTO VAL_RGT,VAL_PARENTID FROM T_MENU WHERE MENUID=P_MENUID; -- 将所有rgt大于当前菜单rgt值的菜单的rgt+2 UPDATE T_MENU SET RGT=RGT+2 WHERE RGT>VAL_RGT; -- 将所有lft大于当前菜单rgt值的菜单的lft+2 UPDATE T_MENU SET LFT=LFT+2 WHERE LFT>VAL_RGT; -- 插入新的菜单lft=rgt+1,rgt=rgt+2 INSERT INTO T_MENU(NAME,URL,PARENTID,LFT,RGT) VALUES(P_MENUNAME,P_MENUURL,VAL_PARENTID,VAL_RGT+1,VAL_RGT+2); -- 显示结果 SELECT * FROM T_MENU; END;
调用示例,CALL AppendMenu(2,'角色管理','');
3)、删除菜单DelMenu,代码如下
CREATE DEFINER=`root`@`localhost` PROCEDURE `DelMenu`( IN P_MENUID int(11) ) BEGIN DECLARE VAL_LFT INT(4); DECLARE VAL_RGT INT(4); DECLARE VAL_WIDTH INT(4); -- 获取当前菜单的lft和rgt值 SELECT LFT,RGT INTO VAL_LFT,VAL_RGT FROM T_MENU WHERE MENUID=P_MENUID; SET VAL_WIDTH=VAL_RGT-VAL_LFT+1; -- 删除lft到rgt之间的菜单 DELETE FROM T_MENU WHERE LFT BETWEEN VAL_LFT AND VAL_RGT; -- 将所有的右边界大于第一步中得到的rgt的所有节点的rgt的值减去第一步中得到的宽度width UPDATE T_MENU SET RGT=RGT-VAL_WIDTH WHERE RGT>VAL_RGT; -- 将所有的左边界大于第一步中得到的rgt的所有节点的lft的值减去第一步中得到的宽度width UPDATE T_MENU SET LFT=LFT-VAL_WIDTH WHERE LFT>VAL_RGT; -- 显示结果 SELECT * FROM T_MENU; END;
调用示例 CALL DelMenu(2);
4)、每个存储过程的实现和算法具体参考代码注释
1)、在执行过插入菜单操作后的数据如下
2)、利用sql语句查询出所有菜单及其层次关系,代码如下
SELECT MENU.MENUID, MENU.PARENTID, MENU.NAME, MENU.URL, MENU.LFT, MENU.RGT, COUNT(PARENT.MENUID) MENULEVEL FROM T_MENU MENU,T_MENU PARENT WHERE MENU.LFT BETWEEN PARENT.LFT AND PARENT.RGT GROUP BY MENU.MENUID,MENU.PARENTID,MENU.NAME,MENU.URL,MENU.LFT,MENU.RGT ORDER BY MENULEVEL
结果如下