目前基本上的系统都会涉及到权限的控制,而且粒度都比较小,一般都要控制到具体窗口的具体操作上。而要达到这种要求,一个可行的数据库设计将显得非常有帮助。下面我们就设计一个通用型的数据库来达到权限的控制
操作系统: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
结果如下