4

适用于MES、WMS、ERP等管理系统的实体下拉框设计 - 不想只会CURD的猿某人

 2 years ago
source link: https://www.cnblogs.com/hyx1229/p/16478504.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

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

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

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

2482875-20220714173444858-1921455251.png

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

2482875-20220714173528899-79408595.png

单独请求ResourceController

2482875-20220714173551756-1639528916.png

请求公共接口CommonBoxController

2482875-20220714173601892-964588142.png

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

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

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK