角色权限管理组件

角色权限管理:role basic access control,简称rbac

权限管理:
表:
	用户表
	角色表
	用户角色关系表
	
	菜单表
	权限表
	操作表
	角色权限操作关系表
	
功能:
	- 权限控制
	- 动态菜单

原理:
- 权限控制
	a. 登录时,获取当前用户所有权限,放置在Session中
		request.session['rbac_permission_session_key'] = 'asdf'
		{
			'/index.html': [G,P...]
		}
	b. 其他请求,在session中检测是否具有权限
		中间件 + 配置文件
- 动态菜单
	a. 登录时,获取当前用户菜单和权限,放置在Session中
	
	b. 其他请求,在session中获取菜单,动态生成(simple_tag)

数据库表设计

from django.db import models


class User(models.Model):
    """
    用户表
    """
    username = models.CharField(verbose_name='用户名', max_length=32)
    password = models.CharField(verbose_name='密码', max_length=64)
    email = models.EmailField(verbose_name='邮箱')

    def __str__(self):
        return self.username


class Role(models.Model):
    """
    角色表
    """
    caption = models.CharField(verbose_name='角色', max_length=32)

    def __str__(self):
        return self.caption


class User2Role(models.Model):
    """
    用户角色关系表
    """
    user = models.ForeignKey(User, verbose_name='用户', related_name='roles')
    role = models.ForeignKey(Role, verbose_name='角色', related_name='users')

    def __str__(self):
        return '%s-%s' % (self.user.username, self.role.caption,)


class Menu(models.Model):
    """
    菜单表
    """
    caption = models.CharField(verbose_name='菜单名称', max_length=32)
    parent = models.ForeignKey('self', verbose_name='父菜单', related_name='p', null=True, blank=True)

    def __str__(self):
        prev = ""
        parent = self.parent
        while True:
            if parent:
                prev = prev + '-' + str(parent.caption)
                parent = parent.parent
            else:
                break
        return '%s-%s' % (prev, self.caption,)


class Permission(models.Model):
    """
    权限
    """
    caption = models.CharField(verbose_name='权限', max_length=32)
    url = models.CharField(verbose_name='URL正则', max_length=128)
    menu = models.ForeignKey(Menu, verbose_name='所属菜单', related_name='permissions',null=True,blank=True)

    def __str__(self):
        return "%s-%s" % (self.caption, self.url,)


class Action(models.Model):
    """
    操作:增删改查
    """
    caption = models.CharField(verbose_name='操作标题', max_length=32)
    code = models.CharField(verbose_name='方法', max_length=32)

    def __str__(self):
        return self.caption


class Permission2Action2Role(models.Model):
    """
    权限操作关系表
    """
    permission = models.ForeignKey(Permission, verbose_name='权限URL', related_name='actions')
    action = models.ForeignKey(Action, verbose_name='操作', related_name='permissions')
    role = models.ForeignKey(Role, verbose_name='角色', related_name='p2as')

    class Meta:
        unique_together = (
            ('permission', 'action', 'role'),
        )

    def __str__(self):
        return "%s-%s-%s" % (self.permission, self.action, self.role,)
ORM代码

获取用户权限以及中间件权限控制

URL路由系统
#urls.py
from app02 import views as views2

    url(r'^auto-login.html$', views2.login),
    url(r'^auto-index.html$', views2.index),

中间件
#settings.py
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    'middleware.md.M1',
]

配置中间件
#md.py
from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import HttpResponse
import re
class M1(MiddlewareMixin):

    def process_request(self,request,*args,**kwargs):
        valid = ['/auth-login.html','/auth-index.html']
        if request.path_info not in valid:
            action = request.GET.get('md') # GET
            user_permission_dict = request.session.get('user_permission_dict')
            if not user_permission_dict:
                return HttpResponse('无权限')

            # action_list = user_permission_dict.get(request.path_info)
            flag = False
            for k,v in user_permission_dict.items():
                if re.match(k,request.path_info):
                    if action in v:
                        flag = True
                        break
            if not flag:
                return HttpResponse('无权限')


视图函数
#views.py
def login(request):
    if request.method == "GET":
        return render(request,'login2.html')
    else:
        user_permission_dict = {
            '/ah-index.html/':["GET","POST","DEL","Edit"],
            '/order.html/':["GET","POST","DEL","Edit"],
            '/index-(d+).html/':["GET","POST","DEL","Edit"],
        }
        request.session['user_permission_dict'] = user_permission_dict
        return HttpResponse('登录成功')

def index(request):
    # http://127.0.0.1:8000/auth-index.html?md=GET
    return HttpResponse('登陆,并且有权限才能看见我')
权限控制示例

输出动态菜单

urlpatterns = [
    url(r'^app02_test.html', views2.app02_test),
]
urls.py
from django.contrib import admin

# Register your models here.
from app02 import models

admin.site.register(models.User)
admin.site.register(models.Role)
admin.site.register(models.User2Role)
admin.site.register(models.Menu)
admin.site.register(models.Permission)
admin.site.register(models.Action)
admin.site.register(models.Permission2Action2Role)
app02/admin.py
def app02_test(request):
    """
    需要用户名或用户ID,产出:用户关联所有菜单
    :param request:
    :return:
    """

    # 所有菜单:处理成当前用关联的菜单
    all_menu_list = models.Menu.objects.all().values('id', 'caption', 'parent_id')
    """
    [
        {'id':1, 'caption':'菜单1', parent_id:None},
        {'id':2, 'caption':'菜单2', parent_id:None},
        {'id':3, 'caption':'菜单3', parent_id:None},
        {'id':4, 'caption':'菜单1-1', parent_id:1},
    ]

    {
        1:{'id':1, 'caption':'菜单1', parent_id:None,status:False,opened:False,child:[]},
        2:{'id':2, 'caption':'菜单2', parent_id:None,status:False,opened:False,child:[]},
        3:{'id':3, 'caption':'菜单3', parent_id:None,status:False,opened:False,child:[]},
        5:{'id':4, 'caption':'菜单1-1', parent_id:1,status:False,opened:False,child:[]},
    }
   """
    user = models.User.objects.filter(username='alex').first()
    role_list = models.Role.objects.filter(users__user=user)
    permission_list = models.Permission2Action2Role.objects.filter(role__in=role_list).values('permission__id',
                                                                                              'permission__url',
                                                                                              'permission__menu_id',
                                                                                              'permission__caption').distinct()



    """

    [
        {'permission__url':'/order.html','permission__caption': '订单管理','permission__menu_id': 1 },
        {'permission__url':'/order.html','permission__caption': '订单管理','permission__menu_id': 2 },
        {'permission__url':'/order.html','permission__caption': '订单管理','permission__menu_id': 3 },
        {'permission__url':'/order.html','permission__caption': '订单管理','permission__menu_id': 4 },
    ]
    """
    ##### 将权限挂靠到菜单上 ########
    all_menu_dict = {}
    for row in all_menu_list:
        row['child'] = []  # 添加孩子
        row['status'] = False  # 是否显示菜单
        row['opened'] = False  # 是否默认打开
        all_menu_dict[row['id']] = row




    for per in permission_list:
        if not per['permission__menu_id']:
            continue

        item = {
            'id': per['permission__id'],
            'caption': per['permission__caption'],
            'parent_id': per['permission__menu_id'],
            'url': per['permission__url'],
            'status': True,
            'opened': False
        }

        # print(item["url"])
        if re.match(per['permission__url'],request.path_info):
        # if re.match(per['permission__url'], "/orders.html"):
            item['opened'] = True
        pid = item['parent_id']
        all_menu_dict[pid]['child'].append(item)

        # 将当前权限前辈status=True
        temp = pid  # 1.父亲ID
        while not all_menu_dict[temp]['status']:
            all_menu_dict[temp]['status'] = True
            temp = all_menu_dict[temp]['parent_id']
            if not temp:
                break

        # 将当前权限前辈opened=True
        if item['opened']:
            temp1 = pid  # 1.父亲ID
            while not all_menu_dict[temp1]['opened']:
                all_menu_dict[temp1]['opened'] = True
                temp1 = all_menu_dict[temp1]['parent_id']
                if not temp1:
                    break
    # ############ 处理菜单和菜单之间的等级关系 ############
    """
    all_menu_dict = {
        1:{'id':1, 'caption':'菜单1', parent_id:None,status:False,opened:False,child:[{'permission__url':'/order.html','permission__caption': '订单管理','permission__menu_id': 1 },]},
        2:{'id':2, 'caption':'菜单2', parent_id:None,status:False,opened:False,child:[]},
        3:{'id':3, 'caption':'菜单3', parent_id:None,status:False,opened:False,child:[]},
        5:{'id':4, 'caption':'菜单1-1', parent_id:1,status:False,opened:False,child:[]},
    }


    all_menu_list= [
        {'id':1, 'caption':'菜单1', parent_id:None,status:False,opened:False,child:[{'permission__url':'/order.html','permission__caption': '订单管理','permission__menu_id': 1 }, {'id':4, 'caption':'菜单1-1', parent_id:1,status:False,opened:False,child:[]},]},
        {'id':2, 'caption':'菜单2', parent_id:None,status:False,opened:False,child:[]},
        {'id':3, 'caption':'菜单3', parent_id:None,status:False,opened:False,child:[]},

    ]
    """

    result = []
    for row in all_menu_list:
        pid = row['parent_id']
        if pid:
            all_menu_dict[pid]['child'].append(row)
        else:
            result.append(row)


    ##################### 结构化处理结果 #####################
    # print(result)
    # for row in result:
    #     # print(row['caption'], row['status'], row['opened'], )
    #     print(row)

    ##################### 通过结构化处理结果,生成菜单开始 #####################

    def menu_tree(menu_list):
        tpl1 = """
        <div class='menu-item'>
            <div class='menu-header'>{0}</div>
            <div class='menu-body {2}'>{1}</div>
        </div>
        """
        tpl2 = """
        <a href='{0}' class='{1}'>{2}</a>
        """

        menu_str = ""
        for menu in menu_list:
            if not menu['status']:
                continue
            # menu: 菜单,权限(url)
            if menu.get('url'):
                # 权限
                menu_str += tpl2.format(menu['url'],'active' if menu['opened'] else "",menu['caption'])
            else:
                # 菜单
                if menu['child']:
                    child_html = menu_tree(menu['child'])
                else:
                    child_html = ""
                menu_str += tpl1.format(menu['caption'], child_html,"" if menu['opened'] else 'hide')

        return menu_str

    menu_html = menu_tree(result)




    return render(request, "menu_html.html", {"menu_html":menu_html})
app02/views.py
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        .menu-body{
            margin-left: 20px;
        }
        .menu-body a{
            display: block;
        }
        .menu-body a.active{
            color: red;
        }
        .hide{
            display: none;
        }
    </style>
</head>
<body>

    {{ menu_html|safe }}

    <script src="/static/jquery-3.2.1.js"></script>
    <script>
        $(function () {
            $(".menu-header").click(function () {
                $(this).next().removeClass("hide").parent().siblings().find(".menu-body").addClass("hide")
            })
        })
    </script>
</body>
</html>
menu_html.html

生成APP组件

生成公共app
       - 权限限制
       - 生成菜单
python3 manage.py startapp rbac
#白名单url, 不验证

VALID_URL = [
    '/app01/.*',
    '/app02/.*',
    '/login.html',
    '/logout.html',
]
config.py
from django.db import models


class User(models.Model):
    """
    用户表
    """
    username = models.CharField(verbose_name='用户名', max_length=32)
    password = models.CharField(verbose_name='密码', max_length=64)
    email = models.EmailField(verbose_name='邮箱')

    def __str__(self):
        return self.username


class Role(models.Model):
    """
    角色表
    """
    caption = models.CharField(verbose_name='角色', max_length=32)

    def __str__(self):
        return self.caption


class User2Role(models.Model):
    """
    用户角色关系表
    """
    user = models.ForeignKey(User, verbose_name='用户', related_name='roles')
    role = models.ForeignKey(Role, verbose_name='角色', related_name='users')

    def __str__(self):
        return '%s-%s' % (self.user.username, self.role.caption,)


class Menu(models.Model):
    """
    菜单表
    """
    caption = models.CharField(verbose_name='菜单名称', max_length=32)
    parent = models.ForeignKey('self', verbose_name='父菜单', related_name='p', null=True, blank=True)

    def __str__(self):
        prev = ""
        parent = self.parent
        while True:
            if parent:
                prev = prev + '-' + str(parent.caption)
                parent = parent.parent
            else:
                break
        return '%s-%s' % (prev, self.caption,)


class Permission(models.Model):
    """
    权限
    """
    caption = models.CharField(verbose_name='权限', max_length=32)
    url = models.CharField(verbose_name='URL正则', max_length=128)
    menu = models.ForeignKey(Menu, verbose_name='所属菜单', related_name='permissions',null=True,blank=True)

    def __str__(self):
        return "%s-%s" % (self.caption, self.url,)


class Action(models.Model):
    """
    操作:增删改查
    """
    caption = models.CharField(verbose_name='操作标题', max_length=32)
    code = models.CharField(verbose_name='方法', max_length=32)

    def __str__(self):
        return self.caption


class Permission2Action2Role(models.Model):
    """
    权限操作关系表
    """
    permission = models.ForeignKey(Permission, verbose_name='权限URL', related_name='actions')
    action = models.ForeignKey(Action, verbose_name='操作', related_name='permissions')
    role = models.ForeignKey(Role, verbose_name='角色', related_name='p2as')

    class Meta:
        unique_together = (
            ('permission', 'action', 'role'),
        )

    def __str__(self):
        return "%s-%s-%s" % (self.permission, self.action, self.role,)
models.py
#验证中间件


from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import HttpResponse
from rbac import config
import re


class RbacMiddleware(MiddlewareMixin):

    def process_request(self,request,*args,**kwargs):
        for pattern in config.VALID_URL:
            if re.match(pattern,request.path_info):
                return None

        action = request.GET.get('md') # GET
        user_permission_dict = request.session.get('user_permission_dict')
        if not user_permission_dict:
            return HttpResponse('无权限')

        # action_list = user_permission_dict.get(request.path_info)
        flag = False
        for k,v in user_permission_dict.items():
            if re.match(k,request.path_info):
                if action in v:
                    flag = True
                    break
        if not flag:
            return HttpResponse('无权限')
/middleware/md.py
import re
from rbac import models
from django.utils.safestring import mark_safe

def permission_session(user_id,request):
    """

    :param user_id:  rbac中的user表中一条数据id
    :param request:
    :return:
    """
    # obj = models.User.objects.filter(username='杨明').first()
    #
    # # x = models.User2Role.objects.filter(user_id=obj.id)
    # # [User2Role,User2Role,User2Role]
    #
    # role_list = models.Role.objects.filter(users__user_id=obj.id)
    # # [Role,]
    # from django.db.models import Count
    # # permission_list = models.Permission2Action2Role.objects.filter(role__in=role_list).values('permission__url','action__code').annotate(c=Count('id'))
    # permission_list = models.Permission2Action2Role.objects.filter(role__in=role_list).values('permission__url','action__code').distinct()
    """
    [
        {permission_url: '/index.html', action_code:'GET'},
        {permission_url: '/index.html', action_code:'POST'},
        {permission_url: '/index.html', action_code:'DEL'},
        {permission_url: '/index.html', action_code:'Edit'},
        {permission_url: '/order.html', action_code:'GET'},
        {permission_url: '/order.html', action_code:'POST'},
        {permission_url: '/order.html', action_code:'DEL'},
        {permission_url: '/order.html', action_code:'Edit'},
    ]
    放在Session中
    /index.html?md=GET

    {
        '/index.html': [GET,POST,DEL,Edit],
        '/order.html': [GET,POST,DEL,Edit],
    }

    """

    user_permission_dict = {
        '/ah-index.html': ["GET","POST","DEL","Edit"],
        '/order.html':  ["GET","POST","DEL","Edit"],
        '/index-(d+).html':  ["GET","POST","DEL","Edit"],
    }

    request.session['user_permission_dict'] = user_permission_dict


def menu(user_id,current_url):
    """
    根据用户ID,当前URL:获取用户所有菜单以及权限,是否显示,是否打开
    :param user_id:
    :param current_url:
    :return:
    """
    # 所有菜单:处理成当前用关联的菜单
    all_menu_list = models.Menu.objects.all().values('id','caption','parent_id')
    user = models.User.objects.filter(id=user_id).first()
    role_list = models.Role.objects.filter(users__user=user)
    permission_list = models.Permission2Action2Role.objects.filter(role__in=role_list).values('permission__id','permission__url','permission__menu_id','permission__caption').distinct()
    ##### 将权限挂靠到菜单上 ########
    all_menu_dict = {}
    for row in all_menu_list:
        row['child'] = []      # 添加孩子
        row['status'] = False # 是否显示菜单
        row['opened'] = False # 是否默认打开
        all_menu_dict[row['id']] = row

    for per in permission_list:
        if not per['permission__menu_id']:
            continue

        item = {
            'id':per['permission__id'],
            'caption':per['permission__caption'],
            'parent_id':per['permission__menu_id'],
            'url': per['permission__url'],
            'status': True,
            'opened': False
        }
        if re.match(per['permission__url'],current_url):
            item['opened'] = True
        pid = item['parent_id']
        all_menu_dict[pid]['child'].append(item)

        # 将当前权限前辈status=True
        temp = pid # 1.父亲ID
        while not all_menu_dict[temp]['status']:
            all_menu_dict[temp]['status'] = True
            temp = all_menu_dict[temp]['parent_id']
            if not temp:
                break

        # 将当前权限前辈opened=True
        if item['opened']:
            temp1 = pid # 1.父亲ID
            while not all_menu_dict[temp1]['opened']:
                all_menu_dict[temp1]['opened'] = True
                temp1 = all_menu_dict[temp1]['parent_id']
                if not temp1:
                    break
    # ############ 处理菜单和菜单之间的等级关系 ############
    result = []
    for row in all_menu_list:
        pid = row['parent_id']
        if pid:
            all_menu_dict[pid]['child'].append(row)
        else:
            result.append(row)


    ##################### 结构化处理结果 #####################
    for row in result:
        print(row['caption'],row['status'],row['opened'],row)


    def menu_tree(menu_list):
        tpl1 = """
        <div class='menu-item'>
            <div class='menu-header'>{0}</div>
            <div class='menu-body {2}'>{1}</div>
        </div>
        """
        tpl2 = """
        <a href='{0}' class='{1}'>{2}</a>
        """

        menu_str = ""
        for menu in menu_list:
            if not menu['status']:
                continue
            # menu: 菜单,权限(url)
            if menu.get('url'):
                # 权限
                menu_str += tpl2.format(menu['url'],'active' if menu['opened'] else "",menu['caption'])
            else:
                # 菜单
                if menu['child']:
                    child_html = menu_tree(menu['child'])
                else:
                    child_html = ""
                menu_str += tpl1.format(menu['caption'], child_html,"" if menu['opened'] else 'hide')

        return menu_str
    menu_html = menu_tree(result)
    return menu_html


# simple_tag
def css():
    v = """
        <style>
        .hide{
            display: none;
        }
        .menu-body{
            margin-left: 20px;
        }
        .menu-body a{
            display: block;
        }
        .menu-body a.active{
            color: red;
        }
    </style>
        """
    return v

# simple_tag
def js():
    v = """
        <script>
        $(function(){

            $('.menu-header').click(function(){
                $(this).next().removeClass('hide').parent().siblings().find('.menu-body').addClass('hide');

            })

        })
    </script>
    """
    return v
service.py

a. 以后调用 

#自定义的中间件加入到settings

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    'rbac.middleware.md.RbacMiddleware',    
]
settings.py
#后端
from rbac import service
  
1. 用户登录后,拿到用户的ID,调用permission_session()函数(函数代码还没写)
   函数获取用户角色的权限,格式如:
   “”“
    user_permission_dict = {
        '/ah-index.html': ["GET","POST","DEL","Edit"],
        '/order.html':  ["GET","POST","DEL","Edit"],
        '/index-(d+).html':  ["GET","POST","DEL","Edit"],
    }
   ”“”
  
    def login():
      permission_session(用户ID,request)
      return .....
  
2.setting中加入中间件,如上
  
  
3.#获取菜单
    current_url= request.pathinfo
    menu_list = service.menu(用户ID,current_url)
  
4.尽量用simple_tag
    css = servicr.css()
    js = servicr.js()
  
5.前端
  
    {{ css|safe }}
    {{ menu_list|safe }}
    {{ js|safe }}  

代码下载地址:http://files.cnblogs.com/files/luchuangao/rbac.zip

生产环境使用

rbac组件完整版下载地址:http://files.cnblogs.com/files/luchuangao/rbac%E5%AE%8C%E6%95%B4%E7%89%88.zip

a. RBAC原理:

原理:
    1. 简单管理
        角色单一,无需使用权限管理

    2. 角色多管理(权限)
        a. 登录
            session放置用户信息(检测是否已经登录)
            session放置权限信息(检测是否有权访问)
                {
                    '/index.html':[GET,EDIT],
                    '/order.html':[GET,EDIT],
                    '/xxx.html':[GET,EDIT...],
                    '/xxx.html':[GET,EDIT...],
                    '/xxx.html':[GET,EDIT...],
                    '/xxx.html':[GET,EDIT...],
                    '/xxx.html':[GET,EDIT...],
                    '/xxx.html':[GET,EDIT...],
                    '/xxx.html':[GET,EDIT...],
                    '/xxx.html':[GET,EDIT...],
                    '/xxx.html':[GET,EDIT...],
                    '/xxx.html':[GET,EDIT...],
                    '/xxx.html':[GET,EDIT...],
                }
                
            session放置菜单权限信息(用于生成动态多级菜单)
            
        b. 访问网站其他功能: http://www.baiuc.om/xxx.hmtl
            - 获取当前访问的URL, request.path_info
            - 
                    /xxx.hmtl?md=get
                匹配1
                    /xxx.hmtl
                    session放置权限信息(检测是否有权访问)
                    {
                        '/index.html':[GET,EDIT],
                        '/order.html':[GET,EDIT],
                        '/xxx.html':[GET,EDIT...],
                    }
                    
                匹配2
                    /xxx.hmtl
                    session放置权限信息(检测是否有权访问)
                    {
                        '/index.html':[GET,EDIT],
                        '/order.html':[GET,EDIT],
                        '/xxx.html':[GET,EDIT...],
                    }
                
                request.permission_code = "EDIT"
                request.permission_code_list = [GET,EDIT...]
                
                
                PS: 中间件实现
                
        c. 视图函数
        
                def xxx(request):
                    request.permission_code = "EDIT"              # 业务逻辑的编写
                    request.permission_code_list = [GET,EDIT...]  # 前端显示功能按钮
                    
        d. 模板
        
        
        e. 创建动态菜单【多级菜单】
                session中获取菜单权限信息(用于生成动态多级菜单)
                    - 当前用户权限
                    - 所有菜单
                1. 权限挂到菜单上
                2. 菜单父子关系处理
                3. 递归生成菜单
                
                辅助:
                    css
                    js
                    
                
                推荐:simple_tag
        
    
    
使用:【详细见README】
    1. 导入rbac
        -- 中间件
        - service
        - simple_tag
        
    2. 注册app
    
    3. 用户登录
          初始化权限信息: service.initail_permission(request,user_id)
          
          
    4. settings中配置中间件
    
    5. {% rbac_menu reqeust %}
            
        
    其他配置:
        ...
RBAC角色管理原理及使用

b. 应用实例

具体功能:
	临时工:
		报障单     trouble.html
			查看列表 	look
			查看详细 	detail
	
	普通员工:
		报障单     trouble.html
			创建   		post
			删除   		del
			修改   		edit
			查看列表 	look
			查看详细 	detail
		
	运维人员:
		报账单
		解决报障单 trouble-kill.html
		
	总监:
		报障单
		解决报障单
		报表       report.html

报障系统示例代码:http://files.cnblogs.com/files/luchuangao/rbacdemo.zip

制作报表参考:制作报表工具

原文地址:https://www.cnblogs.com/luchuangao/p/7229163.html