一、前言 上一篇大概說了下abp通用樹形模塊如何使用,本篇主要分析下設計思路。 日常開發中會用到很多樹狀結構的數據,比如:產品的多級分類、省市區縣,大多數系統也會用到類似“通用字典/數據字典”的功能,為系統各個地方提下拉框選擇的數據源。abp提供了一個模塊化系統,只要按它的約定就可以實現一個通用的樹 ...
一、前言
上一篇大概說了下abp通用樹形模塊如何使用,本篇主要分析下設計思路。
日常開發中會用到很多樹狀結構的數據,比如:產品的多級分類、省市區縣,大多數系統也會用到類似“通用字典/數據字典”的功能,為系統各個地方提下拉框選擇的數據源。abp提供了一個模塊化系統,只要按它的約定就可以實現一個通用的樹形數據的模塊,這樣公司的多個系統都可以使用,也可以用類似nuget的方式提供給別人使用。
先列舉下它的功能
- 通過nuget方便安裝和升級
- 配置簡單
- 預設已經提供“通用字典”功能
- 實體、管理器、應用服務都是抽象類,結合泛型 狠容易擴展實現自己的樹形結構
二、必備知識
這不是abp入門級的文章,是探討系統模塊化開發的一種思路。所以要求對abp有經驗,完整看過abp文檔,對涉及到的模塊、依賴註入、啟動配置、許可權、菜單、本地化等等概念有清晰的認識
三、包和源碼
源碼地址:https://github.com/bxjg1987/abpGeneralModules
nuget:Install-Package BXJG.GeneralTree -Version 1.0.2
線上地址: http://test.cqsifang.com/ 賬號密碼:admin zlj.com (別胡來,拜托...)
源碼倉庫中還有通用的文件模塊、附件模塊,後期會講講;nuget搜索bxjg可以找到這幾個相關的包
四、總體設計
為了便於說明,我們以常見的產品無限級分類來說明,所有樹形結構數據有這麼幾個特點:
- 有個ParentId屬性指向父級節點,
- 有個code,數據格式類似:00001.00001,一個是能簡單體現產品分類節點的層級結構,二個是方便將來查詢某個分類及其後代分類下的所有產品 where categoryCode like '00001%
- 當然還有Name屬性
上面說了定義實體類的關鍵點,然後我們需要提供一個對應的Manager(這個是abp定義領域服務的套路),來主負責CRUD和節點間的移動操作,其實最麻煩的就是自動處理code,新增和修改時要根據所屬父節點的code自動生成子節點的code,移動時就更複雜了,要重新計算當前節點及其兄弟節點及其所有後代節點的code,這還涉及到目標節點和源節點。為了便於擴展,上面的領域服務還得將被處理的實體泛型話,文章後續會具體說明
最後按abp套路我們還需要提一個應用服務,它核心操作就是調用上面提供的Manager做節點的crud操作,在此基礎上按abp的套路應該提供許可權驗證、處理實體與dto之間的轉換。同理為了便於將來模塊的使用方進行擴展,我們應該應用抽象類和泛型的威力,文章後續會具體說明
最後我們如何提供Repository呢?參考abp zero的思路,我們將這個遺留到調用方來定義。在我們的領域服務Manager和應用服務AppService中都是通過依賴註入註入的IRepository<TEntity,TKey>
核心的3個東東定義好後,我們分別實現一個預設類,實現一個“通用字典”,將來調用方可以繼承我們的類並提供泛型參數來實現他們自己的樹形結構。
另外我們將所有的代碼放在一個項目中,因為模塊足夠小,功能少 這樣調用方用起來更方便
註意一點,上面以產品分類來說明只是便於理解,既然要提供擴展能力,設計時只能從抽象的角度來看待樹形數據,否則設計出來的東西很容易最後發現不夠抽象,此廢話只可意會不可言傳
最後是abp相關的:本地化定義、模塊定義及依賴關係、許可權提供器、菜單提供器、動態webapi的處理
五、實體類
實體類我這裡只是說明,具體源碼請訪問頂部的github
GeneralTreeEntity<TEntity>
它定義了樹形結構數據的通用樹形,Id、Code、Name、Parent等
泛型TEntity是子節點的類型,由於是樹,實際上也是父節點的類型,這種設計是方便將來模塊使用方在實現自定義的樹形結構時拿到的Parent樹形和Children樹形,是他們自己定義的類型
主鍵Id屬性,我定死了long類型,其實也可以使用泛型的TKey,思來想去簡單起見直接定死吧
public class GeneralTreeEntity : GeneralTreeEntity<GeneralTreeEntity>
為了提供我們預設實現的“通用字典”功能,我們定義一個預設的子類GeneralTreeEntity,將來系統中那些簡單的數據可以直接使用它
IsSysDefine表示此節點是否是系統預定義的,將來不允許刪除
IsTree是否是樹形,將來可能會用到,因為某些下拉框數據可能不需要多層次的,比如:民族,學歷
六、領域服務
按套路我們提供一個抽象的領域服務Manager類,和一個為了實現“通用字典”功能的預設實現
public abstract class GeneralTreeManager<TEntity> : DomainService where TEntity : GeneralTreeEntity<TEntity>
它的主要職責總體設計也說了一嘴,完成crud和move移動操作,難點是處理code的自動生成,尤其是移動節點時,節點原來位置之後的其它節點及其後代節點、目標節點之後的其它節點及其後代節點的code的生成
內部對數據的操作直接註入IRepository<TEntity, long>,因此按abp的套路,預設情況下使用EF時,調用方只需要在他的DBContext中頂一個DBSet就可以了
public class GeneralTreeManager : GeneralTreeManager<GeneralTreeEntity>
實現“通用數據字典”的預設領域服類,因為核心功能父類基本都完成了。所以幾乎沒代碼
七、應用服務
按套路一個抽象類,一個預設實現類,內部核心操作是上面提供的Manager來完成的,應用服務主要是出去許可權和dto之間的轉換
public class GeneralTreeAppServiceBase< TEntity, TDto, TEditDto> : ApplicationService, IGeneralTreeAppServiceBase<TDto, TEditDto>
where TEntity : GeneralTreeEntity<TEntity>
where TDto : GeneralTreeGetTreeNodeBaseDto<TDto>, new()
where TEditDto : GeneralTreeNodeEditBaseDto
另外它提供了一些通用方法,一個樹形數據通常在3個地方唄使用,以產品分類為例,在產品分類的管理頁面、在產品的搜索欄應該提供產品類別的選擇、在產品編輯頁面應該有個下拉框選擇當前產品所屬類別,其它樹形數據都有類似的場景,因此抽象的應用服務處理了這部分功能
public class GeneralTreeAppService : GeneralTreeAppServiceBase< GeneralTreeEntity, GeneralTreeDto, GeneralTreeEditDt, GeneralTreeManager>,IGeneralTreeAppService
按套路我們為“通用字典”提供了一個預設的實現類
應用服務中的DTO定義、應用服務介面我這裡省略了,這是abp的常規套路,請看源碼
八、abp相關套路:模塊、本地化、許可權、菜單
模塊和本地化就不說了,abp的常規套路
由於我們提供了“通用字典”,預設情況下是可以直接用作你項目的,調用方完全可以按abp的套路在自己的模塊中來配置許可權和菜單,但是預設我們的模塊也提供了,調用方完全可以在主機的PermissionProvicer和NavigationProvider的合適的位置配置許可權和菜單。參考GeneralTreeModuleConfig源碼
九、總結
abp本身提供了模塊化方式,如果運用得當我們的系統可以由很多小模塊組成,將來更容易維護、升級和復用
就好比我們目前提供的通用樹,如果你使用的是abp,那麼完全可以拿去就用,類似的“通用附件”模塊,因為我們的多個系統或者同一個系統都可能會用到附件的功能,到處複製代碼是下下策。