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

打印 上一主题 下一主题

主题 852|帖子 852|积分 2556

场景

该设计多适用于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
前端
前端就是写个组件去请求接口,传递参数,这里就不细说了
后端
先粗浅的介绍需要准备的东西:

  • 自定义服务描述类,包含:服务接口,服务实现,泛型实体类型,生命周期
  • 定义单例存储器:定义Ndo字典,用于存储对应的服务描述,以实体完全限定名为key,定义存和取的方法
  • 通过反射获取指定程序集中实现了IComboxQuery接口并且必须实现了IComboxQuery的下拉服务的服务描述,再将其注入到IOC容器中,并同时在存储器的字典中添加映射关系。
  • 统一获取服务的Hub:从存储器中根据实体名称获取对应的服务描述,再根据自定义服务描述类中的服务接口类型从IOC容器中获取实现的IComboxQuery
详细说明
Ndo:其实就是普通的实体的意思。
首先通过提供的IComboxQuery接口和IComboxQuery接口约束Controller或Service必须实现GetNdoCombox方法。也就是说所有需要下拉的实体的服务都要实现IComboxQuery。(IComboxQuery继承于IComboxQuery)
程序启动时利用反射将实现了IComboxQuery并且实现了IComboxQuery的服务添加到IOC容器和存储器的字典中去,以实体完全限定名为key,value为自定义的服务描述类。
定义统一获取服务的Hub,从存储器中根据实体名称获取对应的服务描述,再根据自定义服务描述类中的服务接口类型从IOC容器中获取实现IComboxQuery的Controller或Service,然后调用GetNdoCombox方法
定义统一的Controller或Service,随便定义一个方法定义需要的参数为EntityName和filterText,方法中使用统一获取服务的Hub,通过参数EntityName获取实际实现IComboxQuery的对象,然后调用GetNdoCombox返回数据。
核心的查询逻辑仍然是由服务自己实现的,因为不同的实体,过滤条件的字段名不一样,Hub只负责调用方法得到结果,不关心具体实现。
代码
返回的数据NdoDto
  1. public class NdoDto
  2. {
  3.     public virtual Guid Id { get; set; }
  4.     public virtual string Name { get; set; }
  5.     public virtual DateTime CreationTime { get; set; }
  6. }
复制代码
公共接口查询的参数类
  1. /// <summary>
  2. /// 下拉框查询的模糊搜索输入
  3. /// </summary>
  4. public class GetQueryFilterInput
  5. {
  6.     /// <summary>
  7.     /// 类型全名称,不涉及业务,用于区分本次请求是哪个实体的接口
  8.     /// </summary>
  9.     public virtual string Name { get; set; }
  10.     /// <summary>
  11.     /// 模糊查询
  12.     /// </summary>
  13.     public virtual string FilterText { get; set; }
  14. }
复制代码
统一规范的公共接口
  1. /// <summary>
  2. /// 下拉查询
  3. /// </summary>
  4. public interface IComboxQuery
  5. {
  6.     Task<List<NdoDto>> GetCombox(GetQueryFilterInput input);
  7. }
  8. /// <summary>
  9. /// 下拉查询
  10. /// </summary>
  11. public interface IComboxQuery<TEntity> : IComboxQuery
  12. {
  13. }
复制代码
自定义的服务映射描述类
  1. /// <summary>
  2.     /// 服务映射描述
  3.     /// </summary>
  4.     public class SampleServiceDescriptor
  5.     {
  6.         /// <summary>
  7.         /// 瞬时依赖注入服务接口
  8.         /// </summary>
  9.         public static Type TransientInterface { get; } = typeof(ITransientDependency);
  10.         /// <summary>
  11.         /// 单例依赖注入服务接口
  12.         /// </summary>
  13.         public static Type SingletonInterface { get; } = typeof(ISingletonDependency);
  14.         /// <summary>
  15.         /// 服务类型 接口
  16.         /// </summary>
  17.         public virtual Type ServiceType { get; }
  18.         /// <summary>
  19.         /// 实现类型
  20.         /// </summary>
  21.         public virtual Type ImplementationType { get; }
  22.         /// <summary>
  23.         /// 建模实体类型
  24.         /// </summary>
  25.         public virtual Type EntityType { get; }
  26.         /// <summary>
  27.         /// 服务依赖注入生命周期
  28.         /// </summary>
  29.         public virtual ServiceLifetime ServiceLifetime { get; }
  30.         /// <summary>
  31.         /// 依赖注入服务
  32.         /// </summary>
  33.         /// <param name="serviceType">服务类型</param>
  34.         /// <param name="implementationType">实现类型</param>
  35.         public SampleServiceDescriptor(Type serviceType, Type implementationType)
  36.         {
  37.             this.ServiceType = serviceType;
  38.             this.ImplementationType = implementationType;
  39.             if (serviceType != null && serviceType.GenericTypeArguments.Length > 0)
  40.             {
  41.                 // 获取IComboxQuery<>中的泛型参数TEntity
  42.                 this.EntityType = serviceType.GenericTypeArguments[0];
  43.             }
  44.             if (SingletonInterface.IsAssignableFrom(this.ImplementationType))
  45.             {
  46.                 this.ServiceLifetime = ServiceLifetime.Singleton;
  47.             }
  48.             else
  49.             {
  50.                 this.ServiceLifetime = ServiceLifetime.Transient;
  51.             }
  52.         }
  53.         /// <summary>
  54.         /// 转换为 <see cref="ServiceDescriptor"/>
  55.         /// </summary>
  56.         /// <returns></returns>
  57.         public ServiceDescriptor ToServiceDescriptor()
  58.         {
  59.             return new ServiceDescriptor(this.ServiceType, this.ImplementationType, this.ServiceLifetime);
  60.         }
  61.     }
复制代码
程序启动时的扫描器(反射获取实现了接口的服务)
  1. /// <summary>
  2. /// 依赖注入服务描述器
  3. /// </summary>
  4. public static class SampleServiceDescriptorHelper
  5. {
  6.     /// <summary>
  7.     /// 扫描程序集中的某个接口的实现
  8.     /// </summary>
  9.     /// <param name="interfaceType">接口</param>
  10.     /// <param name="genericInterfaceTypes">接口泛型实现</param>
  11.     /// <param name="assemblies">程序集列表</param>
  12.     /// <returns></returns>
  13.     public static IEnumerable<SampleServiceDescriptor> ScanAssembliesServices
  14.         (Type interfaceType, IEnumerable<Type> genericInterfaceTypes, params Assembly[] assemblies)
  15.     {
  16.         // 泛型接口转字典
  17.         var genericInterfaceTypeDict = new Dictionary<Type, bool>();
  18.         foreach (var item in genericInterfaceTypes)
  19.         {
  20.             genericInterfaceTypeDict[item] = true;
  21.         }
  22.         // 遍历程序集中所有的符合条件的类型
  23.         foreach (var assembly in assemblies)
  24.         {
  25.             var services = assembly.GetTypes()
  26.                 .Where(o => interfaceType.IsAssignableFrom(o)
  27.                        && o.IsPublic
  28.                        && !o.IsInterface
  29.                        && !o.IsAbstract
  30.                       )
  31.                 .Select(o =>
  32.                         {
  33.                             // 筛选某个接口
  34.                             var entityInterfaceType = o.GetInterfaces()
  35.                                 .Where(x =>
  36.                                        {
  37.                                            if (!x.IsGenericType)
  38.                                            {
  39.                                                return false;
  40.                                            }
  41.                                            var genericTypeDefinition = x.GetGenericTypeDefinition();
  42.                                            return genericInterfaceTypeDict.ContainsKey(genericTypeDefinition);
  43.                                        })
  44.                                 .FirstOrDefault();
  45.                             // entityInterfaceType = IComboxQuery<> 目前只有一种
  46.                             return new SampleServiceDescriptor(entityInterfaceType, o);
  47.                         })
  48.                 .Where(o => o != null && o.ServiceType != null);
  49.             foreach (var service in services)
  50.             {
  51.                 yield return service;
  52.             }
  53.         }
  54.     }
  55.     // interfaceType用于获取所有实现了IComboxQuery的类型
  56.     // genericInterfaceTypes用于筛选,必须要实现了IComboxQuery<>的类型,因为需要获取其TEntity的类型
  57.     // 如果只是实现了IComboxQuery的类型,是没有TEntity的,会导致ComboxQueryInfoStorage中无法添加映射关系
  58. }
复制代码
单例的存储器
  1. public class ComboxQueryInfoStorage : IComboxQueryInfoStorage
  2. {
  3.     /// <summary>
  4.     ///  ModelingComboxQueryInfo 存储器实例
  5.     /// </summary>
  6.     public static IComboxQueryInfoStorage Instace { get; set; } = new ComboxQueryInfoStorage();
  7.     /// <summary>
  8.     /// 数据存储器
  9.     /// </summary>
  10.     protected readonly Dictionary<string, SampleServiceDescriptor> _Dict;
  11.     protected ComboxQueryInfoStorage()
  12.     {
  13.         this._Dict = new Dictionary<string, SampleServiceDescriptor>();
  14.     }
  15.     public void Add(params SampleServiceDescriptor[] comboxQueryInfos)
  16.     {
  17.         foreach (var item in comboxQueryInfos)
  18.         {
  19.             this._Dict[item.EntityType.FullName] = item;
  20.         }
  21.     }
  22.     public SampleServiceDescriptor Get(string name)
  23.     {
  24.         if (this._Dict.TryGetValue(name,out var comboxQueryInfo))
  25.         {
  26.             return comboxQueryInfo;
  27.         }
  28.         throw new Exception($"found Ndo type: {name}");
  29.     }
  30. }
复制代码
统一获取服务的Hub
  1. public class ComboxQueryHub : IComboxQueryHub
  2. {
  3.     /// <summary>
  4.     /// 依赖注入容器
  5.     /// </summary>
  6.     protected readonly IServiceProvider _serviceProvider;
  7.     /// <summary>
  8.     /// 构造函数
  9.     /// </summary>
  10.     /// <param name="serviceProvider"></param>
  11.     public ComboxQueryHub(IServiceProvider serviceProvider)
  12.     {
  13.         _serviceProvider = serviceProvider;
  14.     }
  15.     public async Task<List<NdoDto>> GetCombox(GetQueryFilterInput input)
  16.     {
  17.         var comboxQuery = this.GetComboxQuery(input.Name);
  18.         return await comboxQuery.GetCombox(input);
  19.     }
  20.     public IComboxQuery GetComboxQuery(string name)
  21.     {
  22.         var comboxQueryInfo = ComboxQueryInfoStorage.Instace.Get(name);
  23.         var comboxQuery = _serviceProvider.GetService(comboxQueryInfo.ServiceType) as IComboxQuery;
  24.         return comboxQuery;
  25.     }
  26. }
复制代码
用于将服务注册到IOC和存储器的扩展类
  1. public static class ComboxQueryExtensions
  2. {
  3.     /// <summary>
  4.     /// ComboxQuery 接口类型
  5.     /// </summary>
  6.     public static Type InterfaceType { get; } = typeof(IComboxQuery);
  7.     /// <summary>
  8.     /// IComboxQuery 接口类型
  9.     /// </summary>
  10.     public static List<Type> GenericInterfaceTypes { get; } = new List<Type>()
  11.     {
  12.         typeof(IComboxQuery<>)
  13.     };
  14.     /// <summary>
  15.     /// 注册程序集中的 ComboxQuery
  16.     /// </summary>
  17.     /// <returns></returns>
  18.     public static void RegistrarComboxQuery(this IServiceCollection services, params Assembly[] assemblies)
  19.     {
  20.         // query hub
  21.         if (!services.Any(x=>x.ServiceType == typeof(IComboxQueryHub)))
  22.         {
  23.             services.AddTransient<IComboxQueryHub, ComboxQueryHub>();
  24.         }
  25.         // querys
  26.         var sampleServiceDescriptors = ScanComboxQuerys(assemblies);
  27.         foreach (var sampleServiceDescriptor in sampleServiceDescriptors)
  28.         {
  29.             if (services.Any(x => x.ServiceType == sampleServiceDescriptor.ServiceType))
  30.             {
  31.                 continue;
  32.             }
  33.             ComboxQueryInfoStorage.Instace.Add(sampleServiceDescriptor);
  34.             if (sampleServiceDescriptor.ServiceLifetime == ServiceLifetime.Singleton)
  35.             {
  36.                 services.AddSingleton(sampleServiceDescriptor.ServiceType,sampleServiceDescriptor.ImplementationType);
  37.             }
  38.             else
  39.             {
  40.                 services.AddTransient(sampleServiceDescriptor.ServiceType, sampleServiceDescriptor.ImplementationType);
  41.             }
  42.         }
  43.     }
  44.     /// <summary>
  45.     /// 扫描程序集中的 ComboxQuery 实现
  46.     /// </summary>
  47.     /// <param name="assemblies"></param>
  48.     /// <returns></returns>
  49.     public static IEnumerable<SampleServiceDescriptor> ScanComboxQuerys(params Assembly[] assemblies)
  50.     {
  51.         return SampleServiceDescriptorHelper.ScanAssembliesServices(
  52.             InterfaceType,
  53.             GenericInterfaceTypes,
  54.             assemblies
  55.         );
  56.     }
  57. }
复制代码
使用

在启动类中注册服务
  1. builder.Services.RegistrarComboxQuery(typeof(Program).Assembly);
复制代码
人员建模:PersonController
  1. public class PersonController : ApiControllerBase, IComboxQuery<Person>
  2. {
  3.     [HttpPost]
  4.     public Task<List<NdoDto>> GetCombox(GetQueryFilterInput input)
  5.     {
  6.         var persons = Person.GetPeoples();
  7.         var ndos = persons.Select(x => new NdoDto
  8.                                   {
  9.                                       Id = x.Id,
  10.                                       Name = x.PersonName,
  11.                                   }).ToList();
  12.         return Task.FromResult(ndos);
  13.     }
  14. }
复制代码
设备建模:ResourceController
  1. public class ResourceController : ApiControllerBase, IComboxQuery<Resource>
  2. {
  3.     [HttpPost]
  4.     public Task<List<NdoDto>> GetCombox(GetQueryFilterInput input)
  5.     {
  6.         var resources = Resource.GetResources();
  7.         var ndos = resources.Select(x => new NdoDto
  8.                                     {
  9.                                         Id = x.Id,
  10.                                         Name = x.ResourceName
  11.                                     }).ToList();
  12.         return Task.FromResult(ndos);
  13.     }
  14. }
复制代码
统一查询接口:CommonBoxController
  1. public class CommonBoxController : ApiControllerBase
  2. {
  3.     /// <summary>
  4.     /// ioc容器
  5.     /// </summary>
  6.     protected readonly IServiceProvider _serviceProvider;
  7.     public CommonBoxController(IServiceProvider serviceProvider)
  8.     {
  9.         _serviceProvider = serviceProvider;
  10.     }
  11.     [HttpPost]
  12.     public virtual async Task<List<NdoDto>> GetNdoCombox(GetQueryFilterInput input)
  13.     {
  14.         var queryHub = this._serviceProvider.GetService<IComboxQueryHub>();
  15.         return await queryHub.GetCombox(input);
  16.     }
  17. }
复制代码
效果

单独请求PersonController

单独请求ResourceController

请求公共接口CommonBoxController

代码仓库

地址:https://gitee.com/huang-yuxiang/common-combox/tree/main/
版权声明
作者:不想只会CURD的猿某人
更多原著文章请参考:https://www.cnblogs.com/hyx1229/

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

正序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

涛声依旧在

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表