企业级工作流解决方案(十五)--集成Abp和ng-alain--Abp其他改造


配置功能增强

  Abp定义了各种配置接口,但是没有定义这些配置数据从哪里来,但是管理配置数据对于一个应用程序来说,是必不可少的一件事情。

  .net的配置数据管理,一般放在Web.config文件或者App.config文件里面,.net core也是定义在一个json的配置文件里面。我们也可以自定义configSection,但是对于稍微大型一点的应用,配置可能非常的复杂,代码比较规范的,对于每一个配置或者配置组都写好注释,但是毕竟是比较麻烦的一件事情。

  我们可以对所有的配置进行集中的管理,把所有的配置放到一个固定的应用程序目录下面,每一个功能模块定义一个配置文件,配置文件名称做为分组名称,比如我们可以放到应用的Configuration/AppSettings目录下,配置文件格式如下:

   那么我们可以在应用启动的时候,读取此目录下的所有文件,放到全局静态的字典里面,那么我们调用的时候,只需要调用静态方法,传递分组和key即可,比如:AppSettingManager.GetSetting("WorkflowNotice","SignalrTaskTitle"),代码比较简单,实现如下:

public static class AppSettingManager
    {
        #region Private Memeber

        private static Dictionary<string, Dictionary<string, string>> dictAppsettings;
        static AppSettingManager()
        {
            dictAppsettings = new Dictionary<string, Dictionary<string, string>>();
        }

        private static string BaseFolderPath
        {
            get
            {
                return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Configuration/AppSettings");
            }
        }

        private static Dictionary<string, string> LoadSettings(string filePath)
        {
            if (!File.Exists(filePath))
            {
                return new Dictionary<string, string>(0);
            }
            Dictionary<string, string> rst = new Dictionary<string, string>();
            XElement doc = XElement.Load(filePath);
            foreach (var x in doc.Descendants("add"))
            {
                if (x.Attribute("key") == null || x.Attribute("key").Value == null || x.Attribute("key").Value.Trim().Length <= 0)
                {
                    throw new ApplicationException("配置文件格式有错误,存在add节点,但是没有key属性,路径为: " + filePath + ", 请检查!");
                }
                string key = x.Attribute("key").Value.Trim().ToUpper();
                if (rst.ContainsKey(key))
                {
                    throw new ApplicationException($"配置文件存在相同的Key[{x.Attribute("key").Value.Trim()}],路径为:{filePath},请检查!");
                }
                string value = x.Attribute("value") == null ? null : (x.Attribute("value").Value == null ? null : x.Attribute("value").Value.Trim());
                value = value ?? x.Value;
                rst.Add(key, value);
            }
            return rst;
        }

        #endregion

        public static void InitAppsettings()
        {
            var strFileInfos = Directory.GetFiles(BaseFolderPath, "*.config");
            foreach(var strFileInfo in strFileInfos)
            {
                var fileName = Path.GetFileNameWithoutExtension(strFileInfo);
                var fileNameAppsetting = LoadSettings(strFileInfo);
                dictAppsettings.Add(fileName, fileNameAppsetting);
            }
        }

        public static string GetSetting(string fileTitle, string key)
        {
            if(!dictAppsettings.ContainsKey(fileTitle))
            {
                return "";
            }
            key = key.ToUpper();

            if(!dictAppsettings[fileTitle].ContainsKey(key))
            {
                return "";
            }

            return dictAppsettings[fileTitle][key];
        }
    }

   还有另外一种情况,我们定义了配置接口,比如abp定义了各种配置接口,但是配置数据也需要从其他地方读取出来,常见的方式也是配置文件,这种情况,我们可以定义Xml与配置实现的映射,启动的时候读取每一个文件映射到接口配置实现类。

配置帮助类:

public class XmlConfigProvider
    {
        public static T GetConfig(string fileName, string relativePath = "")
        {
            if(string.IsNullOrEmpty(relativePath))
            {
                relativePath = @"Configuration\XmlConfig";
            }


            string fileFullName = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, relativePath, fileName);
            if(!File.Exists(fileFullName))
            {
                return default(T);
            }
            return LoadFromXml(fileFullName);
        }

        private static T LoadFromXml(string filePath)
        {
            FileStream fs = null;
            try
            {
                XmlSerializer serializer = new XmlSerializer(typeof(T));
                fs = new FileStream(filePath, FileMode.Open, FileAccess.Read);
                return (T)serializer.Deserialize(fs);
            }
            finally
            {
                if (fs != null)
                {
                    fs.Close();
                    fs.Dispose();
                }
            }
        }
    }

配置实现类举例:

[Serializable]
    [XmlRoot]
    public class SocketServiceConfiguration : ISocketServiceConfiguration
    {
        [XmlAttribute]
        public int Backlog { get; set; }
        /// 
        /// 客户端解码类型
        /// 
        [XmlAttribute]
        public EMessageCode MessageCode { get; set; }
        /// 
        /// 端口
        /// 
        [XmlAttribute]
        public int Port { get; set; }

        [XmlAttribute]
        public bool Libuv { get; set; }

        public SocketServiceConfiguration()
        {
            Backlog = 100;
            Libuv = false;
            MessageCode = EMessageCode.MessagePack;
        }
    }

使用,可以在Module里面读取

// 注册客户端配置,固定从Xml文件读取
            SocketServiceConfiguration socketServiceConfiguration = XmlConfigProvider.GetConfig("SocketServiceConfiguration.xml");
            IocManager.IocContainer.Register(
                Component
                    .For()
                    .Instance(socketServiceConfiguration)
            );

T4模版,部分代码自动生成

  有很多方便的代码生成工具,好不好用我就不说了,只要能够提高我们平时的编码效率,减少一些繁琐的基础编码工作,对我们有帮助,总归是好的。

  T4模版,直接在VS里面,编辑之后保存,就会根据模版生成对应的代码文件,我们可以自定义代码生成规则,完全的自定义,可见即可得。

  我这里的配置,是围绕着一个Xml文件,内容是对一个实体信息集合的描述,Xml文件格式如下:

<FrameworkTemplates>
  <FrameworkTemplate Name="WF_FlowType" Type="DomainEntity" DataTableName="WF_FlowType" Inherit="AuditedEntity<Guid>, IMayHaveTenant">
    <TemplateItem>
      <Field>IdField>
      <CnName>主键CnName>
      <Type>guidType>
      <IsRequred>falseIsRequred>
    TemplateItem>
    <TemplateItem>
      <Field>TypeNameField>
      <CnName>类型名称CnName>
      <Type>stringType>
      <IsRequred>trueIsRequred>
      <MaxLength>50MaxLength>
    TemplateItem>
    <TemplateItem>
      <Field>ParentIdField>
      <CnName>父节点IdCnName>
      <Type>guidType>
      <IsRequred>falseIsRequred>
    TemplateItem>
    <TemplateItem>
      <Field>NotesField>
      <CnName>备注CnName>
      <Type>stringType>
      <IsRequred>falseIsRequred>
      <MaxLength>500MaxLength>
    TemplateItem>
  FrameworkTemplate>
……
FrameworkTemplates>

  那个我们可以根据这个Xml文件的描述,生成DbContext、DomainEntity、CreateDto、UpdateDto、ApplicationService等公共的信息,每一个类都需要标识为partial,方便我们编写非公共的部分代码。生成DomainEntity代码举例:

<#@ template language="C#" hostSpecific="true" #>
<#@ output extension=".cs" #>
<#@ assembly name="System.Xml"#>
<#@ import namespace="System.Xml" #>
<#@ assembly name="EnvDTE" #>
<#@ import namespace="Microsoft.VisualStudio.TextTemplating" #>
<#@ include file="$(SolutionDir)\Resources\T4\TemplateFilemanager.CS.ttinclude" #>
<#
    var manager = TemplateFileManager.Create(this);
    string strProjectName = "Workflow";
    EnvDTE.DTE dte = (EnvDTE.DTE) ((IServiceProvider) this.Host).GetService(typeof(EnvDTE.DTE));
    XmlDocument doc = new XmlDocument();
    var strSolutionPath = System.IO.Path.GetDirectoryName(dte.Solution.FullName);
    var strTemplateFilePath = System.IO.Path.Combine(strSolutionPath, @"Resources\Modules\Workflow.xml");
    doc.Load(strTemplateFilePath);
    var frameworkTemplateNodes = doc.SelectNodes("/FrameworkTemplates/FrameworkTemplate[@Type='DomainEntity']");
    foreach (XmlNode templateNode in frameworkTemplateNodes)
    {
    manager.StartNewFile(templateNode.Attributes["Name"].Value + ".cs");
    var inheritNode = templateNode.Attributes["Inherit"];
    var strInherit = "";
#>
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;

using CK.Sprite.Domain.Entities;
using CK.Sprite.Domain.Entities.Auditing;
using CK.Workflow.DomainInterface;

namespace CK.<#= strProjectName #>.DomainEntity
{
<# if(inheritNode==null || inheritNode.Value=="") // ---输出Class---
{
#>
    public partial class <#= templateNode.Attributes["Name"].Value #>
<#
}
else
{
strInherit = templateNode.Attributes["Inherit"].Value;
#>
    public partial class <#= templateNode.Attributes["Name"].Value #> : <#= strInherit #>
<#
}   // ---End输出Class---
#>
    {

    <#
        foreach (XmlNode itemNode in templateNode.ChildNodes)
                {
                    string tempTypeName = "";
                    string tempFieldName = "";
                    string tempFieldCnName = "";
                    string tempIsRequred = "";
                    string tempMaxLength = "";
                    string strIsRequred = "";
                    string strMaxLength = "";
                    if (itemNode.LocalName == "TemplateItem")
                    {
                        tempFieldName = itemNode.ChildNodes[0].InnerText;
                        tempFieldCnName = itemNode.ChildNodes[1].InnerText;
                        tempTypeName = itemNode.ChildNodes[2].InnerText;
                        tempIsRequred = itemNode.ChildNodes[3].InnerText;
                        for(var i=0;i< itemNode.ChildNodes.Count;i++ )
                        {
                            if(itemNode.ChildNodes[i].Name == "MaxLength")
                            {
                                tempMaxLength = itemNode.ChildNodes[i].InnerText;
                            }
                        }
                        if(tempFieldName == "Id")
                        {
                            continue;
                        }
                        if(tempIsRequred.ToLower()=="true")
                        {
                            strIsRequred = "[Required]";
                        }
                        if (!string.IsNullOrEmpty(tempMaxLength) && tempMaxLength != "0")
                        {
                            strMaxLength = "[StringLength("+ tempMaxLength + ")]";
                        }
                        switch (tempTypeName)
                        {
                            case "int":
                                if (tempIsRequred.ToLower() == "false")
                                {
                                    tempTypeName = "int?";
                                }
                                break;
                            case "double":
                                if (tempIsRequred.ToLower() == "false")
                                {
                                    tempTypeName = "double?";
                                }
                                break;
                            case "date":
                            case "datetime":
                                if (tempIsRequred.ToLower() == "false")
                                {
                                    tempTypeName = "DateTime?";
                                }
                                else
                                {
                                    tempTypeName = "DateTime";
                                }
                                break;
                            case "guid":
                                if (tempIsRequred.ToLower() == "false")
                                {
                                    tempTypeName = "Guid?";
                                }
                                else
                                {
                                    tempTypeName = "Guid";
                                }
                                break;
                        }
#>
/// 
        /// <#= tempFieldCnName #>
        /// 
        <#= strIsRequred #>
        <#= strMaxLength #>
        public <#= tempTypeName #> <#= tempFieldName #> { get; set; }
        <#
            }
            }
            if (!string.IsNullOrEmpty(strInherit))
                {
                    List autoFields = new List();
                    var inheritList = strInherit.Replace(" ","").Split(new char[] { ',' });
                    foreach (var inherit in inheritList)
                    {
                        switch (inherit)
                        {
                            case "IMayHaveTenant":
                                autoFields.Add("public int? TenantId { get; set; }");
                                break;
                            case "IMustHaveTenant":
                                autoFields.Add("public int TenantId { get; set; }");
                                break;
                            case "IPassivable":
                                autoFields.Add("public bool IsActive { get; set; }");
                                break;
                            case "IHasCreationTime":
                                autoFields.Add("public DateTime CreationTime { get; set; }");
                                break;
                            case "ICreationAudited":
                                autoFields.Add("public DateTime CreationTime { get; set; }");
                                autoFields.Add("public long? CreatorUserId { get; set; }");
                                break;
                            case "IHasModificationTime":
                                autoFields.Add("public DateTime? LastModificationTime { get; set; }");
                                break;
                            case "IModificationAudited":
                                autoFields.Add("public DateTime? LastModificationTime { get; set; }");
                                autoFields.Add("public long? LastModifierUserId { get; set; }");
                                break;
                            case "ISoftDelete":
                                autoFields.Add("public bool IsDeleted { get; set; }");
                                break;
                            case "IHasDeletionTime":
                                autoFields.Add("public DateTime? DeletionTime { get; set; }");
                                autoFields.Add("public DateTime CreationTime { get; set; }");
                                break;
                            case "IDeletionAudited":
                                autoFields.Add("public DateTime? DeletionTime { get; set; }");
                                autoFields.Add("public long? DeleterUserId { get; set; }");
                                autoFields.Add("public DateTime CreationTime { get; set; }");
                                break;
                            case "IAudited":
                                autoFields.Add("public DateTime? LastModificationTime { get; set; }");
                                autoFields.Add("public long? LastModifierUserId { get; set; }");
                                autoFields.Add("public DateTime CreationTime { get; set; }");
                                autoFields.Add("public long? CreatorUserId { get; set; }");
                                break;
                            case "IFullAudited":
                                autoFields.Add("public bool IsDeleted { get; set; }");
                                autoFields.Add("public long? DeleterUserId { get; set; }");
                                autoFields.Add("public DateTime? DeletionTime { get; set; }");
                                autoFields.Add("public DateTime? LastModificationTime { get; set; }");
                                autoFields.Add("public long? LastModifierUserId { get; set; }");
                                autoFields.Add("public DateTime CreationTime { get; set; }");
                                autoFields.Add("public long? CreatorUserId { get; set; }");
                                break;
                        }
                    }
                    autoFields = autoFields.Distinct().ToList();
                    foreach(var autoField in autoFields)
                    {
                    #>

        <#= autoField #>
                    <#
                    }
                }
        #>

    }
}
<#
    }
    manager.Process();
#>

  另外,我们还可以定义前端的List和Form页面,甚至不用编写前端代码都可以。

  对于Abp的改造还有其他一些地方,比如Signalr的消息通知、Application是否登录权限验证等,相对来说比较简单,就不描述了。

  前面两个大的章节的内容就告一段落了,这些是做为整体工作流引擎的框架依赖,所以写在前面,这部分内容是脱离工作流独立存在的,现在在构思工作流部分的内容,工作流部分可能需要对WWF有所了解的朋友才更加容易理解,但是做为研发流程引擎的朋友应该也可以提供一些思路,问题可以直接留言或者QQ联系我:523477776

  到目前为止还没有人进行过评论,也比较影响写文章的激情,不知道是否写得有问题,如果有问题对其他人造成错误的引导,那就提前说一声抱歉了。