適用於MES、WMS、ERP等管理系統的實體下拉框設計

来源:https://www.cnblogs.com/hyx1229/archive/2022/07/14/16478504.html
-Advertisement-
Play Games

場景 該設計多適用於MES,ERP,WMS 等管理類型的項目。 在做管理類型項目的時候,前端經常會使用到下拉框,比如:設備,工廠等等。下拉組件中一般只需要他們的ID,Name屬性,而這些數據都創建於其他模塊並存儲在資料庫中。 如圖: 寫一個設備的下拉組件的數據需要通過請求後端去獲取,如:localh ...


場景

該設計多適用於MESERPWMS 等管理類型的項目。

在做管理類型項目的時候,前端經常會使用到下拉框,比如:設備,工廠等等。下拉組件中一般只需要他們的ID,Name屬性,而這些數據都創建於其他模塊並存儲在資料庫中。

如圖:

寫一個設備的下拉組件的數據需要通過請求後端去獲取,如:localhost:5555/api/services/app/Resource/GetNdoCombox,然後攜帶參數filterText

寫一個工廠的下拉組件也是同樣的要去請求,如:localhost:5555/api/services/app/Factory/GetNdoCombox

如果你的後端代碼足夠規範,那麼就會像我寫的這樣,每個需要有下拉需求的實體建模都有一個GetNdoCombox的介面。

問題點

為了代碼的復用性,你一定不會每個用到設備下拉的地方都去寫select標簽,然後請求數據再渲染,而是會封裝一個設備下拉框和一個工廠下拉框。於是便出現了一些問題:

前端下拉組件除了請求的介面不一樣,placeholder不一樣,其他的代碼都是盡數相同的。如果有下拉需求的地方多了,就會出現很多個xx下拉組件,如何優化?如果你有此類相同需求的問題時值得參考這個方案。

方案

思路

在後端寫一個介面做統一查詢,前端做一個統一的下拉組件,請求後端的統一查詢介面,前端傳遞標識性的參數給後端,後端通過參數自動匹配並查詢前端所需要的值。那麼重點就在後端如何實現這樣的匹配邏輯呢?

核心實現

我的實踐架構:.NET CORE + VUE

前端

前端就是寫個組件去請求介面,傳遞參數,這裡就不細說了

後端

先粗淺的介紹需要準備的東西:

  1. 自定義服務描述類,包含:服務介面,服務實現,泛型實體類型,生命周期
  2. 定義單例存儲器:定義Ndo字典,用於存儲對應的服務描述,以實體完全限定名為key,定義存和取的方法
  3. 通過反射獲取指定程式集中實現了IComboxQuery介面並且必須實現了IComboxQuery<>的下拉服務的服務描述,再將其註入到IOC容器中,並同時在存儲器的字典中添加映射關係。
  4. 統一獲取服務的Hub:從存儲器中根據實體名稱獲取對應的服務描述,再根據自定義服務描述類中的服務介面類型從IOC容器中獲取實現的IComboxQuery

詳細說明

Ndo:其實就是普通的實體的意思。

首先通過提供的IComboxQuery介面和IComboxQuery介面約束ControllerService必須實現GetNdoCombox方法。也就是說所有需要下拉的實體的服務都要實現IComboxQuery。(IComboxQuery繼承於IComboxQuery)

程式啟動時利用反射將實現了IComboxQuery並且實現了IComboxQuery的服務添加到IOC容器和存儲器的字典中去,以實體完全限定名為key,value為自定義的服務描述類。

定義統一獲取服務的Hub,從存儲器中根據實體名稱獲取對應的服務描述,再根據自定義服務描述類中的服務介面類型從IOC容器中獲取實現IComboxQuery的ControllerService,然後調用GetNdoCombox方法

定義統一的ControllerService,隨便定義一個方法定義需要的參數為EntityNamefilterText,方法中使用統一獲取服務的Hub,通過參數EntityName獲取實際實現IComboxQuery的對象,然後調用GetNdoCombox返回數據。

核心的查詢邏輯仍然是由服務自己實現的,因為不同的實體,過濾條件的欄位名不一樣,Hub只負責調用方法得到結果,不關心具體實現。

代碼

返回的數據NdoDto

public class NdoDto
{
    public virtual Guid Id { get; set; }
    public virtual string Name { get; set; }
    public virtual DateTime CreationTime { get; set; }
}

公共介面查詢的參數類

/// <summary>
/// 下拉框查詢的模糊搜索輸入
/// </summary>
public class GetQueryFilterInput
{
    /// <summary>
    /// 類型全名稱,不涉及業務,用於區分本次請求是哪個實體的介面
    /// </summary>
    public virtual string Name { get; set; }
    /// <summary>
    /// 模糊查詢
    /// </summary>
    public virtual string FilterText { get; set; }
}

統一規範的公共介面

/// <summary>
/// 下拉查詢
/// </summary>
public interface IComboxQuery
{
    Task<List<NdoDto>> GetCombox(GetQueryFilterInput input);
}

/// <summary>
/// 下拉查詢
/// </summary>
public interface IComboxQuery<TEntity> : IComboxQuery
{

}

自定義的服務映射描述類

/// <summary>
    /// 服務映射描述
    /// </summary>
    public class SampleServiceDescriptor
    {
        /// <summary>
        /// 瞬時依賴註入服務介面
        /// </summary>
        public static Type TransientInterface { get; } = typeof(ITransientDependency);
        /// <summary>
        /// 單例依賴註入服務介面
        /// </summary>
        public static Type SingletonInterface { get; } = typeof(ISingletonDependency);

        /// <summary>
        /// 服務類型 介面
        /// </summary>
        public virtual Type ServiceType { get; }

        /// <summary>
        /// 實現類型
        /// </summary>
        public virtual Type ImplementationType { get; }

        /// <summary>
        /// 建模實體類型
        /// </summary>
        public virtual Type EntityType { get; }

        /// <summary>
        /// 服務依賴註入生命周期
        /// </summary>
        public virtual ServiceLifetime ServiceLifetime { get; }

        /// <summary>
        /// 依賴註入服務
        /// </summary>
        /// <param name="serviceType">服務類型</param>
        /// <param name="implementationType">實現類型</param>
        public SampleServiceDescriptor(Type serviceType, Type implementationType)
        {
            this.ServiceType = serviceType;
            this.ImplementationType = implementationType;

            if (serviceType != null && serviceType.GenericTypeArguments.Length > 0)
            {
                // 獲取IComboxQuery<>中的泛型參數TEntity
                this.EntityType = serviceType.GenericTypeArguments[0];
            }


            if (SingletonInterface.IsAssignableFrom(this.ImplementationType))
            {
                this.ServiceLifetime = ServiceLifetime.Singleton;
            }
            else
            {
                this.ServiceLifetime = ServiceLifetime.Transient;
            }
        }

        /// <summary>
        /// 轉換為 <see cref="ServiceDescriptor"/>
        /// </summary>
        /// <returns></returns>
        public ServiceDescriptor ToServiceDescriptor()
        {
            return new ServiceDescriptor(this.ServiceType, this.ImplementationType, this.ServiceLifetime);
        }
    }

程式啟動時的掃描器(反射獲取實現了介面的服務)

/// <summary>
/// 依賴註入服務描述器
/// </summary>
public static class SampleServiceDescriptorHelper
{
    /// <summary>
    /// 掃描程式集中的某個介面的實現
    /// </summary>
    /// <param name="interfaceType">介面</param>
    /// <param name="genericInterfaceTypes">介面泛型實現</param>
    /// <param name="assemblies">程式集列表</param>
    /// <returns></returns>
    public static IEnumerable<SampleServiceDescriptor> ScanAssembliesServices
        (Type interfaceType, IEnumerable<Type> genericInterfaceTypes, params Assembly[] assemblies)
    {
        // 泛型介面轉字典
        var genericInterfaceTypeDict = new Dictionary<Type, bool>();
        foreach (var item in genericInterfaceTypes)
        {
            genericInterfaceTypeDict[item] = true;
        }

        // 遍歷程式集中所有的符合條件的類型
        foreach (var assembly in assemblies)
        {
            var services = assembly.GetTypes()
                .Where(o => interfaceType.IsAssignableFrom(o)
                       && o.IsPublic
                       && !o.IsInterface
                       && !o.IsAbstract
                      )
                .Select(o =>
                        {
                            // 篩選某個介面
                            var entityInterfaceType = o.GetInterfaces()
                                .Where(x =>
                                       {
                                           if (!x.IsGenericType)
                                           {
                                               return false;
                                           }
                                           var genericTypeDefinition = x.GetGenericTypeDefinition();

                                           return genericInterfaceTypeDict.ContainsKey(genericTypeDefinition);
                                       })
                                .FirstOrDefault();
                            // entityInterfaceType = IComboxQuery<> 目前只有一種
                            return new SampleServiceDescriptor(entityInterfaceType, o);
                        })
                .Where(o => o != null && o.ServiceType != null);

            foreach (var service in services)
            {
                yield return service;
            }
        }
    }
    // interfaceType用於獲取所有實現了IComboxQuery的類型
    // genericInterfaceTypes用於篩選,必須要實現了IComboxQuery<>的類型,因為需要獲取其TEntity的類型
    // 如果只是實現了IComboxQuery的類型,是沒有TEntity的,會導致ComboxQueryInfoStorage中無法添加映射關係
}

單例的存儲器

public class ComboxQueryInfoStorage : IComboxQueryInfoStorage
{
    /// <summary>
    ///  ModelingComboxQueryInfo 存儲器實例
    /// </summary>
    public static IComboxQueryInfoStorage Instace { get; set; } = new ComboxQueryInfoStorage();

    /// <summary>
    /// 數據存儲器
    /// </summary>
    protected readonly Dictionary<string, SampleServiceDescriptor> _Dict;

    protected ComboxQueryInfoStorage()
    {
        this._Dict = new Dictionary<string, SampleServiceDescriptor>();
    }

    public void Add(params SampleServiceDescriptor[] comboxQueryInfos)
    {
        foreach (var item in comboxQueryInfos)
        {
            this._Dict[item.EntityType.FullName] = item;
        }
    }

    public SampleServiceDescriptor Get(string name)
    {
        if (this._Dict.TryGetValue(name,out var comboxQueryInfo))
        {
            return comboxQueryInfo;
        }
        throw new Exception($"found Ndo type: {name}");
    }
}

統一獲取服務的Hub

public class ComboxQueryHub : IComboxQueryHub
{
    /// <summary>
    /// 依賴註入容器
    /// </summary>
    protected readonly IServiceProvider _serviceProvider;

    /// <summary>
    /// 構造函數
    /// </summary>
    /// <param name="serviceProvider"></param>
    public ComboxQueryHub(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    public async Task<List<NdoDto>> GetCombox(GetQueryFilterInput input)
    {
        var comboxQuery = this.GetComboxQuery(input.Name);
        return await comboxQuery.GetCombox(input);
    }

    public IComboxQuery GetComboxQuery(string name)
    {
        var comboxQueryInfo = ComboxQueryInfoStorage.Instace.Get(name);
        var comboxQuery = _serviceProvider.GetService(comboxQueryInfo.ServiceType) as IComboxQuery;
        return comboxQuery;
    }
}

用於將服務註冊到IOC和存儲器的擴展類

public static class ComboxQueryExtensions
{
    /// <summary>
    /// ComboxQuery 介面類型
    /// </summary>
    public static Type InterfaceType { get; } = typeof(IComboxQuery);

    /// <summary>
    /// IComboxQuery 介面類型
    /// </summary>
    public static List<Type> GenericInterfaceTypes { get; } = new List<Type>()
    {
        typeof(IComboxQuery<>)
    };

    /// <summary>
    /// 註冊程式集中的 ComboxQuery
    /// </summary>
    /// <returns></returns>

    public static void RegistrarComboxQuery(this IServiceCollection services, params Assembly[] assemblies)
    {
        // query hub
        if (!services.Any(x=>x.ServiceType == typeof(IComboxQueryHub)))
        {
            services.AddTransient<IComboxQueryHub, ComboxQueryHub>();
        }

        // querys
        var sampleServiceDescriptors = ScanComboxQuerys(assemblies);
        foreach (var sampleServiceDescriptor in sampleServiceDescriptors)
        {
            if (services.Any(x => x.ServiceType == sampleServiceDescriptor.ServiceType))
            {
                continue;
            }

            ComboxQueryInfoStorage.Instace.Add(sampleServiceDescriptor);

            if (sampleServiceDescriptor.ServiceLifetime == ServiceLifetime.Singleton)
            {
                services.AddSingleton(sampleServiceDescriptor.ServiceType,sampleServiceDescriptor.ImplementationType);
            }
            else
            {
                services.AddTransient(sampleServiceDescriptor.ServiceType, sampleServiceDescriptor.ImplementationType);
            }
        }
    }

    /// <summary>
    /// 掃描程式集中的 ComboxQuery 實現
    /// </summary>
    /// <param name="assemblies"></param>
    /// <returns></returns>
    public static IEnumerable<SampleServiceDescriptor> ScanComboxQuerys(params Assembly[] assemblies)
    {
        return SampleServiceDescriptorHelper.ScanAssembliesServices(
            InterfaceType,
            GenericInterfaceTypes,
            assemblies
        );
    }
}

使用

在啟動類中註冊服務

builder.Services.RegistrarComboxQuery(typeof(Program).Assembly);

人員建模:PersonController

public class PersonController : ApiControllerBase, IComboxQuery<Person>
{
    [HttpPost]
    public Task<List<NdoDto>> GetCombox(GetQueryFilterInput input)
    {
        var persons = Person.GetPeoples();
        var ndos = persons.Select(x => new NdoDto
                                  {
                                      Id = x.Id,
                                      Name = x.PersonName,
                                  }).ToList();
        return Task.FromResult(ndos);
    }
}

設備建模:ResourceController

public class ResourceController : ApiControllerBase, IComboxQuery<Resource>
{
    [HttpPost]
    public Task<List<NdoDto>> GetCombox(GetQueryFilterInput input)
    {
        var resources = Resource.GetResources();
        var ndos = resources.Select(x => new NdoDto
                                    {
                                        Id = x.Id,
                                        Name = x.ResourceName
                                    }).ToList();
        return Task.FromResult(ndos);
    }
}

統一查詢介面:CommonBoxController

public class CommonBoxController : ApiControllerBase
{
    /// <summary>
    /// ioc容器
    /// </summary>
    protected readonly IServiceProvider _serviceProvider;

    public CommonBoxController(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }
    [HttpPost]
    public virtual async Task<List<NdoDto>> GetNdoCombox(GetQueryFilterInput input)
    {
        var queryHub = this._serviceProvider.GetService<IComboxQueryHub>();

        return await queryHub.GetCombox(input);
    }
}

效果

單獨請求PersonController

單獨請求ResourceController

請求公共介面CommonBoxController

代碼倉庫

地址:https://gitee.com/huang-yuxiang/common-combox/tree/main/

版權聲明

作者:不想只會CURD的猿某人

更多原著文章請參考:https://www.cnblogs.com/hyx1229/


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

-Advertisement-
Play Games
更多相關文章
  • 預設參數的坑 定義一個函數,傳入一個list,添加一個end再返回 def add_end(L=[]): L.append('END') return L 正常調用時,結果似乎不錯 print (add_end([1,2,3])) #[1, 2, 3, 'END'] 使用預設參數調用時,一開始結果也 ...
  • 作為JAVA開發中最典型的異常類型,甚至可能是很多程式員入行之後收到的第一份異常大禮包類型,NullPointException也似乎成為了一種魔咒,應該如何去打破呢?一起來探討下吧 ...
  • 總結:Java的Stack嚴格意義來說並不能說是Stack,因為它通過直接繼承Vector類,繼承了Vector所有的公有方法,它是一個擁有所有Vector容器方法的棧! @SuppressWarnings 該批註的作用是給編譯器一條指令,告訴它對被批註的代碼元素內部的某些警告保持靜默。 | all ...
  • 數組和結點這兩種數據結構之間的差異,決定了LinkedList相比ArrayList擁有更高的插入和刪除效率,而隨機訪問效率不如ArrayList。 transient transient只能用來修飾成員變數(field),被transient修飾的成員變數不參與序列化過程。 序列化: JVM中的J ...
  • MongoDB集群搭建 MongoDB集群簡介 mongodb 集群搭建的方式有三種: 主從備份(Master - Slave)模式,或者叫主從複製模式。 副本集(Replica Set)模式 分片(Sharding)模式 其中,第一種方式基本沒什麼意義,官方也不推薦這種方式搭建。另外兩種分別就是副 ...
  • 之前有一個同事突然我問了我一個問題,說在foreach當中能不能刪除list裡面的元素,我當時大概說了一下是否能刪除,以及原因;接下來我們來探討一下是否能夠如此; (1)遍歷元素 首先,我們一一段代碼為例: String[] array = {"1", "2", "3"}; for (String ...
  • 杭電oj 網站實時狀態 (hdu.edu.cn) 2032 楊輝三角 楊輝三角,是二項式繫數在三角形中的一種幾何排列,中國南宋數學家楊輝1261年所著的《詳解九章演算法》一書中出現。在歐洲,帕斯卡(1623 1662)在1654年發現這一規律,所以這個表又叫做帕斯卡三角形。帕斯卡的發現比楊輝要遲393 ...
  • 兄弟們,今天來實現一下用Python計算1到500的偶數總和,灰常簡單,檢驗一下大家基礎學的怎麼樣! 涉及到的知識點 range 使用 for 迴圈 推導式 函數調用 # 這應該都學過吧,如果剛剛接觸Python,基礎都還沒怎麼學的話,加Q群 279199867,領取2022最新的Python視頻教 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...