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

来源: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
  • 前言 在我們開發過程中基本上不可或缺的用到一些敏感機密數據,比如SQL伺服器的連接串或者是OAuth2的Secret等,這些敏感數據在代碼中是不太安全的,我們不應該在源代碼中存儲密碼和其他的敏感數據,一種推薦的方式是通過Asp.Net Core的機密管理器。 機密管理器 在 ASP.NET Core ...
  • 新改進提供的Taurus Rpc 功能,可以簡化微服務間的調用,同時可以不用再手動輸出模塊名稱,或調用路徑,包括負載均衡,這一切,由框架實現並提供了。新的Taurus Rpc 功能,將使得服務間的調用,更加輕鬆、簡約、高效。 ...
  • 順序棧的介面程式 目錄順序棧的介面程式頭文件創建順序棧入棧出棧利用棧將10進位轉16進位數驗證 頭文件 #include <stdio.h> #include <stdbool.h> #include <stdlib.h> 創建順序棧 // 指的是順序棧中的元素的數據類型,用戶可以根據需要進行修改 ...
  • 前言 整理這個官方翻譯的系列,原因是網上大部分的 tomcat 版本比較舊,此版本為 v11 最新的版本。 開源項目 從零手寫實現 tomcat minicat 別稱【嗅虎】心有猛虎,輕嗅薔薇。 系列文章 web server apache tomcat11-01-官方文檔入門介紹 web serv ...
  • C總結與剖析:關鍵字篇 -- <<C語言深度解剖>> 目錄C總結與剖析:關鍵字篇 -- <<C語言深度解剖>>程式的本質:二進位文件變數1.變數:記憶體上的某個位置開闢的空間2.變數的初始化3.為什麼要有變數4.局部變數與全局變數5.變數的大小由類型決定6.任何一個變數,記憶體賦值都是從低地址開始往高地 ...
  • 如果讓你來做一個有狀態流式應用的故障恢復,你會如何來做呢? 單機和多機會遇到什麼不同的問題? Flink Checkpoint 是做什麼用的?原理是什麼? ...
  • C++ 多級繼承 多級繼承是一種面向對象編程(OOP)特性,允許一個類從多個基類繼承屬性和方法。它使代碼更易於組織和維護,並促進代碼重用。 多級繼承的語法 在 C++ 中,使用 : 符號來指定繼承關係。多級繼承的語法如下: class DerivedClass : public BaseClass1 ...
  • 前言 什麼是SpringCloud? Spring Cloud 是一系列框架的有序集合,它利用 Spring Boot 的開發便利性簡化了分散式系統的開發,比如服務註冊、服務發現、網關、路由、鏈路追蹤等。Spring Cloud 並不是重覆造輪子,而是將市面上開發得比較好的模塊集成進去,進行封裝,從 ...
  • class_template 類模板和函數模板的定義和使用類似,我們已經進行了介紹。有時,有兩個或多個類,其功能是相同的,僅僅是數據類型不同。類模板用於實現類所需數據的類型參數化 template<class NameType, class AgeType> class Person { publi ...
  • 目錄system v IPC簡介共用記憶體需要用到的函數介面shmget函數--獲取對象IDshmat函數--獲得映射空間shmctl函數--釋放資源共用記憶體實現思路註意 system v IPC簡介 消息隊列、共用記憶體和信號量統稱為system v IPC(進程間通信機制),V是羅馬數字5,是UNI ...