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


    场景

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

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

    如图:

    image-20220714153011391

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

    使用

    人员建模: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

    image-20220714165311481

    单独请求ResourceController

    image-20220714165342685

    请求公共接口CommonBoxController

    image-20220714165623402

    代码仓库

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

    版权声明

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

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

  • 相关阅读:
    工业互联网数字化中台解决方案
    LightGBM调参与模型权重可视化
    Move 双子星之一 Sui 生态有哪些项目值得关注
    python一键采集高质量陪玩,心动主播随心选......
    基于java web个人财务管理系统
    cloudstack中SecondaryStorageManagerImpl
    593. 有效的正方形 : 简单几何运用题
    新版 Next.js 从入门到入土
    someip 入门
    【题解】P8817 [CSP-S 2022] 假期计划(bfs,dfs)
  • 原文地址:https://www.cnblogs.com/hyx1229/p/16478504.html