一步一步搭建,功能最全的許可權管理系統之動態路由菜單(一)

来源:https://www.cnblogs.com/cyzf/p/18096545
-Advertisement-
Play Games

一、前言 這是一篇搭建許可權管理系統的系列文章。 隨著網路的發展,信息安全對應任何企業來說都越發的重要,而本系列文章將和大家一起一步一步搭建一個全新的許可權管理系統。 說明:由於搭建一個全新的項目過於繁瑣,所有作者將挑選核心代碼和核心思路進行分享。 二、技術選擇 三、開始設計 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

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 本文分享自華為雲社區《構建大型Web應用Flask中的Blueprints指南》,作者: 檸檬味擁抱。 什麼是Blueprints? Blueprints是Flask中的一種模式,用於將應用程式分解為可重用的模塊。每個藍圖實際上是一個包含一組路由、視圖和靜態文件的Python模塊。通過使用藍圖,我們 ...
  • MoneyPrinterTurbo —— 一個利用大模型,一鍵生成短視頻的開源項目。只需輸入視頻主題或關鍵詞,就可以全自動生成視頻文案、視頻素材、視頻字幕、視頻背景音樂,最後合成一個高清的短視頻。 ...
  • 本文介紹基於R語言中的Ternary包,繪製三元圖(Ternary Plot)的詳細方法;其中,我們就以RGB三色分佈圖為例來具體介紹~ ...
  • 本文介紹瞭如何快速搭建一個基於大型語言模型(LLM)的混元聊天應用。強調了開發速度的重要性,並指出了使用Streamlit這一工具的優勢,特別是對於不熟悉前端代碼的開發者來說,Streamlit提供了一種快速構建聊天應用的方法。 ...
  • 前言 aardio中有些經常使用的庫,換個項目總需要複製一下,還不便於修改。雖然可以直接把它放到aardio\lib目錄下,也是不便於共用給其他人使用。 最近偶然翻到編輯器里的工具->開發環境->擴展庫發佈工具,就想著可以像官方一樣,發佈自己的擴展庫,也便於分享給大家使用,最好能像官方擴展庫一樣線上 ...
  • 隨著汽車的普及和使用頻率的增加,車輛的維修保養成為了車主們經常需要面對的問題。為了提供更好的服務,挖數據平臺提供了一個維修保養記錄統計介面,讓用戶可以方便地查詢車輛的保養記錄和維修記錄。本文將對該介面進行詳細解析,並介紹其使用方法和應用場景。 首先,我們來看一下該介面的具體功能。該介面可以查詢車輛的 ...
  • 在使用Django等框架來操作MySQL時,實際上底層還是通過Python來操作的,首先需要安裝一個驅動程式,在Python3中,驅動程式有多種選擇,比如有pymysql以及mysqlclient等。使用pip命令安裝mysqlclient失敗應如何解決? 安裝的python版本說明 機器同時安裝了 ...
  • Csharper中的表達式樹 這節課來瞭解一下表示式樹是什麼? 在C#中,表達式樹是一種數據結構,它可以表示一些代碼塊,如Lambda表達式或查詢表達式。表達式樹使你能夠查看和操作數據,就像你可以查看和操作代碼一樣。它們通常用於創建動態查詢和解析表達式。 一、認識表達式樹 為什麼要這樣說?它和委托有 ...
一周排行
    -Advertisement-
    Play Games
  • C#.Net的BCL提供了豐富的類型,最基礎的是值類型、引用類型,而他們的共同(隱私)祖先是 System.Object(萬物之源),所以任何類型都可以轉換為Object。 ...
  • 最近有群友咨詢C#如何調用Python?小編嘗試Python.NET過程中遭遇的版本相容性和環境配置難題,小編決定尋找一個更為簡單、穩定且對初學者友好的解決方案。小編搜索一番,除了Python.NET之外,還有其他途徑能夠幫助我們輕鬆地在C#項目調用Python腳本,那就是通過命令行調用,使用 Sy ...
  • .NET中特性+反射 實現數據校驗 在.NET中,我們可以使用特性+反射來實現數據校驗。特性是一種用於為程式中的代碼添加元數據的機制。元數據是與程式中的代碼相關聯的數據,但不直接成為代碼的一部分。通過特性,我們可以為類、方法、屬性等添加額外的信息,這些信息可以在運行時通過反射獲取和使用。 對反射不太 ...
  • Biwen.Settings 是一個簡易的配置項管理模塊,主要的作用就是可以校驗並持久化配置項,比如將自己的配置存儲到資料庫中,JSON文件中等 使用上也是很簡單,只需要在服務中註入配置, 比如我們有一個GithubSetting的配置項,我們只需要定義好對象然後註入到Service中即可: [De ...
  • EDP是一套集組織架構,許可權框架【功能許可權,操作許可權,數據訪問許可權,WebApi許可權】,自動化日誌,動態Interface,WebApi管理等基礎功能於一體的,基於.net的企業應用開發框架。通過友好的編碼方式實現數據行、列許可權的管控。 ...
  • 前言 VB.NET,全名Visual Basic .NET,是Microsoft .NET框架的一部分,是一種面向對象的編程語言。它繼承了Visual Basic的易用性,同時增加了對面向對象編程的支持。VB.NET提供了大量的內置函數,使得開發者可以更容易地處理字元串、數學計算、文件和目錄訪問等任 ...
  • 自定義可移動點二維坐標軸控制項 目錄 路由參數 坐標軸控制項定義 Demo 路由參數 X_YResultCollection為當前X軸對應Y軸值存儲字典 public class ResultCollectionChangedEventArgs(RoutedEvent routedEvent, obje ...
  • 自定義分頁控制項 tip: 該控制項的樣式用的是materialDesign庫,需要下載Nuget包 Code Xaml <UserControl x:Class="TestTool.CustomControls.PagingControl" xmlns="http://schemas.microsof ...
  • 最近群里有個小伙伴把Dapper遷移SqlSugar幾個不能解決的問題進行一個彙總,我正好寫一篇文章來講解一下 一、sql where in傳參問題: SELECT * FROM users where id IN @ids 答: SqlSugar中應該是 var sql="SELECT * FRO ...
  • 安裝nuget包 Wesky.Net.OpenTools 1.0.8或以上版本。支持.net framework 4.6以上版本,以及所有.net core以及以上版本引用。 開發一個簡單的Winform界面,用來測試使用。如需該winform的demo,可以在公眾號【Dotnet Dancer】後 ...