CosPlayPermissionDEMO
数据库设计分析
举个例子,一个初创公司中(CEO,产品总监,技术攻城狮,搬砖的)...寥寥几人,每个人可能会同时扮演多种角色(每种角色相对应都有一定不同的权限)
那么,人 角色 权限三者间存在一种怎样的联系(又该怎样生成数据库表)
So,可以确定了 用户表、角色表、权限表
- 用户与角色是多对多的关系。
- URL表(权限表)对应的权限的行为(功能表)是多对多的关系。
- 现在角色可以分配相应的功能了,它们也是多对多的关系。
- 那么还有一件事,权限应该挂载在菜单下面,所以菜单表和权限表又是一种ForeignKey的关系
class Userinfo(models.Model): """ 用户表 """ nickname = models.CharField(max_length=32) password = models.CharField(max_length=32) def __str__(self): return self.nickname class Role(models.Model): """ 角色表 """ caption = models.CharField(max_length=32) def __str__(self): return self.caption class UserinfoToRole(models.Model): """ 用户可以扮演多种角色 角色也可以对应多个用户 """ u = models.ForeignKey('Userinfo') r = models.ForeignKey('Role') def __str__(self): return '%s-%s' %(self.u.nickname, self.r.caption) class Url(models.Model): """ 权限Url表 和菜单有外键关系 应该挂载在相应的菜单下面 """ caption = models.CharField(max_length=32) url = models.CharField(max_length=32) menu = models.ForeignKey('Menu', null=True, blank=True) def __str__(self): return '%s-%s' %(self.caption, self.url) class Action(models.Model): """ 功能权限表 例如 1、增 2、删 3、改 4、查 """ caption = models.CharField(max_length=32) code = models.CharField(max_length=32) def __str__(self): return self.code class UrlToAction(models.Model): """ 权限分配小功能 一个URL可能会有增 删功能,另一个可能全有 """ url = models.ForeignKey('Url') a = models.ForeignKey('Action') def __str__(self): return '%s-%s' %(self.url.caption, self.a.caption) class RoleToUrlToAction(models.Model): """ 角色分配权限表,功能可以对应多种角色,角色也可以对应多种功能 """ uTa = models.ForeignKey('UrlToAction') r = models.ForeignKey('Role') def __str__(self): return '%s-%s' %(self.r.caption, self.uTa) class Menu(models.Model): """ 菜单表 """ caption = models.CharField(max_length=32) m = models.ForeignKey('self', related_name='mTom', null=True, blank=True) def __str__(self): return self.caption
根据当前登陆用户获取所对应角色的权限及相应权限行为(功能)系列
用户登陆成功之后,经一系列验证之后......
可以通过用户名去获取当前登陆用户扮演了哪些角色,这些角色下面又有哪些权限下的所有功能???
通过上面的表关系,,,大概可以通过四种方式可以获取当前用户所扮演的角色
login_username = request.POST.get('user') # 根据登陆用户获取 用户扮演的角色 login_user = models.Userinfo.objects.filter(nickname=login_username).first() # 方式一 # role_list = models.UserinfoToRole.objects.filter(u=login_user) # 方式二 # role_list = models.Role.objects.filter(userinfotorole__u=login_user) # 方式三 role_list = models.Role.objects.filter(userinfotorole__u__nickname=login_username) # 方式四 # 如果有多对多第三字段,通过多对多字段取 # 前提:m = models.ManyToManyField("Role") # user_obj = models.User.objects.get(username=username) # role_list = user_obj.m.all()
上面代码中,获取所扮演的角色列表role_list,接下来应该这么些角色相应的都有哪些功能(各类权限url下的功能)
# 获取角色下所有的权限 # 个人所有权限都将保存在session中,日后作匹配使用且无法实时更新,需重新登陆生效 # 方式一 # roleTourlToaction_list = models.RoleToUrlToAction.objects.filter(r__in=role_list) # 方式二 # 不同角色可能相对应同样的功能,故而去重 roleTourlToaction_list = models.UrlToAction.objects.filter(roletourltoaction__r__in=role_list).\ values('url__url', 'a__code').distinct()
现在可以公开的情报:
- 获取个人的所有权限列表,放置在session当中。可以之后在对用户Url(权限)访问进行比较。缺点:无法获取实时权限信息,需重新登陆
- 获取到所有功能后,可以通过Url去重的方式获取用户权限(Url)
- 且应该在菜单中显示的权限
menu_leaf_list = models.UrlToAction.objects.\ filter(roletourltoaction__r__in=role_list).exclude(url__menu__isnull=True).\ values('url__id', 'url__url', 'url__caption', 'url__menu').distinct()
接下来应该构建一些东西了,并且非常巧妙......
A、构建权限(叶子节点)字典
menu_leaf_dict = {} for item in menu_leaf_list: item = { 'id': item['url__id'], 'url': item['url__url'], 'caption': item['url__caption'], 'parent_id': item['url__menu'], 'child': [] } if item['parent_id'] in menu_leaf_dict: menu_leaf_dict[item['parent_id']].append(item) else: menu_leaf_dict[item['parent_id']] = [item, ] import re if re.match(item['url'], request.path): item['open'] = True open_leaf_parent_id = item['parent_id'] # 此步构建了权限字典(字典的键为菜单的ID,即权限挂载在哪个菜单下) # 且用正则验证当前用户访问url和权限url进行匹配, 返回成功即为打开状态 # print(menu_leaf_dict)
B、构建所有菜单字典
# 获取所有的菜单列表(每条数据为一个字典) menu_list = models.Menu.objects.values('id', 'caption', 'm__id') menu_dict = {} for item in menu_list: item['child'] = [] # 为每个菜单设置一个孩子列表 item['status'] = False # 是否显示 item['open'] = False # 是否打开 menu_dict[item['id']] = item # 菜单字典赋值操作 # 此步构建了菜单字典(键为每条菜单的id, 值为每条菜单数据并附加了一些内容)
C、将Url(权限)挂载在与之对应菜单字典上(找父亲啊找父亲),生成全新的菜单字典
for k, v in menu_leaf_dict.items(): menu_dict[k]['child'] = v parent_id = k # 将后代中有叶子节点的菜单标记为【显示】 while parent_id: menu_dict[parent_id]['status'] = True parent_id = menu_dict[parent_id]['parent_id'] # 将已经选中的菜单标记为【展开】 while open_leaf_parent_id: menu_dict[open_leaf_parent_id]['open'] = True open_leaf_parent_id = menu_dict[open_leaf_parent_id]['parent_id'] # 此步将权限(url)挂载到了菜单的最后一层 # 并且将权限的所有直接父级的status改为了True !妙哉 # 再且若用户当前访问Url与权限(url)匹配,open则为打开状态 !妙哉妙哉 # 返回了全新的菜单字典 # print(menu_dict)
D、处理等级关系,场景应用:层级评论...
result = [] for row in menu_dict.values(): if not row['parent_id']: # 表示为根级菜单 result.append(row) else: # 子级菜单相应的去父菜单的child下面 menu_dict[row['parent_id']]['child'].append(row) print(result) # 此步将所有的层级关系做了处理,形成简洁明了的树形结构
E、页面HTML显示菜单层级显示(递归实现)
response = '' tpl = """ <div class="item {0}"> <div class="title">{1}</div> <div class="content">{2}</div> </div> """ for row in result: # 如果状态为False,则不显示 if not row['status']: continue active = '' if row['open']: print('ok') active = 'active' title = row['caption'] content = menu_cotent(row['child']) response += tpl.format(active, title, content) return render(request, 'index.html', {'response': response}) def menu_cotent(child_list): """ 递归生成html :param child_list: 子级列表 :return: """ response = '' tpl = """ <div class="item {0}"> <div class="title">{1}</div> <div class="content">{2}</div> </div> """ for row in child_list: if not row['status']: # status continue active = '' if row['open']: # open_leaf_parent_id active = 'active' if 'url' in row: # 如果url存在于row中, 则表示到了最终权限节点 response += """<a href="%s" class="%s">%s</a>""" \ %(row['url'], active, row['caption']) else: title = row['caption'] content = menu_cotent(row['child']) response += tpl.format(active, title, content) return response
F、优化(类之整理)
上述代码貌似看起来很繁琐,So!下面代码将上文中数据构建及生成多级菜单封装到了类里面
class MenuHelper(object): def __init__(self, request, username): # 当前请求的request对象 self.request = request # 当前登陆的用户 self.username = username # 当前访问Url self.current_url = request.path # 获取当前用户的所有权限 self.permission2action_dict = None # 获取在菜单中显示的权限 self.menu_leaf_list = None # 获取所有菜单 self.menu_list = None self.session_data() def session_data(self): # 获取用户的所有权限信息, 作用于用户访问 permission_dict = self.request.session.get('permission_info') if permission_dict: self.permission2action_dict = permission_dict['permission2action_dict'] self.menu_leaf_list = permission_dict['menu_leaf_list'] self.menu_list = permission_dict['menu_list'] else: # 获取当前登陆用户所有角色 role_list = models.Role.objects.filter(userinfotorole__u=self.username) # 获取角色的所有行为列表 roleTourlToaction_list = models.UrlToAction.objects.filter(roletourltoaction__r__in=role_list).\ values('url__url', 'a__code').distinct() # 构建行为字典 roleTourlToaction_dict = {} for item in roleTourlToaction_list: if item['url__url'] in roleTourlToaction_dict: roleTourlToaction_dict[item['url__url']].append(item['a__code']) else: roleTourlToaction_dict[item['url__url']] = [item['a__code'], ] # 获取菜单的叶子节点, 即显示在菜单的最后一层 menu_leaf_list = models.UrlToAction.objects. \ filter(roletourltoaction__r__in=role_list).exclude(url__menu__isnull=True). \ values('url__id', 'url__url', 'url__caption', 'url__menu').distinct() # 获取所有的菜单列表 menu_list = models.Menu.objects.values('id', 'caption', 'parent_id') self.request.session['permission_info'] = { 'permission2action_dict': roleTourlToaction_dict, 'menu_leaf_list': menu_leaf_list, 'menu_list': menu_list } self.permission2action_dict = roleTourlToaction_dict self.menu_leaf_list = menu_leaf_list self.menu_list = menu_list def menu_data(self): menu_leaf_dict = {} open_leaf_parent_id = None # 归并所有的叶子节点 for item in self.menu_leaf_list: item = { 'id': item['url__id'], 'url': item['url__url'], 'caption': item['url__caption'], 'parent_id': item['url__menu'], 'child': [], 'status': False, 'open': False } if item['parent_id'] in menu_leaf_dict: menu_leaf_dict[item['parent_id']].append(item) else: menu_leaf_dict[item['parent_id']] = [item, ] import re if re.match(item['url'], self.current_url): item['open'] = True open_leaf_parent_id = item['parent_id'] # 生成菜单字典 menu_dict = {} for item in self.menu_list: item['child'] = [] item['status'] = False item['open'] = False menu_dict[item['id']] = item # 将叶子节点添加到菜单字典中... for k, v in menu_leaf_dict.items(): menu_dict[k]['child'] = v parent_id = k # 将后代中有叶子节点的菜单标记为【显示】 while parent_id: menu_dict[parent_id]['status'] = True parent_id = menu_dict[parent_id]['parent_id'] # 将已经选中的菜单标记为【展开】 while open_leaf_parent_id: menu_dict[open_leaf_parent_id]['open'] = True open_leaf_parent_id = menu_dict[open_leaf_parent_id]['parent_id'] # 生成树形结构数据 result = [] for row in menu_dict.values(): if not row['parent_id']: result.append(row) else: menu_dict[row['parent_id']]['child'].append(row) return result def menu_tree(self): response = '' tpl = """ <div class="item {0}"> <div class="title">{1}</div> <div class="content">{2}</div> </div> """ result = self.menu_data() for row in result: if not row['status']: continue active = '' if row['open']: print('ok') active = 'active' title = row['caption'] content = self.menu_cotent(row['child']) response += tpl.format(active, title, content) return response def menu_cotent(self, child_list): response = '' tpl = """ <div class="item {0}"> <div class="title">{1}</div> <div class="content">{2}</div> </div> """ for row in child_list: if not row['status']: # status continue active = '' if row['open']: # open_leaf_parent_id active = 'active' if 'url' in row: # 如果url存在于row中, 则表示到了最终权限节点 response += """<a href="%s" class="%s">%s</a>""" \ % (row['url'], active, row['caption']) else: title = row['caption'] content = self.menu_cotent(row['child']) response += tpl.format(active, title, content) return response
权限管理简单应用
Views
class MenuHelper(object): def __init__(self, request, username, current_url): # 当前请求的request对象 self.request = request # 当前登陆的用户 self.username = username # 当前访问Url self.current_url = current_url # 获取当前用户的所有权限 self.permission2action_dict = None # 获取在菜单中显示的权限 self.menu_leaf_list = None # 获取所有菜单 self.menu_list = None self.session_data() def session_data(self): # 获取用户的所有权限信息, 作用于用户访问 permission_dict = self.request.session.get('permission_info') if permission_dict: self.permission2action_dict = permission_dict['permission2action_dict'] self.menu_leaf_list = permission_dict['menu_leaf_list'] self.menu_list = permission_dict['menu_list'] else: # 获取当前登陆用户所有角色 role_list = models.Role.objects.filter(userinfotorole__u__nickname=self.username) # 获取角色的所有行为列表 roleTourlToaction_list = list(models.UrlToAction.objects.filter(roletourltoaction__r__in=role_list).\ values('url__url', 'a__code').distinct()) # 构建行为字典 roleTourlToaction_dict = {} for item in roleTourlToaction_list: if item['url__url'] in roleTourlToaction_dict: roleTourlToaction_dict[item['url__url']].append(item['a__code']) else: roleTourlToaction_dict[item['url__url']] = [item['a__code'], ] # 获取菜单的叶子节点, 即显示在菜单的最后一层 menu_leaf_list = list(models.UrlToAction.objects. \ filter(roletourltoaction__r__in=role_list).exclude(url__menu__isnull=True). \ values('url__id', 'url__url', 'url__caption', 'url__menu').distinct()) # 获取所有的菜单列表 menu_list = list(models.Menu.objects.values('id', 'caption', 'parent_id')) self.request.session['permission_info'] = { 'permission2action_dict': roleTourlToaction_dict, 'menu_leaf_list': menu_leaf_list, 'menu_list': menu_list } self.permission2action_dict = roleTourlToaction_dict self.menu_leaf_list = menu_leaf_list self.menu_list = menu_list def menu_data(self): menu_leaf_dict = {} open_leaf_parent_id = None # 归并所有的叶子节点 for item in self.menu_leaf_list: item = { 'id': item['url__id'], 'url': item['url__url'], 'caption': item['url__caption'], 'parent_id': item['url__menu'], 'child': [], 'status': True, 'open': False } if item['parent_id'] in menu_leaf_dict: menu_leaf_dict[item['parent_id']].append(item) else: menu_leaf_dict[item['parent_id']] = [item, ] import re if re.match(item['url'], self.current_url): item['open'] = True open_leaf_parent_id = item['parent_id'] # 生成菜单字典 menu_dict = {} for item in self.menu_list: item['child'] = [] item['status'] = False item['open'] = False menu_dict[item['id']] = item # 将叶子节点添加到菜单字典中... for k, v in menu_leaf_dict.items(): menu_dict[k]['child'] = v parent_id = k # 将后代中有叶子节点的菜单标记为【显示】 while parent_id: menu_dict[parent_id]['status'] = True parent_id = menu_dict[parent_id]['parent_id'] print(menu_dict) # 将已经选中的菜单标记为【展开】 while open_leaf_parent_id: menu_dict[open_leaf_parent_id]['open'] = True open_leaf_parent_id = menu_dict[open_leaf_parent_id]['parent_id'] # 生成树形结构数据 result = [] for row in menu_dict.values(): if not row['parent_id']: result.append(row) else: menu_dict[row['parent_id']]['child'].append(row) return result def menu_tree(self): response = '' tpl = """ <div class="item {0}"> <div class="title">{1}</div> <div class="content">{2}</div> </div> """ result = self.menu_data() for row in result: if not row['status']: continue active = '' if row['open']: active = 'active' title = row['caption'] content = self.menu_cotent(row['child']) response += tpl.format(active, title, content) return response def menu_cotent(self, child_list): response = '' tpl = """ <div class="item {0}"> <div class="title">{1}</div> <div class="content">{2}</div> </div> """ for row in child_list: if not row['status']: # status continue active = '' if row['open']: # open_leaf_parent_id active = 'active' if 'url' in row: # 如果url存在于row中, 则表示到了最终权限节点 response += """<a href="%s" class="%s">%s</a>""" \ % (row['url'], active, row['caption']) else: title = row['caption'] content = self.menu_cotent(row['child']) response += tpl.format(active, title, content) return response def login(request): user_request_url = '/girl.html' login_user = request.GET.get('user') obj = MenuHelper(request, login_user, user_request_url) string = obj.menu_tree() return render(request, 'index.html',{'menu_string': string})
Html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <style> .content{ margin-left: 20px; display: none; } .content a{ display: block; } .active>.content{ display: block; } </style> </head> <body> {{ menu_string | safe }} </body> </html>
通过更改URL(相当于用户访问的权限url)从而看到显示的菜单权限
权限管理实际应用
更新中...