如何在应用系统中实现数据权限的控制功能(2)

880 查看

关于数据权限的控制,可能我们在做很多大型一点的系统都会碰到过,可能每个人设计和解决问题的思路都有所不同,本文介绍我自己框架里面的解决思路。从上一篇《如何在应用系统中实现数据权限的控制功能》里面我们可能对权限控制和数据权限的控制有了一个初步的了解,本文接着进一步介绍在应用系统中,如何集成数据权限的控制功能。

1、数据权限实现思路分析

为了实现数据权限的控制,我们需要在通用的权限系统里面保存好对应角色具有哪些组织机构的数据权限,然后在应用系统中调用API进行过滤数据处理即可。
为了实现以上的功能需求,我们需要在权限系统里面,角色控制哪里增加一个数据权限的数据存储。
实际的应用系统,当用户登陆成功后,我们获取并记录好其可以管理的公司或者部门,如果是主管的角色,可能有多个公司的数据可以管理,那么可以在程序的顶部,让用户选择管理那个公司的数据即可,如果切换公司,那么刷新现有的界面数据显示就可以了。



在用户成功登陆后,我们可以记录用户的相关权限控制信息,如他所能控制数据的公司或者部门,把它记录下来。

Portal.gc.CompanyList = BLLFactory<RoleData>.Instance.GetBelongCompanysByUser(info.ID);
List<int> deptList = BLLFactory<RoleData>.Instance.GetBelongDeptsByUser(info.ID);
Portal.gc.DeptList = deptList;

然后存储用户默认的公司ID,并根据用户是否为管理员(超级管理员、公司管理员),然后构造一个通用的过滤条件,放到全局缓存里面,方便各个模块使用,如下代码所示。

//设置选定的公司ID(默认为用户所在公司的ID)
Cache.Instance["SelectedCompanyID"] = info.Company_ID;
//设置过滤条件给界面基类使用
string filterCondition = string.Format(" Company_ID = '{0}' ", info.Company_ID);
if (!Portal.gc.IsAdmin)
{
    if (deptList.Count > 0)
    {
        filterCondition += string.Format(" AND Dept_ID IN ({0})", string.Join(",", deptList));
    }
    else
    {
        filterCondition += string.Format(" AND Creator = '{0}' ", info.ID);
    }
}
Cache.Instance["DataFilterCondition"] = filterCondition;

在主界面的时候,我们可以根据用户所能管理的公司数据,在顶部初始化公司列表,方便切换选择,以下是初始化的代码。

//添加受管理的公司机构
//判断如果用户管理的公司数据多于一个,那么就显示选择单位列表,并绑定公司数据
if (Portal.gc.CompanyList.Count > 1)
{
    this.repositoryCompanyItem.Items.Clear();
    foreach (int company in Portal.gc.CompanyList)
    {
        OUInfo companyInfo = BLLFactory<OU>.Instance.FindByID(company);
        if (companyInfo != null)
        {
            this.repositoryCompanyItem.Items.Add(new CListItem(companyInfo.Name, companyInfo.ID.ToString()));
        }
    }

    //多于一个显示公司下拉列表
    this.barCompanyItem.Visibility = DevExpress.XtraBars.BarItemVisibility.Always;
}
else
{
    //只有一个公司时候,屏蔽公司选择列表
    this.barCompanyItem.Visibility = DevExpress.XtraBars.BarItemVisibility.Never;
}

如果多于一个公司,那么正常的需求是可以切换公司来查看其它公司的数据的,要实现这个功能,那么就需要修改登陆的那个全局的过滤条件:Cache.Instance["DataFilterCondition"]了。

我们来看看代码的实现,其主要的逻辑就是获取用户选择的公司ID,然后根据公司、部门信息,重新构建一个全局的过滤条件,并重新缓存到对应的键值里面去,供后面的窗体实现数据的过滤更新。

CListItem item = this.barCompanyItem.EditValue as CListItem;
if (item != null)
{
    //设置选定的公司ID
    Cache.Instance["SelectedCompanyID"] = item.Value;
    SetSelectedCompanyName();

    //设置过滤条件给界面基类使用
    string filterCondition = string.Format(" Company_ID = '{0}' ", item.Value);
    if (!Portal.gc.IsAdmin)
    {
        if (Portal.gc.DeptList.Count > 0)
        {
            filterCondition += string.Format(" AND Dept_ID IN ({0})", string.Join(",", Portal.gc.DeptList));
        }
        else
        {
            filterCondition += string.Format(" AND Creator = '{0}' ", Portal.gc.UserInfo.ID);
        }
    }
    Cache.Instance["DataFilterCondition"] = filterCondition;

如果需要对已有的窗体实现数据更新,那么遍历窗体,并统一实现数据刷新即可。

//遍历全部窗口,更新
foreach (WHC.Framework.BaseUI.BaseDock form in this.MdiChildren)
{
    form.SelectedCompanyID = item.Value;
    form.DataFilterCondition = filterCondition;
    form.FormOnLoad();
}

string message = string.Format("您已经切换数据显示:{0}", item.Text);
MessageDxUtil.ShowTips(message);

2、窗体数据过滤的实现

从上面的步骤代码,我们可以看到如何构建一个全局的过滤条件,但是我们获取数据的时候,如何才能实现数据权限的控制,让用户所能看到的数据在可控的范围内呢?

我们知道,一般窗体数据列表的绑定操作类似如下代码所示

/// <summary>
/// 绑定列表数据
/// </summary>
private void BindData()
{
    //entity
    this.winGridViewPager1.DisplayColumns = displayColumns;
    this.winGridViewPager1.ColumnNameAlias = CallerFactory<ICustomerService>.Instance.GetColumnNameAlias();//字段列显示名称转义

    string where = GetConditionSql();
    PagerInfo pagerInfo = this.winGridViewPager1.PagerInfo;
    List<CustomerInfo> list = CallerFactory<ICustomerService>.Instance.FindWithPager(where, ref pagerInfo);
    this.winGridViewPager1.DataSource = new WHC.Pager.WinControl.SortableBindingList<CustomerInfo>(list);
    this.winGridViewPager1.PrintTitle = "客户信息列表";
}

所以主要的数据控制,就在函数GetConditionSql()里面了,那么这个里面,我们如何整合前面的过滤条件呢?

下面是一个案例代码。

/// <summary>
/// 根据查询条件构造查询语句
/// </summary> 
private string GetConditionSql()
{
    //如果存在高级查询对象信息,则使用高级查询条件,否则使用主表条件查询
    SearchCondition condition = advanceCondition;
    if (condition == null)
    {
        condition = new SearchCondition();
        if(customGridLookUpEdit1.EditValue != null)
        {
            condition.AddCondition("ID", customGridLookUpEdit1.EditValue.ToString(), SqlOperator.Equal);
        }
        condition.AddCondition("Deleted", 0, SqlOperator.Equal);//不显示删除的
    }
    string where = condition.BuildConditionSql().Replace("Where", "");

    //如果是单击节点得到的条件,则使用树列表的,否则使用查询条件的
    if (!string.IsNullOrEmpty(treeConditionSql))
    {
        where = treeConditionSql + " AND Deleted = 0 ";//不显示删除的
    }

    //数据权限的过滤:过滤规则,如果指定公司,以公司过滤,如果进一步指定部门,以公司+部门进行过滤;否则以个人的数据展示
    //如果过滤条件不为空,那么需要进行过滤
    if (!string.IsNullOrEmpty(this.DataFilterCondition))
    {
        where += string.Format(" AND {0}", this.DataFilterCondition);
    }
    return where;
}

我们主要关注下上面红色部分即可,因为我们已经加上了标准的过滤条件了,这样我们就可以看到自己管理的数据了。
为了实现统一的数据控制,我们要求整个业务表的设计,需要引入下面几个标准的字段,这样就能很好使用过滤条件进行数据的过滤了。




前面也介绍到了,窗体可以统一刷新,其奥秘就是它们遵循统一的一个数据加载接口,我们初始化窗体数据的函数代码如下所示。

/// <summary>
/// 编写初始化窗体的实现,可以用于刷新
/// </summary>
public override void FormOnLoad()
{
    InitDictItem();

    BindData();
    InitCustomerPage();
}

所以它们就能够统一调用FormOnLoad来统一刷新数据,就是这个道理。

//遍历全部窗口,更新
foreach (WHC.Framework.BaseUI.BaseDock form in this.MdiChildren)
{
    form.SelectedCompanyID = item.Value;
    form.DataFilterCondition = filterCondition;
    form.FormOnLoad();
}

以上就是我对数据权限控制的一些心得和实现思路,希望大家能够体会其中的思路,并批判性的提出意见和建议。