适用于MES、WMS、ERP等管理系统的实体下拉框设计

场景

该设计多适用于MESERPWMS 等管理类型的项目。

在做管理类型项目的时候,前端经常会使用到下拉框,比如:设备,工厂等等。下拉组件中一般只需要他们的ID,Name属性,而这些数据都创建于其他模块并存储在数据库中。

如图:

适用于MES、WMS、ERP等管理系统的实体下拉框设计

写一个设备的下拉组件的数据需要通过请求后端去获取,如: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

适用于MES、WMS、ERP等管理系统的实体下拉框设计

单独请求ResourceController

适用于MES、WMS、ERP等管理系统的实体下拉框设计

请求公共接口CommonBoxController

适用于MES、WMS、ERP等管理系统的实体下拉框设计

代码仓库

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

版权声明

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

更多原著文章请参考:https://www.cnblogs.com/hyx1229/

发表评论

相关文章