• X-Admin&ABP框架开发-代码生成器


      在日常开发中,有时会遇到一些相似的代码,甚至是只要CV一次,改几个名称,就可以实现功能了,而且总归起来,都可以由一些公用的页面更改而来,因此,结合我日常开发中使用到的页面,封装一个适合自己的代码生成器,仅处于入门阶段,包括生成的代码结构都仅是把框架展示出来,内部详细暂时没得,针对于应用服务中的接口和实现,相关Dto,MVC中的控制器、视图及视图模型进行了模板制作及生成相关的文件。

    一、设计思路

      方案一:开始想到的是,搞个控制台,然后给一个.cs文件,然后控制台去解析其中的命名空间,类名,属性,再用配置好的razor模板去替换,再生成相关的一些文件出来,但是发现,万事开头难,第一步去解析cs文件就不好搞,找了网上的资料,不太好弄,干脆想了下,放弃这种方案,因为想到了另一种常用的方案。

     

      方案二:直接在控制台中,配置控制台去访问数据库,然后给定指定表名,去读取数据库中的表和字段,再反过来去生成相关文件,但是这里会遇到一个这样的问题,比如我使用的是mysql,mysql本身有个配置表名大小写忽视的,这样一来,获取到的表名都将是小写打头,尽管可能配置了是区分大小写,但是,我设计表时,采用Pre_table,形式区分业务表,比如是CRM模块需要用到的CRM_Client,那将用CRM打头,后面这部分Client才是实际代码中的类名,种种问题都有可能,但是作为没有那么多可能性下,比如没得前缀,不区分大小写,形式简单,那么可以考虑使用。此时,想到了abp中的Migrator控制台并想到了方案三。

      方案三:如果说直接搞一个控制台在代码中,模仿Abp自带的Migrator一样,启动后,给定类名,通过反射去取得该类的属性名,岂不是美滋滋,需要哪个类的相关文件,只需启动,然后输入类名,即可得到相关的文件。这几种方案的前提都是在Dto文件中会展示所有实体字段,如果需要选择性的使用字段,则还需借助人工智能,以人力去完成更改生成的文件。

    二、Razor引擎的使用

      我选择了方案二作为入手去实现,并且采用Razor引擎作为模板解析的工具。Nuget引入RazorEngine.NetCore包,开始实现依靠模板生成代码。 

    1、先尝试下Razor引擎,控制台中CV下Razor引擎提供的Demo,引入相关命名空间,学习下如何去使用。

    string template = "Hello @Model.Name, welcome to RazorEngine!";
    var result = Engine.Razor.RunCompile(template, "templateKey", null, new { Name = "World" });
    Console.WriteLine(result);

     运行完毕,可以获取到运行结果,需要注意的是,如果是在linux或是mac跑会得到错误,该问题是Razor引擎本身的问题,暂时只能在window下跑。

    2、熟悉了下Razor的使用方式后,开始使用简单文件形式填充一些数据模拟生成过程。

    首先,一个文件作为填充模板,一个文件内存储Json数据作为数据源,程序启动时加载两个文件。

    var templatePage = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "SimpleCOders\Templates", "templatePage.txt");
    TemplatePage = File.ReadAllText(templatePage);
    
    var templatePageJson = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "SimpleCOders\Templates", "templatePageJson.json");
    TemplatePageJson = File.ReadAllText(templatePageJson);

    其次,数据源整理成相应类结构,得到批量待解析数据。

    var templatePageJsonList = JsonConvert.DeserializeObject<List<PageDataModel>>(TemplatePageJson);
    
    foreach (var templatePageJson in templatePageJsonList)
    {
        RazorParse(
            templatePageJson.Index ?? 1,
            templatePageJson.Date,
            templatePageJson.Index - 1,
            templatePageJson.Index + 1,
            templatePageJson.Content
        );
    }

    最后,设计一下解析器,将读取到的数据源,进行解析成相关的类,然后依次按照模板生成文件

    var entityResult = Engine.Razor.RunCompile(TemplatePage, "templatePageKey", null, new
    {
        PostData = (date ?? DateTime.Now).ToString("yyyy-MM-dd"),
        PrevIndex = prev.Value,
        NextIndex = next.Value,
        ContentHtml = content
    });

    按照一条数据便是一个模板文件去生成可以得到批量生成文件。

    三、适合自己的简单代码生成器

      开始着手适合自己的简单代码生成器,思路一致,只是增加了需要读取数据库这一环节。

    1、模板制作,以应用服务接口为例,常用的增删改查进行封装,利用Razor语法进行填充处理,此处对于主键类型,没有进行处理,只能支持诸如int、long之类的,后期在继续优化。

    using Abp.Application.Services;
    using Abp.Application.Services.Dto;
    using System.Collections.Generic;
    using System.Threading.Tasks;
    using @Model.ProjectNameSpace.@Model.ProjectModule.@(Model.EntityName)s.Dto;
    
    namespace @Model.ProjectNameSpace.@Model.ProjectModule.@(Model.EntityName)s
    {
        /// <summary>
        /// @(Model.EntityDescription)应用服务接口
        /// </summary>
        public interface I@(Model.EntityName)AppService : IApplicationService
        {
            /// <summary>
            /// 获取@(Model.EntityDescription)数据集合
            /// </summary>
            /// <param name="input"></param>
            /// <returns></returns>
            Task<PagedResultDto<@(Model.EntityName)ListDto>> GetPaged@(Model.EntityName)(GetPaged@(Model.EntityName)Input input);
    
            /// <summary>
            /// 获取@(Model.EntityDescription)编辑信息
            /// </summary>
            /// <param name="input"></param>
            /// <returns></returns>
            Task<Get@(Model.EntityName)ForEditOutput> Get@(Model.EntityName)ForEdit(NullableIdDto<@Model.EntityKeyType> input);
    
            /// <summary>
            /// 创建或修改@(Model.EntityDescription)信息
            /// </summary>
            /// <param name="input"></param>
            /// <returns></returns>
            Task CreateOrUpdate@(Model.EntityName)(CreateOrUpdate@(Model.EntityName)Input input);
    
            /// <summary>
            /// 删除@(Model.EntityDescription)
            /// </summary>
            /// <param name="input"></param>
            /// <returns></returns>
            Task Delete@(Model.EntityName)(List<EntityDto<@Model.EntityKeyType>> inputs);
        }
    }

     2、设置相应的解析器,与之前的尝试不同,这次使用了具体的类型,这是Razor中的另一种方式,解析完毕后将文件按照指定路径保存,尽量符合项目的路径存储。

    var iRazorAppService = Engine.Razor.RunCompile(IRazorAppService, nameof(IRazorAppService), typeof(TemplateParseModel), templateParseModel);
    UtilHelper.Save(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, applicationPath, $"I{templateParseModel.EntityName}AppService.cs"), iRazorAppService);
    builder.Append(iRazorAppService);

     3、数据库连接读取表结构,控制台下,采用直接读取的形式,不走DbContext方式,Nuget中引入MySql.Data包(我本地用的Mysql),增加Appsettings.json文件并配置好连接字符串,用sql语句形式直接读取数据库中的信息,此处封装了一个DbHelper类及将读取到的信息封装到指定类中。

    using (var SqlConnection = new MySqlConnection(connectionStr))
    {
        SqlConnection.Open();
        var columsInfo = string.Format(@"select table_name,column_name,ordinal_position,is_nullable,data_type,character_maximum_length,column_key,column_comment
            from information_schema.COLUMNS
            where table_schema = '{0}' and table_name = '{1}'", dbschema, tablename);
    
        MySqlCommand mySqlCommand = new MySqlCommand(columsInfo, SqlConnection);
        MySqlDataReader dataReader = mySqlCommand.ExecuteReader();
    
        List<ColumnInfo> sqlDatasList = new List<ColumnInfo>();
        while (dataReader.Read())
        {
            var columnInfo = new ColumnInfo()
            {
                TableName = dataReader[dataReader.GetName(0)].ToString(),
                Name = dataReader[dataReader.GetName(1)].ToString(),
                OrdinalPosition = StringExtension.GetValueOrNull<int>(dataReader[dataReader.GetName(2)].ToString()),
                IsNullable = dataReader[dataReader.GetName(3)].ToString(),
                DataType = dataReader[dataReader.GetName(4)].ToString(),
                CharacterMaximumLength = StringExtension.GetValueOrNull<int>(dataReader[dataReader.GetName(2)].ToString()),
                ColumnKey = dataReader[dataReader.GetName(6)].ToString(),
                ColumnComment = dataReader[dataReader.GetName(7)].ToString(),
            };
            sqlDatasList.Add(columnInfo);
        }
    
        dataReader.Close();
        SqlConnection.Close();
        return sqlDatasList;
    

    4、启动后输入表名、实体名、实体描述(并未保存到数据库中),再通过手动将其加入到项目中,诸如命名空间及模块名称都加入到了配置文件中,方便配置,至少相对手动去一个个添加来讲,减少了部分工作量,也达到了辅助的效果,但是要达到全面辅助,还得在进行继续优化,针对其中的类等等,暂时没有加入属性,只放置了Id、Name等等,之后得考虑把数据库中字段也循环输出到模板文件中。

     至此,依靠Razor引擎制作一个简单的(算是减少了工作量)代码生成器初步完成了,年后继续完善,加入丰富的功能,并移入到框架中作为提高生产力的手段。新年快乐~

     仓库地址:https://gitee.com/530521314/Partner.TreasureChest.git

    2020-01-01,望技术有成后能回来看见自己的脚步
  • 相关阅读:
    node.js---sails项目开发(3)
    node.js---sails项目开发(2)
    基于 phantomjs 的自动化测试---(1)
    node.js---sails项目开发(1)
    mongoose基于mongodb的数据评论设计
    js复杂数据格式提交
    [LeetCode]Rotate Image
    [LeetCode]Minimum Path Sum
    [LeetCode]Generate Parentheses
    [LeetCode]Gray Code
  • 原文地址:https://www.cnblogs.com/CKExp/p/11996382.html
Copyright © 2020-2023  润新知