精华内容
下载资源
问答
  • 所谓数据分析的权限控制,就是指针对不同用户分别配置不同访问规则,使他们在登录后看到的数据不一样。有些东西不是你想看,想看就能看,想看还得问问俺老孙答不答应… 首先我们来捋捋分析的权限是如何体现在...

    哈哈哈哈,打不过我吧,没有办法我就是这么强大,哈哈哈哈,追不上我吧,没有办法被我打败啦,哈哈哈哈,看不见我吧,分析权限根本没在怕!

    所谓数据分析的权限控制,就是指针对不同的用户分别配置不同的访问规则,使他们在登录后看到的数据不一样。有些东西不是你想看,想看就能看的,想看还得问问俺老孙答不答应…

    首先我们来捋捋分析的权限是如何体现在数据中的。

    一、分析中的用户权限

    在很多应用系统中,经常有两个典型的用户,root 和 guest。对于数据分析来说,root 用户权限最大,可以查询全部表的信息; guest 用户只有部分权限:只能查看个别表的信息。例如:

    root 用户的查询页面 demo 如下图:

    1png

    guest 用户的查询页面 demo 如下图:

    2png

    怎么样,大不相同吧,guest 们是不是有种被“歧视”的感觉?那么,是怎么做到让你看啥你就只能看啥的呢?其实并没那么奇妙,说白了 root 和 guest 用户不同的权限,只是因为对应了不同的可视文件(后缀名为 vsb),而 vsb 文件中一些表和字段的可见性设置就是决定权限的最根本因素。具体而言,其中对于表的可见性又分为可见、不可见或者条件可见。

    可视文件?什么鬼?——不要着急,接下来就教你制作可视文件。

    二、使用可视文件控制分析权限

    同学们可以翻到《当多维分析碰到预定义语义》,回顾一下如何创建元数据文件,然后,我们就可以继续讲解如何创建可视文件了。

    在菜单栏中点击【系统】-【导入元数据】,将元数据文件中的表导入。

    下面用 root.vsb 和 guest.vsb 给大家板书一下。敲黑板,注意了…

    首先编辑 root 用户的可视文件 root.vsb,对于 root 用户我们可以使他看见所有表的信息所以对每一个表我们都给他可见的权限:

    3png

    然后再编辑编辑 guest 用户的可视文件 guest.vsb。对于 guest 用户,诸如 vip 客户等一些表不想让他看到,所以将这些表等设为不可见:

    4png

    “表可见性”设置为可见时,用户就可以正常查询该表的数据。而设置为不可见时,在查询该表数据时就会报错。如果设置为条件可见,那么将根据设定的条件,在查询表的数据时强制过滤。这样,通过使用不同的可见性配置文件,就可以使不同用户浏览到的记录不同了。

    接下来的问题来了,怎么使用这个可见性配置文件呢?简单地说,就是通过 Tag 属性传递。不过,润乾官方提供的只是查询工具,本身不具备用户权限系统。因此,客户在把查询页面引入到自己的系统后,还需要在相应的 jsp 文件中的 java 代码里根据当前用户准备不同的 Tag 属性值,从而达到权限控制的目的。(关于 Tag 属性的详细介绍需要同学们去自学《程序员参考》查询分析控件。)

    例如,如果当前用户是 root ,那就通过 Tag 属性传递如下值:

    String visibility = “root.vsb”;//root.vsb: 对元数据中的所有表都可见

    而如果当前用户是 guest 则需要通过 Tag 属性传递如下值:
    String visibility= “guest.vsb”;//guest.vsb: 仅对元数据中的订单表、 产品类别和产品表可见

    然后,再用下面的标签就可以了。

    5png

    润乾报表提供了 qyx.jsp 页面,直接把这个放进去即可。

    三、使用宏灵活控制分析权限

    现在我们知道了,想要完成权限控制,只需要制作不同的 vsb 文件就可以了。但是实际场景中真的要这么实现就很复杂了。例如在这样一个销售场景中,要求每个员工只能查询自己的订单情况

    如果 ** 按照上面的思路:** 有 N 个销售代表就要做 N 份可视文件,拿某个销售代表“李芳”为例,需要把订单表的“表可见性”设置为“条件可见”,并输入相应的条件:

    6png

    那么当李芳查询的时候在 WEB 端的结果就会像下面这样:

    7png

    不过,如果真要这么做,那么有多少个雇员就要做多少个可视文件,那岂不是废了老劲了?这时候我们就该想有没有哪个大神能来帮帮我们了……

    不用急!找到了润乾报表中“宏”一切就迎刃而解了。

    宏可以实现动态条件,对于这个实际场景问题,一个可视文件就可以轻松解决:

    8png

    首先,我们要知道宏只能用在条件可见里,其中宏名称就是 ${} 中变量名称(这个例子中,宏名称就是“雇员”);宏值就是 ${} 里面变量的值,在实际执行中,系统会用接收到的宏值替换掉宏名称。接下来我们就通过两个场景看看宏值怎么传递的:

    场景一:某个雇员登录

    在使用了宏之后,系统会利用宏拼接查询条件:

    SELECT * FROM 订单 where 雇员 ID = ${雇员}

    假设登录的雇员为“李芳”,从登录的 session 界面获取到雇员值为“李芳”的雇员 ID 为 3,将宏值 3 传递进去后就会得到:

    SELECT * FROM 订单 WHERE 雇员 ID = 3

    为了把正确的宏值传递过来,就需要程序猿带上我们的 Java 代码上场了。在集成到应用系统后,应该由应用系统负责提供当前登录用户的信息,通过 session 传递过来。例如下面 getParamValues().put() 方法。(例子中为了演示直接在 jsp 写了固定值,需要手动改这个值来模拟不同登录用户)

    9png

    场景二:某个经理登录

    我们还是将条件拼接上……

    不过不对呀,好像不符合要求呀,我们想要的是经理看到所有员工的订单信息,员工只能看到自己的订单信息。这个宏似乎解决不了问题了——没关系,没什么是多加一个宏解决不了的问题:

    10png

    上图的代码首先从登录的界面获取到登录者雇员 ID,判断是否是经理 ,如果是经理用户登录,就将经理宏的值设置为 1=1,使得所有数据都满足“1=1 or 雇员 ID= 登录用户 ID”,也就查出所有的数据了。

    而如果是普通员工登录,就将经理宏的值设置为 1=0,这样对于“1=0 or 雇员 ID= 登录用户 ID”来说,只有登录用户的 ID 满足条件,也就限制普通员工用户只能看自己的数据了。

    假设登录的是郑建杰经理,在拼接条件并传递宏值后,SQL 为:

    SELECT * FROM 订单 WHERE 1=1 or 雇员 ID = 5

    查询时就可以看到所有雇员的订单信息:

    11png

    四、DQLTableFilter 对象简述

    在上面的代码中,我们用到了 DQLTableFilter 对象。每个 DQLTableFilter 对象中存储了多个表的权限条件(也就是 vsb 里的可视条件),同时也存储了一套宏的具体值。在 session 里可以设置多个 DQLTableFilter 对象,给不同权限的人使用。具体选用哪个 DQLTableFilter 对象,是通过它的名称控制的,默认的是“default”。在拼 SQL 的时候,就会自动追加涉及到的表的权限条件,同时替换宏值。这样,当有用户登录时,可以从 session 中获取到当前登录用户的 DQLTableFilter 对象,其中包括和当前用户匹配的一套宏值,这样在条件拼接时默认拼接当前用户的这套宏值,这样就可以简单实现权限分配了。

    当然一个宏值,不仅限于是一个固定的字段值,也可以是字段名、比较符、字段值等任何内容,只要确保可视条件里的宏被宏值替换后,是一个合法的条件表达式即可。

    不用 N+1 个可视文件,不用 N+1 个可视条件,只要一个可视文件,只要一个可视条件,就这么 easy,有了可视文件,老板再也不用担心角色分配了,有了“宏”,员工再也不用担心制作可视文件了。

    五、针对字段控制权限

    到这里并没有结束,权限控制不仅局限于表还可以深入到某个表中的某个字段。我们继续举栗子,假设不想让“赵军”看到”上级”列,只需要在不可视字段中添加上“上级”字段即可:

    12png

    此时 web 端,分析雇员表中“赵军”(ID=200005)就不会看到“上级”信息:

    13png

    看到这里的宝宝相信已经 get 到了一项新技能,开始了吗?奥,已经结束了,如果新技能还没有 get 过瘾,就赶紧来润乾报表《分析教程》入坑吧…

    展开全文
  • 今天,我就详细介绍一下在 Aspx应用开发平台 中数据权限的实现方式。  在 Aspx应用开发平台 中,数据权限有四种实现方式: 页面权限;模块权限;数据访问权限;表单权限; 1. 页面权限  在 Aspx应用开发平台...

      在园中,讲数据权限的文章很多,但大部分文章都是浅尝即止,只讲到数据库设计就没了,往往最关键的部分,如何在项目中实现,讲得很少。今天,我就详细介绍一下在 Aspx应用开发平台 中数据权限的实现方式。

     

      在 Aspx应用开发平台 中,数据权限有四种实现方式:

    • 页面权限;
    • 模块权限;
    • 数据访问权限;
    • 表单权限;

    1. 页面权限

      在 Aspx应用开发平台 中,页面通过【菜单对象】来进行管理,用户通过点击菜单来访问页面,因此,我们在【菜单】编辑时指定相应的用户角色即可实现权限控制。

      当 用户直接输入页面Url访问时,系统可以通过检查Url找到对应的菜单,进而检查授权角色是否合法。

      

    2. 模块权限

      我们通常会将一组功能相近的功能页面。组成一个【功能模块】,分配给相应的用户使用,在 Aspx应用开发平台 中,这个功能是通过【导航栏目设置】完成的。一般情况下,要求分配用户的粒度更为细腻,比如要求直接分配给某个用户、某个部门、某个职务等等,为此,在 【导航栏目设置】中,我们采用 【用户选择控件】来绑定用户。

      

      打开 【选择用户】页面,我们可以按 用户组、角色、部门、职务等等选择,也可以直接选定用户。

     

    3.数据访问权限

      在项目开发中,经常会有这样的需求:

    • 角色A是系统管理员,可以查看修改所有的数据,
    • 角色B是普通用户。只能查看修改自己的数据;
    • 角色C是部门领导,可以查看修改本部门所有用户的数据;
    • 未提交的订单可以修改,已经提交的订单不能修改;
    • 新提交的数据,在三天内可以修改,超过三天后锁定;
    • 。。。

      不难发现,很多数据权限的要求都是相同的,为此,我们参考Window的安全策略模式,定义了【数据策略】这一权限控制模型。在 Aspx应用开发平台 中,微系统控件是通过 绑定 用户角色数据策略 的对应关系,实现数据访问权限控制。

     

     

      那么【数据策略】又是怎样组成的呢?

     

      【数据策略】由 【数据筛选条件】对象和相应的界面操作定义组成,由 【数据筛选条件】给出数据的筛选条件,比对数据是否符合筛选条件,符合条件的数据用定义的操作界面进行操作。【数据筛选条件】是实现 【筛选器】接口的对象。

     

     

    ExpandedBlockStart.gif筛选器接口
    namespace ObjectService.Filter
    {
        using System;
        using System.Data;
        
        /// 
    <summary>
        /// 筛选器接口
        /// 
    <example>
        ///  
    <code source="..\ObjectService\Filter\IFilter.cs" lang="c#" ></code>
        /// 
    </example>
        /// 
    </summary>
        public interface IFilter
        {
            /// 
    <summary>
            /// 获得数据筛选条件
            /// 
    </summary>
            /// 
    <param name="dataEntityName">数据实体名称</param>
            /// 
    <returns>数据筛选条件</returns>
            string GetRecordFilter(string dataEntityName);
          
            /// 
    <summary>
            /// 判断记录是否符合数据筛选条件
            /// 
    </summary>
            /// 
    <param name="dataEntityName">数据实体名称</param>
            /// 
    <param name="key">主键</param>
            /// 
    <returns>符合数据筛选条件标志</returns>
            bool InFilter(string dataEntityName, object key);

            /// 
    <summary>
            /// 判断记录是否符合数据筛选条件
            /// 
    </summary>
            /// 
    <param name="row">数据列</param>
            /// 
    <returns>符合数据筛选条件标志</returns>
            bool InFilter(DataRow row);


            /// 
    <summary>
            /// 判断当前用户是否符合数据筛选条件
            /// 
    </summary>
            /// 
    <param name="row">数据列</param>
            /// 
    <param name="userId">用户编号</param>
            /// 
    <returns>符合数据筛选条件标志</returns>
            bool InFilter(DataRow row,string userId);

        }
         
    }

     

      

       【部门数据筛选器】的实现代码:

     

    ExpandedBlockStart.gif部门数据筛选器
    namespace AspxWork.OAHelper.HR
    {
        using ObjectService;
        using System;
        using System.Data;
        using UserHelper;
        using ObjectService.Filter;

        /// 
    <summary>
        /// 部门数据筛选对象
        /// 
    </summary>
        public class DepartmentFilter : IFilter
        {
            /// 
    <summary>
            /// 获得数据筛选条件
            /// 
    </summary>
            /// 
    <param name="dataEntityName">数据实体名称</param>
            /// 
    <returns>数据筛选条件</returns>
            public string GetRecordFilter(string dataEntityName)
            {
                string departmentId = WebControlLibrary.Globals.User.DepartmentId;

                ObjectService.IDataEntityProvider dataEntity = ObjectService.Factory.CreateDataEntity(dataEntityName);

                return dataEntity.Table.Columns["DepartmentId"] != null ? DepartmentHandler.Current.GetFilter("DepartmentId", departmentId) : String.Format(" UserId in ( Select UserId from t_Users where {0} )", DepartmentHandler.Current.GetFilter("Department", departmentId));
            }

            /// 
    <summary>
            /// 判断记录是否符合数据筛选条件
            /// 
    </summary>
            /// 
    <param name="dataEntityName">数据实体名称</param>
            /// 
    <param name="key">主键</param>
            /// 
    <returns>符合数据筛选条件标志</returns>
            public bool InFilter(string dataEntityName, object key)
            {
                ObjectService.IDataEntityProvider dataEntity = ObjectService.Factory.CreateDataEntity(dataEntityName);

                DataRow row = dataEntity.Edit(key);

                string departmentId = WebControlLibrary.Globals.User.DepartmentId;

                return dataEntity.Table.Columns["UserId"] != null ? DepartmentHandler.Current.IsInDepartment(row["UserId"].ToString(),departmentId) : false;
            }

            /// 
    <summary>
            /// 判断记录是否符合数据筛选条件
            /// 
    </summary>
            /// 
    <param name="row">数据列</param>
            /// 
    <returns>符合数据筛选条件标志</returns>
            public bool InFilter(DataRow row)
            {
                string departmentId = WebControlLibrary.Globals.User.DepartmentId;

                return row.Table.Columns["UserId"] != null ? DepartmentHandler.Current.IsInDepartment(row["UserId"].ToString(), departmentId) : false;
         
            }


            /// 
    <summary>
            /// 判断当前用户是否符合数据筛选条件
            /// 
    </summary>
            /// 
    <param name="row">数据列</param>
            /// 
    <param name="userId">用户编号</param>
            /// 
    <returns>符合数据筛选条件标志</returns>
            public bool InFilter(DataRow row, string userId)
            {
                string departmentId = UserHelper.UserHandler.Current[userId].DepartmentId;

                return row.Table.Columns["UserId"] != null ? DepartmentHandler.Current.IsInDepartment(row["UserId"].ToString(), departmentId) : false;
            }
        }
    }

     

     

      【数据策略】又是怎样和 【微系统构件】结合在一起运作呢?如果你仔细看过前面文章中,【微系统构件】的程序代码,你就会发现,每个读取数据的方法都是这样的:

     

    ExpandedBlockStart.gif获得数据筛选条件代码
            /// <summary>
            /// 获得符合数据筛选条件的DataView对象,并排序
            /// 
    </summary>
            /// 
    <param name="recordFilter">数据筛选条件</param>
            /// 
    <param name="order">排序条件</param>
            /// 
    <returns>DataView对象</returns>
            public virtual DataView GetDataView(string recordFilter, string order)
            {
                System.Data.DataSet dataSet = new System.Data.DataSet();

                DataAccess.LoadByCondition(dataSet,GetRecordFilter(recordFilter),order, ViewName);

                return new DataView(dataSet.Tables[this.ViewName]);
            }

           /// 获得对应的数据筛选条件
            /// 
    </summary>
            /// 
    <param name="recordFilter">数据筛选条件</param>
            /// 
    <returns>数据筛选条件</returns>
            protected virtual string GetRecordFilter( string recordFilter)
            {
                return ObjectService.Factory.GetRecordFilter(TableName, recordFilter);
            }

     

     

       对象工厂从微系统构件绑定的数据策略和用户角色中,读出对应的数据筛选条件,插入现有的数据筛选条件,达到数据访问控制的目的。

     

      此外,在数据列表页面,我们通过在数据列表控件的列生成事件中,创建数据访问权限对象,根据数据策略,获得数据访问权限,控制列的生成,实现控制数据列的权限。

     

    ExpandedBlockStart.gif数据列权限
    void Grid_ItemContentCreated(object sender, GridItemContentCreatedEventArgs e)
            {
                DataRow row = (DataRow)e.Item.DataItem;

                string html = String.Empty;
                
                
                AspxWork.FormHelper.Popedom.Popedom currentPopedom;

                //获得当前操作权限对象
                if (isSetPopedom)
                {
                    currentPopedom = popedom;
                }
                else
                {
                    if (popedomEntity != null)
                        currentPopedom = new AspxWork.FormHelper.Popedom.Popedom(popedomEntity, WebControlLibrary.Globals.User.UserId, row);
                    else
                        currentPopedom = new AspxWork.FormHelper.Popedom.Popedom(tableName, WebControlLibrary.Globals.User.UserId, row);
                }

                bool isReadOnly = currentPopedom.IsReadonly;

                bool isAllowDelete = currentPopedom.IsAllowDelete;

                bool isAllowEdit = currentPopedom.IsAllowEdit;


                DataStatusEntity dataStatus = StatusHandler.Current.GetDataStatus(TableName, row);

                if (dataStatus != null)
                {
                    isAllowDelete = dataStatus.IsAllowDelete && currentPopedom.IsAllowDelete;

                    isAllowEdit = dataStatus.IsAllowEdit && popedom.IsAllowEdit;

                    if (!dataStatus.IsAllowEdit || !currentPopedom.IsAllowEdit)
                        isReadOnly = true;
                }


                if (currentPopedom.IsAllowView)
                {
                    

                    string key = WebControlLibrary.Url.SetRequestUrl("
    &Key=" + row[DataEntity.KeyName].ToString(), "Key");

                    if (this.TableEntity.IsOtherWindow)
                    {
                        if (isReadOnly)
                            html = "
    <href=\"" + parameter.ReadonlyUrl + key + "\">查看</a>  | ";
                        else
                        {
                            html = "
    <href=\"" + parameter.ReadonlyUrl + key + "\">查看</a>  | ";
                            html += "
    <href=\"" + parameter.EditUrl + key + "\">编辑</a>  | ";
                        }
                    }
                    else
                    {
                        if (!isReadOnly)
                            html = String.Format("
    <href=\"javascript:Grid.edit(Grid.getItemFromClientId('0 {0}'));\">", row[DataEntity.KeyName].ToString()) + "编辑</a>  | ";
                    }

                    
                }


                if (isAllowDelete)
                {
                    html += String.Format("
    <href=\"javascript:GridDelete(Grid.getItemFromClientId('0 {0}'))\">删除</a>", row[DataEntity.KeyName].ToString());
                }

                if (!String.IsNullOrEmpty(html))
                    e.Content.Controls.Add(new LiteralControl(html));
            }

     

    3.表单权限

      由于 Aspx应用开发平台 的表单都是通过表单设计器设计构建的,因此,对表单权限的控制就非常简单了,可以用代码直接控制:

                //设置表单为只读
                this.ControlContainer.IsReadOnly = true;

                //设置表单的第二个控件为只读
                this.ControlContainer[1].IsReadOnly = true;

       

      也可以通过设计器设置控制,比如 在工作流设置中,设置表单权限:

     

     

      感兴趣的同仁,可以到我们的网站 http://www.doasp.cn/ 下载学习版,有完整的实现源代码。

     

    转载于:https://www.cnblogs.com/ynfengjun/archive/2010/10/18/1853961.html

    展开全文
  • 数据权限控制是几乎每一个业务系统都要实现的一...这里分享一个基于mybatis的数据权限框架,可以做到对业务代码无侵入,数据权限的热插拔。 git地址:https://github.com/caixunshi/mybatis-datalimit 欢迎Star! ...

    数据权限控制是几乎每一个业务系统都要实现的一个功能,很多业务系统采用硬编码的方式将数据权限控制逻辑写入业务代码中,这种实现方式让我们的业务代码中充斥着大量的与业务无关的重复代码,并且数据权限有变化需求就必须 修改代码->测试->发布,增加了需求响应时间。

    这里分享一个基于mybatis的数据权限框架,可以做到对业务代码无侵入,数据权限的热插拔。

    git地址:https://github.com/caixunshi/mybatis-datalimit

    欢迎Star!

    展开全文
  • 问题一:和分页拦截器冲突,造成拦截器执行不了问题 刚开始解决方法是 import com.yzc.aboatedu.interceptor.ExecutorInterceptor; import org.apache.ibatis.session.SqlSessionFactory; import org.slf4j....

    问题一:和分页的拦截器冲突,造成拦截器执行不了的问题

    刚开始解决方法是

    import com.yzc.aboatedu.interceptor.ExecutorInterceptor;
    import org.apache.ibatis.session.SqlSessionFactory;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.ApplicationListener;
    import org.springframework.context.event.ContextRefreshedEvent;
    import org.springframework.stereotype.Component;
     
    import java.util.List;
     
    @Component
    public class StartSysListener implements ApplicationListener<ContextRefreshedEvent> {
     
      private static Logger log = LoggerFactory.getLogger(StartSysListener.class);
      
      @Autowired
      private ExecutorInterceptor executorInterceptor;
      @Autowired
      private List<SqlSessionFactory> sqlSessionFactoryList;
     
     
      @Override
      public void onApplicationEvent(ContextRefreshedEvent event) {
     
        this.addMyInterceptor();
     
      }
     
      private void addMyInterceptor() {
        log.debug("添加自定义Mybatis SQL拦截器.");
        for (SqlSessionFactory sqlSessionFactory : sqlSessionFactoryList) {
          sqlSessionFactory.getConfiguration().addInterceptor(executorInterceptor);
        }
      }
     
    }

    后面发现要么SQL就拼接不上要么,要么SQL就重复拼接。后来查看了PageHelper的官方对拦截器的说明:

    https://github.com/pagehelper/Mybatis-PageHelper/blob/master/wikis/zh/Interceptor.md

    把拦截器的定义按他的规范来使用,然而拦截器能执行,也不需要上面的starterListner的监听。但是问题还是一样,拼接SQL会重复。

    后来查了些资料,得到如下解决方案:

    @Configuration
    @AutoConfigureAfter(PageHelperAutoConfiguration.class)
    public class TestLogAutoConfiguration {
        @Autowired
        private List<SqlSessionFactory> sqlSessionFactoryList;
    
        @PostConstruct
        public void addMyInterceptor() {
            DataPermissionInterceptor e = new DataPermissionInterceptor();
            for (SqlSessionFactory sqlSessionFactory : sqlSessionFactoryList) {
                sqlSessionFactory.getConfiguration().addInterceptor(e);
            }
        }
    }

    原则就是在加载PageHelper的配置之后加载该配置,添加拦截器,保证执行顺序。

    问题二:如何拦截SQL,实现权限SQL语句拼接

    首先肯定需要进行SQL拦截,必然的拦截器不能少,所以第一步,自定义拦截器

    /**
     * 封装基础拦截器,针对不同的操作进行不同的处理
     */
    @Intercepts({
    	@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
    	@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
    	}
    )
    @Slf4j
    public class DataPermissionInterceptor  implements Interceptor {
    
    	private static final Integer MAPPED_STATEMENT_INDEX = 0;
    	private static final Integer PARAM_OBJ_INDEX = 1;
    	private static final Integer ROW_BOUNDS_INDEX = 2;
    	private static final Integer RESULT_HANDLER_INDEX = 3;
    	private static final Integer CACHE_KEY_INDEX = 4;
    	private static final Integer BOUND_SQL_INDEX = 5;
    	private static final String COUNT_PRE = "_COUNT";
    
    	@Autowired
    	private SpringUtil springUtil;
    
    	@Override
    	public Object intercept(Invocation invocation) throws Throwable {
    		Object[] args = invocation.getArgs();
    		MappedStatement ms = (MappedStatement) args[MAPPED_STATEMENT_INDEX];
    		Object parameter = args[PARAM_OBJ_INDEX];
    		RowBounds rowBounds = (RowBounds) args[ROW_BOUNDS_INDEX];
    		ResultHandler resultHandler = (ResultHandler) args[RESULT_HANDLER_INDEX];
    		Executor executor = (Executor) invocation.getTarget();
    		CacheKey cacheKey;
    		BoundSql boundSql;
    		//由于逻辑关系,只会进入一次
    		if(args.length == 4){
    			//4 个参数时
    			boundSql = ms.getBoundSql(parameter);
    			cacheKey = executor.createCacheKey(ms, parameter, rowBounds, boundSql);
    		} else {
    			//6 个参数时
    			cacheKey = (CacheKey) args[CACHE_KEY_INDEX];
    			boundSql = (BoundSql) args[BOUND_SQL_INDEX];
    		}
    
    		
    		return executor.query(newMappedStatement, parameter, rowBounds, resultHandler, cacheKey, boundSql);
    	}

    然后考虑第一个问题,不是所有的查询都要拦截,那么就必然是标注了的才需要去拦截。

    目标明确,解决方法也就很明确了:注解

    自定义注解如下:

    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.METHOD})
    public @interface DataAuth {
    
        String empId();
    
        String project();
    
        String org() default "";
    
        String orderBy();
    }
    

    这个注解很好理解,作用域是方法级别,就是要查那个员工的,那个部门的,那个项目的,最后再排序下。

    OK有了注解,就需要在拦截器里面进行捕获该注解:

    	/**
    	 * 获取数据权限注解信息
    	 *
    	 * @param mappedStatement
    	 * @return
    	 */
    	private DataAuth getPermissionByDelegate(MappedStatement mappedStatement) {
    		DataAuth dataAuth = null;
    		try {
    			String id = mappedStatement.getId();
    			//统计SQL取得注解也是实际查询id上得注解,所以需要去掉_COUNT
    			if(id.contains(COUNT_PRE)){
    				id = id.replace(COUNT_PRE,"");
    			}
    			String className = id.substring(0, id.lastIndexOf("."));
    			String methodName = id.substring(id.lastIndexOf(".") + 1, id.length());
    			final Class<?> cls = Class.forName(className);
    			final Method[] method = cls.getMethods();
    			for (Method me : method) {
    				if (me.getName().equals(methodName) && me.isAnnotationPresent(DataAuth.class)) {
    					dataAuth = me.getAnnotation(DataAuth.class);
    				}
    			}
    		} catch (Exception e) {
    			e.printStackTrace();
    		}
    		return dataAuth;
    	}
    

    这个就是反射了,没什么很难理解的,唯一需要注意的细节就是,如果是使用PageHelper的话,会默认先生存一个查询总数的SQL。这里需要共用改注解,而PageHelper的生成查询总量的方法其实就在该查询ID的后面追加_count

    private static final String COUNT_PRE = "_COUNT";

    所以这里捕获注解的时候需要去原查询的Id上去捕获,保证统计方法也能拼接上权限SQL。

    考虑第二个问题:如何拼接SQL,也就是捕获原SQL生成新SQL

    获取原SQL,这个比较简单

    //原sql
    String sql = boundSql.getSql();

    拼接SQL得到新SQL

    		String newSql = permissionSql(sql,dataAuth,boundSql);
    	/**
    	 * 权限关联
    	 * @param sql
    	 * @param dataAuth
    	 * @param boundSql
    	 */
    	private String permissionSql(String sql,DataAuth dataAuth, BoundSql boundSql) {
    		String oauthSql = getDataPermission(dataAuth.empId(),dataAuth.project(),dataAuth.org());
    		log.info("权限SQL:{}"+oauthSql);
    		String newSql = sql +oauthSql + dataAuth.orderBy();
    		return newSql;
    	}
    	/**
    	 * 获取数据权限
    	 * @param ownerField
    	 * @param projectFiled
    	 * @return
    	 */
    	private String getDataPermission(String ownerField,String projectFiled,String orgIdField){
    		BaseEmployee employee = AuthSqlUtil.getBaseEmployee();
    		String dataPermission = AuthSqlUtil.getCurrentMenuCodeDataPermission();
    		String sql = "";
    		if(DataPermission.ONE.getCode().equals(dataPermission)){
    			if(StringUtils.isNotBlank(ownerField)) {
    				sql += " and " + ownerField + " = '" + employee.getId() + "' ";
    			}
    			if(StringUtils.isNotBlank(orgIdField)){
    				sql += " and " + orgIdField + " = '" + employee.getOrgId() + "' ";
    			}
    			log.info("当前个人权限");
    		}
    		//本部及以下
    		if(DataPermission.THREE.getCode().equals(dataPermission)) {
    			//当前登录人所在组织和所有下级组织
    			String suborg = AuthSqlUtil.getSubOrg();
    			if(StringUtils.isNotBlank(orgIdField)){
    				sql += " and find_in_set("+orgIdField+",'"+suborg+"')>0";
    			}
    			String employeeMyOrgAndSubordinate = AuthSqlUtil.getSubOrgEmp();
    			if(StringUtils.isNotBlank(ownerField)){
    				sql += " and find_in_set("+ownerField+",'"+employeeMyOrgAndSubordinate+"')>0";
    			}
    		}
    		//全部
    		if(DataPermission.FOUR.getCode().equals(dataPermission)){
    			//当前登录人所在的项目(任意上级有项目属性都可以,每个人只能看自己所在项目的数据)
    			String project = AuthSqlUtil.getProject();
    			if(project!=null) {
    			}
    		}
    		//本军团
    		if(DataPermission.FIVE.getCode().equals(dataPermission)){
    			String groupOrgs = AuthSqlUtil.getGroupOrg();
    			if(StringUtils.isNotBlank(orgIdField)){
    				sql += " and find_in_set("+orgIdField+",'"+groupOrgs+"')>0";
    			}
    			String result = AuthSqlUtil.getGroupOrgEmp();
    			if(StringUtils.isNotBlank(ownerField)){
    				sql += " and find_in_set("+ownerField+",'"+result+"')>0";
    			}
    		}
    		//上级部门级以下
    		if(DataPermission.SIX.getCode().equals(dataPermission)){
    			String upperOrgId = employee.getParentOrgId();
    			String upperorg = AuthSqlUtil.getUpperOrg();
    			if(StringUtils.isNotBlank(orgIdField)){
    				sql += " and find_in_set("+orgIdField+",'"+upperorg+"')>0";
    			}
    			String result = AuthSqlUtil.getUpperOrgEmp();
    			if(StringUtils.isNotBlank(ownerField)){
    				sql += " and find_in_set("+ownerField+",'"+result+"')>0";
    			}
    		}
    		//存在关联部门
    		if(DataPermission.SEVEN.getCode().equals(dataPermission)) {
    			//本部及关联部门
    			String employeeManyOrg = AuthSqlUtil.getOtherOrg();
    			if(StringUtils.isNotBlank(orgIdField)){
    				sql += " and find_in_set("+orgIdField+",'"+employeeManyOrg+"')>0";
    			}
    			String result =  AuthSqlUtil.getOtherOrgEmp();
    			if(StringUtils.isNotBlank(ownerField)){
    				sql += " and find_in_set("+ownerField+",'"+result+"')>0";
    			}
    		}
    		return sql;
    	}

    使新的SQL生效

    MappedStatement newMappedStatement = setCurrentSql(ms,parameter,boundSql,newSql);
    args[MAPPED_STATEMENT_INDEX] = newMappedStatement;
    	/**
    	 * 设置sql
    	 * @param mappedStatement
    	 * @param paramObj
    	 * @param boundSql
    	 * @param sql
    	 * @return
    	 */
    	private MappedStatement setCurrentSql(MappedStatement mappedStatement, Object paramObj,BoundSql boundSql,String sql) {
    		BoundSqlSource boundSqlSource = new BoundSqlSource(boundSql);
    		MappedStatement newMappedStatement = copyFromMappedStatement(mappedStatement, boundSqlSource);
    		MetaObject metaObject = MetaObject.forObject(newMappedStatement,
    				new DefaultObjectFactory(), new DefaultObjectWrapperFactory(),
    				new DefaultReflectorFactory());
    		metaObject.setValue("sqlSource.boundSql.sql", sql);
    		return newMappedStatement;
    	}
    
    	/**
    	 * 获取MappedStatement
    	 * @param invo
    	 * @return
    	 */
    	private MappedStatement getMappedStatement(Invocation invo) {
    		Object[] args = invo.getArgs();
    		Object mappedStatement = args[MAPPED_STATEMENT_INDEX];
    		return (MappedStatement) mappedStatement;
    	}
    
    	/**
    	 * 自定义私有SqlSource
    	 */
    	private class BoundSqlSource implements SqlSource {
    
    		private BoundSql boundSql;
    
    		private BoundSqlSource(BoundSql boundSql) {
    			this.boundSql = boundSql;
    		}
    
    		@Override
    		public BoundSql getBoundSql(Object parameterObject) {
    			return boundSql;
    		}
    	}
    
    	/**
    	 * copy
    	 * @param ms
    	 * @param newSqlSource
    	 * @return
    	 */
    	private MappedStatement copyFromMappedStatement(MappedStatement ms, SqlSource newSqlSource) {
    		MappedStatement.Builder builder = new MappedStatement.Builder(ms.getConfiguration(), ms.getId(), newSqlSource, ms.getSqlCommandType());
    		builder.resource(ms.getResource());
    		builder.fetchSize(ms.getFetchSize());
    		builder.statementType(ms.getStatementType());
    		builder.keyGenerator(ms.getKeyGenerator());
    		if (ms.getKeyProperties() != null && ms.getKeyProperties().length > 0) {
    			builder.keyProperty(ms.getKeyProperties()[0]);
    		}
    		builder.timeout(ms.getTimeout());
    		builder.parameterMap(ms.getParameterMap());
    		builder.resultMaps(ms.getResultMaps());
    		builder.resultSetType(ms.getResultSetType());
    		builder.cache(ms.getCache());
    		builder.flushCacheRequired(ms.isFlushCacheRequired());
    		builder.useCache(ms.isUseCache());
    		return builder.build();
    	}

    当然这里的权限数据,是通过过滤器和线程来进行捕获和维护,至于具体的实现就比较简单,大家根据自己的组织结构去实现就行。

    这里需要注意的一个问题,过滤器和spring security结合的时候,可能会导致,执行两次。解决方法很简单,通过请求头增加判断逻辑即可。

       HttpServletRequest request = (HttpServletRequest)servletRequest;
            String authCode = request.getHeader(Constant.AUTHORIZATION);
            if(StringUtils.isBlank(authCode)){
                chain.doFilter(servletRequest, servletResponse);
                return;
    
            }
    public class AuthSqlUtil {
    
        private static final ThreadLocal<String> currentMenuCodeDataPermission = new ThreadLocal<>();
        private static final ThreadLocal<String> subOrg = new ThreadLocal<>();
        private static final ThreadLocal<String> subOrgEmp = new ThreadLocal<>();
        private static final ThreadLocal<String> groupOrg = new ThreadLocal<>();
        private static final ThreadLocal<String> groupOrgEmp = new ThreadLocal<>();
        private static final ThreadLocal<String> otherOrg = new ThreadLocal<>();
        private static final ThreadLocal<String> otherOrgEmp = new ThreadLocal<>();
        private static final ThreadLocal<String> upperOrg = new ThreadLocal<>();
        private static final ThreadLocal<String> upperOrgEmp = new ThreadLocal<>();
        private static final ThreadLocal<String> project = new ThreadLocal<>();
        private static final ThreadLocal<BaseEmployee> baseEmployee = new ThreadLocal<>();
    
        public static String getCurrentMenuCodeDataPermission() {
            return currentMenuCodeDataPermission.get();
        }
    
        public static void setCurrentMenuCodeDataPermission(String dataPermission) {
            currentMenuCodeDataPermission.set(dataPermission);
        }
    
        public static String getOtherOrg(){
            return otherOrg.get();
        }
    
        public static void setOtherOrg(String org){
            otherOrg.set(org);
        }
        public static String getOtherOrgEmp(){
            return otherOrgEmp.get();
        }
    
        public static void setOtherOrgEmp(String orgEmp){
            otherOrgEmp.set(orgEmp);
        }
        public static String getSubOrg(){
            return subOrg.get();
        }
    
        public static void setSubOrg(String org){
            subOrg.set(org);
        }
        public static String getSubOrgEmp(){
            return subOrgEmp.get();
        }
    
        public static void setSubOrgEmp(String orgEmp){
            subOrgEmp.set(orgEmp);
        }
        public static String getGroupOrg(){
            return groupOrg.get();
        }
    
        public static void setGroupOrg(String org){
            groupOrg.set(org);
        }
        public static String getGroupOrgEmp(){
            return groupOrgEmp.get();
        }
    
        public static void setGroupOrgEmp(String orgEmp){
            groupOrgEmp.set(orgEmp);
        }
        public static String getUpperOrg(){
            return upperOrg.get();
        }
    
        public static void setUpperOrg(String org){
            upperOrg.set(org);
        }
        public static String getUpperOrgEmp(){
            return upperOrgEmp.get();
        }
    
        public static void setUpperOrgEmp(String orgEmp){
            upperOrgEmp.set(orgEmp);
        }
        public static String getProject(){
            return project.get();
        }
    
        public static void setProject(String p){
            project.set(p);
        }
    
        public static void setBaseEmployee(BaseEmployee employee){
            baseEmployee.set(employee);
        }
    
        public static BaseEmployee getBaseEmployee(){
            return baseEmployee.get();
        }
    }
    

     

    展开全文
  • 虽然OpenJWeb中集成了Spring Security,不过数据权限并没有按照Spring Security中 ACL框架,而是采用了一种更容易理解的方式,同样也能够实查询,编辑,删除操作的数据权限控制. 不需要为每一数据记录设置访问用户,只...
  • 1 数据库实现权限操作既是管理又是超级管理员需要五表关联1.1 四表联动1.2 表设计models.py# 权限系统四标关联:角色表、用户表、节点表、权限表# 角色表class RoleModel(models.Model):name = models.CharField...
  • 虽然OpenJWeb中集成了Spring Security,不过数据权限并没有按照Spring Security中 ACL框架,而是采用了一种更容易理解的方式,同样也能够实查询,编辑,删除操作的数据权限控制. 不需要为每一数据记录设置访问用户,只...
  • 基于mybatis拦截器实现数据权限

    万次阅读 2018-12-02 11:41:38
    数据权限是很多系统常见的功能,实现方式也是很多的,最近在做项目的时候,自己基于mybatis拦截器做了一个数据权限的功能。 **功能设计 a) 需要做数据权限功能的表加上一个权限id字段。 权限id可以不仅仅是...
  • 上面这两种数据权限的管控方式,可以通过spring aop + 自定义注解来实现。 一、自定义注解 自定义参数过滤注解,要在注解中获取到方法上需要进行数据过滤的参数名。代码如下: import java.lang....
  • 实现web数据同步四种方式 ======================================= 1、nfs实现web数据共享2、rsync +inotify实现web数据同步3、rsync+sersync更快更节约资源实现web数据同步4、unison+inotify实现web数据双向同步...
  • Tips:本项目使用Maven搭建,项目实现的分页效果,使用是最原始的方式,未使用分页插件! 数据库环境搭建 创建数据库: create database emp_sys; 创建数据表: 员工表 create table employee( id int primary ...
  • Mybatis 拦截器实现数据权限

    千次阅读 2017-08-25 13:24:25
    之前接触的数据权限一般是在SQL中加入userid=xx之类条件,但是这样方式有比较大局限性,侵入性过大,对原有项目改到也很大。 2.思考 考虑到Mybatis拦截器能够拦截SQL执行整个过程,因为我们可以考虑SQL
  • 本文主要介绍通过zookeeper原生api实现数据节点增,删,改,查,及其ACL权限管理。 package com.ilike.zk;import java.io.IOException; import org.apache.zookeeper.CreateMode; import org.apache....
  • 本文主要介绍通过zookeeper原生api实现数据节点增,删,改,查,及其ACL权限管理。package com.ilike.zk;import java.io.IOException;import org.apache.zookeeper.AsyncCallback; import org.apache....
  • 这里是设置数据权限的入口 这里可以选择需要设置权限范围的权限项目 点击选择按钮会出来 选数据集权限项目的窗体 本文转自 jirigala 51CTO博客,原文链接:http://blog.51cto.com/234797...
  • 功能权限和数据权限

    2019-10-14 23:25:44
    功能权限指的是以功能为维度的限制,不如查看保险系统里的保单数据,新增...功能权限的实现方式可以通过角色、功能菜单组合方式实现,系统化程度高,数据权限只能通过代码逻辑来控制,个性化程度较高,一般来说,...
  • 后台管理员可以新增前台成员,不通的成员具有不同的权限,有权限的人员可以看到一些操作按钮或者数据列表,反之则看不到。 那么也就是说,前端需要根据登录用户的权限来渲染指定的dom。 1、components文件下新建...
  • 就接着撸jiiiiiin权限系统 ,可是我发现漏了一个东西,就是一更时候,针对v-access指令默认模式,通过校验对应资源所需接口列表来检查登录用户是否具有访问权限,但是那个时候对比是接口url,数据结构如下: ...
  • 我们经常需要实现灵活配置数据访问权限控制,例如只有某些用户可以看到“开户行,银行帐户,公司税号”等字段,其他用户都不能访问,也不能查看这几个字段,类似需求在通用权限管理系统里是如何实现的?...
  • C库采用SOCK_DGRAM方式构建icmp包,避开raw socket必须root权限的限制,实现ping功能。 Github: https://github.com/bgylde/PingForAndroid ping实现方式 1. 通常实现方式 这种方式是直接创建网络层的socket,...
  • mybatis整合数据权限

    2019-05-18 12:53:00
    关于数据权限的实现, 个人想了两种实现方式 第一种是基于AOP, 配置相应的注解, 在切面中将数据权限的参数值强制设置到请求参数中去, 然后dao层利用mybatis的动态sql, 将权限值拼接进去, 该方案有前提条件, 数据权限...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 3,573
精华内容 1,429
关键字:

数据权限的实现方式