一、前言 這是一篇搭建許可權管理系統的系列文章。 隨著網路的發展,信息安全對應任何企業來說都越發的重要,而本系列文章將和大家一起一步一步搭建一個全新的許可權管理系統。 說明:由於搭建一個全新的項目過於繁瑣,所有作者將挑選核心代碼和核心思路進行分享。 二、技術選擇 三、開始設計 1、自主搭建vue前端和. ...
一、前言
這是一篇搭建許可權管理系統的系列文章。
隨著網路的發展,信息安全對應任何企業來說都越發的重要,而本系列文章將和大家一起一步一步搭建一個全新的許可權管理系統。
說明:由於搭建一個全新的項目過於繁瑣,所有作者將挑選核心代碼和核心思路進行分享。
二、技術選擇
三、開始設計
1、自主搭建vue前端和.net core webapi後端,網上有很多搭建教程。
這是我搭建的
後端: 前端:
搭建好之後,vue需要把基礎配置做好,比如路由、響應請求等,網上都有教程。
vue配置較為簡單,webapi的框架我使用DDD領域啟動設計方式,各個層的介紹如下下。
- ProjectManageWebApi webapi介面層,屬於啟動項
- Model 業務模型,代表著系統中的具體業務對象。
- Infrastructure 倉儲層,是數據存儲層,提供持久化對象的方法。
- Domain 領域層,是整個系統運行時核心業務對象的載體,是業務邏輯處理的領域。
- Subdomain 子域,子域是領域層更加細微的劃分,處理整個系統最核心業務邏輯。
- Utility 工具層,存放系統的輔助工具類。
2、搭建資料庫
菜單對於一個系統來說,是必不可少的,我們搭建許可權管理系統就從這裡開始
任務:建立菜單表,並通過程式把菜單動態載入到頁面,實現樹形菜單。
這是我的菜單表結構
我採用的是一張表存儲系統菜單,用id和pid存儲上下級關係。當然這不是唯一的,根據情況可以把它拆分層多張表。
3、創建基礎倉儲和菜單倉儲
在webapi中Infrastructure 倉儲層創建基礎倉儲,以便提供持久化支持。
我orm框架使用的是dapper來提共資料庫和編程語言間的映射關係。
首先需要建立一個增刪改查的倉儲介面,大致如下:
/// <summary> /// 倉儲介面定義 /// </summary> public interface IRepository { } /// <summary> /// 定義泛型倉儲介面 /// </summary> /// <typeparam name="T">實體類型</typeparam> /// <typeparam name="object">主鍵類型</typeparam> public interface IRepository<T> : IRepository where T : class, new() { /// <summary> /// 新增 /// </summary> /// <param name="entity">實體</param> /// <param name="innserSql">新增sql</param> /// <returns></returns> int Insert(T entity, string innserSql); /// <summary> /// 修改 /// </summary> /// <param name="entity">實體</param> /// <param name="updateSql">更新sql</param> /// <returns></returns> int Update(T entity, string updateSql); /// <summary> /// 刪除 /// </summary> /// <param name="deleteSql">刪除sql</param> /// <returns></returns> int Delete(string key,string deleteSql); /// <summary> /// 根據主鍵獲取模型 /// </summary> /// <param name="key">主鍵</param> /// <param name="selectSql">查詢sql</param> /// <returns></returns> T GetByKey(string key, string selectSql); /// <summary> /// 獲取所有數據 /// </summary> /// <param name="selectAllSql">查詢sql</param> /// <returns></returns> List<T> GetAll(string selectAllSql); /// <summary> /// 根據唯一主鍵驗證數據是否存在 /// </summary> /// <param name="id">主鍵</param> /// <param name="selectSql">查詢sql</param> /// <returns>返回true存在,false不存在</returns> bool IsExist(string id, string selectSql); /// <summary> /// dapper通用分頁方法 /// </summary> /// <typeparam name="T">泛型集合實體類</typeparam> /// <param name="pageResultModel">分頁模型</param> /// <returns></returns> PageResultModel<T> GetPageList<T>(PageResultModel pageResultModel); }View Code
然後實現這個倉儲介面
/// <summary> /// 倉儲基類 /// </summary> /// <typeparam name="T">實體類型</typeparam> /// <typeparam name="TPrimaryKey">主鍵類型</typeparam> public abstract class Repository<T> : IRepository<T> where T : class, new() { /// <summary> /// 刪除 /// </summary> /// <param name="deleteSql">刪除sql</param> /// <returns></returns> public int Delete(string key, string deleteSql) { using var connection = DataBaseConnectConfig.GetSqlConnection(); return connection.Execute(deleteSql, new { Key = key }); } /// <summary> /// 根據主鍵獲取模型 /// </summary> /// <param name="id">主鍵</param> /// <param name="selectSql">查詢sql</param> /// <returns></returns> public T GetByKey(string id, string selectSql) { using var connection = DataBaseConnectConfig.GetSqlConnection(); return connection.QueryFirstOrDefault<T>(selectSql, new { Key = id }); } /// <summary> /// 獲取所有數據 /// </summary> /// <param name="selectAllSql">查詢sql</param> /// <returns></returns> public List<T> GetAll(string selectAllSql) { using var connection = DataBaseConnectConfig.GetSqlConnection(); return connection.Query<T>(selectAllSql).ToList(); } /// <summary> /// 新增 /// </summary> /// <param name="entity">新增實體</param> /// <param name="innserSql">新增sql</param> /// <returns></returns> public int Insert(T entity, string innserSql) { using var connection = DataBaseConnectConfig.GetSqlConnection(); return connection.Execute(innserSql, entity); } /// <summary> /// 根據唯一主鍵驗證數據是否存在 /// </summary> /// <param name="id">主鍵</param> /// <param name="selectSql">查詢sql</param> /// <returns>返回true存在,false不存在</returns> public bool IsExist(string id, string selectSql) { using var connection = DataBaseConnectConfig.GetSqlConnection(); var count = connection.QueryFirst<int>(selectSql, new { Key = id }); if (count > 0) return true; else return false; } /// <summary> /// 更新 /// </summary> /// <param name="entity">更新實體</param> /// <param name="updateSql">更新sql</param> /// <returns></returns> public int Update(T entity, string updateSql) { using var connection = DataBaseConnectConfig.GetSqlConnection(); return connection.Execute(updateSql, entity); } /// <summary> /// 分頁方法 /// </summary> /// <typeparam name="T">泛型集合實體類</typeparam> /// <param name="pageResultModel">分頁模型</param> /// <returns></returns> public PageResultModel<T> GetPageList<T>(PageResultModel pageResultModel) { PageResultModel<T> resultModel = new(); using var connection = DataBaseConnectConfig.GetSqlConnection(); int skip = 1; var orderBy = string.Empty; if (pageResultModel.pageIndex > 0) { skip = (pageResultModel.pageIndex - 1) * pageResultModel.pageSize + 1; } if (!string.IsNullOrEmpty(pageResultModel.orderByField)) { orderBy = string.Format(" ORDER BY {0} {1} ", pageResultModel.orderByField, pageResultModel.sortType); } StringBuilder sb = new StringBuilder(); sb.AppendFormat("SELECT COUNT(1) FROM {0} where 1=1 {1};", pageResultModel.tableName, pageResultModel.selectWhere); sb.AppendFormat(@"SELECT * FROM(SELECT ROW_NUMBER() OVER( {3}) AS RowNum,{0} FROM {1} where 1=1 {2} ) AS result WHERE RowNum >= {4} AND RowNum <= {5} ", pageResultModel.tableField, pageResultModel.tableName, pageResultModel.selectWhere, orderBy, skip, pageResultModel.pageIndex * pageResultModel.pageSize); using var reader = connection.QueryMultiple(sb.ToString()); resultModel.total = reader.ReadFirst<int>(); resultModel.data = reader.Read<T>().ToList(); return resultModel; } }View Code
以上兩段代碼就實現了對資料庫的增刪改查。當然在上述倉儲介面中有一個分頁查詢介面,它對於的模型如下
/// <summary> /// 分頁模型 /// </summary> public class PageResultModel { /// <summary> /// 當前頁 /// </summary> public int pageIndex { get; set; } /// <summary> /// 每頁顯示條數 /// </summary> public int pageSize { get; set; } /// <summary> /// 查詢表欄位 /// </summary> public string tableField { get; set; } /// <summary> /// 查詢表 /// </summary> public string tableName { get; set; } /// <summary> /// 查詢條件 /// </summary> public string selectWhere { get; set; } /// <summary> /// 查詢條件json /// </summary> public string filterJson { get; set; } /// <summary> /// 當前菜單id /// </summary> public string menuId { get; set; } /// <summary> /// 排序欄位(不能為空) /// </summary> public string orderByField { get; set; } /// <summary> /// 排序類型 /// </summary> public string sortType { get; set; } /// <summary> /// 總數 /// </summary> public int total { get; set; } } /// <summary> /// 查詢數據 /// </summary> /// <typeparam name="T"></typeparam> public class PageResultModel<T> : PageResultModel { /// <summary> /// 數據 /// </summary> public List<T> data { get; set; } }
上述代碼解釋:上述倉儲介面中定義了所有基礎介面,因為它們都是資料庫操作最基本的存在,為了統一管理,降低耦合把它們定義到倉儲中,以備後用。
建立好基礎倉儲後,我們需要建立菜單表的倉儲
菜單倉儲介面
/// <summary> /// 菜單倉儲 /// </summary> public interface ISysMenuRepository : IRepository<Menu> {}
菜單倉儲介面實現
/// <summary> /// 菜單倉儲 /// </summary> public class SysMenuRepository : Repository<Menu>, ISysMenuRepository {}
上述代碼解釋:可以看見上述代碼繼承了IRepository和Repository,這說明菜單擁有了增刪改查等功能。
4、創建領域服務,遞歸組織樹形菜單結構
在Domain領域層創建領域服務介面和實現介面
領域服務介面
/// <summary> /// 菜單服務介面 /// </summary> public interface ISysMenuService { /// <summary> /// 獲取所有菜單--上下級關係 /// </summary> /// <returns></returns> List<MenuDao> GetAllChildren(); }
領域介面實現
/// <summary> /// 菜單服務實現 /// </summary> public class SysMenuService : ISysMenuService { //倉儲介面 private readonly ISysMenuRepository _menuRepository; /// <summary> /// 構造函數 實現依賴註入 /// </summary> /// <param name="userRepository">倉儲對象</param> public SysMenuService(ISysMenuRepository menuRepository) { _menuRepository = menuRepository; } /// <summary> /// 獲取菜單--上下級關係 /// </summary> /// <returns></returns> public List<MenuDao> GetAllChildren() { var list = _menuRepository.GetMenusList(); var menuDaoList = MenuCore.GetMenuDao(list); return menuDaoList; } }
5、在Subdomain子域中創建菜單核心代碼
為什麼在子域中創建菜單核心,應該菜單是整個系統的核心之一,考慮到之後系統會頻繁使用,所以創建在子域中,以便提供給其他業務領域使用
下麵是遞歸菜單實現樹形結構的核心
public static class MenuCore { #region 用於菜單導航的樹形結構 /// <summary> /// 遞歸獲取菜單結構--呈現上下級關係 /// 用於菜單的樹形結構 /// </summary> /// <returns></returns> public static List<MenuDao> GetMenuDao(List<Menu> menuList) { List<MenuDao> list = new(); List<MenuDao> menuListDto = new(); foreach (var item in menuList) { MenuDao model = new() { Title = item.MenuTitle, Icon = item.MenuIcon, Id = item.MenuUrl + "?MneuId=" + item.Id, MenuKey = item.Id, PMenuKey = item.Pid, Component = item.Component, Path = item.Path, RequireAuth = item.RequireAuth, Name = item.Name, Redirect = item.Redirect, IsOpen = item.IsOpen }; list.Add(model); } foreach (var data in list.Where(f => f.PMenuKey == 0 && f.IsOpen)) { var childrenList = GetChildrenMenu(list, data.MenuKey); data.children = childrenList.Count == 0 ? null : childrenList; menuListDto.Add(data); } return menuListDto; } /// <summary> /// 實現遞歸 /// </summary> /// <param name="moduleOutput"></param> /// <param name="id"></param> /// <returns></returns> private static List<MenuDao> GetChildrenMenu(List<MenuDao> moduleOutput, int id) { List<MenuDao> sysShowTempMenus = new(); //得到子菜單 var info = moduleOutput.Where(w => w.PMenuKey == id && w.IsOpen).ToList(); //迴圈 foreach (var sysMenuInfo in info) { var childrenList = GetChildrenMenu(moduleOutput, sysMenuInfo.MenuKey); //把子菜單放到Children集合里 sysMenuInfo.children = childrenList.Count == 0 ? null : childrenList; //添加父級菜單 sysShowTempMenus.Add(sysMenuInfo); } return sysShowTempMenus; } }
以上便是後端實現動態菜單的核心代碼,到這一節點,後端的工作基本完成。
在Controller創建好介面後,運行後端代碼,出現如圖所示,便說明成功。
6、vue 動態路由搭建
配置vue動態路由前,需要看你選擇的前端框架是什麼,不同的框架,解析的欄位不一樣,我選擇的是layui vue,動態配置如下:
export const generator = ( routeMap: any[], parentId: string | number, routeItem?: any | [], ) => { return routeMap //.filter(item => item.menuKey === parentId) .map(item => { const { title, requireAuth, menuKey } = item || {}; const currentRouter: RouteRecordRaw = { // 如果路由設置了 path,則作為預設 path,否則 路由地址 動態拼接生成如 /dashboard/workplace path: item.path, // 路由名稱,建議唯一 //name: `${item.id}`, // meta: 頁面標題, 菜單圖標, 頁面許可權(供指令許可權用,可去掉) meta: { title, requireAuth, menuKey }, name: item.name, children: [], // 該路由對應頁面的 組件 (動態載入 @/views/ 下麵的路徑文件) component: item.component && defineRouteComponentKeys.includes(item.component) ? defineRouteComponents[item.component] : () => url.value, }; // 為了防止出現後端返回結果不規範,處理有可能出現拼接出兩個 反斜杠 if (!currentRouter.path.startsWith('http')) { currentRouter.path = currentRouter.path.replace('//', '/'); } // 重定向 item.redirect && (currentRouter.redirect = item.redirect); if (item.children != null) { // 子菜單,遞歸處理 currentRouter.children = generator(item.children, item.menuKey, currentRouter); } if (currentRouter.children === undefined || currentRouter.children.length <= 0) { currentRouter.children; } return currentRouter; }) .filter(item => item); };View Code
通過以上代碼,獲取動態路由,然後把它加入到你的路由菜單中,這樣便實現了頁面菜單動態載入。
四、項目效果
五、說明
以上便是實現vue+webapi實現動態路由的全部核心代碼
註:關註我,我們一起搭建完整的許可權管理系統。
1、預覽地址:http://139.155.137.144:8012
2、qq群:801913255