最近一直在重構系統,看到咱們原來的代碼裏,對於數據權限的實現竟然是在查詢語句裏寫死的。設計模式
正感慨這祖傳代碼怎麼這麼坑,領導就讓我從新設計權限模塊。這....ide
好吧,反正都在重構代碼,直接推翻重來也不算填坑。this
先開始梳理需求,所謂「數據權限」,即通過普通的菜單、按鈕權限後,對用戶能獲取到的數據再進行一次權限校驗。只顯示用戶有權限訪問的數據。spa
通過一番思考我總結出了這個功能的幾個要點:設計
1.權限針對用戶能看到的數據(看不到也就無從操做了),這些數據的來源都是列表查詢。code
2.這些列表查詢都含有須要校驗權限的字段。對象
3.同一種須要校驗權限的字段存在不一樣的權限校驗方式。
blog
分析完需求,我立刻想到了設計模式裏的裝飾器模式。接口
什麼是裝飾器模式?get
裝飾器模式就是當你要給一個對象「穿衣服」時,把衣服封裝起來。穿衣服的類不須要關心「衣服」的實現,只管穿。
這樣一來就能夠在不修改類的狀況下隨意增長或者從新設計「衣服」種類。
代入咱們的場景,裝飾器模式的實現就變成了這樣:
個人query須要通過權限校驗篩選出更少的數據,咱們把權限校驗篩選的操做封裝起來,再設計一個校驗引擎之類的方法來給query選擇該如何校驗。這樣一來原來寫好的query不須要改變。
query不須要改變是最關鍵的。整個系統那麼多列表查詢,若是權限改變了就全都要修改(如原來的那種寫法),那簡直是災難。
簡單瞭解了什麼是裝飾器模式以後,咱們再作一些準備工做就能夠開始寫裝飾器了。
準備工做就是給咱們的query寫一個接口,配合泛型使用,讓裝飾器知道傳進來的query必定含有須要校驗權限的字段。
public interface IAuthorityEntity { /// <summary> /// 管理部門 /// </summary> /// <returns></returns>string MaintainDept { get; set; } }
有了這個接口,咱們如今能夠開始動手寫裝飾器了,首先咱們定義一個裝飾器接口:
這裏使用泛型約束規定傳進來的query必須實現咱們剛纔定義的IAuthorityEntiy接口。
全部的裝飾器都必須實現Filter方法,咱們經過這個方法來將不知足權限的數據過濾掉。
public interface IAuthorityComponent<T> where T : class,IAuthorityEntity { IQueryable<T> Filter(IQueryable<T> query,string key); }
全部裝飾器要實現這個接口,這樣咱們的校驗引擎能夠經過依賴注入的方式來獲取不一樣的裝飾器實現。
接下來咱們先簡單實現幾個基於部門篩選的裝飾器:
public abstract class AbstractDepartmentFilter<T> : IAuthorityComponent<T> where T : class,IAuthorityEntity { protected IOrganizationBLL organizationBLL; public virtual IQueryable<T> Filter(IQueryable<T> query, string departmentID) { query = query.Where(f => f.MaintainDept.Contains(departmentID)); return query; } } /// <summary> /// 本部門 /// </summary> public class DepartmentFilter<T> : AbstractDepartmentFilter<T> where T : class, IAuthorityEntity { } /// <summary> /// 指定部門 /// </summary> public class DesignatedDepartmentFilter<T> : AbstractDepartmentFilter<T> where T : class, IAuthorityEntity { public override IQueryable<T> Filter(IQueryable<T> query, string deptID) { if (!string.IsNullOrWhiteSpace(deptID)) { var keys = deptID.Split(','); query = query.Where(f => keys.Contains(f.MaintainDept)); return query; } return query.Where(f => false); } } /// <summary> /// 本部門及子部門 /// </summary> public class DepartmentAndChildDepartmentFilter<T> : AbstractDepartmentFilter<T> where T : class, IAuthorityEntity { ...... }
好了,有了這些裝飾器,咱們能夠開始寫引擎來裝飾query了。
也是同樣先來個接口和抽象類:
public interface IAuthority<T> where T : class, IAuthorityEntity { IQueryable<T> AuthorityFilter(IQueryable<T> query, EnumAccessScope accessScope, string key); }
public abstract class AbstractAuthority<T> : IAuthority<T> where T : class, IAuthorityEntity { public virtual IQueryable<T> AuthorityFilter(IQueryable<T> query, EnumAccessScope accessScope, string key) { return AuthorityCore(query, accessScope, key); } protected virtual IQueryable<T> AuthorityCore(IQueryable<T> query, EnumAccessScope accessScope, string key) { throw new ExecutionException("該方法未實現"); } }
接下來實現一個基於部門校驗的引擎:
public class DepartmentAuthority<T> : AbstractAuthority<T> where T : class, IAuthorityEntity { IOrganizationBLL organizationBLL; public DepartmentAuthority(IOrganizationBLL organizationBLL) { this.organizationBLL = organizationBLL; } protected override IQueryable<T> AuthorityCore(IQueryable<T> query, EnumAccessScope accessScope, string key) { var deptQuery = query; switch (accessScope) { case EnumAccessScope.All: { break; } case EnumAccessScope.Department: { query = new DepartmentFilter<T>().Filter(deptQuery, key); break; } case EnumAccessScope.DepartmentAndChildDepartment: { query = new DepartmentAndChildDepartmentFilter<T>(organizationBLL).Filter(deptQuery, key); break; } case EnumAccessScope.DesignatedDepartment: { query = new DesignatedDepartmentFilter<T>().Filter(deptQuery, key); break; } default: { throw new Exception("權限讀取錯誤"); } } return query; } }
若是之後新增了一個權限是須要用到不止一種判斷(如指定部門+本部門),在case裏多調一個或多個Filter便可實現「套餐」權限。
寫完引擎以後,接下來就是在外面調用時選擇適合本身的權限套餐了:
這裏跟咱們系統的業務代碼相關性比較高,我就把一些邏輯省略了。
大致思路是注入合適版本的權限引擎,而後將query,權限和參數(如指定的部門ID)傳入引擎。
protected IQueryable<T> Authority<T>(IQueryable<T> query, string controllerName) where T : class, IAuthorityEntity { var factoryKey = string.Empty; var accessScope = 0; //具體的權限獲取和判斷邏輯省略 //這裏使用autofac來注入權限引擎 var factory = AutofacConfig.container.Resolve<AuthorityFactory<T>>(); var authority = factory.Classes[factoryKey]; return authority.AuthorityFilter(query, accessScope, key); }
到這裏代碼其實已經寫完了。
感興趣的朋友們能夠想一想若是要添加一個基於用戶校驗要怎麼寫代碼。
若是有更好的寫法歡迎各位大神交流討論。