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

来源: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
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...